0.100.1: focused What's On cards darken to navy inside a gold ring so logos stay clear; Drive-In shows only the film's artwork, held until the next loads (no backdrop flicker)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,8 @@ android {
|
||||
applicationId = "tv.orbit.classic"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 148
|
||||
versionName = "0.100.0-whats-on"
|
||||
versionCode = 149
|
||||
versionName = "0.100.1-ui-fixes"
|
||||
}
|
||||
buildFeatures { compose = true }
|
||||
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
|
||||
|
||||
@@ -282,7 +282,7 @@ internal fun DriveInReminderCard(reminder: DriveInReminder, nowMs: Long, onGo: (
|
||||
|
||||
/**
|
||||
* The Drive-In on one screen (Paul's v2 design, 4 November 2026): the chosen bill large on the left over its own film
|
||||
* artwork, blended into deep navy, with its ticket; this week's bills down the right. Up and down choose a bill, OK
|
||||
* artwork only (no backdrop scene), blended into deep navy, with its ticket; this week's bills down the right. Up and down choose a bill, OK
|
||||
* watches what's showing now or opens its ticket. The bills come from the Drive-In's own booked plan, untouched.
|
||||
*/
|
||||
@Composable
|
||||
@@ -315,9 +315,11 @@ internal fun DriveInScreen(session: EmbySampleSession, plan: ChannelPlan?, visit
|
||||
else -> false
|
||||
}
|
||||
}) {
|
||||
// Only the film's own artwork, kept on screen until the next one has loaded, so moving between bills never flashes.
|
||||
val picture = rememberFilmPicture(session, selected?.entries?.first()?.media?.item?.id)
|
||||
if (picture != null) Image(picture.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
|
||||
else Image(painterResource(R.drawable.drive_in_lot), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
|
||||
var shown by remember { mutableStateOf<Bitmap?>(null) }
|
||||
LaunchedEffect(picture) { if (picture != null) shown = picture }
|
||||
shown?.let { Image(it.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) }
|
||||
// Deep navy over the art, so it sits inside Orbit and the words read: strongest on the left, top and bottom.
|
||||
Box(Modifier.fillMaxSize().background(Brush.horizontalGradient(0f to Navy.copy(alpha = .96f), .42f to Navy.copy(alpha = .9f), .62f to Navy.copy(alpha = .55f), 1f to Navy.copy(alpha = .35f))))
|
||||
Box(Modifier.fillMaxSize().background(Brush.verticalGradient(0f to Navy.copy(alpha = .9f), .2f to Navy.copy(alpha = .2f), .75f to Navy.copy(alpha = .25f), 1f to Navy.copy(alpha = .95f))))
|
||||
|
||||
@@ -66,7 +66,7 @@ internal class HttpsEmbyTransport(internal val deviceId: String = UUID.randomUUI
|
||||
connection.connectTimeout = 10_000
|
||||
connection.readTimeout = request.readTimeoutMs ?: if (request.path == "/Sessions/Logout") 5_000 else 15_000
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.100.0\"")
|
||||
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.100.1\"")
|
||||
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
|
||||
if (request.method == "POST") {
|
||||
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class SecureMediaDataSource(private val session: EmbySampleSession) : B
|
||||
next.instanceFollowRedirects = false
|
||||
next.connectTimeout = 10_000
|
||||
next.readTimeout = 20_000
|
||||
next.setRequestProperty("User-Agent", "OrbitTV/0.100.0")
|
||||
next.setRequestProperty("User-Agent", "OrbitTV/0.100.1")
|
||||
next.setRequestProperty("Accept-Encoding", "identity")
|
||||
session.mediaHeaders(address).forEach { (key, value) -> next.setRequestProperty(key, value) }
|
||||
if (dataSpec.position != 0L || dataSpec.length != C.LENGTH_UNSET.toLong()) {
|
||||
|
||||
@@ -67,12 +67,12 @@ private fun dayAndTime(startMs: Long, nowMs: Long): String {
|
||||
else java.text.SimpleDateFormat("EEEE HH:mm", java.util.Locale.UK).format(java.util.Date(startMs))
|
||||
}
|
||||
|
||||
/** A card the remote can land on: gold ring and a lift when focused. */
|
||||
/** A card the remote can land on: when focused it darkens to navy inside a gold ring, so white logos stay crisp. */
|
||||
@Composable
|
||||
private fun FocusCard(modifier: Modifier, focus: FocusRequester? = null, onClick: () -> Unit, content: @Composable BoxScope.(Boolean) -> Unit) {
|
||||
var focused by remember { mutableStateOf(false) }
|
||||
Box(modifier.then(if (focus != null) Modifier.focusRequester(focus) else Modifier).onFocusChanged { focused = it.isFocused }
|
||||
.border(if (focused) 2.dp else 0.dp, if (focused) Yellow else Color.Transparent, RoundedCornerShape(8.dp))
|
||||
.border(if (focused) 3.dp else 0.dp, if (focused) Yellow else Color.Transparent, RoundedCornerShape(8.dp))
|
||||
.clip(Corner).clickable(onClick = onClick)) { content(focused) }
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ internal fun WhatsOnScreen(session: EmbySampleSession, plans: Map<Int, ChannelPl
|
||||
val picture = rememberFilmPicture(session, pick.entry.media.item.id)
|
||||
Box(Modifier.fillMaxSize().background(Panel)) {
|
||||
if (picture != null) Image(picture.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
|
||||
else Image(painterResource(R.drawable.drive_in_lot), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop, alpha = .35f)
|
||||
Box(Modifier.fillMaxSize().background(Brush.horizontalGradient(0f to Navy.copy(alpha = .97f), .45f to Navy.copy(alpha = .88f), .75f to Navy.copy(alpha = .35f), 1f to Navy.copy(alpha = .15f))))
|
||||
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Tag("TONIGHT'S PICK", Yellow, 11)
|
||||
@@ -127,11 +126,11 @@ internal fun WhatsOnScreen(session: EmbySampleSession, plans: Map<Int, ChannelPl
|
||||
if (page.tonight.isEmpty()) Text("Nothing else is marked out tonight. The TV Guide has every programme.", color = Muted, fontSize = 12.sp)
|
||||
page.tonight.forEach { item ->
|
||||
FocusCard(Modifier.fillMaxWidth().height(46.dp), onClick = { choose(item) }) { focused ->
|
||||
Row(Modifier.fillMaxSize().background(if (focused) Yellow else Panel).padding(horizontal = 9.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(9.dp)) {
|
||||
Text(if (item.startMs <= now) "NOW" else clock(item.startMs), Modifier.width(44.dp), color = if (focused) Navy else Yellow, fontFamily = BarlowCondensed, fontSize = 18.sp)
|
||||
Row(Modifier.fillMaxSize().background(if (focused) Navy else Panel).padding(horizontal = 9.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(9.dp)) {
|
||||
Text(if (item.startMs <= now) "NOW" else clock(item.startMs), Modifier.width(44.dp), color = Yellow, fontFamily = BarlowCondensed, fontSize = 18.sp)
|
||||
Column(Modifier.weight(1f)) {
|
||||
Logo(item.channel, 56, 17)
|
||||
Text(item.title, color = if (focused) Navy else Color.White, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(item.title, color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,17 +145,17 @@ internal fun WhatsOnScreen(session: EmbySampleSession, plans: Map<Int, ChannelPl
|
||||
if (page.comingUp.isEmpty()) Text("Nothing special is booked for the week ahead yet.", color = Muted, fontSize = 12.sp)
|
||||
page.comingUp.forEach { item ->
|
||||
FocusCard(Modifier.weight(1f).fillMaxHeight(), onClick = { choose(item) }) { focused ->
|
||||
Column(Modifier.fillMaxSize().background(if (focused) Yellow else Panel).padding(10.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Column(Modifier.fillMaxSize().background(if (focused) Navy else Panel).padding(10.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Tag(item.tag)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Logo(item.channel, 56, 17)
|
||||
}
|
||||
Text(item.title, color = if (focused) Navy else Color.White, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(item.title, color = Color.White, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(dayAndTime(item.startMs, now), color = if (focused) Navy else Yellow, fontFamily = BarlowCondensed, fontSize = 16.sp)
|
||||
Text(dayAndTime(item.startMs, now), color = Yellow, fontFamily = BarlowCondensed, fontSize = 16.sp)
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (item.channel == DRIVE_IN_NUMBER) Text("Go to the Drive-In ▶", color = if (focused) Navy else Yellow, fontSize = 10.sp, fontWeight = FontWeight.SemiBold)
|
||||
if (item.channel == DRIVE_IN_NUMBER) Text("Go to the Drive-In ▶", color = Yellow, fontSize = 10.sp, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user