Queue row gestures: album art as grab surface + swipe-to-remove #118

Merged
bvandeusen merged 4 commits from dev into main 2026-08-04 11:37:30 -04:00
Owner

Android queue row gesture work. Web is untouched by design.

#2395 — album art is the grab surface

  • 6dea45a6 The reorder grip no longer occupies its own column. On Android it's gone entirely (long-press-drag the row); on web it sits over the album art, so it costs no horizontal space while staying visible at rest as the only affordance signalling the list is reorderable.
  • a92a9f21 Extracted the reorder accessibility actions to clear detekt LongMethod.

#2435 — swipe left to remove

  • 1910a5ce Replaces the Android X button with SwipeToDismissBox. End-to-start only (enableDismissFromStartToEnd = false) — a right-swipe has no meaning here and leaving it live would delete tracks on a gesture aimed either way. The revealed background is oxblood (LocalActionColors.destructive, not colorScheme.error — the design system keeps "a failure that happened" apart from "an action about to happen") with a trash glyph and the word "Remove".
  • Added a CustomAccessibilityAction("Remove from queue"). Both of the row's gestures are now touch-only, and each replaced a control TalkBack could find; without this, dropping the X would have quietly removed remove-from-queue for anyone not using touch.
  • 8b630e71 detekt TooManyFunctions (12 vs 11) forced a split — QueueRow.kt takes the row and its helpers, QueueScreen.kt keeps screen/list/pill/summary. No behaviour change. Split rather than suppressed because the count was reporting something true: the file held a screen and a heavier row.

Web keeps its X button per operator call — there's enough horizontal room there that the space argument doesn't apply.

Verification

CI green on dev (run 3379: ktlint, detekt, unit tests). No instrumentation lane exists in this module, so the gestures themselves are operator on-device verification — see #2436 for the untested drag math.

Android queue row gesture work. Web is untouched by design. ## #2395 — album art is the grab surface - `6dea45a6` The reorder grip no longer occupies its own column. On Android it's gone entirely (long-press-drag the row); on web it sits *over* the album art, so it costs no horizontal space while staying visible at rest as the only affordance signalling the list is reorderable. - `a92a9f21` Extracted the reorder accessibility actions to clear detekt `LongMethod`. ## #2435 — swipe left to remove - `1910a5ce` Replaces the Android X button with `SwipeToDismissBox`. End-to-start only (`enableDismissFromStartToEnd = false`) — a right-swipe has no meaning here and leaving it live would delete tracks on a gesture aimed either way. The revealed background is oxblood (`LocalActionColors.destructive`, *not* `colorScheme.error` — the design system keeps "a failure that happened" apart from "an action about to happen") with a trash glyph and the word "Remove". - Added a `CustomAccessibilityAction("Remove from queue")`. Both of the row's gestures are now touch-only, and each replaced a control TalkBack could find; without this, dropping the X would have quietly removed remove-from-queue for anyone not using touch. - `8b630e71` detekt `TooManyFunctions` (12 vs 11) forced a split — `QueueRow.kt` takes the row and its helpers, `QueueScreen.kt` keeps screen/list/pill/summary. No behaviour change. Split rather than suppressed because the count was reporting something true: the file held a screen *and* a heavier row. **Web keeps its X button** per operator call — there's enough horizontal room there that the space argument doesn't apply. ## Verification CI green on `dev` (run 3379: ktlint, detekt, unit tests). No instrumentation lane exists in this module, so the gestures themselves are operator on-device verification — see #2436 for the untested drag math.
bvandeusen added 4 commits 2026-08-04 11:37:17 -04:00
feat(player): album art is the queue's grab surface — #2395
test-web / test (push) Successful in 34s
android / Build + lint + test (push) Failing after 1m30s
6dea45a634
The grip icon took a column out of every queue row, competing with the title
for space — worst on Android, where the row is narrowest and the icon plus
its 12dp gap cost roughly 36dp. Operator pre-approved dropping the icon and
making the album art the drag surface; that's what this does.

## Android: the gesture change is the load-bearing part

Moved the drag from the grip onto the thumbnail AND switched
detectDragGestures → detectDragGesturesAfterLongPress. That second half is
not cosmetic. The grip was a small target, so a plain drag detector on it
never competed with anything; a 48dp thumbnail is a large chunk of every
row, and with a plain detector any vertical pan starting on artwork would be
swallowed as a reorder instead of scrolling the queue. The list would have
felt broken exactly where it's easiest to touch. Long-press-then-drag
separates the three gestures: pan scrolls, long-press reorders, tap still
plays (the detector doesn't consume a plain tap, so it reaches the row's
clickable).

Dropping the grip also removed its contentDescription ("Reorder track"),
which was the ONLY thing telling a screen reader this list could be
reordered — and a long-press drag isn't operable with TalkBack regardless.
Added "Move up"/"Move down" custom accessibility actions on the row, the
Android counterpart to the web row's ArrowUp/ArrowDown. Without them this
change would have quietly removed reordering for anyone not using touch.

## Web: the grip was never the drag surface

`use:draggable` is on the row, not the handle, so dragging already worked
from anywhere — the grip's only unique jobs were being the visual cue and
the keyboard target. It now sits OVER the art, costing zero horizontal
space, and keeps both jobs.

Deliberately still VISIBLE at rest, just quiet, with the scrim appearing
only on hover/focus. Overlaying already solved the space complaint, so
hiding it buys nothing and would cost the only cue that the queue is
reorderable — on touch especially, which has no hover.

## Scope walked back

Also considered the web PlaylistTrackRow, which carries an identical grip.
Left alone: it has no album art, so the approved direction doesn't apply,
and its handle is already the smallest of the three at 14px. Forcing
consistency would have meant inventing a third treatment for a surface
nobody complained about. (Android has no playlist reorder at all — that
parity gap is pre-existing and out of scope here.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
QueueRow hit 61 statements against detekt's 60 — the semantics block I added
for the screen-reader move actions pushed it one over.

Extracted to a `Modifier.queueReorderActions` extension, which mirrors the
`queueReorderDrag` extension from the same change: the row now composes two
named modifiers, one for the gesture and one for the accessibility actions,
instead of carrying either inline. Better than suppressing the rule — the
suppression would have been permanent and the split reads better anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(player): swipe a queue row left to remove it — #2435
android / Build + lint + test (push) Failing after 1m25s
1910a5ce61
Replaces the trailing X button on the Android queue row, for the same
reason #2395 replaced the grip: horizontal space in the narrowest row in
the app. Web keeps its X — the operator's call, and the right one, since
the constraint being solved doesn't exist there.

SwipeToDismissBox with enableDismissFromStartToEnd = false; a right-swipe
means nothing here and would only delete tracks on a mis-aimed gesture.
The red fill under the row is oxblood (LocalActionColors.destructive), not
colorScheme.error — the design system keeps those apart because an error
is a failure that happened and a destructive action is one about to.

Adds a "Remove from queue" custom accessibility action. Both gestures the
row now relies on are touch-only, and each replaced a control TalkBack
could find, so without this the change would have quietly removed
remove-from-queue for anyone not using touch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6vZoJ4Se5YyaqdtGVkap5
refactor(player): split the queue row out of QueueScreen.kt — #2435
android / Build + lint + test (push) Successful in 3m40s
8b630e71ca
detekt TooManyFunctions: the swipe work took the file to 12 functions
against a limit of 11. Suppressing it was the option; splitting is the
better one, because the seam was already there — the row carries two
gestures, a swipe background, and its own accessibility surface, which is
more behaviour than the screen that merely lists it.

QueueScreen.kt keeps the screen, list, pill, and summary (4). QueueRow.kt
takes the row and its helpers (8). No behaviour change: same code, same
order, per-file imports recomputed, QueueRow internal so QueueList can
still call it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6vZoJ4Se5YyaqdtGVkap5
bvandeusen merged commit 57d2299180 into main 2026-08-04 11:37:30 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/minstrel#118