mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-26 22:42:17 +00:00
feat: implement 'Up Next from furthest episode' preference in Continue Watching settings and update related logic across components
This commit is contained in:
parent
515f3de17f
commit
97f0eeb5da
12 changed files with 224 additions and 47 deletions
|
|
@ -55,6 +55,7 @@ import com.nuvio.app.features.watched.releasedEpisodesForSeason
|
|||
import com.nuvio.app.features.watchprogress.CurrentDateProvider
|
||||
import com.nuvio.app.features.watchprogress.WatchProgressRepository
|
||||
import com.nuvio.app.features.watchprogress.buildPlaybackVideoId
|
||||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
||||
import com.nuvio.app.features.watching.application.WatchingActions
|
||||
import com.nuvio.app.features.watching.application.WatchingState
|
||||
|
||||
|
|
@ -148,11 +149,13 @@ fun MetaDetailsScreen(
|
|||
}
|
||||
val movieProgress = watchProgressUiState.byVideoId[meta.id]
|
||||
?.takeUnless { it.isCompleted }
|
||||
val seriesAction = remember(watchProgressUiState.entries, watchedUiState.items, meta, todayIsoDate) {
|
||||
val cwPrefs by ContinueWatchingPreferencesRepository.uiState.collectAsStateWithLifecycle()
|
||||
val seriesAction = remember(watchProgressUiState.entries, watchedUiState.items, meta, todayIsoDate, cwPrefs.upNextFromFurthestEpisode) {
|
||||
meta.seriesPrimaryAction(
|
||||
entries = watchProgressUiState.entries,
|
||||
watchedItems = watchedUiState.items,
|
||||
todayIsoDate = todayIsoDate,
|
||||
preferFurthestEpisode = cwPrefs.upNextFromFurthestEpisode,
|
||||
)
|
||||
}
|
||||
val seriesPauseDescription = remember(seriesAction, meta.id, meta.videos) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ internal fun MetaDetails.nextReleasedEpisodeAfter(
|
|||
seasonNumber = seasonNumber,
|
||||
episodeNumber = episodeNumber,
|
||||
)
|
||||
return sortedEpisodes
|
||||
val candidates = sortedEpisodes
|
||||
.dropWhile { episode ->
|
||||
buildPlaybackVideoId(
|
||||
content = WatchingContentRef(type = type, id = id),
|
||||
|
|
@ -60,9 +60,10 @@ internal fun MetaDetails.nextReleasedEpisodeAfter(
|
|||
) != watchedVideoId
|
||||
}
|
||||
.drop(1)
|
||||
.firstOrNull { episode ->
|
||||
.filter { episode ->
|
||||
isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = episode.released)
|
||||
}
|
||||
return candidates.firstOrNull { normalizeSeasonNumber(it.season) > 0 }
|
||||
}
|
||||
|
||||
internal data class SeriesPrimaryAction(
|
||||
|
|
@ -79,6 +80,7 @@ internal fun MetaDetails.seriesPrimaryAction(
|
|||
entries: List<WatchProgressEntry>,
|
||||
watchedItems: List<WatchedItem>,
|
||||
todayIsoDate: String,
|
||||
preferFurthestEpisode: Boolean = true,
|
||||
): SeriesPrimaryAction? =
|
||||
decideSeriesPrimaryAction(
|
||||
content = WatchingContentRef(type = type, id = id),
|
||||
|
|
@ -86,6 +88,7 @@ internal fun MetaDetails.seriesPrimaryAction(
|
|||
progressRecords = entries.map(WatchProgressEntry::toDomainProgressRecord),
|
||||
watchedRecords = watchedItems.map(WatchedItem::toDomainWatchedRecord),
|
||||
todayIsoDate = todayIsoDate,
|
||||
preferFurthestEpisode = preferFurthestEpisode,
|
||||
)?.toLegacySeriesPrimaryAction()
|
||||
|
||||
internal fun MetaVideo.playLabel(): String =
|
||||
|
|
|
|||
|
|
@ -56,10 +56,11 @@ fun HomeScreen(
|
|||
val watchedUiState by WatchedRepository.uiState.collectAsStateWithLifecycle()
|
||||
val watchProgressUiState by WatchProgressRepository.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
val latestCompletedBySeries = remember(watchProgressUiState.entries, watchedUiState.items) {
|
||||
val latestCompletedBySeries = remember(watchProgressUiState.entries, watchedUiState.items, continueWatchingPreferences.upNextFromFurthestEpisode) {
|
||||
WatchingState.latestCompletedBySeries(
|
||||
progressEntries = watchProgressUiState.entries,
|
||||
watchedItems = watchedUiState.items,
|
||||
preferFurthestEpisode = continueWatchingPreferences.upNextFromFurthestEpisode,
|
||||
)
|
||||
}
|
||||
val completedSeriesCandidates = remember(latestCompletedBySeries) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ internal fun LazyListScope.continueWatchingSettingsContent(
|
|||
isTablet: Boolean,
|
||||
isVisible: Boolean,
|
||||
style: ContinueWatchingSectionStyle,
|
||||
upNextFromFurthestEpisode: Boolean,
|
||||
) {
|
||||
item {
|
||||
SettingsSection(
|
||||
|
|
@ -58,6 +59,22 @@ internal fun LazyListScope.continueWatchingSettingsContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
SettingsSection(
|
||||
title = "UP NEXT BEHAVIOR",
|
||||
isTablet = isTablet,
|
||||
) {
|
||||
SettingsGroup(isTablet = isTablet) {
|
||||
SettingsSwitchRow(
|
||||
title = "Up Next from furthest episode",
|
||||
description = "When enabled, Up Next always continues from the furthest watched episode. When disabled, it follows from the most recently watched episode — useful if you rewatch earlier episodes.",
|
||||
checked = upNextFromFurthestEpisode,
|
||||
isTablet = isTablet,
|
||||
onCheckedChange = ContinueWatchingPreferencesRepository::setUpNextFromFurthestEpisode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ fun ContinueWatchingSettingsScreen(
|
|||
isTablet = false,
|
||||
isVisible = continueWatchingPreferencesUiState.isVisible,
|
||||
style = continueWatchingPreferencesUiState.style,
|
||||
upNextFromFurthestEpisode = continueWatchingPreferencesUiState.upNextFromFurthestEpisode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ object WatchingState {
|
|||
fun latestCompletedBySeries(
|
||||
progressEntries: List<WatchProgressEntry>,
|
||||
watchedItems: List<WatchedItem>,
|
||||
preferFurthestEpisode: Boolean = true,
|
||||
): Map<WatchingContentRef, WatchingCompletedEpisode> {
|
||||
val contentRefs = buildSet {
|
||||
progressEntries.forEach { entry ->
|
||||
|
|
@ -65,6 +66,7 @@ object WatchingState {
|
|||
content = content,
|
||||
progressRecords = progressRecords,
|
||||
watchedRecords = watchedRecords,
|
||||
preferFurthestEpisode = preferFurthestEpisode,
|
||||
)?.let { completed -> content to completed }
|
||||
}.toMap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,10 +42,11 @@ fun nextReleasedEpisodeAfter(
|
|||
compareBy<WatchingReleasedEpisode>({ normalizeSeasonNumber(it.seasonNumber) }, { it.episodeNumber ?: 0 }),
|
||||
)
|
||||
val watchedVideoId = buildPlaybackVideoId(content, seasonNumber, episodeNumber)
|
||||
return sortedEpisodes
|
||||
val candidates = sortedEpisodes
|
||||
.dropWhile { episode -> buildPlaybackVideoId(content, episode.seasonNumber, episode.episodeNumber, episode.videoId) != watchedVideoId }
|
||||
.drop(1)
|
||||
.firstOrNull { episode -> isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = episode.releasedDate) }
|
||||
.filter { episode -> isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = episode.releasedDate) }
|
||||
return candidates.firstOrNull { normalizeSeasonNumber(it.seasonNumber) > 0 }
|
||||
}
|
||||
|
||||
fun decideSeriesPrimaryAction(
|
||||
|
|
@ -54,6 +55,7 @@ fun decideSeriesPrimaryAction(
|
|||
progressRecords: List<WatchingProgressRecord>,
|
||||
watchedRecords: List<WatchingWatchedRecord>,
|
||||
todayIsoDate: String,
|
||||
preferFurthestEpisode: Boolean = true,
|
||||
): WatchingSeriesPrimaryAction? {
|
||||
val resumeRecord = resumeProgressForSeries(
|
||||
content = content,
|
||||
|
|
@ -63,6 +65,7 @@ fun decideSeriesPrimaryAction(
|
|||
content = content,
|
||||
progressRecords = progressRecords,
|
||||
watchedRecords = watchedRecords,
|
||||
preferFurthestEpisode = preferFurthestEpisode,
|
||||
)
|
||||
|
||||
if (shouldPreferResume(resumeRecord = resumeRecord, latestCompletedEpisode = latestCompletedEpisode)) {
|
||||
|
|
@ -78,9 +81,10 @@ fun decideSeriesPrimaryAction(
|
|||
todayIsoDate = todayIsoDate,
|
||||
)
|
||||
} else {
|
||||
episodes
|
||||
val sorted = episodes
|
||||
.sortedWith(compareBy<WatchingReleasedEpisode>({ normalizeSeasonNumber(it.seasonNumber) }, { it.episodeNumber ?: 0 }))
|
||||
.firstOrNull { episode -> isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = episode.releasedDate) }
|
||||
val released = sorted.filter { episode -> isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = episode.releasedDate) }
|
||||
released.firstOrNull { normalizeSeasonNumber(it.seasonNumber) > 0 } ?: released.firstOrNull()
|
||||
}
|
||||
|
||||
return nextEpisode?.let { episode ->
|
||||
|
|
|
|||
|
|
@ -81,46 +81,53 @@ fun latestCompletedSeriesEpisode(
|
|||
content: WatchingContentRef,
|
||||
progressRecords: List<WatchingProgressRecord>,
|
||||
watchedRecords: List<WatchingWatchedRecord>,
|
||||
preferFurthestEpisode: Boolean = true,
|
||||
): WatchingCompletedEpisode? {
|
||||
val progressMarker = progressRecords
|
||||
.asSequence()
|
||||
.filter { record ->
|
||||
record.content == content &&
|
||||
record.isCompleted &&
|
||||
record.seasonNumber != null &&
|
||||
record.episodeNumber != null
|
||||
}
|
||||
.mapNotNull { record ->
|
||||
val seasonNumber = record.seasonNumber ?: return@mapNotNull null
|
||||
val episodeNumber = record.episodeNumber ?: return@mapNotNull null
|
||||
WatchingCompletedEpisode(
|
||||
seasonNumber = seasonNumber,
|
||||
episodeNumber = episodeNumber,
|
||||
markedAtEpochMs = record.lastUpdatedEpochMs,
|
||||
)
|
||||
}
|
||||
.maxByOrNull { marker -> marker.markedAtEpochMs }
|
||||
|
||||
val watchedMarker = watchedRecords
|
||||
.asSequence()
|
||||
.filter { record ->
|
||||
record.content == content &&
|
||||
record.seasonNumber != null &&
|
||||
record.episodeNumber != null
|
||||
}
|
||||
.mapNotNull { record ->
|
||||
val seasonNumber = record.seasonNumber ?: return@mapNotNull null
|
||||
val episodeNumber = record.episodeNumber ?: return@mapNotNull null
|
||||
WatchingCompletedEpisode(
|
||||
seasonNumber = seasonNumber,
|
||||
episodeNumber = episodeNumber,
|
||||
markedAtEpochMs = record.markedAtEpochMs,
|
||||
)
|
||||
}
|
||||
.maxByOrNull { marker -> marker.markedAtEpochMs }
|
||||
|
||||
return listOfNotNull(progressMarker, watchedMarker)
|
||||
.maxByOrNull { marker -> marker.markedAtEpochMs }
|
||||
val ordering = if (preferFurthestEpisode) {
|
||||
compareBy<WatchingCompletedEpisode>(
|
||||
{ normalizeSeasonNumber(it.seasonNumber) },
|
||||
{ it.episodeNumber },
|
||||
{ it.markedAtEpochMs },
|
||||
)
|
||||
} else {
|
||||
compareBy<WatchingCompletedEpisode> { it.markedAtEpochMs }
|
||||
}
|
||||
val allMarkers = buildList {
|
||||
progressRecords
|
||||
.asSequence()
|
||||
.filter { record ->
|
||||
record.content == content &&
|
||||
record.isCompleted &&
|
||||
record.seasonNumber != null &&
|
||||
record.episodeNumber != null
|
||||
}
|
||||
.mapNotNullTo(this) { record ->
|
||||
val seasonNumber = record.seasonNumber ?: return@mapNotNullTo null
|
||||
val episodeNumber = record.episodeNumber ?: return@mapNotNullTo null
|
||||
WatchingCompletedEpisode(
|
||||
seasonNumber = seasonNumber,
|
||||
episodeNumber = episodeNumber,
|
||||
markedAtEpochMs = record.lastUpdatedEpochMs,
|
||||
)
|
||||
}
|
||||
watchedRecords
|
||||
.asSequence()
|
||||
.filter { record ->
|
||||
record.content == content &&
|
||||
record.seasonNumber != null &&
|
||||
record.episodeNumber != null
|
||||
}
|
||||
.mapNotNullTo(this) { record ->
|
||||
val seasonNumber = record.seasonNumber ?: return@mapNotNullTo null
|
||||
val episodeNumber = record.episodeNumber ?: return@mapNotNullTo null
|
||||
WatchingCompletedEpisode(
|
||||
seasonNumber = seasonNumber,
|
||||
episodeNumber = episodeNumber,
|
||||
markedAtEpochMs = record.markedAtEpochMs,
|
||||
)
|
||||
}
|
||||
}
|
||||
return allMarkers.maxWithOrNull(ordering)
|
||||
}
|
||||
|
||||
fun normalizeSeasonNumber(seasonNumber: Int?): Int = seasonNumber?.coerceAtLeast(0) ?: 0
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import kotlinx.serialization.json.Json
|
|||
private data class StoredContinueWatchingPreferences(
|
||||
val isVisible: Boolean = true,
|
||||
val style: ContinueWatchingSectionStyle = ContinueWatchingSectionStyle.Wide,
|
||||
val upNextFromFurthestEpisode: Boolean = true,
|
||||
)
|
||||
|
||||
object ContinueWatchingPreferencesRepository {
|
||||
|
|
@ -56,6 +57,7 @@ object ContinueWatchingPreferencesRepository {
|
|||
ContinueWatchingPreferencesUiState(
|
||||
isVisible = stored.isVisible,
|
||||
style = stored.style,
|
||||
upNextFromFurthestEpisode = stored.upNextFromFurthestEpisode,
|
||||
)
|
||||
} else {
|
||||
ContinueWatchingPreferencesUiState()
|
||||
|
|
@ -74,12 +76,19 @@ object ContinueWatchingPreferencesRepository {
|
|||
persist()
|
||||
}
|
||||
|
||||
fun setUpNextFromFurthestEpisode(enabled: Boolean) {
|
||||
ensureLoaded()
|
||||
_uiState.value = _uiState.value.copy(upNextFromFurthestEpisode = enabled)
|
||||
persist()
|
||||
}
|
||||
|
||||
private fun persist() {
|
||||
ContinueWatchingPreferencesStorage.savePayload(
|
||||
json.encodeToString(
|
||||
StoredContinueWatchingPreferences(
|
||||
isVisible = _uiState.value.isVisible,
|
||||
style = _uiState.value.style,
|
||||
upNextFromFurthestEpisode = _uiState.value.upNextFromFurthestEpisode,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ data class ContinueWatchingItem(
|
|||
data class ContinueWatchingPreferencesUiState(
|
||||
val isVisible: Boolean = true,
|
||||
val style: ContinueWatchingSectionStyle = ContinueWatchingSectionStyle.Wide,
|
||||
val upNextFromFurthestEpisode: Boolean = true,
|
||||
)
|
||||
|
||||
internal fun WatchProgressEntry.toContinueWatchingItem(): ContinueWatchingItem {
|
||||
|
|
|
|||
|
|
@ -75,4 +75,74 @@ class SeriesContinuityTest {
|
|||
assertEquals("show:1:2", action.videoId)
|
||||
assertEquals(1_500L, action.resumePositionMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decideSeriesPrimaryAction_skips_specials_for_initial_play() {
|
||||
val episodesWithSpecials = listOf(
|
||||
WatchingReleasedEpisode(videoId = "sp1", seasonNumber = 0, episodeNumber = 1, title = "Special 1", releasedDate = "2026-01-01"),
|
||||
WatchingReleasedEpisode(videoId = "ep1", seasonNumber = 1, episodeNumber = 1, title = "Pilot", releasedDate = "2026-01-15"),
|
||||
WatchingReleasedEpisode(videoId = "ep2", seasonNumber = 1, episodeNumber = 2, title = "Episode 2", releasedDate = "2026-01-22"),
|
||||
WatchingReleasedEpisode(videoId = "ep3", seasonNumber = 2, episodeNumber = 1, title = "S2 Premiere", releasedDate = "2026-03-01"),
|
||||
)
|
||||
val action = decideSeriesPrimaryAction(
|
||||
content = show,
|
||||
episodes = episodesWithSpecials,
|
||||
progressRecords = emptyList(),
|
||||
watchedRecords = emptyList(),
|
||||
todayIsoDate = "2026-03-30",
|
||||
)
|
||||
|
||||
assertNotNull(action)
|
||||
assertEquals("Play S1E1", action.label)
|
||||
assertEquals("show:1:1", action.videoId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decideSeriesPrimaryAction_falls_back_to_specials_when_no_main_season() {
|
||||
val specialsOnly = listOf(
|
||||
WatchingReleasedEpisode(videoId = "sp1", seasonNumber = 0, episodeNumber = 1, title = "Special 1", releasedDate = "2026-01-01"),
|
||||
WatchingReleasedEpisode(videoId = "sp2", seasonNumber = 0, episodeNumber = 2, title = "Special 2", releasedDate = "2026-01-15"),
|
||||
)
|
||||
val action = decideSeriesPrimaryAction(
|
||||
content = show,
|
||||
episodes = specialsOnly,
|
||||
progressRecords = emptyList(),
|
||||
watchedRecords = emptyList(),
|
||||
todayIsoDate = "2026-03-30",
|
||||
)
|
||||
|
||||
assertNotNull(action)
|
||||
assertEquals("Play S0E1", action.label)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decideSeriesPrimaryAction_skips_watched_episodes_after_rewatch() {
|
||||
val twoSeasons = listOf(
|
||||
WatchingReleasedEpisode(videoId = "ep1", seasonNumber = 1, episodeNumber = 1, title = "S1E1", releasedDate = "2026-01-01"),
|
||||
WatchingReleasedEpisode(videoId = "ep2", seasonNumber = 1, episodeNumber = 2, title = "S1E2", releasedDate = "2026-01-08"),
|
||||
WatchingReleasedEpisode(videoId = "ep3", seasonNumber = 1, episodeNumber = 3, title = "S1E3", releasedDate = "2026-01-15"),
|
||||
WatchingReleasedEpisode(videoId = "ep4", seasonNumber = 2, episodeNumber = 1, title = "S2E1", releasedDate = "2026-03-01"),
|
||||
WatchingReleasedEpisode(videoId = "ep5", seasonNumber = 2, episodeNumber = 2, title = "S2E2", releasedDate = "2026-03-08"),
|
||||
)
|
||||
val action = decideSeriesPrimaryAction(
|
||||
content = show,
|
||||
episodes = twoSeasons,
|
||||
progressRecords = listOf(
|
||||
// All of season 1 completed
|
||||
WatchingProgressRecord(content = show, videoId = "show:1:1", seasonNumber = 1, episodeNumber = 1, lastUpdatedEpochMs = 100L, isCompleted = true),
|
||||
WatchingProgressRecord(content = show, videoId = "show:1:2", seasonNumber = 1, episodeNumber = 2, lastUpdatedEpochMs = 200L, isCompleted = true),
|
||||
WatchingProgressRecord(content = show, videoId = "show:1:3", seasonNumber = 1, episodeNumber = 3, lastUpdatedEpochMs = 300L, isCompleted = true),
|
||||
// S2E1 completed
|
||||
WatchingProgressRecord(content = show, videoId = "show:2:1", seasonNumber = 2, episodeNumber = 1, lastUpdatedEpochMs = 400L, isCompleted = true),
|
||||
// Re-watched S1E1 recently — newer timestamp but earlier episode
|
||||
WatchingProgressRecord(content = show, videoId = "show:1:1", seasonNumber = 1, episodeNumber = 1, lastUpdatedEpochMs = 900L, isCompleted = true),
|
||||
),
|
||||
watchedRecords = emptyList(),
|
||||
todayIsoDate = "2026-03-30",
|
||||
)
|
||||
|
||||
assertNotNull(action)
|
||||
assertEquals("Up Next S2E2", action.label)
|
||||
assertEquals("show:2:2", action.videoId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,4 +84,63 @@ class WatchingPoliciesTest {
|
|||
assertEquals(3, latestCompleted.episodeNumber)
|
||||
assertEquals(300L, latestCompleted.markedAtEpochMs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun latestCompletedSeriesEpisode_picks_furthest_episode_even_with_older_timestamp() {
|
||||
val latestCompleted = latestCompletedSeriesEpisode(
|
||||
content = show,
|
||||
progressRecords = listOf(
|
||||
WatchingProgressRecord(
|
||||
content = show,
|
||||
videoId = "show:1:5",
|
||||
seasonNumber = 1,
|
||||
episodeNumber = 5,
|
||||
lastUpdatedEpochMs = 100L,
|
||||
isCompleted = true,
|
||||
),
|
||||
WatchingProgressRecord(
|
||||
content = show,
|
||||
videoId = "show:1:1",
|
||||
seasonNumber = 1,
|
||||
episodeNumber = 1,
|
||||
lastUpdatedEpochMs = 600L,
|
||||
isCompleted = true,
|
||||
),
|
||||
),
|
||||
watchedRecords = emptyList(),
|
||||
)
|
||||
|
||||
assertNotNull(latestCompleted)
|
||||
assertEquals(1, latestCompleted.seasonNumber)
|
||||
assertEquals(5, latestCompleted.episodeNumber)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun latestCompletedSeriesEpisode_picks_furthest_season_over_recent_rewatch() {
|
||||
val latestCompleted = latestCompletedSeriesEpisode(
|
||||
content = show,
|
||||
progressRecords = listOf(
|
||||
WatchingProgressRecord(
|
||||
content = show,
|
||||
videoId = "show:2:3",
|
||||
seasonNumber = 2,
|
||||
episodeNumber = 3,
|
||||
lastUpdatedEpochMs = 200L,
|
||||
isCompleted = true,
|
||||
),
|
||||
),
|
||||
watchedRecords = listOf(
|
||||
WatchingWatchedRecord(
|
||||
content = show,
|
||||
seasonNumber = 1,
|
||||
episodeNumber = 1,
|
||||
markedAtEpochMs = 500L,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertNotNull(latestCompleted)
|
||||
assertEquals(2, latestCompleted.seasonNumber)
|
||||
assertEquals(3, latestCompleted.episodeNumber)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue