1 Commits

Author SHA1 Message Date
be-nj
844ea928ef Channel number in the player overlay (inline before the name)
Some checks failed
Build TV app / build (push) Failing after 3s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 01:27:50 +02:00
2 changed files with 44 additions and 10 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 = 20 versionCode = 21
versionName = "0.8.4" versionName = "0.8.5"
} }
// Release signing from environment (see ~/.keys/castarr-release.env on the // Release signing from environment (see ~/.keys/castarr-release.env on the

View File

@@ -28,7 +28,9 @@ 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.geometry.Offset
import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
@@ -37,6 +39,7 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type import androidx.compose.ui.input.key.type
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -98,6 +101,11 @@ fun PlayerScreen(state: AppState) {
} }
} }
/** Soft drop shadow so overlay text stays readable on bright video. */
private val OverlayText = TextStyle(
shadow = Shadow(Color(0xB3000000), Offset(0f, 2f), blurRadius = 8f),
)
private fun formatClock(millis: Long): String = private fun formatClock(millis: Long): String =
SimpleDateFormat("HH:mm", Locale.GERMANY).format(Date(millis)) SimpleDateFormat("HH:mm", Locale.GERMANY).format(Date(millis))
@@ -146,7 +154,8 @@ private fun Overlay(state: AppState) {
.padding(top = 22.dp, end = 30.dp), .padding(top = 22.dp, end = 30.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text(name, color = CastarrColors.muted, fontFamily = AppFont, fontSize = 12.sp) Text(name, color = CastarrColors.muted, fontFamily = AppFont,
style = OverlayText, fontSize = 12.sp)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Box( Box(
Modifier Modifier
@@ -178,18 +187,38 @@ private fun Overlay(state: AppState) {
statusWord, statusWord,
color = CastarrColors.muted, color = CastarrColors.muted,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 12.sp, fontSize = 12.sp,
) )
} }
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
} }
Row(verticalAlignment = Alignment.Bottom) {
// Channel number inline before the name (matches the list
// and the number-pad zapping).
val number = state.currentChannel?.let { current ->
state.activeChannels().indexOfFirst { it.url == current.url } + 1
} ?: 0
if (number > 0) {
Text(
"$number",
color = CastarrColors.accent,
fontFamily = AppFont,
style = OverlayText,
fontSize = 30.sp,
fontWeight = FontWeight.SemiBold,
)
Spacer(Modifier.width(14.dp))
}
Text( Text(
state.currentChannel?.name.orEmpty(), state.currentChannel?.name.orEmpty(),
color = CastarrColors.fg, color = CastarrColors.fg,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 30.sp, fontSize = 30.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
) )
}
val info = state.currentChannel?.let { state.nowNext(it) } val info = state.currentChannel?.let { state.nowNext(it) }
val now = info?.now?.takeUnless { isEpgPlaceholder(it.title) } val now = info?.now?.takeUnless { isEpgPlaceholder(it.title) }
@@ -200,6 +229,7 @@ private fun Overlay(state: AppState) {
now.title, now.title,
color = CastarrColors.fg, color = CastarrColors.fg,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 18.sp, fontSize = 18.sp,
) )
Spacer(Modifier.width(14.dp)) Spacer(Modifier.width(14.dp))
@@ -207,6 +237,7 @@ private fun Overlay(state: AppState) {
"${formatClock(now.start)} ${formatClock(now.stop)}", "${formatClock(now.start)} ${formatClock(now.stop)}",
color = CastarrColors.muted, color = CastarrColors.muted,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 14.sp, fontSize = 14.sp,
) )
} }
@@ -219,6 +250,7 @@ private fun Overlay(state: AppState) {
"Danach: ${next.title} (${formatClock(next.start)})", "Danach: ${next.title} (${formatClock(next.start)})",
color = CastarrColors.faint, color = CastarrColors.faint,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 14.sp, fontSize = 14.sp,
) )
} }
@@ -267,6 +299,7 @@ private fun Overlay(state: AppState) {
"▲ ▼ Sender wechseln", "▲ ▼ Sender wechseln",
color = CastarrColors.faint, color = CastarrColors.faint,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 12.sp, fontSize = 12.sp,
) )
} }
@@ -346,6 +379,7 @@ private fun PillButton(
Text( Text(
label, label,
fontFamily = AppFont, fontFamily = AppFont,
style = OverlayText,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
modifier = Modifier.padding(horizontal = 18.dp, vertical = 9.dp), modifier = Modifier.padding(horizontal = 18.dp, vertical = 9.dp),