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 <cursoragent@cursor.com>
This commit is contained in:
Jos Vooges | STH 2026-09-01 04:04:35 +02:00
parent 6c96b0ca01
commit 57280ef470
2 changed files with 96 additions and 15 deletions

View file

@ -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")

View file

@ -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<String>) {
}
}
@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 }