From c22fbc30be7f9008098a2603f720aeaacfa050cc Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:43:12 +0530 Subject: [PATCH] homescreen fix --- .idea/inspectionProfiles/Project_Default.xml | 50 +++++ .idea/vcs.xml | 6 + .../tv/ui/components/CatalogRowSection.kt | 2 +- .../tv/ui/screens/detail/MetaDetailsScreen.kt | 187 ++++++++++-------- .../nuvio/tv/ui/screens/home/HomeScreen.kt | 5 +- .../nuvio/tv/ui/screens/home/HomeUiState.kt | 2 +- .../nuvio/tv/ui/screens/home/HomeViewModel.kt | 22 ++- 7 files changed, 187 insertions(+), 87 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..f0c6ad08 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,50 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt b/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt index 36278a77..8a2d38b4 100644 --- a/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt +++ b/app/src/main/java/com/nuvio/tv/ui/components/CatalogRowSection.kt @@ -80,7 +80,7 @@ fun CatalogRowSection( ) { items( items = catalogRow.items, - key = { "${catalogRow.catalogId}_${it.id}" } + key = { "${catalogRow.type}_${catalogRow.catalogId}_${it.id}" } ) { item -> ContentCard( item = item, diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/detail/MetaDetailsScreen.kt b/app/src/main/java/com/nuvio/tv/ui/screens/detail/MetaDetailsScreen.kt index d43227da..76742890 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/detail/MetaDetailsScreen.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/detail/MetaDetailsScreen.kt @@ -122,41 +122,94 @@ private fun MetaDetailsContent( val isSeries = meta.type == ContentType.SERIES || meta.videos.isNotEmpty() val nextEpisode = episodesForSeason.firstOrNull() - TvLazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(bottom = 48.dp) - ) { - // Hero section with backdrop - item { - HeroSection( - meta = meta, - nextEpisode = nextEpisode, - onPlayClick = { - val videoId = if (isSeries && nextEpisode != null) { - nextEpisode.id - } else { - meta.id - } - onPlayClick(videoId) - } + Box(modifier = Modifier.fillMaxSize()) { + // Sticky background image - stays fixed in place while content scrolls + Box(modifier = Modifier.fillMaxSize()) { + AsyncImage( + model = meta.background ?: meta.poster, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + + // Light global dim so text remains readable; the main fade happens in the section below the hero. + Box( + modifier = Modifier + .fillMaxSize() + .background(NuvioColors.Background.copy(alpha = 0.08f)) + ) + + // Left side gradient for better text readability + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.horizontalGradient( + colors = listOf( + NuvioColors.Background.copy(alpha = 0.85f), + NuvioColors.Background.copy(alpha = 0.5f), + Color.Transparent + ), + startX = 0f, + endX = 600f + ) + ) ) } - // Season tabs and episodes for series - if (isSeries && seasons.isNotEmpty()) { + // Scrollable content on top of the fixed background + TvLazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 48.dp) + ) { + // Hero content section (fills screen height, content at bottom) item { - SeasonTabs( - seasons = seasons, - selectedSeason = selectedSeason, - onSeasonSelected = onSeasonSelected + HeroContentSection( + meta = meta, + nextEpisode = nextEpisode, + onPlayClick = { + val videoId = if (isSeries && nextEpisode != null) { + nextEpisode.id + } else { + meta.id + } + onPlayClick(videoId) + } ) } - item { - EpisodesRow( - episodes = episodesForSeason, - onEpisodeClick = onEpisodeClick - ) + // Season tabs and episodes for series - fully transparent over backdrop + if (isSeries && seasons.isNotEmpty()) { + item { + // This section owns the fade-to-dark background (smooth at top, darker as you scroll down). + Box( + modifier = Modifier + .fillMaxWidth() + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + NuvioColors.Background.copy(alpha = 0.55f), + NuvioColors.Background.copy(alpha = 0.85f), + NuvioColors.Background + ) + ) + ) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + SeasonTabs( + seasons = seasons, + selectedSeason = selectedSeason, + onSeasonSelected = onSeasonSelected + ) + EpisodesRow( + episodes = episodesForSeason, + onEpisodeClick = onEpisodeClick + ) + Spacer(modifier = Modifier.height(140.dp)) + } + } + } } } } @@ -164,62 +217,22 @@ private fun MetaDetailsContent( @OptIn(ExperimentalTvMaterial3Api::class) @Composable -private fun HeroSection( +private fun HeroContentSection( meta: Meta, nextEpisode: Video?, onPlayClick: () -> Unit ) { + // Full height hero with content at bottom Box( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() + .height(540.dp), // Full screen height for TV + contentAlignment = Alignment.BottomStart ) { - // Background image - AsyncImage( - model = meta.background ?: meta.poster, - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop - ) - - // Gradient overlay - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - NuvioColors.Background.copy(alpha = 0.3f), - NuvioColors.Background.copy(alpha = 0.7f), - NuvioColors.Background - ), - startY = 0f, - endY = Float.POSITIVE_INFINITY - ) - ) - ) - - // Left side gradient for better text readability - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.horizontalGradient( - colors = listOf( - NuvioColors.Background.copy(alpha = 0.8f), - NuvioColors.Background.copy(alpha = 0.4f), - Color.Transparent - ), - startX = 0f, - endX = 800f - ) - ) - ) - - // Content + // Content at the bottom Column( modifier = Modifier - .fillMaxSize() + .fillMaxWidth() .padding(start = 48.dp, end = 48.dp, bottom = 32.dp), verticalArrangement = Arrangement.Bottom ) { @@ -488,12 +501,21 @@ private fun SeasonTabs( Column( modifier = Modifier .fillMaxWidth() + .background( + Brush.verticalGradient( + colors = listOf( + NuvioColors.Background.copy(alpha = 0.2f), + NuvioColors.Background.copy(alpha = 0.4f) + ) + ) + ) .padding(horizontal = 48.dp, vertical = 24.dp) ) { TabRow( selectedTabIndex = selectedIndex, modifier = Modifier.fillMaxWidth(), - separator = { Spacer(modifier = Modifier.width(24.dp)) } + separator = { Spacer(modifier = Modifier.width(24.dp)) }, + containerColor = Color.Transparent ) { seasons.forEachIndexed { index, season -> Tab( @@ -524,8 +546,17 @@ private fun EpisodesRow( onEpisodeClick: (Video) -> Unit ) { TvLazyRow( - modifier = Modifier.fillMaxWidth(), - contentPadding = PaddingValues(horizontal = 48.dp), + modifier = Modifier + .fillMaxWidth() + .background( + Brush.verticalGradient( + colors = listOf( + NuvioColors.Background.copy(alpha = 0.3f), + NuvioColors.Background.copy(alpha = 0.5f) + ) + ) + ), + contentPadding = PaddingValues(horizontal = 48.dp, vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(episodes, key = { it.id }) { episode -> diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt index 6e790149..27deeac4 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeScreen.kt @@ -61,7 +61,7 @@ fun HomeScreen( ) { items( items = uiState.catalogRows, - key = { "${it.addonId}_${it.catalogId}" } + key = { "${it.addonId}_${it.type}_${it.catalogId}" } ) { catalogRow -> CatalogRowSection( catalogRow = catalogRow, @@ -72,7 +72,8 @@ fun HomeScreen( viewModel.onEvent( HomeEvent.OnLoadMoreCatalog( catalogId = catalogRow.catalogId, - addonId = catalogRow.addonId + addonId = catalogRow.addonId, + type = catalogRow.type.toApiString() ) ) } diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeUiState.kt b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeUiState.kt index e206ff07..51d3da14 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeUiState.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeUiState.kt @@ -11,6 +11,6 @@ data class HomeUiState( sealed class HomeEvent { data class OnItemClick(val itemId: String, val itemType: String) : HomeEvent() - data class OnLoadMoreCatalog(val catalogId: String, val addonId: String) : HomeEvent() + data class OnLoadMoreCatalog(val catalogId: String, val addonId: String, val type: String) : HomeEvent() data object OnRetry : HomeEvent() } diff --git a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeViewModel.kt b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeViewModel.kt index 10a7aada..0f358cd1 100644 --- a/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/nuvio/tv/ui/screens/home/HomeViewModel.kt @@ -37,7 +37,7 @@ class HomeViewModel @Inject constructor( fun onEvent(event: HomeEvent) { when (event) { is HomeEvent.OnItemClick -> navigateToDetail(event.itemId, event.itemType) - is HomeEvent.OnLoadMoreCatalog -> loadMoreCatalogItems(event.catalogId, event.addonId) + is HomeEvent.OnLoadMoreCatalog -> loadMoreCatalogItems(event.catalogId, event.addonId, event.type) HomeEvent.OnRetry -> loadAllCatalogs() } } @@ -59,7 +59,11 @@ class HomeViewModel @Inject constructor( // Build catalog order based on addon manifest order addons.forEach { addon -> addon.catalogs.forEach { catalog -> - val key = "${addon.id}_${catalog.id}" + val key = catalogKey( + addonId = addon.id, + type = catalog.type.toApiString(), + catalogId = catalog.id + ) if (key !in catalogOrder) { catalogOrder.add(key) } @@ -91,7 +95,11 @@ class HomeViewModel @Inject constructor( ).collect { result -> when (result) { is NetworkResult.Success -> { - val key = "${addon.id}_${catalog.id}" + val key = catalogKey( + addonId = addon.id, + type = catalog.type.toApiString(), + catalogId = catalog.id + ) catalogsMap[key] = result.data updateCatalogRows() } @@ -104,8 +112,8 @@ class HomeViewModel @Inject constructor( } } - private fun loadMoreCatalogItems(catalogId: String, addonId: String) { - val key = "${addonId}_${catalogId}" + private fun loadMoreCatalogItems(catalogId: String, addonId: String, type: String) { + val key = catalogKey(addonId = addonId, type = type, catalogId = catalogId) val currentRow = catalogsMap[key] ?: return if (currentRow.isLoading || !currentRow.hasMore) return @@ -157,4 +165,8 @@ class HomeViewModel @Inject constructor( private fun navigateToDetail(itemId: String, itemType: String) { _uiState.update { it.copy(selectedItemId = itemId) } } + + private fun catalogKey(addonId: String, type: String, catalogId: String): String { + return "${addonId}_${type}_${catalogId}" + } }