From 57280ef470f69df5b8e29ad5c8c92c600805f01f Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 1 Sep 2026 04:04:35 +0200 Subject: [PATCH] Tighten detail action buttons for readability on backdrops. Use compact solid buttons and a quieter back control so labels stay clear over hero art. Co-authored-by: Cursor --- apps/android-tv/app/build.gradle.kts | 4 +- .../vonas/mediacluster/tv/ui/DetailScreens.kt | 107 +++++++++++++++--- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/apps/android-tv/app/build.gradle.kts b/apps/android-tv/app/build.gradle.kts index 7148cdc..48fb57d 100644 --- a/apps/android-tv/app/build.gradle.kts +++ b/apps/android-tv/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "nl.vonas.mediacluster.tv" minSdk = 24 targetSdk = 35 - versionCode = 16 - versionName = "0.7.9" + versionCode = 17 + versionName = "0.8.0" buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"") ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/DetailScreens.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/DetailScreens.kt index d4672c3..91af488 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/DetailScreens.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/DetailScreens.kt @@ -45,15 +45,16 @@ 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 androidx.tv.material3.Border import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.Glow import androidx.tv.material3.Surface import androidx.tv.material3.Text import coil.compose.AsyncImage import kotlinx.coroutines.delay import kotlinx.coroutines.launch import nl.vonas.mediacluster.tv.data.ApiClient -import nl.vonas.mediacluster.tv.ui.components.McButton import nl.vonas.mediacluster.tv.ui.theme.McColors import org.json.JSONArray import org.json.JSONObject @@ -198,6 +199,86 @@ private fun MetaChips(parts: List) { } } +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +private fun DetailBackButton(onClick: () -> Unit) { + Surface( + onClick = onClick, + shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)), + scale = ClickableSurfaceDefaults.scale(focusedScale = 1.05f), + glow = ClickableSurfaceDefaults.glow(focusedGlow = Glow(elevationColor = Color.Transparent, elevation = 0.dp)), + colors = ClickableSurfaceDefaults.colors( + containerColor = Color.Transparent, + focusedContainerColor = Color(0x33FFFFFF), + contentColor = Color(0xFFCFCFCF), + focusedContentColor = Color.White, + ), + border = ClickableSurfaceDefaults.border( + focusedBorder = Border( + border = androidx.compose.foundation.BorderStroke(1.dp, Color.White), + shape = RoundedCornerShape(4.dp), + ), + ), + ) { + Text( + "Terug", + modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), + color = Color.Unspecified, + fontSize = 13.sp, + fontWeight = FontWeight.Medium, + ) + } +} + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +private fun DetailActionButton( + label: String, + primary: Boolean = false, + focusRequester: FocusRequester? = null, + onClick: () -> Unit, +) { + val bg = if (primary) Color.White else Color(0xE6181818) + val bgFocus = if (primary) Color(0xFFE8E8E8) else Color.White + val fg = if (primary) Color.Black else Color.White + Surface( + onClick = onClick, + modifier = if (focusRequester != null) Modifier.focusRequester(focusRequester) else Modifier, + shape = ClickableSurfaceDefaults.shape(shape = RoundedCornerShape(4.dp)), + scale = ClickableSurfaceDefaults.scale(focusedScale = 1.05f), + glow = ClickableSurfaceDefaults.glow(focusedGlow = Glow(elevationColor = Color.Transparent, elevation = 0.dp)), + colors = ClickableSurfaceDefaults.colors( + containerColor = bg, + focusedContainerColor = bgFocus, + contentColor = fg, + focusedContentColor = Color.Black, + ), + border = if (primary) { + ClickableSurfaceDefaults.border() + } else { + ClickableSurfaceDefaults.border( + border = Border( + border = androidx.compose.foundation.BorderStroke(1.dp, Color(0x55FFFFFF)), + shape = RoundedCornerShape(4.dp), + ), + focusedBorder = Border( + border = androidx.compose.foundation.BorderStroke(1.dp, Color.White), + shape = RoundedCornerShape(4.dp), + ), + ) + }, + ) { + Text( + text = label, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + color = Color.Unspecified, + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + ) + } +} + @OptIn(ExperimentalTvMaterial3Api::class) @Composable fun MovieDetailScreen( @@ -286,7 +367,7 @@ fun MovieDetailScreen( .padding(start = 44.dp, end = 20.dp, top = 28.dp, bottom = 28.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { - McButton("← Terug", onClick = onBack) + DetailBackButton(onClick = onBack) Spacer(Modifier.height(8.dp)) DetailLogoOrTitle(logoUrl, title) MetaChips(metaParts) @@ -320,18 +401,18 @@ fun MovieDetailScreen( } if (error != null) Text(error!!, color = McColors.Danger) Spacer(Modifier.height(4.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { files.firstOrNull()?.let { (id, _) -> - McButton("▶ Afspelen", primary = true, focusRequester = playFocus) { + DetailActionButton("Afspelen", primary = true, focusRequester = playFocus) { onPlay(id, title, 0) } } if (!trailerKey.isNullOrBlank()) { - McButton("Trailer") { + DetailActionButton("Trailer") { openYoutubeTrailer(context, trailerKey!!) } } - McButton("Als gekeken") { + DetailActionButton("Als gekeken") { scope.launch { runCatching { api.markWatched(movieId = movieId) } onBack() @@ -341,9 +422,9 @@ fun MovieDetailScreen( if (files.size > 1) { Spacer(Modifier.height(4.dp)) Text("Versies", color = Color.White, fontSize = 15.sp, fontWeight = FontWeight.SemiBold) - LazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { items(files) { (id, label) -> - McButton(label.take(42), primary = id == files.first().first) { + DetailActionButton(label.take(36), primary = id == files.first().first) { onPlay(id, title, 0) } } @@ -458,7 +539,7 @@ fun SeriesDetailScreen( verticalArrangement = Arrangement.spacedBy(12.dp), ) { item { - McButton("← Terug", onClick = onBack) + DetailBackButton(onClick = onBack) Spacer(Modifier.height(10.dp)) DetailLogoOrTitle(logoUrl, title) MetaChips(metaParts) @@ -475,14 +556,14 @@ fun SeriesDetailScreen( } if (error != null) Text(error!!, color = McColors.Danger) Spacer(Modifier.height(6.dp)) - Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { episodes.firstOrNull()?.let { ep -> - McButton("▶ Afspelen", primary = true, focusRequester = firstEpFocus) { + DetailActionButton("Afspelen", primary = true, focusRequester = firstEpFocus) { onPlay(ep.fileId, ep.id, "$title · S${ep.season}E${ep.number}") } } if (!trailerKey.isNullOrBlank()) { - McButton("Trailer") { openYoutubeTrailer(context, trailerKey!!) } + DetailActionButton("Trailer") { openYoutubeTrailer(context, trailerKey!!) } } } if (seasons.size > 1) { @@ -492,7 +573,7 @@ fun SeriesDetailScreen( LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { items(seasons) { s -> val n = s.optInt("seasonNumber") - McButton( + DetailActionButton( "Seizoen $n", primary = n == selectedSeason, ) { selectedSeason = n }