diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt index 1f4ceef7..91538d6e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/TmdbEntityBrowseScreen.kt @@ -90,6 +90,7 @@ fun TmdbEntityBrowseScreen( WatchedRepository.ensureLoaded() WatchedRepository.uiState }.collectAsStateWithLifecycle() + val fullyWatchedSeriesKeys by WatchedRepository.fullyWatchedSeriesKeys.collectAsStateWithLifecycle() val loadFailedMessage = stringResource(Res.string.details_browse_load_failed, entityName) LaunchedEffect(entityKind, entityId) { @@ -122,6 +123,7 @@ fun TmdbEntityBrowseScreen( data = state.data, sourceType = sourceType, watchedKeys = watchedUiState.watchedKeys, + fullyWatchedSeriesKeys = fullyWatchedSeriesKeys, onOpenMeta = onOpenMeta, ) } @@ -150,6 +152,7 @@ private fun EntityBrowseContent( data: TmdbEntityBrowseData, sourceType: String, watchedKeys: Set, + fullyWatchedSeriesKeys: Set, onOpenMeta: (MetaPreview) -> Unit, ) { val backgroundUrl = remember(data.rails, sourceType) { @@ -192,6 +195,7 @@ private fun EntityBrowseContent( WideEntityBrowseContent( data = data, watchedKeys = watchedKeys, + fullyWatchedSeriesKeys = fullyWatchedSeriesKeys, onOpenMeta = onOpenMeta, ) } else if (data.rails.isEmpty()) { @@ -225,6 +229,7 @@ private fun EntityBrowseContent( title = entityRailTitle(rail), items = rail.items, watchedKeys = watchedKeys, + fullyWatchedSeriesKeys = fullyWatchedSeriesKeys, headerHorizontalPadding = 20.dp, onPosterClick = onOpenMeta, ) @@ -242,6 +247,7 @@ private fun EntityBrowseContent( private fun WideEntityBrowseContent( data: TmdbEntityBrowseData, watchedKeys: Set, + fullyWatchedSeriesKeys: Set, onOpenMeta: (MetaPreview) -> Unit, ) { Row( @@ -292,6 +298,7 @@ private fun WideEntityBrowseContent( title = entityRailTitle(rail), items = rail.items, watchedKeys = watchedKeys, + fullyWatchedSeriesKeys = fullyWatchedSeriesKeys, headerHorizontalPadding = 0.dp, onPosterClick = onOpenMeta, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailPosterRailSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailPosterRailSection.kt index ba229f7d..b5b570e4 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailPosterRailSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailPosterRailSection.kt @@ -25,6 +25,7 @@ fun DetailPosterRailSection( items: List, watchedKeys: Set, modifier: Modifier = Modifier, + fullyWatchedSeriesKeys: Set = emptySet(), showHeader: Boolean = true, headerHorizontalPadding: Dp = 0.dp, horizontalScrollPadding: Dp = 0.dp, @@ -50,6 +51,7 @@ fun DetailPosterRailSection( isWatched = WatchingState.isPosterWatched( watchedKeys = watchedKeys, item = item, + fullyWatchedSeriesKeys = fullyWatchedSeriesKeys, ), onClick = onPosterClick?.let { { it(item) } }, onLongClick = onPosterLongClick?.let { { it(item) } }, 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 5610f69c..a71689d6 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 @@ -63,14 +63,15 @@ object SimklWatchedSyncAdapter : TrackingWatchedProvider { intent = TrackingRefreshIntent.AUTOMATIC, origin = SimklRefreshOrigin.WATCHED_ITEMS, ) - return SimklSyncRepository.state.value.snapshot.animeAlternateWatchedKeys() + val snapshot = SimklSyncRepository.state.value.snapshot + return snapshot.animeAlternateWatchedKeys() + snapshot.movieAlternateWatchedKeys() } override fun observeExtraWatchedKeys(profileId: Int): kotlinx.coroutines.flow.Flow> = SimklSyncRepository.state .map { state -> SimklAnimeWatchedFallback.clearOptimisticRemovals() - state.snapshot.animeAlternateWatchedKeys() + state.snapshot.animeAlternateWatchedKeys() + state.snapshot.movieAlternateWatchedKeys() } .distinctUntilChanged() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklProjections.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklProjections.kt index 31ba673e..fccf66d1 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklProjections.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/simkl/SimklProjections.kt @@ -142,6 +142,26 @@ internal fun SimklSyncSnapshot.animeAlternateWatchedKeys(): Set { return extraKeys } +/** + * Builds watched keys under alternate content IDs for completed/watched movies. + * This allows entity browsers showing movies under tmdb: IDs to display watched badges + * even though the primary watched key uses an IMDB ID. + */ +internal fun SimklSyncSnapshot.movieAlternateWatchedKeys(): Set { + val extraKeys = linkedSetOf() + entries.forEach { entry -> + if (entry.mediaType != SimklMediaType.MOVIES) return@forEach + if (entry.lastWatchedAt == null && entry.status != SimklListStatus.COMPLETED) return@forEach + val media = entry.media ?: return@forEach + val contentId = media.canonicalContentId() ?: return@forEach + val alternateIds = media.alternateContentIds() - contentId + alternateIds.forEach { altId -> + extraKeys += watchedItemKey("movie", altId) + } + } + return extraKeys +} + internal fun SimklSyncSnapshot.toSimklProgressEntries(): List = playback .mapNotNull(SimklPlaybackSession::toWatchProgressEntry)