From 4dd08f29a6368d0a557ff25b9b1324c2ab326e07 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Sat, 28 Mar 2026 21:52:18 +0530 Subject: [PATCH] feat: Extend MetaDetails and MetaDetailsParser to include status and hasScheduledVideos fields; enhance DetailSeriesContent for better episode handling --- .../app/features/details/MetaDetailsModels.kt | 2 ++ .../app/features/details/MetaDetailsParser.kt | 9 +++++ .../details/components/DetailSeriesContent.kt | 35 ++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsModels.kt index 0e55fdc06..035aa7436 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsModels.kt @@ -9,6 +9,7 @@ data class MetaDetails( val logo: String? = null, val description: String? = null, val releaseInfo: String? = null, + val status: String? = null, val imdbRating: String? = null, val runtime: String? = null, val genres: List = emptyList(), @@ -18,6 +19,7 @@ data class MetaDetails( val awards: String? = null, val language: String? = null, val website: String? = null, + val hasScheduledVideos: Boolean = false, val links: List = emptyList(), val videos: List = emptyList(), ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt index 0a99d26e5..7d3b74e2b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/MetaDetailsParser.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.intOrNull import kotlinx.serialization.json.jsonObject @@ -26,6 +27,7 @@ internal object MetaDetailsParser { logo = meta.string("logo"), description = meta.string("description"), releaseInfo = meta.string("releaseInfo"), + status = meta.string("status"), imdbRating = meta.string("imdbRating"), runtime = meta.string("runtime"), genres = meta.stringList("genres"), @@ -35,6 +37,7 @@ internal object MetaDetailsParser { awards = meta.string("awards"), language = meta.string("language"), website = meta.string("website"), + hasScheduledVideos = meta.behaviorHints().boolean("hasScheduledVideos") == true, links = links, videos = meta.videos(), ) @@ -77,6 +80,12 @@ internal object MetaDetailsParser { private fun JsonObject.int(name: String): Int? = this[name]?.jsonPrimitive?.intOrNull + private fun JsonObject.boolean(name: String): Boolean? = + this[name]?.jsonPrimitive?.booleanOrNull + + private fun JsonObject.behaviorHints(): JsonObject = + this["behaviorHints"] as? JsonObject ?: JsonObject(emptyMap()) + private fun JsonObject.directors(links: List): List { val appExtras = this["app_extras"] as? JsonObject val topLevel = stringListOrCsv("director") 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 4f4d7942e..4ca4621d9 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 @@ -57,6 +57,27 @@ fun DetailSeriesContent( progressByVideoId: Map = emptyMap(), onEpisodeClick: ((MetaVideo) -> Unit)? = null, ) { + if (meta.type != "series") return + + if (meta.videos.isEmpty()) { + DetailSection( + title = "Episodes", + modifier = modifier, + ) { + Text( + text = when { + meta.status.equals("Not yet aired", ignoreCase = true) || meta.hasScheduledVideos -> + "Episodes have not been published by this addon yet." + else -> + "This addon did not provide episode metadata for this series." + }, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + return + } + val groupedEpisodes = remember(meta.videos) { log.d { "videos count=${meta.videos.size}, type=${meta.type}" } val withSeasonOrEp = meta.videos.filter { it.season != null || it.episode != null } @@ -69,7 +90,19 @@ fun DetailSeriesContent( .groupBy { normalizeSeasonNumber(it.season) } } - if (groupedEpisodes.isEmpty()) return + if (groupedEpisodes.isEmpty()) { + DetailSection( + title = "Episodes", + modifier = modifier, + ) { + Text( + text = "This addon returned videos for the series, but none included season or episode numbers.", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + return + } val seasons = groupedEpisodes.keys.sortedBy(::seasonSortKey) val defaultSeason = seasons.first()