Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,14 @@ fun HomeScreen(
preloadedCategories
}
val displayHeroItem = uiState.heroItem ?: preloadedHeroItem
?: displayCategories.firstOrNull()?.items?.firstOrNull()
?: if (uiState.categories.isEmpty()) {
// Only fall through to first-row hero while the ViewModel is still
// publishing its initial categories. Once categories are populated,
// the hero-update LaunchedEffect drives heroItem from focused cards.
displayCategories.firstOrNull()?.items?.firstOrNull()
} else {
null
}
val displayHeroLogo = uiState.heroLogoUrl ?: preloadedHeroLogoUrl
val displayHeroOverview = uiState.heroOverviewOverride
val latestDisplayCategories by rememberUpdatedState(displayCategories)
Expand Down Expand Up @@ -708,8 +715,7 @@ fun HomeScreen(
if (categoriesSnapshot.isEmpty() || focusState.isSidebarFocused) return@collectLatest
if (focusSnapshot.focusedItemKey.isBlank()) return@collectLatest
if (focusSnapshot.focusedItemKey == focusSnapshot.heroItemKey) return@collectLatest
categoriesSnapshot
.getOrNull(focusSnapshot.rowIndex)
categoriesSnapshot.getOrNull(focusSnapshot.rowIndex)
?.items
?.getOrNull(focusSnapshot.itemIndex)
?: return@collectLatest
Expand Down
24 changes: 20 additions & 4 deletions app/src/main/kotlin/com/arflix/tv/ui/screens/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,8 @@ class HomeViewModel @Inject constructor(
}

private fun isActionableMediaItem(item: MediaItem): Boolean {
// Synthetic collection tiles (Netflix/Disney/HBO service cards) carry
// a hash-based id in MediaItem.id that is not a real TMDB id — treating
// them as actionable triggers TMDB /images 404s in the logo preloader.
// Non-actionable items are expected during filtering: invalid IDs cannot be opened,
// placeholders are synthetic UI entries, and collection tiles use their own handling.
return item.id > 0 && !item.isPlaceholder && !isCollectionItem(item)
}

Expand Down Expand Up @@ -1922,7 +1921,24 @@ class HomeViewModel @Inject constructor(
// Launch the independent CW fetch
launchContinueWatchingFetch()

val heroItem = chooseInitialHero(categories)
// During catalog-triggered reloads, chooseInitialHero can pick
// the first Continue Watching item and overwrite the currently
// focused hero. Preserve the existing hero when possible, and
// let the focus watcher correct it on the next focus change.
val heroItem = if (_uiState.value.heroItem != null) {
val currentHero = _uiState.value.heroItem!!
// Preserve current hero during reload. Try to find it in the fresh
// categories; if the same id/mediaType still exists, use the fresh
// instance to ensure reference consistency. If not found, keep the
// old hero — it's still valid UI and the hero-update LaunchedEffect
// will correct it when the user moves focus.
categories.asSequence()
.flatMap { it.items.asSequence() }
.firstOrNull { it.id == currentHero.id && it.mediaType == currentHero.mediaType }
?: currentHero
} else {
chooseInitialHero(categories)
}

// Preload logos for the first visible rows so card overlays appear immediately.
// Skip IPTV items — their channel logo is already in item.image.
Expand Down
Loading