From bbb57baf81319cd6b2f452ca4ddc8b6b913402a0 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 15 Sep 2026 20:55:24 +0200 Subject: [PATCH] Fix EPG showing literal null between title and category on Android TV. Treat org.json optString JSON-null as missing when parsing programme fields. --- .../vonas/mediacluster/tv/data/ApiClient.kt | 24 ++++++++++++------- .../nl/vonas/mediacluster/tv/ui/TvScreen.kt | 10 ++++---- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt index 82b73b2..f29b531 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt @@ -603,19 +603,27 @@ class ApiClient( } private fun JSONObject.toEpgProgram(): EpgProgram? { - val title = optString("title").ifBlank { return null } + val title = optClean("title") ?: return null return EpgProgram( title = title, - subTitle = optString("subTitle").ifBlank { null }, - description = optString("description").ifBlank { null }, - imageUrl = optString("imageUrl").ifBlank { null }, - category = optString("category").ifBlank { null }, - episode = optString("episode").ifBlank { null }, - start = optString("start").ifBlank { null }, - end = optString("end").ifBlank { null }, + subTitle = optClean("subTitle"), + description = optClean("description"), + imageUrl = optClean("imageUrl"), + category = optClean("category"), + episode = optClean("episode"), + start = optClean("start"), + end = optClean("end"), ) } + /** org.json optString geeft bij JSON null de literal "null" — die negeren we. */ + private fun JSONObject.optClean(key: String): String? { + if (!has(key) || isNull(key)) return null + val raw = optString(key, "").trim() + if (raw.isEmpty() || raw.equals("null", ignoreCase = true)) return null + return raw + } + private fun authed(url: String): Request.Builder { val b = Request.Builder().url(url) val t = token diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/TvScreen.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/TvScreen.kt index c482798..4eafde6 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/TvScreen.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/TvScreen.kt @@ -847,7 +847,7 @@ private fun RichNowBlock(program: EpgProgram, isLive: Boolean) { formatEpgTime(program.start, program.end)?.let { time -> Text(time, color = McColors.TextDim, fontSize = 12.sp) } - program.episode?.takeIf { it.isNotBlank() }?.let { + program.episode?.takeIf { it.isNotBlank() && !it.equals("null", ignoreCase = true) }?.let { Text(it, color = Color(0xFF888888), fontSize = 11.sp) } } @@ -860,10 +860,10 @@ private fun RichNowBlock(program: EpgProgram, isLive: Boolean) { overflow = TextOverflow.Ellipsis, lineHeight = 20.sp, ) - program.subTitle?.takeIf { it.isNotBlank() }?.let { + program.subTitle?.takeIf { it.isNotBlank() && !it.equals("null", ignoreCase = true) }?.let { Text(it, color = Color(0xFFAAAAAA), fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) } - program.category?.takeIf { it.isNotBlank() }?.let { + program.category?.takeIf { it.isNotBlank() && !it.equals("null", ignoreCase = true) }?.let { Text(it, color = Color(0xFF666666), fontSize = 11.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) } } @@ -919,8 +919,8 @@ private fun ScheduleRow(program: EpgProgram, isNow: Boolean) { overflow = TextOverflow.Ellipsis, ) val meta = listOfNotNull( - program.episode, - program.subTitle?.takeIf { it.isNotBlank() }, + program.episode?.takeIf { it.isNotBlank() && !it.equals("null", ignoreCase = true) }, + program.subTitle?.takeIf { it.isNotBlank() && !it.equals("null", ignoreCase = true) }, ).joinToString(" · ") if (meta.isNotBlank()) { Text(meta, color = Color(0xFF666666), fontSize = 11.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)