feat: enhance HomeScreen with catalog rendering callback and add AppLaunchOverlay

This commit is contained in:
tapframe 2026-03-30 13:03:36 +05:30
parent 0a9b519a3c
commit 375f13f5f6
2 changed files with 64 additions and 8 deletions

View file

@ -8,11 +8,16 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Search
@ -35,12 +40,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
@ -95,6 +101,9 @@ import com.nuvio.app.features.watched.toWatchedItem
import com.nuvio.app.features.watched.watchedItemKey
import com.nuvio.app.features.watchprogress.ContinueWatchingItem
import kotlinx.serialization.Serializable
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.app_logo_wordmark
import org.jetbrains.compose.resources.painterResource
@Serializable
object TabsRoute
@ -277,10 +286,11 @@ private fun MainAppContent(
LibraryRepository.ensureLoaded()
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val watchedUiState by remember {
WatchedRepository.ensureLoaded()
WatchedRepository.uiState
}.collectAsStateWithLifecycle()
val watchedUiState by remember {
WatchedRepository.ensureLoaded()
WatchedRepository.uiState
}.collectAsStateWithLifecycle()
var initialHomeReady by rememberSaveable { mutableStateOf(false) }
val onPlay: (String, String, String, String, String, String?, String?, String?, Int?, Int?, String?, String?, String?, Long?) -> Unit =
{ type, videoId, parentMetaId, parentMetaType, title, logo, poster, background, seasonNumber, episodeNumber, episodeTitle, episodeThumbnail, pauseDescription, resumePositionMs ->
@ -365,7 +375,9 @@ private fun MainAppContent(
) {
composable<TabsRoute> {
Scaffold(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.alpha(if (initialHomeReady) 1f else 0f),
containerColor = MaterialTheme.colorScheme.background,
contentWindowInsets = WindowInsets(0),
bottomBar = {
@ -434,6 +446,7 @@ private fun MainAppContent(
onContinueWatchingSettingsClick = { navController.navigate(ContinueWatchingSettingsRoute) },
onAddonsSettingsClick = { navController.navigate(AddonsSettingsRoute) },
onAccountSettingsClick = { navController.navigate(AccountSettingsRoute) },
onInitialHomeContentRendered = { initialHomeReady = true },
)
}
}
@ -669,6 +682,10 @@ private fun MainAppContent(
}
},
)
if (!initialHomeReady) {
AppLaunchOverlay(modifier = Modifier.fillMaxSize())
}
}
}
@ -687,6 +704,7 @@ private fun AppTabHost(
onContinueWatchingSettingsClick: () -> Unit = {},
onAddonsSettingsClick: () -> Unit = {},
onAccountSettingsClick: () -> Unit = {},
onInitialHomeContentRendered: () -> Unit = {},
) {
Box(modifier = modifier.fillMaxSize()) {
keepAliveTab(
@ -699,6 +717,7 @@ private fun AppTabHost(
onPosterLongClick = onPosterLongClick,
onContinueWatchingClick = onContinueWatchingClick,
onContinueWatchingLongPress = onContinueWatchingLongPress,
onFirstCatalogRendered = onInitialHomeContentRendered,
)
}
keepAliveTab(
@ -733,6 +752,33 @@ private fun AppTabHost(
}
}
@Composable
private fun AppLaunchOverlay(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.background(MaterialTheme.colorScheme.background)
.zIndex(10f),
contentAlignment = Alignment.Center,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(Res.drawable.app_logo_wordmark),
contentDescription = "Nuvio",
modifier = Modifier
.fillMaxWidth(0.48f)
.height(44.dp),
contentScale = ContentScale.Fit,
)
Spacer(modifier = Modifier.height(24.dp))
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
}
}
}
@Composable
private fun BoxScope.keepAliveTab(
selected: Boolean,

View file

@ -3,6 +3,7 @@ package com.nuvio.app.features.home
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
@ -37,6 +38,7 @@ fun HomeScreen(
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null,
onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null,
onFirstCatalogRendered: (() -> Unit)? = null,
) {
LaunchedEffect(Unit) {
AddonRepository.initialize()
@ -134,6 +136,14 @@ fun HomeScreen(
val showHeroSkeleton = showHeroSlot &&
homeUiState.heroItems.isEmpty() &&
isResolvingHeroSources
var firstCatalogReported by remember { mutableStateOf(false) }
LaunchedEffect(homeUiState.sections.firstOrNull()?.key, onFirstCatalogRendered) {
if (firstCatalogReported || homeUiState.sections.isEmpty()) return@LaunchedEffect
coroutineContext[MonotonicFrameClock]?.withFrameNanos { }
firstCatalogReported = true
onFirstCatalogRendered?.invoke()
}
NuvioScreen(
modifier = modifier,