0.2.68
This commit is contained in:
@@ -1936,7 +1936,16 @@ continuously while somebody is merely browsing, so:
|
||||
home hero) and `MembyChoiceChip`. There were three: this one, a hand-rolled copy in the hero
|
||||
with the same look and different metrics, and raw `androidx.tv.material3.Button`s with
|
||||
glyphs typed into their labels ("▶ Resume", "✓ 30 min"), which picked up theme colours
|
||||
nothing around them uses.
|
||||
nothing around them uses. `MembyArtworkPlayCue` is the fourth member and the same rule: the
|
||||
circular Play mark over a focused artwork card, on the launcher's posters *and* on a detail
|
||||
page's episode rows, which used to draw their own black disc at their own diameter.
|
||||
|
||||
**And one card-focus language on the detail pages**, `Modifier.detailCardFocus` in
|
||||
`ui/detail/DetailCardFocus.kt`. The cast grid, the "More like this" posters and the Extras
|
||||
thumbnails had three copies of the same lift-and-grow at 1.06, 1.06 and 1.05 over 110ms,
|
||||
100ms and 100ms; nobody chose three figures, they were written on three days. The animated
|
||||
value is read only inside the `graphicsLayer` block, so travelling a grid redraws two cards
|
||||
rather than recomposing every card in it — which is why it is a modifier and not a wrapper.
|
||||
|
||||
**"Close Memby?" is the one full-stop dialog**, `ui/ExitConfirmation.kt`, drawn when the
|
||||
viewer has asked to be asked (`confirmExitMemby`). It is the panel that appears *over*
|
||||
@@ -2104,8 +2113,13 @@ midnight, which is why the zone offset is a parameter to the pure `localEpochDay
|
||||
arrived in API 24 and this app ships to 23, so that arithmetic is written out by hand.
|
||||
|
||||
**Detail pages** are one editorial layout shared by movies and series: `DetailPageScaffold`
|
||||
in `ui/DetailPageComponents.kt` over the pure vocabulary in `ui/detail/DetailFacts.kt` and
|
||||
`ui/detail/DetailTabs.kt`. It is a full-bleed cinematic hero — backdrop under two scrims,
|
||||
in `ui/DetailPageComponents.kt` over the pure vocabulary in `ui/detail/DetailFacts.kt`,
|
||||
`ui/detail/DetailTabs.kt` and `ui/detail/DetailHeroPhases.kt`. Movie, series and episode are
|
||||
three sets of parameters to that one scaffold, never three layouts — the shared pieces are
|
||||
`DetailHeroActions` (so a Continue Watching page cannot grow its own Play button metrics),
|
||||
`DetailIdentity`, `DetailStripFrame` and `Modifier.detailCardFocus`, and anything that has to
|
||||
differ between them is a scaffold parameter so the difference is stated in one place. It is a
|
||||
full-bleed cinematic hero — backdrop under two scrims,
|
||||
logo or title, `heroFacts` line (year · length · certificate) with the score and format
|
||||
badges trailing it, genres, three lines of synopsis, one recommendation reason, then Play
|
||||
and the circular secondary actions — with an uppercase tab strip on a hairline rule anchored
|
||||
@@ -2122,15 +2136,71 @@ because a show's synopsis is already in the hero and what a show is *for* is its
|
||||
- **The hero collapses instead.** `detailHeroCollapsed(zone)` is the whole rule and it is
|
||||
pure and tested: whole while focus is on Play, out of the way the moment focus is below it.
|
||||
A single `animateFloatAsState` drives it and is read **only inside layout and draw
|
||||
lambdas** — `Modifier.collapsingHeight` for the band and `graphicsLayer` for the cross-fade
|
||||
— so a collapse costs a measure pass rather than recomposing the page sixty times a second.
|
||||
Two details hold the promise that no focused thing ever resizes: the hero is measured at
|
||||
its *full* height and slid upward under a clip, so nothing inside it re-wraps; and the pane
|
||||
takes `weight(1f)`, so it simply receives what the hero gives up.
|
||||
- **What is left is `DetailCollapsedHeader`** — the title and the fact line and nothing else.
|
||||
Not a shrunken hero: the synopsis, the ratings, the reason and the actions all answer "is
|
||||
this worth watching", which somebody down in the Cast grid has already answered. What
|
||||
remains is only what stops the page becoming anonymous.
|
||||
lambdas** — `Modifier.collapsingHeight` for the band and `graphicsLayer` for everything
|
||||
else — so a collapse costs a measure pass rather than recomposing the page sixty times a
|
||||
second. Two details hold the promise that no focused thing ever resizes: the hero is
|
||||
measured at its *full* height and slid upward under a clip, so nothing inside it re-wraps;
|
||||
and the pane takes `weight(1f)`, so it simply receives what the hero gives up.
|
||||
- **One number drives the whole transformation**, and what each part of it means is
|
||||
`ui/detail/DetailHeroPhases.kt`. The height, the backdrop's opacity, the deepening wash,
|
||||
the supporting text, the action row and the pinned header are **bands of that one value**
|
||||
rather than animations of their own — which is what makes the collapse read as one
|
||||
movement, and what stops the parts drifting out of step when it is interrupted half way.
|
||||
`DetailHeroPhaseTest` pins the *ordering* rather than the figures: the supporting half is
|
||||
always at least as far gone as the primary half (that is progressive disclosure, stated as
|
||||
an assertion), the two headings are never both on screen, and the plate always leads the
|
||||
pinned header in. It is a **critically damped spring**, not a tween: a held D-pad produces
|
||||
a press every few frames, and a spring retargets from wherever it is rather than restarting
|
||||
a duration — and it can never overshoot past 1, which on a value driving opacities would be
|
||||
a flicker.
|
||||
- **The backdrop recedes; it never disappears.** `heroArtworkAlpha` takes it down as the
|
||||
content takes over and the wash over it deepens as a gradient, so the picture settles back
|
||||
rather than a sheet being drawn across it. Behind the pinned header is a soft plate
|
||||
(`pinnedScrimAlpha`) occupying exactly the band the header lands in and fading from nothing
|
||||
at its top edge — a **gradient, never a bar**: the point is a perceptible separation
|
||||
between a fixed header and scrolling content, and a solid block reads as a phone toolbar.
|
||||
- **What is left is `DetailCollapsedHeader`** — the title, the fact line, and on an episode
|
||||
the show and `S03E04`. Not a shrunken hero: the synopsis, the ratings, the reason and the
|
||||
actions all answer "is this worth watching", which somebody down in the Cast grid has
|
||||
already answered. What remains is only what stops the page becoming anonymous. The compact
|
||||
eyebrow is its own parameter (`pinnedEyebrow`) because "SEASON 3 · EPISODE 4" spelled out
|
||||
pushes the fact line off the end of a one-line header.
|
||||
- **The hero is a budget, and the reading column is what pays.** The expanded content is
|
||||
anchored to the bottom of its band and grows upward, so `DetailHeroMetrics.TopInset` is the
|
||||
ceiling it may not cross — and with a ceiling, something has to give. What gives is the
|
||||
**supporting block** (ratings, genres, synopsis, pace, reason), which is the Column's one
|
||||
`weight(1f, fill = false)` child: weighted children are measured from what the unweighted
|
||||
ones left over, so a two-line title costs prose rather than costing the primary action its
|
||||
shape. Getting that inversion wrong is precisely the 0.2.67 regression — an episode page
|
||||
reached from Continue Watching carries more than any other variant, and the Play button,
|
||||
being last in the column, was handed whatever height was left and rendered as a squeezed
|
||||
sliver. It is the same inversion the home hero already makes, and it is now asserted:
|
||||
`EpisodeDetailScreenshotTest` renders the most crowded hero the app can produce and fails
|
||||
the build if the primary action is under its natural height.
|
||||
- **Prose gives way a whole line at a time.** `Modifier.wholeLines` reports a height rounded
|
||||
down to the last complete line, with the clip *outside* it — a `Text` handed less room
|
||||
still draws every line it was asked for, and without the clip the dropped lines painted
|
||||
over whatever the column placed underneath. Two lines instead of three reads as nothing at
|
||||
all; prose sliced through the middle of its letters reads as a rendering fault.
|
||||
`DetailHeroMetrics.SynopsisLines` is **two**, not three: the whole description is one press
|
||||
away in Overview, and the third line was the difference between the movie hero fitting its
|
||||
band and overflowing it.
|
||||
- **The identity block reserves its height only where a logo could actually appear.**
|
||||
Deciding whether a logo is legible means fetching and decoding it, so every page begins on
|
||||
the text fallback and changes its mind a moment later — a swap that used to move everything
|
||||
under it on the opening frame. `logoUrl != null` is known synchronously from the image
|
||||
tags, so a title Emby holds no logo for is drawn at its natural height and gives the space
|
||||
to the synopsis. Very wide and very tall logos are both a letterboxed picture inside a
|
||||
fixed box: a logo can never change the page's shape.
|
||||
- **The ratings strip is the one place it does not reserve.** Most of a library has no
|
||||
scores — an episode is rated as its series, a household with no MDBList key has none at all
|
||||
— and 42dp held open for them was 42dp taken off the synopsis on every one of those pages.
|
||||
It sits inside the flexible block, so a strip arriving late costs a line of prose rather
|
||||
than moving anything the viewer is aiming at.
|
||||
- **The circular actions name themselves.** A heart, a tick, a bookmark and a film reel are
|
||||
guesses at three metres, so the focused one's description is printed on a **reserved** line
|
||||
under the row — reserved, because a line that appeared when focus reached the second button
|
||||
would move the whole hero on every press of Right.
|
||||
- **A pane fits its slot, or is a grid.** Prose panes (Overview, Details) do not scroll — a
|
||||
page that scrolls *and* has tabs gives the D-pad two meanings for Down — and now have
|
||||
roughly three times the room they had, since `detailPaneHeight(viewportHeight)` is what the
|
||||
@@ -2145,6 +2215,15 @@ because a show's synopsis is already in the hero and what a show is *for* is its
|
||||
one tab while a different one stays open would need a second press to mean anything and
|
||||
would show content that contradicts the highlight. A *click* reports itself as arriving in
|
||||
the strip too, or a mouse or a test could change the pane and leave the hero over it.
|
||||
They are still **marked separately**, and that is not a contradiction: the accent underline
|
||||
is *selection* — which pane is open — and the plate behind the label is *focus* — where the
|
||||
remote is. One causes the other while the viewer is in the strip, and they come apart the
|
||||
moment they press Down, which is the case the distinction exists for. The season scroller
|
||||
on an episode page marks them the same two ways for the same reason.
|
||||
- **Both bands wear `DetailStripFrame`** — the plate, the gutters, the hairline and the
|
||||
chevron. The tab strip and the season scroller define the same fold, and two hand-written
|
||||
copies of it is exactly how the two pages came to sit their content a couple of pixels
|
||||
apart.
|
||||
- **The strip is content-driven, and frozen once the viewer is inside it.** `detailTabs` is
|
||||
pure over a `DetailTabAvailability`, and `rememberDetailTabs` recomputes it only while
|
||||
focus is still in the hero. That is the seam between two real failures: a tab leading to an
|
||||
@@ -2169,7 +2248,19 @@ because a show's synopsis is already in the hero and what a show is *for* is its
|
||||
destroys `rememberSaveable` with it. `RestoreDetailFocus` focuses Play first (it exists on
|
||||
frame one, so the remote is live) and then restores the band, once, only if that band has
|
||||
something placed to land on. Deliberately not persisted: a TV switched on the next morning
|
||||
should open a show where the *show* is up to.
|
||||
should open a show where the *show* is up to. The band is reported on the way *in* to it,
|
||||
never on every focus move inside it — the pane's `hasFocus` fires for each card a viewer
|
||||
walks past, and each of those was a write into the store, on the one press that must feel
|
||||
free.
|
||||
- **Returning from the player comes back to the page it was launched from.**
|
||||
`MainActivity` holds a `DetailsReturn` — the item, the trail and the airing notice — across
|
||||
the playback and reopens on `LifecycleResumeEffect`. Deliberately a *reopen* rather than
|
||||
leaving the page composed under the player: the page has to tell the viewer the truth, and
|
||||
the episode they just finished is now watched, the film is now part-way through, and the
|
||||
next episode is a different one, so the item is re-requested on the way in. Reopening also
|
||||
runs the same restore path a Back press through the trail already uses, which is what
|
||||
brings back the tab, the season, the grid offset and the band that held focus — rather than
|
||||
depending on Compose having held focus across another activity's window.
|
||||
- Every `focusProperties { up/down/left/right = … }` target must be attached **on the
|
||||
current frame**. Season chips and episode cards do not exist while the episode request is
|
||||
in flight, on a one-season show, or on any tab but Episodes — pointing at their
|
||||
|
||||
Reference in New Issue
Block a user