diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 6f54ce85a..aa83758b8 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -2072,11 +2072,11 @@ Release has no tag or name Airs %1$s - Airs today - Airs tomorrow + Airs Today + Airs Tomorrow - Airs in %1$d day - Airs in %1$d days + Airs in %1$d Day + Airs in %1$d Days %1$s Today diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt index 292afb191..04f1e3b2d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeContinueWatchingSection.kt @@ -638,12 +638,12 @@ private fun ContinueWatchingCard( ) } val todayIsoDate = CurrentDateProvider.todayIsoDate() - val compactAirDateText = if (item.progressFraction <= 0f && item.seasonNumber != null && item.episodeNumber != null) { - computeAirDateBadgeText(item.released, todayIsoDate, compact = true) + val airDateText = if (item.progressFraction <= 0f && item.seasonNumber != null && item.episodeNumber != null) { + computeAirDateBadgeText(item.released, todayIsoDate) } else { null } - val preferBackdropForNextUp = item.isNextUp && compactAirDateText != null && !item.isReleaseAlert + val preferBackdropForNextUp = item.isNextUp && airDateText != null && !item.isReleaseAlert val imageUrl = item.continueWatchingCardArtworkUrl( useEpisodeThumbnails = useEpisodeThumbnails, preferBackdropForNextUp = preferBackdropForNextUp, @@ -654,8 +654,8 @@ private fun ContinueWatchingCard( } else { null } - val episodeTitle = item.episodeTitle?.trim()?.takeIf { it.isNotBlank() } ?: compactAirDateText - val badgeText = continueWatchingCardBadgeText(item = item, compactAirDateText = compactAirDateText) + val episodeTitle = item.episodeTitle?.trim()?.takeIf { it.isNotBlank() } ?: airDateText + val badgeText = continueWatchingCardBadgeText(item = item, airDateText = airDateText) val backgroundColor = MaterialTheme.colorScheme.background val badgeBackground = when { item.isNewSeasonRelease -> ContinueWatchingNewSeasonBadgeColor @@ -800,7 +800,7 @@ private fun ContinueWatchingCard( @Composable private fun continueWatchingCardBadgeText( item: ContinueWatchingItem, - compactAirDateText: String?, + airDateText: String?, ): String { if (item.progressFraction > 0f) { if (item.durationMs <= 0L) { @@ -827,7 +827,7 @@ private fun continueWatchingCardBadgeText( return when { item.isReleaseAlert && item.isNewSeasonRelease -> stringResource(Res.string.cw_new_season) item.isReleaseAlert -> stringResource(Res.string.cw_new_episode) - compactAirDateText != null -> compactAirDateText + airDateText != null -> airDateText else -> stringResource(Res.string.home_continue_watching_up_next) } } @@ -903,7 +903,7 @@ private fun ContinueWatchingWideCard( else stringResource(Res.string.cw_new_episode) } else -> { - computeAirDateBadgeText(item.released, todayIsoDate, compact = isCompact) + computeAirDateBadgeText(item.released, todayIsoDate) ?: stringResource(Res.string.home_continue_watching_up_next) } } @@ -1018,7 +1018,7 @@ private fun ContinueWatchingPosterCard( else stringResource(Res.string.cw_new_episode) } else -> { - computeAirDateBadgeText(item.released, todayIsoDate, compact = true) + computeAirDateBadgeText(item.released, todayIsoDate) ?: stringResource(Res.string.home_continue_watching_up_next) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/WatchingPolicies.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/WatchingPolicies.kt index 92047bb28..020571dc6 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/WatchingPolicies.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/domain/WatchingPolicies.kt @@ -58,7 +58,8 @@ internal fun shouldSurfaceNextEpisode( todayIsoDate = todayIsoDate, releasedDate = releasedDate, ) ?: return false - if (!showUnairedNextUp || daysUntilRelease <= 0) return false + if (daysUntilRelease <= 0) return true + if (!showUnairedNextUp) return false return !isSeasonRollover || daysUntilRelease <= UpcomingNextSeasonWindowDays } if (!isSeasonRollover) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/AirDateUtils.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/AirDateUtils.kt index d2809caf9..d48bb51d6 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/AirDateUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/AirDateUtils.kt @@ -13,7 +13,6 @@ import co.touchlab.kermit.Logger fun computeAirDateBadgeText( releasedIso: String?, todayIsoDate: String, - compact: Boolean ): String? { if (releasedIso.isNullOrBlank() || todayIsoDate.isBlank()) { return null @@ -31,22 +30,12 @@ fun computeAirDateBadgeText( return when { daysUntil < 0 -> null - daysUntil == 0 -> { - if (compact) stringResource(Res.string.cw_airs_today_short) - else stringResource(Res.string.cw_airs_today) - } - daysUntil == 1 -> { - if (compact) stringResource(Res.string.cw_airs_tomorrow_short) - else stringResource(Res.string.cw_airs_tomorrow) - } - daysUntil in 2..7 -> { - if (compact) pluralStringResource(Res.plurals.cw_airs_in_days_short, daysUntil, daysUntil) - else pluralStringResource(Res.plurals.cw_airs_in_days, daysUntil, daysUntil) - } + daysUntil == 0 -> stringResource(Res.string.cw_airs_today) + daysUntil == 1 -> stringResource(Res.string.cw_airs_tomorrow) + daysUntil in 2..7 -> pluralStringResource(Res.plurals.cw_airs_in_days, daysUntil, daysUntil) else -> { val formattedDate = formatReleaseDateWithoutYear(releasedIso) - if (compact) stringResource(Res.string.cw_airs_date_short, formattedDate) - else stringResource(Res.string.cw_airs_date, formattedDate) + stringResource(Res.string.cw_airs_date, formattedDate) } } } 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 e619ae245..126d80696 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 @@ -285,6 +285,26 @@ class SeriesContinuityTest { assertEquals("s1e2", nextEpisode.videoId) } + @Test + fun nextReleasedEpisodeAfter_keeps_dated_unavailable_episode_on_release_day() { + val episodes = listOf( + WatchingReleasedEpisode(videoId = "s1e1", seasonNumber = 1, episodeNumber = 1, title = "Episode 1", releasedDate = "2026-07-01"), + WatchingReleasedEpisode(videoId = "s1e2", seasonNumber = 1, episodeNumber = 2, title = "Episode 2", releasedDate = "2026-07-12T20:00:00", available = false), + ) + + val nextEpisode = nextReleasedEpisodeAfter( + content = show, + episodes = episodes, + seasonNumber = 1, + episodeNumber = 1, + todayIsoDate = "2026-07-12", + showUnairedNextUp = true, + ) + + assertNotNull(nextEpisode) + assertEquals("s1e2", nextEpisode.videoId) + } + @Test fun decideSeriesPrimaryAction_falls_back_to_specials_when_no_main_season() { val specialsOnly = listOf( diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/WatchingPoliciesTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/WatchingPoliciesTest.kt index 33d3f12ed..b5da7cd1b 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/WatchingPoliciesTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/domain/WatchingPoliciesTest.kt @@ -114,6 +114,20 @@ class WatchingPoliciesTest { ) } + @Test + fun shouldSurfaceNextEpisode_keeps_dated_unavailable_episode_visible_on_release_day() { + assertTrue( + shouldSurfaceNextEpisode( + watchedSeasonNumber = 1, + candidateSeasonNumber = 1, + todayIsoDate = "2026-07-12", + releasedDate = "2026-07-12T20:00:00", + showUnairedNextUp = true, + available = false, + ), + ) + } + @Test fun shouldSurfaceNextEpisode_keeps_unavailable_phantom_episode_hidden() { assertFalse(