From dfbae648cd75c8457df0ccb5b53d6929326e0a8f Mon Sep 17 00:00:00 2001
From: KhooLy <73142442+KhooLy@users.noreply.github.com>
Date: Sun, 19 Jul 2026 15:44:41 +0300
Subject: [PATCH] Cache hero billboard pool to disk so it paints immediately on
cold start
---
.../fluxa/app/ui/catalog/HomeProfileStores.kt | 24 +++++++++++++++++++
.../com/fluxa/app/ui/catalog/HomeViewModel.kt | 20 ++++++++++++++--
2 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/HomeProfileStores.kt b/app/src/main/java/com/fluxa/app/ui/catalog/HomeProfileStores.kt
index 453162a..b929f15 100644
--- a/app/src/main/java/com/fluxa/app/ui/catalog/HomeProfileStores.kt
+++ b/app/src/main/java/com/fluxa/app/ui/catalog/HomeProfileStores.kt
@@ -99,3 +99,27 @@ class HomeCategoryCache @Inject constructor(
return if (profile?.isGuest == true || profile == null) "home_cache_guest" else "home_cache_${profile.id}"
}
}
+
+@Singleton
+class HomeBillboardCache @Inject constructor(
+ @ApplicationContext context: Context,
+ private val gson: Gson
+) {
+ private val prefs = context.getSharedPreferences("fluxa_home_billboard_cache", Context.MODE_PRIVATE)
+
+ fun load(profile: UserProfile?): List {
+ val saved = prefs.getString(key(profile), null) ?: return emptyList()
+ return runCatching {
+ val listType = object : TypeToken>() {}.type
+ gson.fromJson>(saved, listType).orEmpty()
+ }.getOrElse { emptyList() }
+ }
+
+ fun save(profile: UserProfile?, pool: List) {
+ prefs.edit().putString(key(profile), gson.toJson(pool)).apply()
+ }
+
+ private fun key(profile: UserProfile?): String {
+ return if (profile?.isGuest == true || profile == null) "billboard_cache_guest" else "billboard_cache_${profile.id}"
+ }
+}
diff --git a/app/src/main/java/com/fluxa/app/ui/catalog/HomeViewModel.kt b/app/src/main/java/com/fluxa/app/ui/catalog/HomeViewModel.kt
index 3cb0b3e..d79568b 100644
--- a/app/src/main/java/com/fluxa/app/ui/catalog/HomeViewModel.kt
+++ b/app/src/main/java/com/fluxa/app/ui/catalog/HomeViewModel.kt
@@ -51,6 +51,7 @@ class HomeViewModel @Inject constructor(
private val watchlistStore: WatchlistStore,
private val searchHistoryStore: SearchHistoryStore,
private val homeCategoryCache: HomeCategoryCache,
+ private val homeBillboardCache: HomeBillboardCache,
private val forgottenContinueWatchingStore: ForgottenContinueWatchingStore,
private val coordinatorFactory: HomeViewModelCoordinatorFactory,
private val externalSyncPushCoordinator: ExternalSyncPushCoordinator,
@@ -254,6 +255,7 @@ class HomeViewModel @Inject constructor(
private var externalContinueWatching: List = emptyList()
private var traktWatchedState: TraktWatchedState = TraktWatchedState()
private var currentActiveProfile: UserProfile? = null
+ private var hasFetchedFreshBillboard = false
private var searchJob: Job? = null
private val _isSearchLoading = MutableStateFlow(false)
val isSearchLoading: StateFlow = _isSearchLoading.asStateFlow()
@@ -349,7 +351,10 @@ class HomeViewModel @Inject constructor(
fetchCs3FeedItems = { feed ->
platformContentGateway.cloudFeedItems(feed.key)
},
- setPool = { billboardState.poolValue = it },
+ setPool = { pool ->
+ billboardState.poolValue = pool
+ homeBillboardCache.save(currentActiveProfile, pool)
+ },
updateContent = billboardRuntime::updateContent,
normalizePool = { items -> items.distinctBy(HomeBillboardRanking::contentIdentityKey) },
startRotation = billboardRuntime::startRotation
@@ -1018,9 +1023,20 @@ class HomeViewModel @Inject constructor(
}
scheduleCs3Refresh()
}
- if (force || billboardState.poolValue.isEmpty()) {
+ if (billboardState.poolValue.isEmpty()) {
+ val cachedPool = homeBillboardCache.load(activeProfile)
+ if (cachedPool.isNotEmpty()) {
+ billboardState.poolValue = cachedPool
+ viewModelScope.launch {
+ billboardRuntime.updateContent(cachedPool[0])
+ billboardRuntime.startRotation()
+ }
+ }
+ }
+ if (force || !hasFetchedFreshBillboard) {
viewModelScope.launch {
billboardLoader.load(activeProfile)
+ hasFetchedFreshBillboard = true
}
}
}