diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/format/ReleaseDateDisplay.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/format/ReleaseDateDisplay.kt new file mode 100644 index 000000000..93d977620 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/format/ReleaseDateDisplay.kt @@ -0,0 +1,32 @@ +package com.nuvio.app.core.format + +private val MONTH_NAMES = listOf( + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +) + +/** + * Formats ISO calendar dates (yyyy-MM-dd or yyyy-MM-ddTHH:mm:ss…) for UI as "2025 February 1". + * Other strings (e.g. year-only "2024", human text from addons) are returned unchanged. + */ +fun formatReleaseDateForDisplay(raw: String): String { + val trimmed = raw.trim() + if (trimmed.isEmpty()) return raw + val datePart = trimmed.substringBefore('T').trim() + val parts = datePart.split('-') + if (parts.size != 3) return raw + val year = parts[0].toIntOrNull() ?: return raw + val month = parts[1].toIntOrNull()?.takeIf { it in 1..12 } ?: return raw + val day = parts[2].toIntOrNull()?.takeIf { it in 1..31 } ?: return raw + return "$year ${MONTH_NAMES[month - 1]} $day" +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt index 46ffdac0c..37fcb63be 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioPosterActionSheet.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.features.home.MetaPreview import kotlinx.coroutines.launch @@ -212,7 +213,7 @@ private fun PosterSheetHeader( overflow = TextOverflow.Ellipsis, ) Text( - text = item.releaseInfo?.takeIf { it.isNotBlank() } + text = item.releaseInfo?.takeIf { it.isNotBlank() }?.let { formatReleaseDateForDisplay(it) } ?: item.type.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase() else char.toString() }, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt index 78e341cf5..acdf2cb59 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/catalog/CatalogScreen.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.core.ui.NuvioBackButton import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding @@ -217,7 +218,7 @@ private fun CatalogPosterTile( maxLines = 2, overflow = TextOverflow.Ellipsis, ) - val detail = item.releaseInfo + val detail = item.releaseInfo?.let { formatReleaseDateForDisplay(it) } if (detail != null) { Text( text = detail, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailAdditionalInfoSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailAdditionalInfoSection.kt index 6476eb396..f2b0ed804 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailAdditionalInfoSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailAdditionalInfoSection.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.features.details.MetaDetails @Composable @@ -27,7 +28,7 @@ fun DetailAdditionalInfoSection( val title = if (isSeriesLike) "Show Details" else "Movie Details" val rows = buildList { meta.status?.let { add("Status" to it) } - meta.releaseInfo?.let { add("Release Info" to it) } + meta.releaseInfo?.let { add("Release Info" to formatReleaseDateForDisplay(it)) } meta.runtime?.let { add("Runtime" to it.uppercase()) } meta.ageRating?.let { add("Certification" to it) } meta.country?.let { add("Origin Country" to it) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt index dcef3d4fd..715da0522 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.features.details.MetaDetails @Composable @@ -37,7 +38,7 @@ fun DetailMetaInfo( verticalArrangement = Arrangement.spacedBy(12.dp), ) { val infoParts = buildList { - meta.releaseInfo?.let { add(it) } + meta.releaseInfo?.let { add(formatReleaseDateForDisplay(it)) } meta.ageRating?.let { add(it) } meta.runtime?.let { add(it.uppercase()) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt index fe4decb0f..ebf894428 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailSeriesContent.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import co.touchlab.kermit.Logger +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge import com.nuvio.app.core.ui.NuvioProgressBar import com.nuvio.app.features.details.MetaDetails @@ -583,7 +584,7 @@ private fun EpisodeCard( overflow = TextOverflow.Ellipsis, ) - video.released?.formattedDate()?.let { formattedDate -> + video.released?.let { formatReleaseDateForDisplay(it) }?.let { formattedDate -> Text( text = formattedDate, style = MaterialTheme.typography.labelMedium.copy( @@ -796,8 +797,3 @@ private fun MetaVideo.episodeBadge(): String = season != null -> "S${season.toString().padStart(2, '0')}" else -> "FILE" } - -private fun String.formattedDate(): String { - val isoDate = substringBefore('T') - return if (isoDate.length == 10) isoDate else this -} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt index 6f5290019..de96857ee 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeHeroSection.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.features.home.MetaPreview import kotlinx.coroutines.launch @@ -254,7 +255,7 @@ private fun HeroContentBlock( } item.releaseInfo?.takeIf { it.isNotBlank() }?.let { info -> HeroMetaDot() - HeroMetaText(text = info) + HeroMetaText(text = formatReleaseDateForDisplay(info)) } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt index 863ba7a1a..a26205014 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt @@ -2,6 +2,7 @@ package com.nuvio.app.features.home.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.core.ui.NuvioPosterCard import com.nuvio.app.core.ui.NuvioPosterShape import com.nuvio.app.features.home.MetaPreview @@ -20,7 +21,7 @@ fun HomePosterCard( imageUrl = item.poster, modifier = modifier, shape = item.posterShape.toNuvioPosterShape(), - detailLine = item.releaseInfo, + detailLine = item.releaseInfo?.let { formatReleaseDateForDisplay(it) }, isWatched = isWatched, onClick = onClick, onLongClick = onLongClick, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt index 4b6ff4073..57aa76034 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage +import com.nuvio.app.core.format.formatReleaseDateForDisplay import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge import com.nuvio.app.core.ui.posterCardClickable import com.nuvio.app.features.home.MetaPreview @@ -304,7 +305,7 @@ private fun DiscoverPosterTile( maxLines = 2, overflow = TextOverflow.Ellipsis, ) - val detail = item.releaseInfo + val detail = item.releaseInfo?.let { formatReleaseDateForDisplay(it) } if (detail != null) { Text( text = detail, diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/core/format/ReleaseDateDisplayTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/core/format/ReleaseDateDisplayTest.kt new file mode 100644 index 000000000..176f82f2d --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/core/format/ReleaseDateDisplayTest.kt @@ -0,0 +1,26 @@ +package com.nuvio.app.core.format + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ReleaseDateDisplayTest { + @Test + fun formatsIsoDate() { + assertEquals("2025 February 1", formatReleaseDateForDisplay("2025-02-01")) + } + + @Test + fun stripsTimePortion() { + assertEquals("2024 January 15", formatReleaseDateForDisplay("2024-01-15T12:30:00Z")) + } + + @Test + fun leavesYearOnlyUnchanged() { + assertEquals("2024", formatReleaseDateForDisplay("2024")) + } + + @Test + fun leavesNonIsoUnchanged() { + assertEquals("TBA", formatReleaseDateForDisplay("TBA")) + } +}