From 8068f34a4edd137fbf8d1b7b5dfdfea79bced836 Mon Sep 17 00:00:00 2001 From: benjamin Date: Wed, 26 Aug 2026 02:20:17 +0200 Subject: [PATCH] fix(live): returning from playback keeps the group you were in 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 --- app/build.gradle.kts | 4 ++-- app/src/main/java/dev/castarr/tv/AppState.kt | 8 ++++++++ app/src/main/java/dev/castarr/tv/ui/LiveScreen.kt | 11 ++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb40884..f16a270 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 diff --git a/app/src/main/java/dev/castarr/tv/AppState.kt b/app/src/main/java/dev/castarr/tv/AppState.kt index 3652e9a..c98a024 100644 --- a/app/src/main/java/dev/castarr/tv/AppState.kt +++ b/app/src/main/java/dev/castarr/tv/AppState.kt @@ -51,6 +51,13 @@ class AppState( /** Channel to restore focus to when the list comes back (#13). */ var lastWatched by mutableStateOf(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() diff --git a/app/src/main/java/dev/castarr/tv/ui/LiveScreen.kt b/app/src/main/java/dev/castarr/tv/ui/LiveScreen.kt index 2a878af..dd1cc3b 100644 --- a/app/src/main/java/dev/castarr/tv/ui/LiveScreen.kt +++ b/app/src/main/java/dev/castarr/tv/ui/LiveScreen.kt @@ -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(