Fix the bar not hiding, OK toggles it, and the guide scrolls (0.35.0)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Orbit TV 0.35.0 verification
|
||||
|
||||
## Changed
|
||||
- Fixed the programme bar not hiding after 5 seconds. It was a bug from the 0.31 stream handover: a player handed over while already playing never reported that it was playing.
|
||||
- OK now closes the programme bar if it's open and opens it if it isn't.
|
||||
- The guide scrolls. It draws only the rows that fit, and moves only when the chosen row would leave the screen, ready for the 25-channel lineup.
|
||||
|
||||
## Checked
|
||||
- assembleDebug, testDebugUnitTest and lintDebug pass; 140 tests, 0 failures. Tests cover the scrolling position, not the bar or OK.
|
||||
- APK signature verified with apksigner.
|
||||
|
||||
## Not checked
|
||||
- Not yet run on the box. The bar and OK need Paul's on-TV check. Scrolling can't be seen until there are more channels than fit.
|
||||
@@ -9,8 +9,8 @@ android {
|
||||
applicationId = "tv.orbit.classic"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 35
|
||||
versionName = "0.34.0-back-closes"
|
||||
versionCode = 36
|
||||
versionName = "0.35.0-guide-scroll"
|
||||
}
|
||||
buildFeatures { compose = true }
|
||||
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
|
||||
|
||||
@@ -41,7 +41,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.34.0\"")
|
||||
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.35.0\"")
|
||||
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
|
||||
if (request.method == "POST") {
|
||||
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
|
||||
|
||||
@@ -106,6 +106,7 @@ internal fun EmbyPlaybackDialog(session: EmbySampleSession, plan: EmbyPlaybackPl
|
||||
// A player handed over from the picture is already on schedule and may never report ready again.
|
||||
var joinedSchedule = runningPlayer != null
|
||||
if (player.playbackState == Player.STATE_READY) ready = true
|
||||
playing = player.isPlaying
|
||||
val listener = object : Player.Listener {
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
val safeReason = generateSequence(error.cause) { it.cause }.filterIsInstance<MediaDeliveryException>().firstOrNull()?.message
|
||||
@@ -206,7 +207,9 @@ internal fun EmbyPlaybackDialog(session: EmbySampleSession, plan: EmbyPlaybackPl
|
||||
KeyEvent.KEYCODE_CHANNEL_UP, KeyEvent.KEYCODE_CHANNEL_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_REWIND, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD)
|
||||
if (!supported) false else {
|
||||
if (event.action == KeyEvent.ACTION_UP) {
|
||||
if (event.action == KeyEvent.ACTION_UP && isOk && scheduled != null) {
|
||||
if (infoVisible) infoVisible = false else showInfo()
|
||||
} else if (event.action == KeyEvent.ACTION_UP) {
|
||||
when (code) {
|
||||
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_ENTER -> {
|
||||
if (scheduled == null && infoVisible) { if (player.isPlaying) player.pause() else player.play() }
|
||||
|
||||
@@ -51,6 +51,16 @@ internal fun guideTitleParts(title: String): Pair<String, String?> {
|
||||
return parts[0] to parts.drop(1).joinToString(" • ").ifBlank { null }
|
||||
}
|
||||
|
||||
/** The first row to draw, moving only as far as it takes to keep the chosen row on screen. */
|
||||
internal fun guideFirstRow(row: Int, first: Int, visible: Int, count: Int): Int {
|
||||
val last = (count - visible).coerceAtLeast(0)
|
||||
return when {
|
||||
row < first -> row
|
||||
row >= first + visible -> row - visible + 1
|
||||
else -> first
|
||||
}.coerceIn(0, last)
|
||||
}
|
||||
|
||||
/** Where the now line sits across the window, or null when now isn't in view. */
|
||||
internal fun guideNowFraction(nowMs: Long, windowStart: Long, windowEnd: Long): Float? =
|
||||
if (nowMs in windowStart until windowEnd) (nowMs - windowStart).toFloat() / (windowEnd - windowStart) else null
|
||||
@@ -68,6 +78,7 @@ internal fun GuideGrid(channels: List<SampleChannel>, selectedNumber: Int, modif
|
||||
var row by remember { mutableIntStateOf(channels.indexOfFirst { it.number == selectedNumber }.coerceAtLeast(0)) }
|
||||
var cursor by remember { mutableLongStateOf(now) }
|
||||
var slotsAhead by remember { mutableIntStateOf(0) }
|
||||
var firstRow by remember { mutableIntStateOf(0) }
|
||||
val gridFocus = remember { FocusRequester() }
|
||||
val toolsFocus = remember { FocusRequester() }
|
||||
var gridFocused by remember { mutableStateOf(true) }
|
||||
@@ -113,6 +124,9 @@ internal fun GuideGrid(channels: List<SampleChannel>, selectedNumber: Int, modif
|
||||
repeat(GUIDE_SLOTS) { Text(clock(windowStart + it * GUIDE_SLOT_MS), Modifier.weight(1f).padding(start = 4.dp), color = Color.White, fontSize = 12.sp, fontWeight = FontWeight.SemiBold) }
|
||||
}
|
||||
}
|
||||
BoxWithConstraints(Modifier.fillMaxWidth().weight(1f)) {
|
||||
val visibleRows = ((maxHeight + 3.dp) / 43.dp).toInt().coerceAtLeast(1)
|
||||
firstRow = guideFirstRow(row, firstRow, visibleRows, channels.size)
|
||||
Column(Modifier.fillMaxWidth().focusRequester(gridFocus).onFocusChanged { gridFocused = it.isFocused }.focusable().onPreviewKeyEvent { event ->
|
||||
val native = event.nativeKeyEvent
|
||||
if (native.action != KeyEvent.ACTION_DOWN) return@onPreviewKeyEvent false
|
||||
@@ -129,7 +143,8 @@ internal fun GuideGrid(channels: List<SampleChannel>, selectedNumber: Int, modif
|
||||
else -> false
|
||||
}
|
||||
}, verticalArrangement = Arrangement.spacedBy(3.dp)) {
|
||||
channels.forEachIndexed { index, line ->
|
||||
channels.subList(firstRow, minOf(firstRow + visibleRows, channels.size)).forEachIndexed { offset, line ->
|
||||
val index = firstRow + offset
|
||||
Row(Modifier.fillMaxWidth().height(40.dp), horizontalArrangement = Arrangement.spacedBy(0.dp)) {
|
||||
val mark = ChannelBrands.forNumber(line.number)
|
||||
Row(Modifier.width(130.dp).fillMaxHeight().background(Panel).padding(start = 6.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -156,6 +171,7 @@ internal fun GuideGrid(channels: List<SampleChannel>, selectedNumber: Int, modif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text("▲▼ Channel ◄► Time OK Watch Back Home", color = Muted, fontSize = 11.sp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.34.0")
|
||||
next.setRequestProperty("User-Agent", "OrbitTV/0.35.0")
|
||||
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()) {
|
||||
|
||||
@@ -82,4 +82,19 @@ class GuideGridTest {
|
||||
assertEquals(1f, programmeProgress(100, 200, 300), .001f)
|
||||
assertEquals(0f, programmeProgress(100, 100, 100), .001f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theGridScrollsOnlyWhenTheChosenRowLeavesTheScreen() {
|
||||
assertEquals(0, guideFirstRow(3, 0, 8, 25))
|
||||
assertEquals(1, guideFirstRow(8, 0, 8, 25))
|
||||
assertEquals(5, guideFirstRow(9, 5, 8, 25))
|
||||
assertEquals(4, guideFirstRow(4, 5, 8, 25))
|
||||
assertEquals(17, guideFirstRow(24, 10, 8, 25))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aShortLineupNeverScrolls() {
|
||||
assertEquals(0, guideFirstRow(2, 0, 8, 3))
|
||||
assertEquals(0, guideFirstRow(0, 4, 8, 3))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user