2 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
be-nj
95f8793938 Bare, larger channel logos (no tile box)
Some checks failed
Build TV app / build (push) Failing after 2s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 01:20:22 +02:00
3 changed files with 47 additions and 19 deletions

View File

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

View File

@@ -337,24 +337,18 @@ private fun ChannelRow(
}
}
/** Channel logo in a small tile; initials while loading or without a logo. */
/** Bare channel logo (they ship transparent); initials as fallback. */
@Composable
private fun LogoTile(channel: Channel) {
val initials = channel.name.split(" ").filter { it.isNotBlank() }
.take(2).map { it.first() }.joinToString("").uppercase()
Box(
Modifier
.size(38.dp)
.clip(RoundedCornerShape(8.dp))
.background(CastarrColors.surfaceFocused),
contentAlignment = Alignment.Center,
) {
Box(Modifier.size(width = 52.dp, height = 40.dp), contentAlignment = Alignment.Center) {
if (channel.logo.isNotEmpty()) {
SubcomposeAsyncImage(
model = channel.logo,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxSize().padding(5.dp),
modifier = Modifier.fillMaxSize(),
loading = { LogoInitials(initials) },
error = { LogoInitials(initials) },
)

View File

@@ -28,7 +28,9 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.geometry.Offset
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.KeyEventType
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.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
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 =
SimpleDateFormat("HH:mm", Locale.GERMANY).format(Date(millis))
@@ -146,7 +154,8 @@ private fun Overlay(state: AppState) {
.padding(top = 22.dp, end = 30.dp),
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))
Box(
Modifier
@@ -178,18 +187,38 @@ private fun Overlay(state: AppState) {
statusWord,
color = CastarrColors.muted,
fontFamily = AppFont,
style = OverlayText,
fontSize = 12.sp,
)
}
Spacer(Modifier.height(4.dp))
}
Text(
state.currentChannel?.name.orEmpty(),
color = CastarrColors.fg,
fontFamily = AppFont,
fontSize = 30.sp,
fontWeight = FontWeight.Medium,
)
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(
state.currentChannel?.name.orEmpty(),
color = CastarrColors.fg,
fontFamily = AppFont,
style = OverlayText,
fontSize = 30.sp,
fontWeight = FontWeight.Medium,
)
}
val info = state.currentChannel?.let { state.nowNext(it) }
val now = info?.now?.takeUnless { isEpgPlaceholder(it.title) }
@@ -200,6 +229,7 @@ private fun Overlay(state: AppState) {
now.title,
color = CastarrColors.fg,
fontFamily = AppFont,
style = OverlayText,
fontSize = 18.sp,
)
Spacer(Modifier.width(14.dp))
@@ -207,6 +237,7 @@ private fun Overlay(state: AppState) {
"${formatClock(now.start)} ${formatClock(now.stop)}",
color = CastarrColors.muted,
fontFamily = AppFont,
style = OverlayText,
fontSize = 14.sp,
)
}
@@ -219,6 +250,7 @@ private fun Overlay(state: AppState) {
"Danach: ${next.title} (${formatClock(next.start)})",
color = CastarrColors.faint,
fontFamily = AppFont,
style = OverlayText,
fontSize = 14.sp,
)
}
@@ -267,6 +299,7 @@ private fun Overlay(state: AppState) {
"▲ ▼ Sender wechseln",
color = CastarrColors.faint,
fontFamily = AppFont,
style = OverlayText,
fontSize = 12.sp,
)
}
@@ -346,6 +379,7 @@ private fun PillButton(
Text(
label,
fontFamily = AppFont,
style = OverlayText,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(horizontal = 18.dp, vertical = 9.dp),