From f0cf10d757d91d0be36b0bc15912e04bd422d88e Mon Sep 17 00:00:00 2001 From: KhooLy <73142442+KhooLy@users.noreply.github.com> Date: Mon, 20 Jul 2026 00:06:38 +0300 Subject: [PATCH] Add an Upcoming home row split from Continue Watching (off by default) New setting under Settings > Appearance > Home Screen: when enabled, Continue Watching items whose next episode/season hasn't released yet move into their own Upcoming row instead of sitting in Continue Watching with nothing to resume. Classification runs async per up-next series item (no active playback progress): HomeContinueWatchingCoordinator resolves the specific lastVideoId episode via the existing getSeasonEpisodes fetch, checks its release date through the new isEpisodeReleased FFI call, and caches the result by id:lastVideoId. Once known, it triggers a dynamic-rows refresh so the split applies without blocking the initial Home paint. HomeCatalogFeedCoordinator/HomeDynamicRowsCoordinator partition the Continue Watching list into continue_watching/upcoming HomeCategory rows via the new isUpcoming lambda, gated by UserProfile.upcomingRowEnabled (resolved through the Rust safe-prefs struct like every other appearance toggle). HomeCategoryPolicy's isContinueWatchingCategory()-gated behavior (card layout, action-row treatment, title passthrough) now also covers the upcoming row so it renders with the same horizontal episode-card treatment. --- .../com/fluxa/app/ui/MainActivityHelpers.kt | 1 + .../catalog/AndroidCatalogHomeDataSource.kt | 4 +-- .../app/ui/catalog/AndroidCatalogUiMappers.kt | 2 +- .../ui/catalog/AndroidSettingsDataSource.kt | 6 ++-- .../ui/catalog/HomeCatalogFeedCoordinator.kt | 17 +++++++++- .../HomeContinueWatchingCoordinator.kt | 33 +++++++++++++++++++ .../ui/catalog/HomeDynamicRowsCoordinator.kt | 20 ++++++++++- .../com/fluxa/app/ui/catalog/HomeViewModel.kt | 2 ++ .../app/core/rust/models/CorePolicyModels.kt | 1 + .../commonMain/resources/i18n/english_us.json | 2 ++ core/src/commonMain/resources/i18n/tr_tr.json | 2 ++ .../fluxa/app/core/rust/FluxaCoreNative.kt | 8 +++++ .../app/data/local/UserProfileSafePrefs.kt | 1 + .../com/fluxa/app/data/local/UserProfile.kt | 1 + .../app/ui/catalog/HomeCategoryPolicy.kt | 6 +++- .../app/ui/catalog/HomeCategoryTitles.kt | 2 +- .../feature/settings/SettingsContracts.kt | 3 +- .../shared/feature/settings/SettingsScreen.kt | 7 ++++ 18 files changed, 108 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/fluxa/app/ui/MainActivityHelpers.kt b/app/src/main/java/com/fluxa/app/ui/MainActivityHelpers.kt index db3f0d9..7183461 100644 --- a/app/src/main/java/com/fluxa/app/ui/MainActivityHelpers.kt +++ b/app/src/main/java/com/fluxa/app/ui/MainActivityHelpers.kt @@ -30,6 +30,7 @@ internal fun UserProfile?.requiresHomeReload(next: UserProfile): Boolean { previous.homeFeedToggles != next.homeFeedToggles || previous.homeFeedOrder != next.homeFeedOrder || previous.safeContinueWatchingEnabled != next.safeContinueWatchingEnabled || + previous.safeUpcomingRowEnabled != next.safeUpcomingRowEnabled || previous.safeContinueWatchingSource != next.safeContinueWatchingSource || previous.safeShowHeroSection != next.safeShowHeroSection } diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogHomeDataSource.kt b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogHomeDataSource.kt index 7ecdd40..51238ce 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogHomeDataSource.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogHomeDataSource.kt @@ -156,7 +156,7 @@ class AndroidCatalogHomeDataSource( categoryType = type, cardLayout = resolveHomeCardLayout(this, profile), artworkPreference = null, - isActionRow = isContinueWatchingCategory() || id == "library", + isActionRow = isContinueWatchingOrUpcomingCategory() || id == "library", topTenEnabled = id in profile?.safeTopTenFeedToggles.orEmpty(), items = items.map { meta -> meta.toCatalogItemUiModel(category = this, profile = profile) } ) @@ -169,7 +169,7 @@ class AndroidCatalogHomeDataSource( cardScale = 1f, showHorizontalLogo = true, topTenRank = null, - isContinueWatchingCard = category?.isContinueWatchingCategory() == true, + isContinueWatchingCard = category?.isContinueWatchingOrUpcomingCategory() == true, loadArtwork = true ) return CatalogItemUiModel( diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogUiMappers.kt b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogUiMappers.kt index 3b1f621..92b8a00 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogUiMappers.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidCatalogUiMappers.kt @@ -14,7 +14,7 @@ import com.fluxa.app.ui.catalog.posterCardHeight import com.fluxa.app.ui.catalog.posterCardWidth fun resolveHomeCardLayout(category: HomeCategory, profile: UserProfile?): String { - return if (category.isContinueWatchingCategory()) { + return if (category.isContinueWatchingOrUpcomingCategory()) { profile?.resolvedContinueWatchingLayout ?: "horizontal" } else if (profile?.safePosterLandscapeMode == true) { "horizontal" diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidSettingsDataSource.kt b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidSettingsDataSource.kt index 4a884c8..92830ee 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/AndroidSettingsDataSource.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/AndroidSettingsDataSource.kt @@ -191,7 +191,8 @@ class AndroidSettingsDataSource( continueWatchingHorizontal = profile.safeContinueWatchingLayout != "vertical", continueWatchingEnabled = profile.safeContinueWatchingEnabled, continueWatchingHideTitles = profile.safeContinueWatchingHideTitles, - continueWatchingSource = profile.safeContinueWatchingSource + continueWatchingSource = profile.safeContinueWatchingSource, + upcomingRowEnabled = profile.safeUpcomingRowEnabled ), appearanceDetail = SettingsAppearanceDetailUiModel( trailerOnDetailHeroEnabled = profile.safeTrailerOnDetailHeroEnabled, @@ -316,7 +317,8 @@ class AndroidSettingsDataSource( continueWatchingLayout = if (value.continueWatchingHorizontal) "horizontal" else "vertical", continueWatchingEnabled = value.continueWatchingEnabled, continueWatchingHideTitles = value.continueWatchingHideTitles, - continueWatchingSource = value.continueWatchingSource + continueWatchingSource = value.continueWatchingSource, + upcomingRowEnabled = value.upcomingRowEnabled ) } diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/HomeCatalogFeedCoordinator.kt b/app/src/main/java/com/fluxa/app/ui/catalog/HomeCatalogFeedCoordinator.kt index ad575fc..e2de040 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/HomeCatalogFeedCoordinator.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/HomeCatalogFeedCoordinator.kt @@ -28,6 +28,7 @@ internal class HomeCatalogFeedCoordinator( private val userAddons: () -> List, private val setUserAddons: (List) -> Unit, private val continueWatchingItems: (String) -> List, + private val isUpcoming: (Meta) -> Boolean, private val normalizeCatalogItems: suspend (List, String, String, String?) -> List, private val setCategories: (List) -> Unit, private val currentCategories: () -> List @@ -38,7 +39,10 @@ internal class HomeCatalogFeedCoordinator( categories.addAll(buildUserCollectionHomeCategories(profile, showAboveContinueWatching = true)) - val continueWatching = if (profile?.safeContinueWatchingEnabled != false) continueWatchingItems(lang) else emptyList() + val allContinueWatching = if (profile?.safeContinueWatchingEnabled != false) continueWatchingItems(lang) else emptyList() + val upcomingEnabled = profile?.safeUpcomingRowEnabled == true + val upcoming = if (upcomingEnabled) allContinueWatching.filter(isUpcoming) else emptyList() + val continueWatching = if (upcomingEnabled) allContinueWatching.filterNot(isUpcoming) else allContinueWatching if (continueWatching.isNotEmpty()) { categories.add( HomeCategory( @@ -50,6 +54,17 @@ internal class HomeCatalogFeedCoordinator( ) ) } + if (upcoming.isNotEmpty()) { + categories.add( + HomeCategory( + AppStrings.t(lang, "settings.upcoming_row"), + upcoming, + "upcoming", + "upcoming", + canLoadMore = false + ) + ) + } categories.addAll(buildUserCollectionHomeCategories(profile, showAboveContinueWatching = false)) val initialFeeds = getMetadataFeeds(profile) .let { orderedMetadataFeeds(it, profile?.homeFeedOrder) } diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/HomeContinueWatchingCoordinator.kt b/app/src/main/java/com/fluxa/app/ui/catalog/HomeContinueWatchingCoordinator.kt index d8594b6..c7ec8cd 100644 --- a/app/src/main/java/com/fluxa/app/ui/catalog/HomeContinueWatchingCoordinator.kt +++ b/app/src/main/java/com/fluxa/app/ui/catalog/HomeContinueWatchingCoordinator.kt @@ -32,6 +32,37 @@ internal class HomeContinueWatchingCoordinator( private val getSeasonEpisodes: suspend (String, Int, String) -> List