This commit is contained in:
ponzischeme89
2026-08-23 14:16:03 +12:00
parent 89ebe21201
commit 63c3df11b2
23 changed files with 789 additions and 758 deletions
+19 -14
View File
@@ -1821,24 +1821,29 @@ asked mid-scene and one that has to survive a dialog and four menu rows is one n
twice. It is deliberately *not* also in `showTrackMenu`'s list: one thing reachable two ways
is one thing whose two entry points drift apart. Things to preserve:
- **The panel is a fade, not a card.** `player_cast_scrim` carries it up from the bottom
edge so the scene stays legible above the names — which is the reason somebody opened it.
The 48dp side inset matches the transport row, so opening it does not shift the column the
title and controls are read in.
- **The heading is the title, not the word "Cast".** The accent eyebrow above already says
what the panel is; repeating the button just pressed costs the line that could confirm
what is being watched.
- **Cast is the paused hero's lower section, not a second full-screen overlay.** Opening it
pauses playback, leaves the hero's logo/title anchor fixed, hides the transport and
crossfades only the poster/synopsis region into `CastPanel`. The panel therefore inherits
the hero's backdrop, scrims, 48dp safe inset, palette, spacing and panel radius rather than
carrying a parallel XML design. Back reverses that hand-off and returns focus to the Cast
transport button.
- **There is no redundant Cast heading.** The button and the faces already establish the
context. `GUEST CAST` is the only section label, and appears only for episode-only people.
- **Episodes read both levels of Emby's credits.** `resolveCast` uses the series' `People`
for the regular row and removes those identities from the episode's `People` to obtain the
guest row. A library with no series cast falls back to the episode row as its primary cast,
so adding guest separation never turns useful metadata into an empty panel.
- **Initials sit behind every portrait** (`castInitials`, pure and tested). Emby has no
photo for a good part of a typical cast, and a row of identical grey rectangles says
nothing about which name is which. They are behind rather than instead of the image, so
nothing has to decide in advance whether artwork will arrive.
- **The focus ring is the `foreground`**, drawn over the artwork, and the portrait takes
`duplicateParentState` because the *card* is what is focusable. A remote has no hover: the
ring and the scale are the only thing saying which face is selected.
- `bindCastPanel` in `ui/player/CastPanel.kt` takes a `CastPanelState` and an injected image
loader, so `CastPanelScreenshotTest` renders the real cards with no player, server or
network → `build/screenshots/cast-panel/`. `loaded` is separate from an empty list because
"still fetching" and "no cast recorded" are different things to be told.
- **Focus enters after composition and loading.** `CastPanel` requests the first regular
face, the first guest when it is the only row, or the first filmography card in a profile.
Guest faces use the same portrait component and horizontal D-pad model as regular cast.
- `CastPanel` takes plain `CastPanelState`, so `CastPanelScreenshotTest` renders it inside the
real paused hero with no player or server `build/screenshots/cast-panel/`. `loaded` is
separate from an empty list because "still fetching" and "no cast recorded" are different
things to be told.
**Time to first frame** is the number playback is judged by, and a *resume* is the worst
case: it is a seek, and a seek over HTTP is several more requests before a single frame is