mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-30 08:09:21 +00:00
Merge pull request #1631 from skoruppa/feat/simkl-anime-tracking
fix: Simkl anime - prevent show wipe, reactive CW update, next episode resolution
This commit is contained in:
commit
4ba3df8b4e
4 changed files with 45 additions and 10 deletions
|
|
@ -568,12 +568,13 @@ fun MetaDetailsScreen(
|
|||
val movieProgress = progressByVideoId[meta.id]
|
||||
?.takeUnless { it.isCompleted }
|
||||
val cwPrefs by ContinueWatchingPreferencesRepository.uiState.collectAsStateWithLifecycle()
|
||||
val seriesAction = remember(watchProgressUiState.entries, watchedUiState.items, meta, todayIsoDate, cwPrefs.upNextFromFurthestEpisode) {
|
||||
val seriesAction = remember(watchProgressUiState.entries, watchedUiState.items, meta, todayIsoDate, cwPrefs.upNextFromFurthestEpisode, watchedUiState.watchedKeys) {
|
||||
meta.seriesPrimaryAction(
|
||||
entries = watchProgressUiState.entries,
|
||||
watchedItems = watchedUiState.items,
|
||||
todayIsoDate = todayIsoDate,
|
||||
preferFurthestEpisode = cwPrefs.upNextFromFurthestEpisode,
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
)
|
||||
}
|
||||
val seriesActionVideo = remember(seriesAction, meta.id, meta.videos) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.nuvio.app.features.details
|
|||
|
||||
import com.nuvio.app.features.watched.WatchedItem
|
||||
import com.nuvio.app.features.watched.normalizeWatchedMarkedAtEpochMs
|
||||
import com.nuvio.app.features.watched.watchedItemKey
|
||||
import com.nuvio.app.features.watching.application.WatchingState
|
||||
import com.nuvio.app.features.watchprogress.WatchProgressEntry
|
||||
import com.nuvio.app.features.watching.domain.WatchingCompletedEpisode
|
||||
import com.nuvio.app.features.watching.domain.WatchingContentRef
|
||||
|
|
@ -144,15 +146,33 @@ internal fun MetaDetails.seriesPrimaryAction(
|
|||
todayIsoDate: String,
|
||||
preferFurthestEpisode: Boolean = true,
|
||||
showUnairedNextUp: Boolean = false,
|
||||
): SeriesPrimaryAction? =
|
||||
seriesPrimaryAction(
|
||||
content = WatchingContentRef(type = type, id = id),
|
||||
watchedKeys: Set<String> = emptySet(),
|
||||
): SeriesPrimaryAction? {
|
||||
val content = WatchingContentRef(type = type, id = id)
|
||||
val effectiveWatchedItems = buildList {
|
||||
addAll(watchedItems.filter { it.type.equals(type, ignoreCase = true) && it.id.equals(id, ignoreCase = true) })
|
||||
if (watchedKeys.isNotEmpty()) {
|
||||
val existingKeys = mapTo(mutableSetOf()) { watchedItemKey(it.type, it.id, it.season, it.episode) }
|
||||
videos.forEach { video ->
|
||||
val season = video.season ?: return@forEach
|
||||
val episode = video.episode ?: return@forEach
|
||||
val key = watchedItemKey(type, id, season, episode)
|
||||
if (key in existingKeys) return@forEach
|
||||
if (WatchingState.isEpisodeWatched(watchedKeys, type, id, video)) {
|
||||
add(WatchedItem(id = id, type = type, season = season, episode = episode, name = "", markedAtEpochMs = 0L))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return seriesPrimaryAction(
|
||||
content = content,
|
||||
entries = entries,
|
||||
watchedItems = watchedItems,
|
||||
watchedItems = effectiveWatchedItems,
|
||||
todayIsoDate = todayIsoDate,
|
||||
preferFurthestEpisode = preferFurthestEpisode,
|
||||
showUnairedNextUp = showUnairedNextUp,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun MetaDetails.seriesPrimaryAction(
|
||||
content: WatchingContentRef,
|
||||
|
|
|
|||
|
|
@ -100,11 +100,13 @@ object SimklWatchedSyncAdapter : TrackingWatchedProvider {
|
|||
|
||||
override suspend fun delete(profileId: Int, items: Collection<WatchedItem>) {
|
||||
if (profileId != ProfileRepository.activeProfileId || items.isEmpty()) return
|
||||
val episodeItems = items.filter { item -> item.season != null && item.episode != null }
|
||||
if (episodeItems.isEmpty()) return
|
||||
// Optimistically mark video IDs as removed so fallback won't show them as watched
|
||||
items.forEach { item -> item.videoId?.let(SimklAnimeWatchedFallback::markOptimisticallyRemoved) }
|
||||
episodeItems.forEach { item -> item.videoId?.let(SimklAnimeWatchedFallback::markOptimisticallyRemoved) }
|
||||
SimklSyncRepository.ensureLoaded()
|
||||
val snapshot = SimklSyncRepository.state.value.snapshot
|
||||
val media = items.map { item ->
|
||||
val media = episodeItems.map { item ->
|
||||
snapshot.mediaReference(
|
||||
contentId = item.id,
|
||||
contentType = item.type,
|
||||
|
|
|
|||
|
|
@ -818,6 +818,7 @@ object WatchedRepository {
|
|||
} else if (source.providerId != null) {
|
||||
// Items not found in local store (e.g. anime resolved via snapshot fallback).
|
||||
// Still push delete to provider so it can remove from remote.
|
||||
// The observer on snapshot changes will re-pull items and update the store.
|
||||
pushDeleteToServer(items = items.toList(), source = source)
|
||||
}
|
||||
}
|
||||
|
|
@ -1010,7 +1011,8 @@ object WatchedRepository {
|
|||
/**
|
||||
* Observes provider extra watched keys (e.g. Simkl anime alternate IDs).
|
||||
* When the provider's snapshot changes (after mutations, syncs), recomputes
|
||||
* extra keys and re-publishes so watchedKeys stays reactive and current.
|
||||
* extra keys, re-pulls watched items, and re-publishes so watchedKeys and
|
||||
* items stay reactive and current.
|
||||
*/
|
||||
private fun startExtraKeysObserverIfNeeded() {
|
||||
if (extraKeysObserverJob != null) return
|
||||
|
|
@ -1021,9 +1023,19 @@ object WatchedRepository {
|
|||
adapter.observeExtraWatchedKeys(currentProfileId)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest { extraKeys ->
|
||||
val changed = providerExtraWatchedKeys[providerId] != extraKeys
|
||||
if (changed) {
|
||||
val keysChanged = providerExtraWatchedKeys[providerId] != extraKeys
|
||||
if (keysChanged) {
|
||||
providerExtraWatchedKeys[providerId] = extraKeys
|
||||
// Re-pull items from provider to reflect snapshot changes
|
||||
// (e.g. after remote episode removal)
|
||||
val freshItems = adapter.pull(
|
||||
profileId = currentProfileId,
|
||||
pageSize = watchedItemsPageSize,
|
||||
)
|
||||
val itemsByKey = freshItems.associateBy { item ->
|
||||
watchedItemKey(item.type, item.id, item.season, item.episode)
|
||||
}.toMutableMap()
|
||||
providerItemsByKey[providerId] = itemsByKey
|
||||
publish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue