From f237471b6a304829de16872fe825836c94704b54 Mon Sep 17 00:00:00 2001 From: Arvin Date: Sun, 5 Apr 2026 14:59:32 +0200 Subject: [PATCH] feat: add IMAX badge and fix DV false-positive detection (#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stream source badges in the stream picker had two issues: 1. The DV (Dolby Vision) badge used `quality.contains("DV", ignoreCase = true)` which matched any string containing "DV" as a substring — including "DVD", "HDVD", "DVDRip", etc. Standard-definition DVD rips were being incorrectly flagged as Dolby Vision. 2. There was no IMAX badge at all. IMAX is one of the most visually distinctive premium formats and users scan source lists for it, but the badge row showed only HDR and DV. A secondary issue: both HDR and DV detection only looked at the pre-extracted `stream.quality` string, which rarely contains IMAX or DV tokens. Those tokens almost always live in the filename / source title (`stream.source` or `stream.behaviorHints.filename`). Changes: - `CompactQualityBadge` now takes the full `StreamSource` instead of just `quality`, and combines `quality + source + behaviorHints.filename` into a single search blob for token detection. - Introduced pre-compiled `DV_REGEX`, `HDR_REGEX`, and `IMAX_REGEX` constants using `\b` word boundaries so "DV" no longer matches "DVD", "DoVi" and "Dolby Vision" still match, and "HDR10" / "HDR10+" match their own badge. - Added an IMAX badge in a distinctive cyan/blue color (0xFF06B6D4) to stand out from HDR (purple) and DV (pink). No change to the call site beyond passing `stream` instead of `stream.quality`. Closes #118 --- .../arflix/tv/ui/components/StreamSelector.kt | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/ui/components/StreamSelector.kt b/app/src/main/kotlin/com/arflix/tv/ui/components/StreamSelector.kt index d702ebe77..57dc21bf1 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/components/StreamSelector.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/components/StreamSelector.kt @@ -935,8 +935,8 @@ private fun GlassyStreamCard( horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically ) { - // Quality badge - CompactQualityBadge(stream.quality) + // Quality badge (resolution + HDR/DV/IMAX detection from full stream metadata) + CompactQualityBadge(stream) // Size if (stream.size.isNotEmpty()) { @@ -1007,14 +1007,34 @@ private fun FilterTab( } } +// Pre-compiled badge detection regexes. All use word boundaries to avoid matching +// "DV" inside "DVD" or "HDVD" (the long-standing bug that made the DV badge appear +// on SD DVDrip sources). IMAX detection was missing entirely before issue #118. +private val DV_REGEX = Regex("""\b(DV|DoVi|Dolby[\s._-]*Vision)\b""", RegexOption.IGNORE_CASE) +private val HDR_REGEX = Regex("""\bHDR(10\+?|10)?\b""", RegexOption.IGNORE_CASE) +private val IMAX_REGEX = Regex("""\bIMAX\b""", RegexOption.IGNORE_CASE) + @OptIn(ExperimentalTvMaterial3Api::class) @Composable -private fun CompactQualityBadge(quality: String) { +private fun CompactQualityBadge(stream: StreamSource) { + // IMAX and DV tokens are almost always in the filename / source title, not the + // pre-extracted `quality` string. Combine all fields we have so detection works + // regardless of where the token lives. This fixes issue #118. + val searchBlob = buildString { + append(stream.quality) + append(' ') + append(stream.source) + append(' ') + append(stream.behaviorHints?.filename.orEmpty()) + } + + val quality = stream.quality val is4K = quality.contains("4K", ignoreCase = true) || quality.contains("2160p") val is1080 = quality.contains("1080p") val is720 = quality.contains("720p") - val isHDR = quality.contains("HDR", ignoreCase = true) - val isDV = quality.contains("DV", ignoreCase = true) || quality.contains("Dolby Vision", ignoreCase = true) + val isHDR = HDR_REGEX.containsMatchIn(searchBlob) + val isDV = DV_REGEX.containsMatchIn(searchBlob) + val isIMAX = IMAX_REGEX.containsMatchIn(searchBlob) val displayText = when { is4K -> "4K" @@ -1079,6 +1099,26 @@ private fun CompactQualityBadge(quality: String) { ) } } + + if (isIMAX) { + // IMAX badge — distinctive cyan/blue to stand out from HDR (purple) and DV (pink). + // Requested in issue #118 as a premium format that deserves its own badge + // since users scan the source list for IMAX-tagged releases. + Box( + modifier = Modifier + .background(Color(0xFF06B6D4).copy(alpha = 0.18f), RoundedCornerShape(4.dp)) + .padding(horizontal = 5.dp, vertical = 2.dp) + ) { + Text( + text = "IMAX", + style = ArflixTypography.caption.copy( + fontSize = 9.sp, + fontWeight = FontWeight.Black + ), + color = Color(0xFF06B6D4) + ) + } + } } }