Restore approved picker Settings centring
This commit is contained in:
parent
a27e922675
commit
7c67596cb6
4 changed files with 24 additions and 11 deletions
|
|
@ -4,10 +4,16 @@
|
||||||
|
|
||||||
## Who’s watching
|
## Who’s watching
|
||||||
|
|
||||||
Round-2 correction: the Settings gear is centred beneath the **last avatar**, independent of selection. Down from **any avatar** reaches it; Up returns to the avatar that supplied focus, including after visiting Settings and returning. Left/Right still traverse only avatars. This supersedes the revision-5 whole-row centring and final-avatar-only Down rule. The 4 px accent straddle ring, 1.08 scale and focus-only Settings caption are unchanged.
|
The Settings gear is horizontally centred beneath the complete people row, independent of person count or selection. Justin confirmed this placement. It uses the same 4 px accent straddle ring as the people, with the existing 1.08 scale and focus-only Settings caption.
|
||||||
|
|
||||||
|
Down from any avatar reaches the gear; Up returns to its origin, including after visiting Settings. Left/Right avatar traversal is unchanged.
|
||||||
|
|
||||||
|
Source — Justin (2026-11-07): “the settings icon moved, but pressing down only works from the last profile icon. I want it to always move there when pressing down in that screen”.
|
||||||
|
|
||||||
Root content Back moves to the nav; the next Back **finishes and removes Wolfskin’s Android task**, returning to the launcher without visiting an old Home/setup interface. This applies to Home, Discover, Movies, Shows and Anime. Cold-launch picker Back exits; a reopened picker first dismisses to its opener’s nav. Pushed title/person/settings/Updates pages pop their parent stack, and menus/sheets/unsaved-settings prompts get first refusal. A singleton full-screen page exits rather than becoming a dead end.
|
Root content Back moves to the nav; the next Back **finishes and removes Wolfskin’s Android task**, returning to the launcher without visiting an old Home/setup interface. This applies to Home, Discover, Movies, Shows and Anime. Cold-launch picker Back exits; a reopened picker first dismisses to its opener’s nav. Pushed title/person/settings/Updates pages pop their parent stack, and menus/sheets/unsaved-settings prompts get first refusal. A singleton full-screen page exits rather than becoming a dead end.
|
||||||
|
|
||||||
|
Source — Justin (2026-11-07): “pressing back enough times should close wolfskin, currently there is no way to go back to projectivity apart from weird methods via the old ui”.
|
||||||
|
|
||||||
## Focus visibility
|
## Focus visibility
|
||||||
|
|
||||||
“Centered” means consistent visibility, not geometric centering. Keep the approved shelf rule: one-card lookahead, shifting after the second-last visible slot, with first/last positions clamped to the actual contents. Short rows do not manufacture empty scroll space. Poster, tile, wide, service, episode, cast and related shelves follow the same rule.
|
“Centered” means consistent visibility, not geometric centering. Keep the approved shelf rule: one-card lookahead, shifting after the second-last visible slot, with first/last positions clamped to the actual contents. Short rows do not manufacture empty scroll space. Poster, tile, wide, service, episode, cast and related shelves follow the same rule.
|
||||||
|
|
@ -18,7 +24,7 @@ Library and Person grids retain six columns and the existing Level 2 row spacing
|
||||||
|
|
||||||
## Home continuation and navigation — round 2 amendment
|
## Home continuation and navigation — round 2 amendment
|
||||||
|
|
||||||
Home has one **Continue watching** lane for in-progress films/episodes and next-up episodes. Use the existing landscape-tile geometry, order by most recent actual watch activity, and show a series once: its resumable episode takes precedence over its next-up episode. Unknown activity dates retain source order after known dates. The non-rotating hero keeps its resume → next-up → newest → pick precedence and is not duplicated in the lane.
|
Justin asked for one **Continue watching** lane merging in-progress films/episodes and next-up episodes. **Implementation choice:** reuse the existing Next up landscape-tile geometry for the merged lane, rather than the old Resume/wide geometry. Order by most recent actual watch activity and show a series once: its resumable episode takes precedence over its next-up episode. Unknown activity dates retain source order after known dates. The non-rotating hero keeps its resume → next-up → newest → pick precedence and is not duplicated in the lane.
|
||||||
|
|
||||||
The lane retains the `cw` identity. Legacy local, server, web and plugin Home settings merge saved Resume/Next up rows at the first watch row's position and preserve that row's view options. Existing serialized types remain readable; new settings offer only Continue watching.
|
The lane retains the `cw` identity. Legacy local, server, web and plugin Home settings merge saved Resume/Next up rows at the first watch row's position and preserve that row's view options. Existing serialized types remain readable; new settings offer only Continue watching.
|
||||||
|
|
||||||
|
|
@ -26,7 +32,7 @@ Every navigation entry owns its own viewport even while an outgoing page and inc
|
||||||
|
|
||||||
An unvisited Home row starts at its leftmost card rather than inheriting horizontal alignment from the previous row. Visited rows retain their remembered column and first-visible position across row changes, title Back and root-tab leave/return. Home does not adopt Discover’s tab-leave memory reset; Library keeps standard grid navigation. Outgoing title/person entries cannot intercept Android Back during their exit animation.
|
An unvisited Home row starts at its leftmost card rather than inheriting horizontal alignment from the previous row. Visited rows retain their remembered column and first-visible position across row changes, title Back and root-tab leave/return. Home does not adopt Discover’s tab-leave memory reset; Library keeps standard grid navigation. Outgoing title/person entries cannot intercept Android Back during their exit animation.
|
||||||
|
|
||||||
Landscape-card bottom gradients extend through the reserved state-line area, ending at the solid bottom backing with no exposed artwork band. The title-gradient top padding is **36 px / 18 dp**, down from 54 px / 27 dp; the approved 92% bottom opacity is unchanged. Captions and state lines retain their existing geometry.
|
Landscape-card bottom gradients extend through the reserved state-line area, ending at the solid bottom backing with no exposed artwork band, as Justin requested. **Implementation choice for the amount:** title-gradient top padding is **36 px / 18 dp**, down from 54 px / 27 dp. The existing approved 92% bottom opacity is unchanged. Captions and state lines retain their existing geometry.
|
||||||
|
|
||||||
|
|
||||||
## Episode artwork and dimensions
|
## Episode artwork and dimensions
|
||||||
|
|
|
||||||
|
|
@ -233,9 +233,13 @@ The stroke is inset within the fixed 20 dp / 40 px height; horizontal border-box
|
||||||
padding includes it. The 6 px dash / 4 px gap follows the browser's 3:2 stroke-width
|
padding includes it. The 6 px dash / 4 px gap follows the browser's 3:2 stroke-width
|
||||||
ratios. Resting borders are white at 22% opacity; focused borders use ink at 40%.
|
ratios. Resting borders are white at 22% opacity; focused borders use ink at 40%.
|
||||||
|
|
||||||
The small gear beneath the last avatar opens Settings with Requests focused.
|
The small gear centred beneath the complete people row opens Settings with Requests focused.
|
||||||
Down from any avatar reaches it; Up returns to the originating avatar, including
|
Down from any avatar reaches it; Up returns to the originating avatar, including
|
||||||
after a Settings visit. Left/Right avatar traversal is unchanged.
|
after a Settings visit. Left/Right avatar traversal is unchanged.
|
||||||
|
The navigation requirement comes from Justin's words: “the settings icon moved,
|
||||||
|
but pressing down only works from the last profile icon. I want it to always
|
||||||
|
move there when pressing down in that screen”. The approved gear placement stays
|
||||||
|
centred beneath the complete people row.
|
||||||
Settings groups hand off to existing upstream pages; Requests opens the household
|
Settings groups hand off to existing upstream pages; Requests opens the household
|
||||||
Coming-in sheet described below. **Advanced settings → Wolfskin screens** is the persistent
|
Coming-in sheet described below. **Advanced settings → Wolfskin screens** is the persistent
|
||||||
rollout switch: turning it off renders upstream Home and library destinations with
|
rollout switch: turning it off renders upstream Home and library destinations with
|
||||||
|
|
@ -251,6 +255,9 @@ pages pop normally. Child dialogs, title menus, request sheets and unsaved-setti
|
||||||
prompts keep Back priority. Singleton full-screen pages also exit; they cannot
|
prompts keep Back priority. Singleton full-screen pages also exit; they cannot
|
||||||
fall through to the older setup/user UI.
|
fall through to the older setup/user UI.
|
||||||
|
|
||||||
|
Source — Justin: “pressing back enough times should close wolfskin, currently
|
||||||
|
there is no way to go back to projectivity apart from weird methods via the old ui”.
|
||||||
|
|
||||||
`rememberWolfskinBackHandler` owns Android Back at the app root, including a
|
`rememberWolfskinBackHandler` owns Android Back at the app root, including a
|
||||||
single-entry `NavDisplay`; full-screen hand-offs use the same `NavigationManager`
|
single-entry `NavDisplay`; full-screen hand-offs use the same `NavigationManager`
|
||||||
exit event rather than exposing the parent setup stack. `WolfskinRootExitTest`
|
exit event rather than exposing the parent setup stack. `WolfskinRootExitTest`
|
||||||
|
|
@ -680,9 +687,8 @@ The [revision-5 amendment](../data/tv-spec/spec.md) and
|
||||||
[native frame prototype delta](../data/tv-v2/feedback-2026-11-06/index.html)
|
[native frame prototype delta](../data/tv-v2/feedback-2026-11-06/index.html)
|
||||||
retain the approved frames except for the requested feedback changes:
|
retain the approved frames except for the requested feedback changes:
|
||||||
|
|
||||||
- Round 2 supersedes revision 5: the picker gear sits under the last avatar,
|
- The picker gear retains its approved whole-row centring and picker-person ring.
|
||||||
is reachable with Down from every avatar, and returns Up to its origin.
|
Down from every avatar reaches it; Up returns to the originating avatar.
|
||||||
It retains the picker-person ring.
|
|
||||||
- Shelf tracks share second-last-slot lookahead, count clamping and settled
|
- Shelf tracks share second-last-slot lookahead, count clamping and settled
|
||||||
offsets. Home records completed frame positioning rather than an animation
|
offsets. Home records completed frame positioning rather than an animation
|
||||||
that may still be cancelled; first mount restores the intended shelf item.
|
that may still be cancelled; first mount restores the intended shelf item.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C
|
||||||
import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L
|
import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L
|
||||||
import org.justinvisser.wolfskin.design.WolfskinTokens.Shell as S
|
import org.justinvisser.wolfskin.design.WolfskinTokens.Shell as S
|
||||||
|
|
||||||
/** The picker covers the nav; Down from any person reaches Settings below the final avatar. */
|
/** The picker covers the nav; Down from any person reaches the centred Settings gear. */
|
||||||
@Composable
|
@Composable
|
||||||
fun WhosWatching(state: ShellState, modifier: Modifier = Modifier) {
|
fun WhosWatching(state: ShellState, modifier: Modifier = Modifier) {
|
||||||
Box(modifier.fillMaxSize().background(C.bg)) {
|
Box(modifier.fillMaxSize().background(C.bg)) {
|
||||||
|
|
@ -53,7 +53,7 @@ private fun PeoplePicker(state: ShellState) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val left = (S.stageWidth - L.gear + (L.pickerAvatar + L.pickerGap) * (state.people.size - 1)) / 2
|
val left = (S.stageWidth - L.gear) / 2
|
||||||
Box(Modifier.offset(left, S.pickerGearTop).size(L.gear).testTag("picker-settings").focusScale(state.gearFocused, WolfskinTokens.Focus.scaleCard)
|
Box(Modifier.offset(left, S.pickerGearTop).size(L.gear).testTag("picker-settings").focusScale(state.gearFocused, WolfskinTokens.Focus.scaleCard)
|
||||||
.focusRing(state.gearFocused, L.gear / 2, WolfskinTokens.Focus.ringPickerRing)
|
.focusRing(state.gearFocused, L.gear / 2, WolfskinTokens.Focus.ringPickerRing)
|
||||||
.clickable { state.gearFocused = true; state.key(ShellKey.Ok) }, contentAlignment = Alignment.Center) {
|
.clickable { state.gearFocused = true; state.key(ShellKey.Ok) }, contentAlignment = Alignment.Center) {
|
||||||
|
|
|
||||||
|
|
@ -49,16 +49,17 @@ class PickerNavigationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun settingsGearIsCenteredUnderTheFinalAvatarForAnyProfileCount() {
|
@Test fun settingsGearIsCenteredUnderTheWholePeopleRowForAnyProfileCount() {
|
||||||
val state = ShellState(fixturePeople(), "justin")
|
val state = ShellState(fixturePeople(), "justin")
|
||||||
rule.setContent { WolfskinTheme { ShellSurface(state) } }
|
rule.setContent { WolfskinTheme { ShellSurface(state) } }
|
||||||
for (count in 1..5) {
|
for (count in 1..5) {
|
||||||
val people = (0 until count).map { ShellPerson("person-$it", "Person $it", "$it", false) }
|
val people = (0 until count).map { ShellPerson("person-$it", "Person $it", "$it", false) }
|
||||||
rule.runOnIdle { state.updatePeople(people); state.gearFocused = true }
|
rule.runOnIdle { state.updatePeople(people); state.gearFocused = true }
|
||||||
rule.mainClock.advanceTimeBy(400)
|
rule.mainClock.advanceTimeBy(400)
|
||||||
|
val first = rule.onNodeWithTag("picker-person:${people.first().key}").fetchSemanticsNode().boundsInRoot
|
||||||
val last = rule.onNodeWithTag("picker-person:${people.last().key}").fetchSemanticsNode().boundsInRoot
|
val last = rule.onNodeWithTag("picker-person:${people.last().key}").fetchSemanticsNode().boundsInRoot
|
||||||
val gear = rule.onNodeWithTag("picker-settings").fetchSemanticsNode().boundsInRoot
|
val gear = rule.onNodeWithTag("picker-settings").fetchSemanticsNode().boundsInRoot
|
||||||
assertEquals("Settings must stay under the last profile for $count people", last.center.x, gear.center.x, .1f)
|
assertEquals("Settings must stay centred under the whole row for $count people", (first.center.x + last.center.x) / 2, gear.center.x, .1f)
|
||||||
assertTrue("Settings must be below the avatars", gear.top > last.bottom)
|
assertTrue("Settings must be below the avatars", gear.top > last.bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue