From 96010101b8e6b2f487f01be0be331805b6eb5113 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Fri, 22 May 2026 18:39:29 +0530 Subject: [PATCH] Fix stale series watched markers --- .../app/features/details/MetaDetailsScreen.kt | 16 +++++++++++++ .../app/features/watched/WatchedRepository.kt | 10 ++++++-- .../watching/application/WatchingActions.kt | 24 +++++++++++++++---- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt index a9ea7373..797a168b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsScreen.kt @@ -344,6 +344,22 @@ fun MetaDetailsScreen( val progressByVideoId = remember(watchProgressUiState.entries) { watchProgressUiState.byVideoId } + LaunchedEffect( + meta.id, + meta.type, + todayIsoDate, + watchedUiState.isLoaded, + watchProgressUiState.hasLoadedRemoteProgress, + watchedUiState.watchedKeys, + watchProgressUiState.entries, + ) { + if (watchedUiState.isLoaded && watchProgressUiState.hasLoadedRemoteProgress) { + WatchingActions.reconcileSeriesWatchedState( + meta = meta, + todayIsoDate = todayIsoDate, + ) + } + } val movieProgress = progressByVideoId[meta.id] ?.takeUnless { it.isCompleted } val cwPrefs by ContinueWatchingPreferencesRepository.uiState.collectAsStateWithLifecycle() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt index a208fe94..3735763f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt @@ -201,6 +201,8 @@ object WatchedRepository { todayIsoDate: String, isEpisodeCompleted: (com.nuvio.app.features.details.MetaVideo) -> Boolean = { false }, ) { + if (!meta.type.isSeriesLikeWatchedType()) return + ensureLoaded() val shouldMarkSeriesWatched = meta.hasWatchedAllMainSeasonEpisodes(todayIsoDate) { episode -> isWatched( @@ -211,11 +213,12 @@ object WatchedRepository { ) || isEpisodeCompleted(episode) } val seriesWatchedItem = meta.toSeriesWatchedItem() + val hasSeriesWatchedMarker = isWatched(id = meta.id, type = meta.type) if (shouldMarkSeriesWatched) { - if (!isWatched(id = meta.id, type = meta.type)) { + if (!hasSeriesWatchedMarker) { markWatched(seriesWatchedItem) } - } else if (isWatched(id = meta.id, type = meta.type)) { + } else if (hasSeriesWatchedMarker) { unmarkWatched(seriesWatchedItem) } } @@ -355,3 +358,6 @@ internal fun shouldUseTraktWatchedSync( isAuthenticated = isAuthenticated, source = source, ) + +private fun String.isSeriesLikeWatchedType(): Boolean = + trim().lowercase() in setOf("series", "show", "tv", "tvshow") diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/application/WatchingActions.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/application/WatchingActions.kt index 4a79954e..4d207422 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/application/WatchingActions.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/application/WatchingActions.kt @@ -7,7 +7,7 @@ import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.watched.WatchedItem import com.nuvio.app.features.watched.WatchedRepository import com.nuvio.app.features.watched.episodePlaybackId -import com.nuvio.app.features.watched.releasedPlayableEpisodes +import com.nuvio.app.features.watched.releasedMainSeasonEpisodes import com.nuvio.app.features.watched.toEpisodeWatchedItem import com.nuvio.app.features.watched.toSeriesWatchedItem import com.nuvio.app.features.watched.toWatchedItem @@ -23,7 +23,7 @@ object WatchingActions { private val actionScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) suspend fun togglePosterWatched(preview: MetaPreview) { - if (preview.type != "series") { + if (!preview.type.isSeriesLikeType()) { WatchedRepository.toggleWatched(preview.toWatchedItem(markedAtEpochMs = 0L)) return } @@ -34,14 +34,23 @@ object WatchingActions { ) val meta = MetaDetailsRepository.fetch(type = preview.type, id = preview.id) if (meta == null) { - WatchedRepository.toggleWatched(preview.toWatchedItem(markedAtEpochMs = 0L)) + if (isCurrentlyWatched) { + WatchedRepository.unmarkWatched(preview.toWatchedItem(markedAtEpochMs = 0L)) + } return } val todayIsoDate = CurrentDateProvider.todayIsoDate() + val releasedMainEpisodes = meta.releasedMainSeasonEpisodes(todayIsoDate) + if (releasedMainEpisodes.isEmpty()) { + if (isCurrentlyWatched) { + WatchedRepository.unmarkWatched(meta.toSeriesWatchedItem()) + } + return + } val seriesItems = buildList { add(meta.toSeriesWatchedItem()) - addAll(meta.releasedPlayableEpisodes(todayIsoDate).map(meta::toEpisodeWatchedItem)) + addAll(releasedMainEpisodes.map(meta::toEpisodeWatchedItem)) } if (isCurrentlyWatched) { @@ -49,7 +58,7 @@ object WatchingActions { } else { WatchedRepository.markWatched(seriesItems) WatchProgressRepository.clearProgress( - meta.releasedPlayableEpisodes(todayIsoDate).map(meta::episodePlaybackId), + releasedMainEpisodes.map(meta::episodePlaybackId), ) } } @@ -97,6 +106,8 @@ object WatchingActions { meta: MetaDetails, todayIsoDate: String = CurrentDateProvider.todayIsoDate(), ) { + if (!meta.type.isSeriesLikeType()) return + WatchedRepository.reconcileSeriesWatchedState( meta = meta, todayIsoDate = todayIsoDate, @@ -149,3 +160,6 @@ object WatchingActions { reconcileSeriesWatchedState(meta) } } + +private fun String.isSeriesLikeType(): Boolean = + trim().lowercase() in setOf("series", "show", "tv", "tvshow")