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 dd354dd0..aa8f6e37 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 @@ -664,6 +664,25 @@ fun HomeScreen( val enabledHomeItems = remember(homeSettingsUiState.items) { homeSettingsUiState.items.filter { it.enabled } } + val visibleSeriesPosterTargets = remember(enabledHomeItems, sectionsMap) { + enabledHomeItems + .filterNot { it.isCollection } + .mapNotNull { settingsItem -> sectionsMap[settingsItem.key] } + .flatMap { section -> section.items.take(HOME_CATALOG_PREVIEW_LIMIT) } + .filter { item -> item.type.isHomeSeriesLikeType() } + .distinctBy { item -> watchedItemKey(item.type, item.id) } + } + LaunchedEffect( + visibleSeriesPosterTargets, + watchedUiState.items, + watchProgressUiState.entries, + ) { + reconcileVisibleSeriesPosterBadges( + items = visibleSeriesPosterTargets, + watchedItems = watchedUiState.items, + progressEntries = watchProgressUiState.entries, + ) + } val hasRenderableCollectionRows = remember(enabledHomeItems, collectionsMap) { enabledHomeItems.any { item -> item.isCollection && collectionsMap[item.key] != null @@ -879,6 +898,56 @@ private const val OPTIMISTIC_NEXT_UP_SEED_WINDOW_MS = 3L * 60L * 1000L private const val NEXT_UP_RESOLUTION_CONCURRENCY = 4 private const val NEXT_UP_RESOLUTION_BATCH_SIZE = NEXT_UP_RESOLUTION_CONCURRENCY +private suspend fun reconcileVisibleSeriesPosterBadges( + items: List, + watchedItems: List, + progressEntries: List, +) { + if (items.isEmpty()) return + val watchedKeys = watchedItems.mapTo(linkedSetOf()) { item -> + watchedItemKey(item.type, item.id, item.season, item.episode) + } + val touchedSeriesIds = buildSet { + watchedItems.forEach { item -> + if (item.type.isHomeSeriesLikeType() && item.season != null && item.episode != null) { + add(item.id) + } + } + progressEntries.forEach { entry -> + if (entry.parentMetaType.isHomeSeriesLikeType() && entry.isEpisode && entry.isEffectivelyCompleted) { + add(entry.parentMetaId) + } + } + } + if (touchedSeriesIds.isEmpty()) return + val todayIsoDate = CurrentDateProvider.todayIsoDate() + withContext(Dispatchers.Default) { + items + .filter { item -> item.id in touchedSeriesIds } + .forEach { item -> + val meta = runCatching { + MetaDetailsRepository.fetch(type = item.type, id = item.id) + }.getOrNull() ?: return@forEach + WatchedRepository.reconcileFullyWatchedSeriesState( + meta = meta, + todayIsoDate = todayIsoDate, + isEpisodeWatched = { episode -> + watchedItemKey(meta.type, meta.id, episode.season, episode.episode) in watchedKeys + }, + isEpisodeCompleted = { episode -> + val playbackId = meta.episodePlaybackId(episode) + progressEntries.any { entry -> + entry.videoId == playbackId && entry.isEffectivelyCompleted + } + }, + ) + } + } +} + +private fun String.isHomeSeriesLikeType(): Boolean = + trim().lowercase() in setOf("series", "show", "tv", "tvshow") + internal data class HomeNextUpResolutionPlan( val initialCandidates: List, val deferredCandidates: List, 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 0f7f5116..ae2e2b91 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 @@ -65,6 +65,9 @@ object WatchingActions { if (isCurrentlyWatched) { WatchedRepository.unmarkWatched(seriesItems) + WatchProgressRepository.clearProgress( + releasedMainEpisodes.map(meta::episodePlaybackId), + ) WatchedRepository.updateFullyWatchedSeries( id = meta.id, type = meta.type, @@ -91,6 +94,7 @@ object WatchingActions { val watchedItem = meta.toEpisodeWatchedItem(episode) if (isCurrentlyWatched) { WatchedRepository.unmarkWatched(watchedItem) + WatchProgressRepository.clearProgress(meta.episodePlaybackId(episode)) } else { WatchedRepository.markWatched(watchedItem) WatchProgressRepository.clearProgress(meta.episodePlaybackId(episode)) @@ -173,6 +177,7 @@ object WatchingActions { val watchedItems = episodes.map(meta::toEpisodeWatchedItem) if (areCurrentlyWatched) { WatchedRepository.unmarkWatched(watchedItems) + WatchProgressRepository.clearProgress(episodes.map(meta::episodePlaybackId)) } else { WatchedRepository.markWatched(watchedItems) WatchProgressRepository.clearProgress(episodes.map(meta::episodePlaybackId))