feat(android): the touch model reaches the other three lists
Plan 019 phases 3 and 4, which finish #63. The queue panel and both playlist detail views get tap-to-play and hold-to-select; the playlist views get swipe-to-queue as well. Phase 3 was not the pure wiring the plan expected, in two places. A tap on a queue row plays that position. Copying track-list's tap -- which sets the queue to the list the row is in -- would rebuild the queue from the queue, discarding its source, its shuffle order and anything inserted by hand. It reads as a no-op and is not one. And the queue panel has no swipe, deliberately. A right swipe means add to the queue everywhere else it exists, and a queue row is already in the queue; the only thing it could mean there is remove, which is the same gesture with the opposite effect one screen away. Removing a queue row is on the row, on its sheet since #60, and now on its selection bar. The assertion is that its rows do not opt in. The reveal became utils/swipe-to-queue.ts rather than being copied into three lists, keyed on a data-swipe attribute so one stylesheet carries the touch-action half of the device fix to rows that are called two different things. Phase 4 was already true and is now asserted: a claimed tap has its click swallowed, so an explore-link inside a row never sees one and tap-to-play wins with no rule of its own. Its test was vacuous when written -- the tap helper sent no click, so there was nothing to swallow -- which also weakened phase 1's. It sends one now. Escape leaves selection mode, from selection-bar rather than from each of the four hosts, since that element exists only while the mode does. The platform's back gesture deliberately does not reach it: the shell owns the history stack and four lists reaching for history is four stacks. That is #200. Verified on the reference phone: a queue row taps to its own index and refuses a swipe, a playlist row queues on a swipe and plays its playlist on a tap, and a hold raises the bar without the menu. Closes #63
This commit is contained in:
@@ -1506,6 +1506,56 @@ rest of the press (a scroll that curves is still a scroll), and a
|
||||
gesture that is not *strictly* more horizontal than vertical is the
|
||||
scroller's.
|
||||
|
||||
**And what a finger *means* on a row is the inversion of what a mouse
|
||||
means, decided per event** (#63). A click selects and a double-click
|
||||
plays; a tap **plays** and a hold enters **selection mode**, in which a
|
||||
tap toggles. The predicate is `pointerType`, never a viewport width and
|
||||
never a platform flag — #64's rule, and with #64's warning: keyed on a
|
||||
width, an Android tablet over 600px gets desktop semantics on a
|
||||
touchscreen, a touchscreen laptop cannot be described at all, and a
|
||||
narrow desktop window gets phone semantics with a mouse.
|
||||
|
||||
There is deliberately **no double-tap**, which #63 asked for. The first
|
||||
tap of one is indistinguishable from a single tap until the interval
|
||||
expires, so tapping would have to wait `DOUBLE_CLICK_GRACE_MS` before
|
||||
acting — 250ms on top of a measured ~100ms play, 3.5x the app's primary
|
||||
interaction, to reach a menu a hold already reaches. So the menu and
|
||||
the action bar are the same surface: `components/selection-bar/` is
|
||||
presentational (a count and a list of actions, no store, no selection),
|
||||
`SelectionController` carries the mode for all four selecting surfaces,
|
||||
and #60's bottom sheet is the overflow behind "More" — so
|
||||
`contextMenuStyles`, `MenuKeyboard` and `menu-surface` are reused
|
||||
rather than reimplemented.
|
||||
|
||||
Three things about it are load-bearing. **A control inside a row keeps
|
||||
its own tap**: the gesture is simply not claimed there, so the click
|
||||
behind it falls through, which is what stops the 44px favourite target
|
||||
(#56) becoming a 44px play target — the queue row's × is the second
|
||||
instance. **A swipe right queues**, and its affordance is
|
||||
`utils/swipe-to-queue.ts` once rather than in each of the three lists
|
||||
that draw it: the row does not move, its *children* do (a row here is
|
||||
`contain: strict` with `overflow: hidden`, so a pane held at the row's
|
||||
original position while the row translates is at a negative offset
|
||||
inside a clipping box and is not painted), the travel is written to the
|
||||
row's own style rather than rendered, and the threshold is a fraction
|
||||
of the row because the row is 424x52 on the reference device. **The
|
||||
queue panel takes the tap and the hold and refuses the swipe**, because
|
||||
a right swipe means *add to the queue* everywhere it exists and a queue
|
||||
row is already in it — the only thing it could mean there is *remove*,
|
||||
which is the same gesture with the opposite effect one screen away.
|
||||
A tap there plays that *position*, too: setting the queue to the queue
|
||||
reads as a no-op and discards its source, its shuffle order and
|
||||
anything inserted by hand.
|
||||
|
||||
Escape leaves the mode, from `selection-bar` rather than from each
|
||||
host, since that element exists only while the mode does — the same
|
||||
exception the overlaid queue's Escape is, *a dismissal, not a
|
||||
shortcut*. The platform's back gesture deliberately does **not** reach
|
||||
it: the shell owns the history stack and four lists each reaching for
|
||||
`history` is four stacks, which is the fault `navStack` was deleted
|
||||
for. That wants one shell-owned register of dismissible surfaces, which
|
||||
is #200.
|
||||
|
||||
**A control revealed by `:hover` is gated on the device having hover,
|
||||
and which way round depends on whether it is the only route to its
|
||||
action.** The gate itself is not optional: a touch long-press
|
||||
|
||||
Reference in New Issue
Block a user