Fix Events crash: bind FocusRequester before TopNav down to hub rail.

This commit is contained in:
Jos Vooges | STH 2026-09-23 01:43:20 +02:00
parent 1f072b7d09
commit 244a8c97ed
2 changed files with 32 additions and 8 deletions

View file

@ -20,8 +20,8 @@ android {
applicationId = "nl.vonas.mediacluster.tv" applicationId = "nl.vonas.mediacluster.tv"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 144 versionCode = 145
versionName = "0.14.81" versionName = "0.14.82"
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"") buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
ndk { ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a") abiFilters += listOf("arm64-v8a", "armeabi-v7a")

View file

@ -133,6 +133,7 @@ fun EventsScreen(
var providerFilter by remember { mutableStateOf<String?>(null) } var providerFilter by remember { mutableStateOf<String?>(null) }
var nowMs by remember { mutableLongStateOf(System.currentTimeMillis()) } var nowMs by remember { mutableLongStateOf(System.currentTimeMillis()) }
var filterBarFocusReady by remember { mutableStateOf(false) } var filterBarFocusReady by remember { mutableStateOf(false) }
var hubRailFocusReady by remember { mutableStateOf(false) }
/** Owner-id van first-rail FocusRequester — voorkomt dispose-race live↔upcoming. */ /** Owner-id van first-rail FocusRequester — voorkomt dispose-race live↔upcoming. */
var railFocusOwner by remember { mutableStateOf<String?>(null) } var railFocusOwner by remember { mutableStateOf<String?>(null) }
val railFocusReady = railFocusOwner != null val railFocusReady = railFocusOwner != null
@ -141,6 +142,7 @@ fun EventsScreen(
val listState = rememberLazyListState() val listState = rememberLazyListState()
val firstRailFocus = remember { FocusRequester() } val firstRailFocus = remember { FocusRequester() }
val restoreCardFocus = remember { FocusRequester() } val restoreCardFocus = remember { FocusRequester() }
val hubRailFocus = remember { FocusRequester() }
suspend fun refresh() { suspend fun refresh() {
runCatching { api.listScheduleEvents() } runCatching { api.listScheduleEvents() }
@ -253,7 +255,7 @@ fun EventsScreen(
val filterBarFocus = remember { FocusRequester() } val filterBarFocus = remember { FocusRequester() }
val firstRailUpTarget = when { val firstRailUpTarget = when {
showFilterBar && filterBarFocusReady -> filterBarFocus showFilterBar && filterBarFocusReady -> filterBarFocus
hasHubRail -> contentFocusRequester hasHubRail && hubRailFocusReady -> hubRailFocus
else -> navFocusRequester else -> navFocusRequester
} }
val filterFocusDown: FocusRequester? = when { val filterFocusDown: FocusRequester? = when {
@ -342,19 +344,24 @@ fun EventsScreen(
} }
val entryReady = when { val entryReady = when {
hasHubRail -> true hasHubRail -> hubRailFocusReady
showFilterBar -> filterBarFocusReady showFilterBar -> filterBarFocusReady
else -> railFocusReady else -> railFocusReady
} }
val needContentSink = when { val needContentSink = when {
loading && events.isEmpty() -> true loading && events.isEmpty() -> true
error != null && events.isEmpty() -> true error != null && events.isEmpty() -> true
hasHubRail -> false // contentFocusRequester mag nooit "los" staan (TopNav.down → crash)
hasHubRail -> !hubRailFocusReady
showFilterBar -> !filterBarFocusReady showFilterBar -> !filterBarFocusReady
!hasRails -> true !hasRails -> true
else -> !railFocusReady else -> !railFocusReady
} }
LaunchedEffect(hasHubRail) {
if (!hasHubRail) hubRailFocusReady = false
}
// Detail → back: card restore; fallback = chips of eerste rail. // Detail → back: card restore; fallback = chips of eerste rail.
LaunchedEffect( LaunchedEffect(
pendingFocusRestore, pendingFocusRestore,
@ -374,7 +381,7 @@ fun EventsScreen(
if (restoredSuccessfully) return if (restoredSuccessfully) return
val entryOk = awaitReady({ val entryOk = awaitReady({
when { when {
hasHubRail -> true hasHubRail -> hubRailFocusReady
showFilterBar -> filterBarFocusReady showFilterBar -> filterBarFocusReady
else -> railFocusReady else -> railFocusReady
} }
@ -499,12 +506,17 @@ fun EventsScreen(
) { ) {
if (hasHubRail) { if (hasHubRail) {
item(key = "hub_rail") { item(key = "hub_rail") {
// Zolang TopNav-sink contentFocusRequester vasthoudt → hubRailFocus op tile.
// Zodra ready → contentFocusRequester op eerste tile.
val hubEntryFocus =
if (hubRailFocusReady) contentFocusRequester else hubRailFocus
EventsHubRail( EventsHubRail(
hubs = hubs, hubs = hubs,
focusRequester = contentFocusRequester, focusRequester = hubEntryFocus,
focusUp = navFocusRequester, focusUp = navFocusRequester,
onOpenF1 = onOpenF1Hub, onOpenF1 = onOpenF1Hub,
onOpenViaplay = onOpenViaplayHub, onOpenViaplay = onOpenViaplayHub,
onFocusReady = { hubRailFocusReady = it },
onFocus = onContentFocused, onFocus = onContentFocused,
) )
} }
@ -523,7 +535,11 @@ fun EventsScreen(
liveCount = live.size, liveCount = live.size,
totalCount = filtered.size, totalCount = filtered.size,
firstFocusRequester = if (hasHubRail) filterBarFocus else contentFocusRequester, firstFocusRequester = if (hasHubRail) filterBarFocus else contentFocusRequester,
focusUp = if (hasHubRail) contentFocusRequester else navFocusRequester, focusUp = when {
hasHubRail && hubRailFocusReady -> contentFocusRequester
hasHubRail -> null
else -> navFocusRequester
},
focusDown = filterFocusDown, focusDown = filterFocusDown,
onFocusReady = { filterBarFocusReady = it }, onFocusReady = { filterBarFocusReady = it },
onFocus = onContentFocused, onFocus = onContentFocused,
@ -1108,6 +1124,7 @@ private fun EventsHubRail(
focusUp: FocusRequester?, focusUp: FocusRequester?,
onOpenF1: (() -> Unit)?, onOpenF1: (() -> Unit)?,
onOpenViaplay: (() -> Unit)?, onOpenViaplay: (() -> Unit)?,
onFocusReady: ((Boolean) -> Unit)? = null,
onFocus: () -> Unit, onFocus: () -> Unit,
) { ) {
LazyRow( LazyRow(
@ -1140,6 +1157,7 @@ private fun EventsHubRail(
accent = accent, accent = accent,
focusRequester = if (index == 0) focusRequester else null, focusRequester = if (index == 0) focusRequester else null,
focusUp = focusUp, focusUp = focusUp,
onFocusReady = if (index == 0) onFocusReady else null,
onOpen = onOpen, onOpen = onOpen,
onFocus = onFocus, onFocus = onFocus,
) )
@ -1156,9 +1174,15 @@ private fun HubRailTile(
accent: Color, accent: Color,
focusRequester: FocusRequester?, focusRequester: FocusRequester?,
focusUp: FocusRequester?, focusUp: FocusRequester?,
onFocusReady: ((Boolean) -> Unit)? = null,
onOpen: () -> Unit, onOpen: () -> Unit,
onFocus: () -> Unit, onFocus: () -> Unit,
) { ) {
// Ready = eerste tile is in composition (niet gekoppeld aan welk FocusRequester-object)
DisposableEffect(Unit) {
onFocusReady?.invoke(true)
onDispose { onFocusReady?.invoke(false) }
}
var focused by remember { mutableStateOf(false) } var focused by remember { mutableStateOf(false) }
Surface( Surface(
onClick = onOpen, onClick = onOpen,