feat: Introduce season support utilities and refactor episode sorting logic

This commit is contained in:
tapframe 2026-03-28 20:58:27 +05:30
parent 69b9f5fabb
commit 44785331bb
4 changed files with 66 additions and 19 deletions

View file

@ -7,14 +7,7 @@ import com.nuvio.app.features.watchprogress.resumeEntryForSeries
internal fun MetaDetails.sortedPlayableEpisodes(): List<MetaVideo> =
videos
.filter { it.season != null || it.episode != null }
.sortedWith(
compareBy<MetaVideo>(
{ it.season ?: Int.MAX_VALUE },
{ it.episode ?: Int.MAX_VALUE },
{ it.released ?: "" },
{ it.title },
),
)
.sortedWith(metaVideoSeasonEpisodeComparator)
internal fun MetaDetails.firstPlayableEpisode(): MetaVideo? =
sortedPlayableEpisodes().firstOrNull()
@ -126,4 +119,3 @@ private fun MetaVideo.isReleasedBy(todayIsoDate: String): Boolean {
?: return true
return releaseDate <= todayIsoDate
}

View file

@ -0,0 +1,25 @@
package com.nuvio.app.features.details
internal const val SPECIALS_SEASON_NUMBER = 0
internal val metaVideoSeasonEpisodeComparator: Comparator<MetaVideo> =
compareBy<MetaVideo>(
{ seasonSortKey(it.season) },
{ it.episode ?: Int.MAX_VALUE },
{ it.released ?: "" },
{ it.title },
)
internal fun normalizeSeasonNumber(seasonNumber: Int?): Int =
if (seasonNumber == null || seasonNumber <= SPECIALS_SEASON_NUMBER) {
SPECIALS_SEASON_NUMBER
} else {
seasonNumber
}
internal fun seasonSortKey(seasonNumber: Int?): Int =
if (seasonNumber == null || seasonNumber <= SPECIALS_SEASON_NUMBER) {
Int.MAX_VALUE
} else {
seasonNumber
}

View file

@ -42,6 +42,9 @@ import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge
import com.nuvio.app.core.ui.NuvioProgressBar
import com.nuvio.app.features.details.MetaDetails
import com.nuvio.app.features.details.MetaVideo
import com.nuvio.app.features.details.metaVideoSeasonEpisodeComparator
import com.nuvio.app.features.details.normalizeSeasonNumber
import com.nuvio.app.features.details.seasonSortKey
import com.nuvio.app.features.watchprogress.WatchProgressEntry
import com.nuvio.app.features.watchprogress.buildPlaybackVideoId
@ -62,20 +65,13 @@ fun DetailSeriesContent(
log.w { "All videos lack season/episode fields! First: ${meta.videos.first()}" }
}
withSeasonOrEp
.sortedWith(
compareBy<MetaVideo>(
{ it.season ?: Int.MAX_VALUE },
{ it.episode ?: Int.MAX_VALUE },
{ it.released ?: "" },
{ it.title },
),
)
.groupBy { it.season ?: 1 }
.sortedWith(metaVideoSeasonEpisodeComparator)
.groupBy { normalizeSeasonNumber(it.season) }
}
if (groupedEpisodes.isEmpty()) return
val seasons = groupedEpisodes.keys.sorted()
val seasons = groupedEpisodes.keys.sortedBy(::seasonSortKey)
val defaultSeason = seasons.first()
var selectedSeason by rememberSaveable(meta.id) { mutableStateOf(defaultSeason) }
val currentSeason = selectedSeason.takeIf { it in groupedEpisodes } ?: defaultSeason

View file

@ -0,0 +1,34 @@
package com.nuvio.app.features.details
import kotlin.test.Test
import kotlin.test.assertEquals
class SeriesSeasonSupportTest {
@Test
fun `normalize season number maps zero and null to specials`() {
assertEquals(SPECIALS_SEASON_NUMBER, normalizeSeasonNumber(0))
assertEquals(SPECIALS_SEASON_NUMBER, normalizeSeasonNumber(null))
assertEquals(2, normalizeSeasonNumber(2))
}
@Test
fun `sorted playable episodes place specials after numbered seasons`() {
val meta = MetaDetails(
id = "show",
type = "series",
name = "Show",
videos = listOf(
MetaVideo(id = "special-1", title = "Special 1", season = 0, episode = 1),
MetaVideo(id = "season-2", title = "Episode 1", season = 2, episode = 1),
MetaVideo(id = "season-1", title = "Episode 1", season = 1, episode = 1),
MetaVideo(id = "special-2", title = "Special 2", season = 0, episode = 2),
),
)
assertEquals(
listOf("season-1", "season-2", "special-1", "special-2"),
meta.sortedPlayableEpisodes().map(MetaVideo::id),
)
}
}