From 9056716c068f1b815848c740fe93efc0bec6abab Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Tue, 12 May 2026 12:38:30 +0530 Subject: [PATCH] fix: update global index fallback logic fixes #1027 --- .../details/SeriesPlaybackResolver.kt | 9 ++++--- .../watching/domain/SeriesContinuity.kt | 9 ++++--- .../details/SeriesPlaybackResolverTest.kt | 27 +++++++++++++++++++ .../watching/domain/SeriesContinuityTest.kt | 24 +++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolver.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolver.kt index ac964731..d2210058 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolver.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolver.kt @@ -98,11 +98,14 @@ internal fun MetaDetails.nextReleasedEpisodeAfter( // Fallback: if the seed wasn't found by season+episode (anime with absolute // numbering on Trakt vs multi-season on addon), try global index matching. if (watchedIndex < 0 && seasonNumber != null && episodeNumber != null) { - val addonSeasons = sortedEpisodes.mapTo(mutableSetOf()) { it.season } + val mainEpisodes = sortedEpisodes.filter { episode -> normalizeSeasonNumber(episode.season) > 0 } + val addonSeasons = mainEpisodes.mapTo(mutableSetOf()) { episode -> + normalizeSeasonNumber(episode.season) + } if (seasonNumber == 1 && addonSeasons.size > 1 && episodeNumber > 0) { val globalIndex = episodeNumber - 1 - if (globalIndex in sortedEpisodes.indices) { - watchedIndex = globalIndex + if (globalIndex in mainEpisodes.indices) { + watchedIndex = sortedEpisodes.indexOf(mainEpisodes[globalIndex]) } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuity.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuity.kt index 59c074ee..10263a55 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuity.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuity.kt @@ -53,11 +53,14 @@ fun nextReleasedEpisodeAfter( // Fallback: if the seed wasn't found by season+episode (anime with absolute // numbering on Trakt vs multi-season on addon), try global index matching. if (watchedIndex < 0 && seasonNumber != null && episodeNumber != null) { - val addonSeasons = sortedEpisodes.mapTo(mutableSetOf()) { it.seasonNumber } + val mainEpisodes = sortedEpisodes.filter { episode -> normalizeSeasonNumber(episode.seasonNumber) > 0 } + val addonSeasons = mainEpisodes.mapTo(mutableSetOf()) { episode -> + normalizeSeasonNumber(episode.seasonNumber) + } if (seasonNumber == 1 && addonSeasons.size > 1 && episodeNumber > 0) { val globalIndex = episodeNumber - 1 - if (globalIndex in sortedEpisodes.indices) { - watchedIndex = globalIndex + if (globalIndex in mainEpisodes.indices) { + watchedIndex = sortedEpisodes.indexOf(mainEpisodes[globalIndex]) } } } diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolverTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolverTest.kt index 1713004f..e5428e16 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolverTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/details/SeriesPlaybackResolverTest.kt @@ -88,4 +88,31 @@ class SeriesPlaybackResolverTest { assertEquals("Up Next • S1E3", action.label) assertEquals("show:1:3", action.videoId) } + + @Test + fun nextReleasedEpisodeAfter_global_index_fallback_ignores_specials() { + val meta = MetaDetails( + id = "show", + type = "series", + name = "Show", + videos = listOf( + MetaVideo(id = "sp1", title = "Special 1", season = 0, episode = 1, released = "2026-01-01"), + MetaVideo(id = "s1e1", title = "Episode 1", season = 1, episode = 1, released = "2026-01-08"), + MetaVideo(id = "s1e2", title = "Episode 2", season = 1, episode = 2, released = "2026-01-15"), + MetaVideo(id = "s2e1", title = "Episode 3", season = 2, episode = 1, released = "2026-01-22"), + MetaVideo(id = "s2e2", title = "Episode 4", season = 2, episode = 2, released = "2026-01-29"), + ), + ) + + val nextEpisode = meta.nextReleasedEpisodeAfter( + seasonNumber = 1, + episodeNumber = 3, + todayIsoDate = "2026-02-01", + ) + + assertNotNull(nextEpisode) + assertEquals(2, nextEpisode.season) + assertEquals(2, nextEpisode.episode) + assertEquals("s2e2", nextEpisode.id) + } } diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuityTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuityTest.kt index cb3f6ba7..5aab3131 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuityTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/SeriesContinuityTest.kt @@ -97,6 +97,30 @@ class SeriesContinuityTest { assertEquals("show:1:1", action.videoId) } + @Test + fun nextReleasedEpisodeAfter_global_index_fallback_ignores_specials() { + val episodesWithSpecials = listOf( + WatchingReleasedEpisode(videoId = "sp1", seasonNumber = 0, episodeNumber = 1, title = "Special 1", releasedDate = "2026-01-01"), + WatchingReleasedEpisode(videoId = "s1e1", seasonNumber = 1, episodeNumber = 1, title = "Episode 1", releasedDate = "2026-01-08"), + WatchingReleasedEpisode(videoId = "s1e2", seasonNumber = 1, episodeNumber = 2, title = "Episode 2", releasedDate = "2026-01-15"), + WatchingReleasedEpisode(videoId = "s2e1", seasonNumber = 2, episodeNumber = 1, title = "Episode 3", releasedDate = "2026-01-22"), + WatchingReleasedEpisode(videoId = "s2e2", seasonNumber = 2, episodeNumber = 2, title = "Episode 4", releasedDate = "2026-01-29"), + ) + + val nextEpisode = nextReleasedEpisodeAfter( + content = show, + episodes = episodesWithSpecials, + seasonNumber = 1, + episodeNumber = 3, + todayIsoDate = "2026-02-01", + ) + + assertNotNull(nextEpisode) + assertEquals(2, nextEpisode.seasonNumber) + assertEquals(2, nextEpisode.episodeNumber) + assertEquals("s2e2", nextEpisode.videoId) + } + @Test fun decideSeriesPrimaryAction_falls_back_to_specials_when_no_main_season() { val specialsOnly = listOf(