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(