channels: never open the screen without a selection (#32)

Coming back from a channel page hands that channel's card the focus, but the
handle only wins when the card is still in the rows. A channel that was
renamed, unsubscribed, or simply never carried in the rows left every row
asking for no focus at all, so opening Channels from the drawer put the remote
nowhere: "when I click on Channels, none is selected, it should behave like
video cards" (#32 review). The fallback row is now chosen by whether the
handle exists anywhere in the rows, and a row whose wanted card never became
focusable takes its first card instead of staying unselected.
This commit is contained in:
Jeena 2026-10-16 13:03:28 +00:00
parent 3854c6999a
commit 45fd1b963e

View file

@ -358,6 +358,14 @@ fun ChannelsScreen(
// as stats stream in the rows re-sort, and re-focusing their first card // as stats stream in the rows re-sort, and re-focusing their first card
// would steal focus mid-navigation (user: "up jumps to the first row"). // would steal focus mid-navigation (user: "up jumps to the first row").
val focusHandedOut = remember { mutableStateOf(false) } val focusHandedOut = remember { mutableStateOf(false) }
// The channel we came back from only wins the focus while it is still
// in the rows. When it isn't (renamed, unsubscribed, or the rows simply
// never carried it) every row used to ask for no focus at all — "when I
// click on Channels, none is selected" (#32 review). Fall back to the
// first row in that case, like the video cards do.
val focusRowExists = focusHandle != null && viewModel.userRows.any {
group -> group.channels.any { it.name + "@" + it.host == focusHandle }
}
LazyColumn(Modifier.fillMaxSize()) { LazyColumn(Modifier.fillMaxSize()) {
viewModel.userRows.forEach { group -> viewModel.userRows.forEach { group ->
item(key = "hdr-" + group.ownerName) { item(key = "hdr-" + group.ownerName) {
@ -379,7 +387,7 @@ fun ChannelsScreen(
onOpenDrawer, onOpenDrawer,
drawerPulse = drawerPulse, drawerPulse = drawerPulse,
focused = !focusHandedOut.value && ( focused = !focusHandedOut.value && (
if (focusHandle != null) isTargetRow if (focusRowExists) isTargetRow
else group == viewModel.userRows.firstOrNull() else group == viewModel.userRows.firstOrNull()
), ),
focusIndex = if (isTargetRow) target else 0, focusIndex = if (isTargetRow) target else 0,
@ -479,6 +487,11 @@ private fun ChannelRow(
return@LaunchedEffect return@LaunchedEffect
} }
} }
// The wanted card never became focusable (its row scrolled past it,
// or the handle went stale): take the first card instead, so the
// screen is never left with nothing selected (#32 review).
val first = focusRequesters[0]
if (first != null && runCatching { first.requestFocus() }.isSuccess) onFocused()
} }
} }
} }