Reach the gear from the top of the menu, and drop a stale message (0.73.1)
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 1m55s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m27s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-02 13:21:36 +00:00
co-authored by Claude Opus 5.5
parent c0901a5693
commit 0582d37982
7 changed files with 19 additions and 7 deletions
+5
View File
@@ -0,0 +1,5 @@
# Orbit TV 0.73.1: gear reach
Found walking through 0.73 on the box. Up from All Channels did nothing, so the gear could only be reached through the picture; it now goes straight there. A leftover "Saved connection restored" message no longer sits at the top of Channels.
On 0.73, by remote keys on the box: Settings opened, Channels showed 29 of 29 on, Watching and Back behaved, Terra and Paradox topped up again on 201 and 106, and the catalogue was unchanged. Checked here: build, lint and all unit tests pass. The fix isn't checked on the box until installed.
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 76
versionName = "0.73.0-only-whats-needed"
versionCode = 77
versionName = "0.73.1-gear-reach"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -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.73.0\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.73.1\"")
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
if (request.method == "POST") {
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
@@ -188,7 +188,6 @@ internal fun EmbyConnectionScreen(modifier: Modifier = Modifier, visible: Boolea
} catch (cancelled: CancellationException) { throw cancelled } catch (_: Exception) { emptyMap() }
session = resumed; sample = resumed.sample; applyChoices(saved)
saveTicket = savedStore.ticket()
message = "Saved connection restored."
} else if (saved != null && session == null) message = "Sign in again to restore your saved channel choices."
} catch (cancelled: CancellationException) { throw cancelled }
catch (failure: EmbyCheckException) {
@@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
@@ -127,12 +128,13 @@ internal fun HomeScreen(session: EmbySampleSession, channels: List<SampleChannel
var now by remember { mutableLongStateOf(System.currentTimeMillis()) }
LaunchedEffect(Unit) { while (true) { delay(1000); now = System.currentTimeMillis() } }
val firstFocus = remember { FocusRequester() }
val gear = LocalGearFocus.current
LaunchedEffect(Unit) { firstFocus.requestFocus() }
Column(modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(Modifier.fillMaxWidth().weight(1f), horizontalArrangement = Arrangement.spacedBy(14.dp)) {
Column(Modifier.width(176.dp).fillMaxHeight(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
homeCategories(channels.map { it.number }).forEachIndexed { index, category ->
HomeCard(Modifier.fillMaxWidth().weight(1f), if (index == 0) firstFocus else null, onClick = { onCategory(category) }) { focused ->
HomeCard(Modifier.fillMaxWidth().weight(1f).focusProperties { if (index == 0 && gear != null) up = gear }, if (index == 0) firstFocus else null, onClick = { onCategory(category) }) { focused ->
Row(Modifier.fillMaxSize().padding(horizontal = 10.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Image(painterResource(category.icon), null, Modifier.size(26.dp), colorFilter = if (focused) ColorFilter.tint(Navy) else null)
Text(category.label, color = if (focused) Navy else Color.White, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, maxLines = 1)
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
@@ -32,6 +33,8 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/** The gear, so the menu's top row can reach it directly. Left to itself, up finds nothing above the first column. */
internal val LocalGearFocus = staticCompositionLocalOf<FocusRequester?> { null }
internal val Barlow = FontFamily(Font(R.font.barlow_regular), Font(R.font.barlow_semibold, FontWeight.SemiBold), Font(R.font.barlow_semibold, FontWeight.Bold))
internal val Navy = Color(0xFF071632)
internal val Panel = Color(0xFF12356B)
@@ -82,7 +85,9 @@ private fun OrbitApp() {
}
// Back on the main menu closes the app.
BackHandler(realChannelAvailable && section != "TV Guide" && section != "Home") { section = "Home" }
val gearFocus = remember { FocusRequester() }
Box(Modifier.fillMaxSize().background(Navy)) {
CompositionLocalProvider(LocalGearFocus provides gearFocus) {
// The Drive-In takes the whole screen.
Column(if (section == "Drive-In") Modifier.fillMaxSize() else Modifier.fillMaxSize().padding(horizontal = 36.dp, vertical = 16.dp)) {
if (section != "Drive-In") {
@@ -92,7 +97,7 @@ private fun OrbitApp() {
Text(SimpleDateFormat("EEE d MMM HH:mm", Locale.UK).format(Date(now)), color = Color.White, fontSize = 17.sp)
if (realChannelAvailable) {
Spacer(Modifier.width(12.dp))
HomeCard(Modifier.size(38.dp, 32.dp), onClick = { section = "Connect Emby" }) { focused ->
HomeCard(Modifier.size(38.dp, 32.dp), gearFocus, onClick = { section = "Connect Emby" }) { focused ->
Image(painterResource(R.drawable.orbit_icon_gear), "Settings", Modifier.fillMaxSize().padding(5.dp), colorFilter = if (focused) ColorFilter.tint(Navy) else null)
}
}
@@ -107,5 +112,6 @@ private fun OrbitApp() {
onChannelAvailable = { realChannelAvailable = it }, onRestoreChecked = { restoreChecked = true },
onGuide = { section = "TV Guide" }, onHome = { section = "Home" }, onDriveIn = { section = "Drive-In" })
}
}
}
}
@@ -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.73.0")
next.setRequestProperty("User-Agent", "OrbitTV/0.73.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()) {