From 327d49d31df7f58b1fa39db87cd58714cf053430 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:30:41 +0530 Subject: [PATCH] fix(simkl): refresh hidden progress immediately --- .../com/nuvio/app/features/home/HomeScreen.kt | 27 +++++++++++++++---- .../simkl/SimklApplicationAdapters.kt | 1 + .../simkl/SimklPlaybackReconciliation.kt | 6 +++++ .../app/features/tracking/TrackingReads.kt | 1 + .../watchprogress/WatchProgressModels.kt | 1 + .../watchprogress/WatchProgressRepository.kt | 24 ++++++++++++----- .../simkl/SimklPlaybackReconciliationTest.kt | 2 ++ .../WatchProgressIdentityTest.kt | 27 +++++++++++++++++++ 8 files changed, 78 insertions(+), 11 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt index 9416828a9..547f2a6d5 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt @@ -195,10 +195,12 @@ fun HomeScreen( val effectiveWatchProgressEntries = remember( watchProgressUiState.entries, + watchProgressUiState.hiddenContentIds, continueWatchingCutoffEpochMs, ) { val visibleProviderEntries = watchProgressUiState.entries.filterNot { entry -> - WatchProgressRepository.isDroppedShow(entry.parentMetaId) + entry.parentMetaId in watchProgressUiState.hiddenContentIds || + WatchProgressRepository.isDroppedShow(entry.parentMetaId) } filterEntriesForContinueWatchingWindow( entries = visibleProviderEntries, @@ -208,6 +210,7 @@ fun HomeScreen( val allNextUpSeedCandidates = remember( watchProgressUiState.entries, + watchProgressUiState.hiddenContentIds, nextUpWatchedItems, progressProviderOwnsCompletedHistory, continueWatchingPreferences.upNextFromFurthestEpisode, @@ -219,7 +222,10 @@ fun HomeScreen( preferFurthestEpisode = continueWatchingPreferences.upNextFromFurthestEpisode, nowEpochMs = WatchProgressClock.nowEpochMs(), shouldUseProgressSeed = WatchProgressRepository::shouldUseAsNextUpSeed, - isContentHidden = WatchProgressRepository::isDroppedShow, + isContentHidden = { contentId -> + contentId in watchProgressUiState.hiddenContentIds || + WatchProgressRepository.isDroppedShow(contentId) + }, ) } @@ -346,6 +352,7 @@ fun HomeScreen( nextUpItemsBySeries, continueWatchingPreferences.showUnairedNextUp, watchedUiState.isLoaded, + watchProgressUiState.hiddenContentIds, ) { cachedSnapshots.first.mapNotNull { cached -> if ( @@ -382,7 +389,10 @@ fun HomeScreen( if (!cachedNextUpHasAired(cached) && !continueWatchingPreferences.showUnairedNextUp) { return@mapNotNull null } - if (WatchProgressRepository.isDroppedShow(cached.contentId)) { + if ( + cached.contentId in watchProgressUiState.hiddenContentIds || + WatchProgressRepository.isDroppedShow(cached.contentId) + ) { return@mapNotNull null } val item = cached.toContinueWatchingItem() ?: return@mapNotNull null @@ -394,9 +404,16 @@ fun HomeScreen( cached.contentId to (sortTimestamp to item) }.toMap() } - val cachedInProgressItems = remember(cachedSnapshots.second, effectiveWatchProgressSource) { + val cachedInProgressItems = remember( + cachedSnapshots.second, + effectiveWatchProgressSource, + watchProgressUiState.hiddenContentIds, + ) { cachedSnapshots.second.mapNotNull { cached -> - if (WatchProgressRepository.isDroppedShow(cached.contentId)) { + if ( + cached.contentId in watchProgressUiState.hiddenContentIds || + WatchProgressRepository.isDroppedShow(cached.contentId) + ) { return@mapNotNull null } cached.resolvedProgressKey() to cached.toContinueWatchingItem() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklApplicationAdapters.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklApplicationAdapters.kt index ed9898141..11f44e84f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklApplicationAdapters.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklApplicationAdapters.kt @@ -197,6 +197,7 @@ object SimklTrackingProgressProvider : TrackingProgressProvider { val state = SimklProgressRepository.uiState.value return TrackingProgressSnapshot( entries = state.entries, + hiddenContentIds = SimklSyncRepository.state.value.snapshot.droppedContentIds(), hasLoadedRemoteProgress = state.hasLoadedRemoteProgress, errorMessage = state.errorMessage, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliation.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliation.kt index cbcd73c55..baebb3c26 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliation.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliation.kt @@ -21,6 +21,12 @@ internal fun SimklSyncSnapshot.isDroppedContent(contentId: String): Boolean = entry.matchesContent(contentId = contentId, trackingProviderItemId = null) } +internal fun SimklSyncSnapshot.droppedContentIds(): Set = + entries.asSequence() + .filter { entry -> entry.status == SimklListStatus.DROPPED } + .mapNotNull { entry -> entry.media?.canonicalContentId() } + .toSet() + private fun WatchedItem.supersedes(progress: WatchProgressEntry): Boolean { if (!type.equals(progress.contentType, ignoreCase = true)) return false if (season != progress.seasonNumber || episode != progress.episodeNumber) return false diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt index f0c500c8e..8adc3110d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/tracking/TrackingReads.kt @@ -110,6 +110,7 @@ interface TrackingWatchedProvider : WatchedSyncAdapter { data class TrackingProgressSnapshot( val entries: List = emptyList(), + val hiddenContentIds: Set = emptySet(), val hasLoadedRemoteProgress: Boolean = false, val errorMessage: String? = null, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt index acdc1adc7..cb7ee8d45 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt @@ -134,6 +134,7 @@ data class WatchProgressEntry( data class WatchProgressUiState( val source: WatchProgressSource = WatchProgressSource.NUVIO_SYNC, val entries: List = emptyList(), + val hiddenContentIds: Set = emptySet(), val hasLoadedRemoteProgress: Boolean = false, ) { val byProgressKey: Map diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt index 741521872..1c19e3f86 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt @@ -13,6 +13,7 @@ import com.nuvio.app.features.details.MetaDetailsRepository import com.nuvio.app.features.player.PlayerPlaybackSnapshot import com.nuvio.app.features.profiles.ProfileRepository import com.nuvio.app.features.tracking.TrackingProgressProvider +import com.nuvio.app.features.tracking.TrackingProgressSnapshot import com.nuvio.app.features.tracking.TrackingProviderId import com.nuvio.app.features.tracking.TrackingProviderRegistry import com.nuvio.app.features.tracking.TrackingSettingsRepository @@ -1340,14 +1341,12 @@ object WatchProgressRepository { private fun publish() { val entries = currentEntries() val sortedEntries = entries.sortedByDescending { it.lastUpdatedEpochMs } - val hasLoadedRemoteProgress = activeProgressProvider() - ?.snapshot() - ?.hasLoadedRemoteProgress - ?: hasLoadedNuvioRemoteProgress - _uiState.value = WatchProgressUiState( + val providerSnapshot = activeProgressProvider()?.snapshot() + _uiState.value = projectWatchProgressUiState( source = activeSource, entries = sortedEntries, - hasLoadedRemoteProgress = hasLoadedRemoteProgress, + providerSnapshot = providerSnapshot, + hasLoadedNuvioRemoteProgress = hasLoadedNuvioRemoteProgress, ) } @@ -1643,3 +1642,16 @@ object WatchProgressRepository { resources.any { resource -> resource.name == "meta" } } + +internal fun projectWatchProgressUiState( + source: WatchProgressSource, + entries: List, + providerSnapshot: TrackingProgressSnapshot?, + hasLoadedNuvioRemoteProgress: Boolean, +): WatchProgressUiState = WatchProgressUiState( + source = source, + entries = entries, + hiddenContentIds = providerSnapshot?.hiddenContentIds.orEmpty(), + hasLoadedRemoteProgress = + providerSnapshot?.hasLoadedRemoteProgress ?: hasLoadedNuvioRemoteProgress, +) diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliationTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliationTest.kt index fe5dab14a..1253b1fbe 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliationTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/simkl/SimklPlaybackReconciliationTest.kt @@ -255,6 +255,7 @@ class SimklPlaybackReconciliationTest { assertTrue(snapshot.reconcileWatchedPlayback().playback.isEmpty()) assertTrue(snapshot.isDroppedContent("tt4574334")) + assertEquals(setOf("tt4574334"), snapshot.droppedContentIds()) } @Test @@ -279,6 +280,7 @@ class SimklPlaybackReconciliationTest { assertEquals(snapshot.playback, snapshot.reconcileWatchedPlayback().playback) assertFalse(snapshot.isDroppedContent("tt4574334")) + assertEquals(setOf("tt1111111"), snapshot.droppedContentIds()) } @Test diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watchprogress/WatchProgressIdentityTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watchprogress/WatchProgressIdentityTest.kt index 39003816f..4ddb5a6f7 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watchprogress/WatchProgressIdentityTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watchprogress/WatchProgressIdentityTest.kt @@ -1,13 +1,40 @@ package com.nuvio.app.features.watchprogress import com.nuvio.app.features.details.MetaDetails +import com.nuvio.app.features.tracking.TrackingProgressSnapshot +import com.nuvio.app.features.tracking.WatchProgressSource import com.nuvio.app.features.watching.sync.ProgressSyncRecord import com.nuvio.app.features.watching.sync.ProgressDeltaEvent import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import kotlin.test.assertTrue class WatchProgressIdentityTest { + @Test + fun `provider hidden content changes progress ui state without changing entries`() { + val entry = entry( + parentMetaId = "show", + videoId = "show:1:2", + progressKey = "show-episode", + lastUpdatedEpochMs = 10L, + ) + val visible = projectWatchProgressUiState( + source = WatchProgressSource.SIMKL, + entries = listOf(entry), + providerSnapshot = TrackingProgressSnapshot(), + hasLoadedNuvioRemoteProgress = false, + ) + val hidden = projectWatchProgressUiState( + source = WatchProgressSource.SIMKL, + entries = listOf(entry), + providerSnapshot = TrackingProgressSnapshot(hiddenContentIds = setOf("show")), + hasLoadedNuvioRemoteProgress = false, + ) + + assertEquals(visible.entries, hidden.entries) + assertNotEquals(visible, hidden) + } @Test fun `provider change during metadata batch schedules one follow up`() {