mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-04 18:42:34 +00:00
feat: add lastAirDate to MetaDetails and related parsing logic for improved TV show metadata handling
This commit is contained in:
parent
cc7d6b6b59
commit
56ccb557ad
8 changed files with 140 additions and 2 deletions
|
|
@ -30,3 +30,17 @@ fun formatReleaseDateForDisplay(raw: String): String {
|
|||
val day = parts[2].toIntOrNull()?.takeIf { it in 1..31 } ?: return raw
|
||||
return "$year ${MONTH_NAMES[month - 1]} $day"
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a release/air string (ISO date, year-only, or timestamp prefix) for compact UI (e.g. year chips).
|
||||
*/
|
||||
fun extractReleaseYearForDisplay(raw: String): Int? {
|
||||
val t = raw.trim()
|
||||
if (t.isEmpty()) return null
|
||||
if (t.length == 4 && t.all { it.isDigit() }) {
|
||||
return t.toIntOrNull()?.takeIf { it in 1000..9999 }
|
||||
}
|
||||
val datePart = t.substringBefore('T').trim()
|
||||
val yearStr = datePart.split('-').firstOrNull() ?: return null
|
||||
return yearStr.toIntOrNull()?.takeIf { it in 1000..9999 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ data class MetaDetails(
|
|||
val logo: String? = null,
|
||||
val description: String? = null,
|
||||
val releaseInfo: String? = null,
|
||||
/** TV: ISO last air date from TMDB (or addon) for year-range display. */
|
||||
val lastAirDate: String? = null,
|
||||
val status: String? = null,
|
||||
val imdbRating: String? = null,
|
||||
val ageRating: String? = null,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ internal object MetaDetailsParser {
|
|||
logo = meta.string("logo"),
|
||||
description = meta.string("description"),
|
||||
releaseInfo = meta.string("releaseInfo"),
|
||||
lastAirDate = meta.string("lastAirDate"),
|
||||
status = meta.string("status"),
|
||||
imdbRating = meta.string("imdbRating"),
|
||||
ageRating = meta.string("ageRating"),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.nuvio.app.features.details
|
||||
|
||||
import com.nuvio.app.core.format.extractReleaseYearForDisplay
|
||||
|
||||
private fun isTvSeriesType(type: String): Boolean =
|
||||
when (type.trim().lowercase()) {
|
||||
"series", "tv", "show", "tvshow" -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun isEndedSeriesStatus(status: String?): Boolean {
|
||||
if (status.isNullOrBlank()) return false
|
||||
val s = status.trim().lowercase()
|
||||
if ("returning" in s || "in production" in s) return false
|
||||
return "ended" in s || "canceled" in s || "cancelled" in s
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact release line under the details hero: movies → year only; TV → "2025 -" or "2021 - 2028".
|
||||
*/
|
||||
fun formatMetaReleaseLineForDetails(meta: MetaDetails): String? {
|
||||
val raw = meta.releaseInfo?.trim()?.takeIf { it.isNotEmpty() } ?: return null
|
||||
if (!isTvSeriesType(meta.type)) {
|
||||
return extractReleaseYearForDisplay(raw)?.toString()
|
||||
}
|
||||
val startYear = extractReleaseYearForDisplay(raw) ?: return raw
|
||||
val endYear = meta.lastAirDate?.let { extractReleaseYearForDisplay(it) }
|
||||
return when {
|
||||
isEndedSeriesStatus(meta.status) && endYear != null ->
|
||||
if (endYear == startYear) startYear.toString()
|
||||
else "$startYear - $endYear"
|
||||
isEndedSeriesStatus(meta.status) -> startYear.toString()
|
||||
else -> "$startYear -"
|
||||
}
|
||||
}
|
||||
|
|
@ -25,8 +25,8 @@ 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
|
||||
import com.nuvio.app.features.details.formatMetaReleaseLineForDetails
|
||||
|
||||
@Composable
|
||||
fun DetailMetaInfo(
|
||||
|
|
@ -38,7 +38,7 @@ fun DetailMetaInfo(
|
|||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
val infoParts = buildList {
|
||||
meta.releaseInfo?.let { add(formatReleaseDateForDisplay(it)) }
|
||||
formatMetaReleaseLineForDetails(meta)?.let { add(it) }
|
||||
meta.ageRating?.let { add(it) }
|
||||
meta.runtime?.let { add(it.uppercase()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ object TmdbMetadataService {
|
|||
if (enrichment != null && settings.useDetails) {
|
||||
updated = updated.copy(
|
||||
releaseInfo = enrichment.releaseInfo ?: updated.releaseInfo,
|
||||
lastAirDate = enrichment.lastAirDate ?: updated.lastAirDate,
|
||||
status = enrichment.status ?: updated.status,
|
||||
ageRating = enrichment.ageRating ?: updated.ageRating,
|
||||
runtime = enrichment.runtimeMinutes?.formatRuntime() ?: updated.runtime,
|
||||
|
|
@ -263,6 +264,8 @@ object TmdbMetadataService {
|
|||
val people = buildPeople(details = details, credits = credits, mediaType = mediaType)
|
||||
val directors = buildDirectors(details = details, credits = credits, mediaType = mediaType)
|
||||
val writers = buildWriters(credits = credits, mediaType = mediaType, hasDirectors = directors.isNotEmpty())
|
||||
val lastAirDate = details.lastAirDate?.trim()?.takeIf(String::isNotBlank)
|
||||
?.takeIf { mediaType == "tv" }
|
||||
val enrichment = TmdbEnrichment(
|
||||
localizedTitle = localizedTitle,
|
||||
description = description,
|
||||
|
|
@ -274,6 +277,7 @@ object TmdbMetadataService {
|
|||
director = directors,
|
||||
writer = writers,
|
||||
releaseInfo = releaseInfo,
|
||||
lastAirDate = lastAirDate,
|
||||
rating = details.voteAverage,
|
||||
runtimeMinutes = details.runtime ?: details.episodeRunTime.firstOrNull(),
|
||||
ageRating = response.fourth.first,
|
||||
|
|
@ -453,6 +457,7 @@ internal data class TmdbEnrichment(
|
|||
val director: List<String>,
|
||||
val writer: List<String>,
|
||||
val releaseInfo: String?,
|
||||
val lastAirDate: String? = null,
|
||||
val rating: Double?,
|
||||
val runtimeMinutes: Int?,
|
||||
val ageRating: String?,
|
||||
|
|
@ -476,6 +481,7 @@ internal data class TmdbEnrichment(
|
|||
director.isNotEmpty() ||
|
||||
writer.isNotEmpty() ||
|
||||
releaseInfo != null ||
|
||||
lastAirDate != null ||
|
||||
rating != null ||
|
||||
runtimeMinutes != null ||
|
||||
ageRating != null ||
|
||||
|
|
@ -725,6 +731,7 @@ private data class TmdbDetailsResponse(
|
|||
val overview: String? = null,
|
||||
@SerialName("release_date") val releaseDate: String? = null,
|
||||
@SerialName("first_air_date") val firstAirDate: String? = null,
|
||||
@SerialName("last_air_date") val lastAirDate: String? = null,
|
||||
val status: String? = null,
|
||||
@SerialName("vote_average") val voteAverage: Double? = null,
|
||||
val runtime: Int? = null,
|
||||
|
|
|
|||
|
|
@ -23,4 +23,14 @@ class ReleaseDateDisplayTest {
|
|||
fun leavesNonIsoUnchanged() {
|
||||
assertEquals("TBA", formatReleaseDateForDisplay("TBA"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun extractsYearFromIso() {
|
||||
assertEquals(2025, extractReleaseYearForDisplay("2025-03-15"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun extractsYearFromYearOnly() {
|
||||
assertEquals(2024, extractReleaseYearForDisplay("2024"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.nuvio.app.features.details
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class MetaDetailsReleaseLineTest {
|
||||
private fun meta(
|
||||
type: String,
|
||||
releaseInfo: String?,
|
||||
status: String? = null,
|
||||
lastAirDate: String? = null,
|
||||
) = MetaDetails(
|
||||
id = "1",
|
||||
type = type,
|
||||
name = "X",
|
||||
releaseInfo = releaseInfo,
|
||||
status = status,
|
||||
lastAirDate = lastAirDate,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun movieShowsYearOnly() {
|
||||
assertEquals("2019", formatMetaReleaseLineForDetails(meta("movie", "2019-07-04")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ongoingSeriesShowsOpenRange() {
|
||||
assertEquals(
|
||||
"2025 -",
|
||||
formatMetaReleaseLineForDetails(
|
||||
meta("series", "2025-01-10", status = "Returning Series", lastAirDate = "2025-03-01"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endedSeriesShowsClosedRange() {
|
||||
assertEquals(
|
||||
"2021 - 2028",
|
||||
formatMetaReleaseLineForDetails(
|
||||
meta("series", "2021-09-01", status = "Ended", lastAirDate = "2028-05-20"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endedSameYearSingleYear() {
|
||||
assertEquals(
|
||||
"2022",
|
||||
formatMetaReleaseLineForDetails(
|
||||
meta("series", "2022-06-01", status = "Ended", lastAirDate = "2022-12-01"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullReleaseInfo() {
|
||||
assertNull(formatMetaReleaseLineForDetails(meta("movie", null)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun endedWithoutLastAirDateShowsSingleYear() {
|
||||
assertEquals(
|
||||
"2020",
|
||||
formatMetaReleaseLineForDetails(meta("series", "2020-04-01", status = "Ended", lastAirDate = null)),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue