Preserve TV Back viewports and person-switch authority
All checks were successful
review/delta-opus Tier C delta review: 0 blockers; 4 non-blocking notes (N1-N4)
gate/round2-candidate Full suite 1308/1308 and Opt 1.1.2/71 checks on this exact head (data/tv-r2-gate5/result.json)
All checks were successful
review/delta-opus Tier C delta review: 0 blockers; 4 non-blocking notes (N1-N4)
gate/round2-candidate Full suite 1308/1308 and Opt 1.1.2/71 checks on this exact head (data/tv-r2-gate5/result.json)
This commit is contained in:
parent
9355288f79
commit
72849f0734
7 changed files with 215 additions and 52 deletions
|
|
@ -1,6 +1,9 @@
|
|||
package org.justinvisser.wolfskin.design
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.BringIntoViewSpec
|
||||
import androidx.compose.foundation.gestures.LocalBringIntoViewSpec
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
|
|
@ -10,6 +13,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -32,6 +36,9 @@ import androidx.tv.material3.Text
|
|||
import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C
|
||||
import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private object MinimumScrollBringIntoViewSpec : BringIntoViewSpec
|
||||
|
||||
/** Native Back belongs to the Activity dispatcher; Escape remains the local Back shortcut. */
|
||||
fun readShellKey(event: KeyEvent): ShellKey? = when (event.key) {
|
||||
Key.DirectionUp -> ShellKey.Up
|
||||
|
|
@ -51,6 +58,7 @@ fun ShellState.handleKey(event: KeyEvent): Boolean {
|
|||
|
||||
/** Content and nav share a single focus policy; upstream full-screen pages bypass this surface. */
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun ShellSurface(
|
||||
state: ShellState,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -96,8 +104,13 @@ fun ShellSurface(
|
|||
if (state.pickerVisible) {
|
||||
WhosWatching(state)
|
||||
} else {
|
||||
FocusRingHost {
|
||||
if (state.route == "settings") SettingsList(state, requestCount = settingsRequestCount ?: state.requests.snapshot.requestCount, installedVersion = settingsInstalledVersion) else content(Modifier.fillMaxSize().focusRequester(contentFocus).focusRestorer().focusGroup())
|
||||
// Logical-focus screens own their scroll position; TV's 30% focus pivot must not recenter it.
|
||||
CompositionLocalProvider(
|
||||
LocalBringIntoViewSpec provides if (handlesContentKeys) MinimumScrollBringIntoViewSpec else LocalBringIntoViewSpec.current,
|
||||
) {
|
||||
FocusRingHost {
|
||||
if (state.route == "settings") SettingsList(state, requestCount = settingsRequestCount ?: state.requests.snapshot.requestCount, installedVersion = settingsInstalledVersion) else content(Modifier.fillMaxSize().focusRequester(contentFocus).focusRestorer().focusGroup())
|
||||
}
|
||||
}
|
||||
NavBar(state, view.listState.firstVisibleItemIndex > 0 || view.listState.firstVisibleItemScrollOffset > L.navSolidAfter.value * 2)
|
||||
ComingInSheet(state)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue