Drawn transport icons instead of glyphs (Android renders those as emoji)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 00:10:05 +02:00
parent ecd6fc7f08
commit 095472cb7f
3 changed files with 51 additions and 11 deletions

View File

@@ -12,8 +12,8 @@ android {
applicationId = "dev.castarr.tv" applicationId = "dev.castarr.tv"
minSdk = 26 minSdk = 26
targetSdk = 35 targetSdk = 35
versionCode = 9 versionCode = 10
versionName = "0.4.1" versionName = "0.5.0"
} }
// Release signing from environment (see ~/.keys/castarr-release.env on the // Release signing from environment (see ~/.keys/castarr-release.env on the

View File

@@ -88,7 +88,7 @@ private fun TopBar(state: AppState) {
Spacer(Modifier.width(18.dp)) Spacer(Modifier.width(18.dp))
} }
TabItem( TabItem(
if (state.screen == AppState.Screen.LIVE) "Einstellungen" else " Zurück zu Live", if (state.screen == AppState.Screen.LIVE) "Einstellungen" else "Zurück zu Live",
selected = false, selected = false,
) { ) {
state.screen = if (state.screen == AppState.Screen.LIVE) AppState.Screen.SETTINGS state.screen = if (state.screen == AppState.Screen.LIVE) AppState.Screen.SETTINGS

View File

@@ -1,5 +1,6 @@
package dev.castarr.tv.ui package dev.castarr.tv.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -24,6 +25,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -32,6 +34,7 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.ClickableSurfaceDefaults
import androidx.tv.material3.LocalContentColor
import androidx.tv.material3.Surface import androidx.tv.material3.Surface
import androidx.tv.material3.Text import androidx.tv.material3.Text
import dev.castarr.tv.AppState import dev.castarr.tv.AppState
@@ -234,11 +237,12 @@ private fun Overlay(state: AppState) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
PillButton( PillButton(
label = if (state.playerState == "paused") "Weiter" else "Pause", label = if (state.playerState == "paused") "Weiter" else "Pause",
icon = if (state.playerState == "paused") PillIcon.PLAY else PillIcon.PAUSE,
focusRequester = playFocus, focusRequester = playFocus,
) { state.player.toggle() } ) { state.player.toggle() }
Spacer(Modifier.width(10.dp)) Spacer(Modifier.width(10.dp))
PillButton(label = "Beenden") { state.stopPlayback() } PillButton(label = "Beenden", icon = PillIcon.STOP) { state.stopPlayback() }
if (state.audioTracks.size > 1) { if (state.audioTracks.size > 1) {
Spacer(Modifier.width(10.dp)) Spacer(Modifier.width(10.dp))
val current = state.audioTracks.firstOrNull { it.selected } val current = state.audioTracks.firstOrNull { it.selected }
@@ -256,9 +260,12 @@ private fun Overlay(state: AppState) {
} }
} }
private enum class PillIcon { PLAY, PAUSE, STOP }
@Composable @Composable
private fun PillButton( private fun PillButton(
label: String, label: String,
icon: PillIcon? = null,
focusRequester: FocusRequester? = null, focusRequester: FocusRequester? = null,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
@@ -273,12 +280,45 @@ private fun PillButton(
focusedContentColor = CastarrColors.onAccent, focusedContentColor = CastarrColors.onAccent,
), ),
) { ) {
Row(
modifier = Modifier.padding(horizontal = 18.dp, vertical = 9.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (icon != null) {
// Drawn, not typed — glyphs like U+23F8 render as emoji on
// Android and have no business on this screen.
val color = LocalContentColor.current
Canvas(Modifier.size(11.dp)) {
when (icon) {
PillIcon.PLAY -> drawPath(
Path().apply {
moveTo(0f, 0f)
lineTo(size.width, size.height / 2f)
lineTo(0f, size.height)
close()
},
color,
)
PillIcon.PAUSE -> {
val bar = size.width * 0.32f
drawRect(color, size = androidx.compose.ui.geometry.Size(bar, size.height))
drawRect(
color,
topLeft = androidx.compose.ui.geometry.Offset(size.width - bar, 0f),
size = androidx.compose.ui.geometry.Size(bar, size.height),
)
}
PillIcon.STOP -> drawRect(color)
}
}
Spacer(Modifier.width(9.dp))
}
Text( Text(
label, label,
fontFamily = AppFont, fontFamily = AppFont,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
modifier = Modifier.padding(horizontal = 18.dp, vertical = 9.dp),
) )
} }
} }
}