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"
minSdk = 24
targetSdk = 36
versionCode = 144
versionName = "0.14.81"
versionCode = 145
versionName = "0.14.82"
buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"")
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")

View file

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