fix(simkl): refresh hidden progress immediately

This commit is contained in:
tapframe 2026-07-24 16:30:41 +05:30
parent 94ec538c10
commit 327d49d31d
8 changed files with 78 additions and 11 deletions

View file

@ -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()

View file

@ -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,
)

View file

@ -21,6 +21,12 @@ internal fun SimklSyncSnapshot.isDroppedContent(contentId: String): Boolean =
entry.matchesContent(contentId = contentId, trackingProviderItemId = null)
}
internal fun SimklSyncSnapshot.droppedContentIds(): Set<String> =
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

View file

@ -110,6 +110,7 @@ interface TrackingWatchedProvider : WatchedSyncAdapter {
data class TrackingProgressSnapshot(
val entries: List<WatchProgressEntry> = emptyList(),
val hiddenContentIds: Set<String> = emptySet(),
val hasLoadedRemoteProgress: Boolean = false,
val errorMessage: String? = null,
)

View file

@ -134,6 +134,7 @@ data class WatchProgressEntry(
data class WatchProgressUiState(
val source: WatchProgressSource = WatchProgressSource.NUVIO_SYNC,
val entries: List<WatchProgressEntry> = emptyList(),
val hiddenContentIds: Set<String> = emptySet(),
val hasLoadedRemoteProgress: Boolean = false,
) {
val byProgressKey: Map<String, WatchProgressEntry>

View file

@ -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<WatchProgressEntry>,
providerSnapshot: TrackingProgressSnapshot?,
hasLoadedNuvioRemoteProgress: Boolean,
): WatchProgressUiState = WatchProgressUiState(
source = source,
entries = entries,
hiddenContentIds = providerSnapshot?.hiddenContentIds.orEmpty(),
hasLoadedRemoteProgress =
providerSnapshot?.hasLoadedRemoteProgress ?: hasLoadedNuvioRemoteProgress,
)

View file

@ -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

View file

@ -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`() {