fix(live): returning from playback keeps the group you were in
Some checks failed
Build TV app / build (push) Failing after 3s

The rail opens whatever gets focused, so when the player closed and focus
landed on "Alle Sender", the view silently switched away from the group
the viewer had been browsing. A restore flag now suppresses that
auto-select until focus has been placed on the channel that was watched.

Completes #13.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 02:20:17 +02:00
parent dd612fc608
commit 8068f34a4e
3 changed files with 18 additions and 5 deletions

View File

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

View File

@@ -51,6 +51,13 @@ class AppState(
/** Channel to restore focus to when the list comes back (#13). */
var lastWatched by mutableStateOf<Channel?>(null)
/**
* True while the list is being restored after playback. The group rail
* opens whatever gets focused, so without this the focus landing there
* would silently switch the view back to "Alle Sender".
*/
var restorePending by mutableStateOf(false)
private val reentryHandler = android.os.Handler(android.os.Looper.getMainLooper())
private var lastStoppedUrl: String = ""
private var lastStoppedAt: Long = 0L
@@ -190,6 +197,7 @@ class AppState(
}
fun stopPlayback() {
restorePending = lastWatched != null
reentryHandler.removeCallbacksAndMessages(null)
lastStoppedUrl = currentChannel?.url.orEmpty()
lastStoppedAt = System.currentTimeMillis()

View File

@@ -106,11 +106,12 @@ fun LiveScreen(state: AppState) {
// A fresh group starts at the top; a return from playback does not.
if (restoreIndex == null) listState.scrollToItem(0)
}
LaunchedEffect(restoreIndex, channels.size) {
if (restoreIndex != null && !restored) {
LaunchedEffect(restoreIndex, channels.size, state.restorePending) {
if (restoreIndex != null && (state.restorePending || !restored)) {
listState.scrollToItem(restoreIndex)
runCatching { restoreFocus.requestFocus() }
restored = true
state.restorePending = false
}
}
val intoList = if (channels.isEmpty()) Modifier
@@ -135,6 +136,7 @@ fun LiveScreen(state: AppState) {
modifier = intoList.then(
if (selected) Modifier.focusRequester(railFocus) else Modifier
),
suppressAutoSelect = { state.restorePending },
) {
state.favoritesOnly = false
state.groupFilter = null
@@ -149,6 +151,7 @@ fun LiveScreen(state: AppState) {
modifier = intoList.then(
if (state.favoritesOnly) Modifier.focusRequester(railFocus) else Modifier
),
suppressAutoSelect = { state.restorePending },
) {
state.favoritesOnly = true
state.groupFilter = null
@@ -172,6 +175,7 @@ fun LiveScreen(state: AppState) {
modifier = intoList.then(
if (state.groupFilter == group) Modifier.focusRequester(railFocus) else Modifier
),
suppressAutoSelect = { state.restorePending },
) {
state.favoritesOnly = false
state.groupFilter = group
@@ -237,6 +241,7 @@ private fun GroupItem(
count: Int,
selected: Boolean,
modifier: Modifier = Modifier,
suppressAutoSelect: () -> Boolean = { false },
onSelect: () -> Unit,
) {
Surface(
@@ -245,7 +250,7 @@ private fun GroupItem(
onClick = onSelect,
modifier = modifier
.fillMaxWidth()
.onFocusChanged { if (it.isFocused) onSelect() },
.onFocusChanged { if (it.isFocused && !suppressAutoSelect()) onSelect() },
shape = ClickableSurfaceDefaults.shape(RoundedCornerShape(10.dp)),
scale = ClickableSurfaceDefaults.scale(focusedScale = 1f),
colors = ClickableSurfaceDefaults.colors(