Publish current app and server
This commit is contained in:
+216
@@ -0,0 +1,216 @@
|
||||
# Design fixes — home screen and detail pages
|
||||
|
||||
Audit of 2026-08-01. Findings only; no product code was changed. The three items under
|
||||
"Confirmed layout bugs" were reproduced by rendering the real composables at TV 1080p
|
||||
(`w960dp-h540dp-television-xhdpi`) through the existing Roborazzi harness — everything
|
||||
else is read from the source.
|
||||
|
||||
**All of it is implemented.** The findings below are left as written — they are the
|
||||
diagnosis, and each one says why the fix is shaped the way it is. What was done:
|
||||
|
||||
| # | Fixed in |
|
||||
|---|----------|
|
||||
| 1 | `HomeMovieHero.kt` — column padding 28→22dp, and a wrapped title stands the synopsis down (`onTextLayout` line count) so Play is never what gets cut. Captured as `df_home-movie-hero-long-title.png`. |
|
||||
| 2 | `DetailPageComponents.detailPaneHeight()` — the slot is derived from the viewport (250–420dp) instead of a hard 250dp. Every technical spec now renders; `df_detail-pane-cast-details.png`. Studio, which both columns claimed, is dropped from the technical column. |
|
||||
| 3 | `DetailFoldPeek` (34dp) holds the tab strip off the bottom edge. |
|
||||
| 4 | The hero's private `heroFacts`/runtime formatter is gone; it calls `detail/DetailFacts.kt`. `HomeComponents.formatTvRuntime` too — one formatter left in the app. |
|
||||
| 5 | `MembyScore` token, and every rating goes through `ratingLabel` (`Locale.US`). The home metadata panel renders the score as its own run of text so it can carry the same gold. |
|
||||
| 6 | "No favourite shows yet" / "Mark a series as a favourite…". |
|
||||
| 7 | `FactSeparator` between facts, `ValueSeparator` inside a fact that holds a list. |
|
||||
| 8 | `ui/theme/DesignTokens.kt`; `HomeComponents` and `DetailPageComponents` colours are aliases of it, and `Theme.kt` uses the same near-blacks. |
|
||||
| 9 | Same — the detail page picked up the raised TV neutrals. |
|
||||
| 10 | Three radii: `MembyChipCorner` 8, `MembyCardCorner` 10, `MembyPanelCorner` 14. |
|
||||
| 11 | `ui/MembyButtons.kt` — `MembyPlayButton`, `MembyPlayChip`, `MembyChoiceChip`. The hero chip, the detail Play button, the metadata panel's "▶ Resume" and the For You time budget all use them. |
|
||||
| 12 | `DetailHero` honours `Settings.showTitleLogo` (new `EmbyRepository.showTitleLogo`) and shares `useTextTitleForLogo` with the screensaver (`ui/TitleLogo.kt`). |
|
||||
| 13 | One `UHD_MIN_WIDTH` (3800) for the badge and the `(4K)` suffix. Unit-tested. |
|
||||
| 14 | `mediaBadges` reads `dynamicRangeLabel`, so HDR10+ stays HDR10+. Unit-tested. |
|
||||
| 15 | One `FocusRequester` per pane in both overlays; none is attached to two live nodes. |
|
||||
| 16 | A series passes `mediaBadges(item)`, and `DetailFactRow` takes 4 badges so the airing badge is not squeezed out. |
|
||||
| 17 | `HomeRowHeaderIcon` / `HomeRowHeaderIconGap` / `HomeRowHeaderSpacing`, used by `MediaRow`, `MyShowsStrip` and `RecentSearchesRow` — which also gained the vertical padding its focus-scaled chips needed. |
|
||||
| 18 | `HomeHeroPick` carries the row a title was drawn from; the caption is no longer the card's slot. Unit-tested. |
|
||||
| 19 | Deleted (≈260 lines: `HomeHero`, `HomeRow`, `ContentCard`, `HomeRowData`, `HomeRowSkeleton` and the two runtime formatters only they used). |
|
||||
| 20 | The peek under the strip plus a chevron at its end. |
|
||||
| Docs | `CLAUDE.md`'s detail-page section rewritten to describe this code, with the token, button and header conventions above it. |
|
||||
|
||||
Screenshots of the result are `app/build/screenshots/df_*.png`
|
||||
(`.\gradlew.bat :app:testDebugUnitTest --tests "*ScreenshotTest"`).
|
||||
|
||||
---
|
||||
|
||||
## Confirmed layout bugs
|
||||
|
||||
These clip real content on a real TV. Fix these first.
|
||||
|
||||
### 1. The featured home hero drops its Play button when the title wraps to two lines
|
||||
|
||||
`ui/HomeMovieHero.kt:171-236`, `ui/MainActivity.kt:137` (`homeHeaderHeight`)
|
||||
|
||||
The card's content column measures ~229dp with a one-line title and ~261dp with two.
|
||||
`homeHeaderHeight(540dp, showHero = true)` yields 248dp, minus the hero row's 16dp top and
|
||||
10dp bottom padding, so the card gets **222dp** — and `FocusScaleContainer` clips it to a
|
||||
14dp rounded rect. Rendered with a two-line title, the kicker, title, fact line and
|
||||
synopsis draw and the green Play chip is **gone entirely**. A one-line title is already
|
||||
7dp over budget; it only survives because the part cut off is the chip's shadow.
|
||||
|
||||
Fix direction: the column is `align(Alignment.CenterStart)` inside a fixed-height box, so
|
||||
overflow is split top and bottom and the button is always the first thing lost. Either
|
||||
give the hero a height derived from its content, cap the title at one line, or drop the
|
||||
synopsis when the title wraps.
|
||||
|
||||
### 2. The Cast & Details tab silently discards every technical spec
|
||||
|
||||
`ui/DetailPageComponents.kt:258-278` (the 250dp pane), `:563-580` (`DetailFocusablePane`),
|
||||
`:613-643` (`DetailCastAndDetailsPane`)
|
||||
|
||||
The tab content slot is a hard `.height(250.dp)` and `DetailFocusablePane` applies
|
||||
`.clip(RoundedCornerShape(10.dp))`. `releaseAndTechnical` builds Released / Certificate /
|
||||
Runtime and then `addAll(specs)` — Video, Codec, Audio, Subtitles, Studio. Rendered at the
|
||||
real slot geometry, only the first three rows survive; the entire output of
|
||||
`technicalSpecs()` is clipped below the fold of a pane that cannot scroll. That is the
|
||||
whole reason the tab exists.
|
||||
|
||||
`DetailOverviewPane` shares the ceiling: a five-line synopsis plus the series "Up next"
|
||||
supporting line pushes its credit rows past the same boundary.
|
||||
|
||||
Fix direction: the pane needs a height budget that accounts for its worst case, or the
|
||||
two-column meta block needs to page/scroll. Note the pane deliberately does not scroll
|
||||
(one screen per tab), so the honest fix is probably fewer rows per column, not a scroller.
|
||||
|
||||
### 3. The detail tab strip sits flush against the bottom screen edge
|
||||
|
||||
`ui/DetailPageComponents.kt:209` (`heroHeight = maxHeight - DetailTabHeight`), `:494-550`
|
||||
|
||||
The selection underline is cut in half at y=1080 in both `detail-movie-more-like-this.png`
|
||||
and `detail-series-cast-details.png`. On a TV with overscan the underline and part of the
|
||||
labels are off-screen. This is the only element in the app with zero safe-area inset —
|
||||
gutters are 36-58dp and the home clock keeps 18dp.
|
||||
|
||||
---
|
||||
|
||||
## Copy and formatting
|
||||
|
||||
### 4. The home hero formats runtime differently from everywhere else
|
||||
|
||||
`ui/HomeMovieHero.kt:351` is a private `heroFacts` shadowing `ui/detail/DetailFacts.kt:62`.
|
||||
Different field order (year · certificate · runtime vs year · runtime · certificate) and
|
||||
`"${it}m"` instead of `formatRuntime`. The checked-in `home-movie-hero.png` shows
|
||||
**"2026 • M • 124m"** in the hero and **"2026 • 2h 4m"** on the card directly beneath it.
|
||||
|
||||
There are three runtime formatters in the app: `DetailFacts.formatRuntime`,
|
||||
`HomeComponents.formatTvRuntime` (private, identical) and this one.
|
||||
|
||||
### 5. The community score changes colour and locale by screen
|
||||
|
||||
Gold `0xFFF5C518` on detail (`DetailPageComponents.kt:484`), grey `MutedText` on home
|
||||
(`HomeComponents.kt:1025`). `HomeMovieHero.kt:355` uses `"★ %.1f".format(it)` with the
|
||||
default locale while every other rating goes through `Locale.US` — a comma decimal in a
|
||||
non-US locale.
|
||||
|
||||
### 6. American spelling in two user-facing strings
|
||||
|
||||
`ui/HomeComponents.kt:1549` "No **favorite** shows yet" and `:1555` "Mark a series as a
|
||||
**favorite** and it'll be waiting here." The rail says Favourites, the quick menu says
|
||||
"Add to favourites", the detail hero action says "Add to Favourites".
|
||||
|
||||
### 7. Four separator styles for the same kind of fact list
|
||||
|
||||
`" • "` (home metadata), `" • "` (detail fact row, schedule metadata, home hero),
|
||||
`" · "` (home genres), `", "` (credit rows).
|
||||
|
||||
---
|
||||
|
||||
## Design-token drift
|
||||
|
||||
### 8. Four near-blacks
|
||||
|
||||
Theme `background 0xFF0B0E11` and `surface 0xFF101418` (`ui/theme/Theme.kt`, effectively
|
||||
unused), home `0xFF090B0D`, detail `0xFF080A0C`. The accent green is duplicated four ways:
|
||||
`EmbyGreen`, `DetailAccent`, and the hero's `0xFF69C762` / `0xFF7BD574`.
|
||||
|
||||
### 9. Secondary-text contrast diverged between the two screens
|
||||
|
||||
`ui/HomeComponents.kt:129-132` carries a comment about raising the neutrals for TV distance
|
||||
(`MutedText 0xFFD0D6DB`, `QuietText 0xFFAEB7BF`). The detail page still uses the pre-fix
|
||||
values (`DetailMutedText 0xFFB6BDC3`, `DetailQuietText 0xFF8C959D`). The two sit side by
|
||||
side the moment a detail page is opened from a row.
|
||||
|
||||
### 10. Corner radii are ad hoc
|
||||
|
||||
9dp home cards, 8dp related posters, 8/10dp cast cards, 14dp featured hero, 11dp mini hero,
|
||||
12dp overlays, 7dp chips, 999dp search chips.
|
||||
|
||||
### 11. Three button languages
|
||||
|
||||
The hand-rolled `DetailPlayButton` (23/12dp padding, 16sp), the hero's hand-rolled play chip
|
||||
(12/7dp, 13sp — same look, different metrics), and raw `androidx.tv.material3.Button` with
|
||||
glyph text in `MediaMetadataPanel` ("▶ Resume") and `ForYouTimeBudget` ("✓ 30 min"), which
|
||||
picks up theme colours nothing else in the app uses.
|
||||
|
||||
---
|
||||
|
||||
## Logic and behaviour
|
||||
|
||||
### 12. `showTitleLogo` is ignored by the detail pages
|
||||
|
||||
The Settings copy promises "shows each title's logo artwork from Emby instead of plain
|
||||
text", but only `ui/screensaver/ScreensaverContent.kt:773` honours it;
|
||||
`ui/DetailPageComponents.kt:322` always fetches the logo. The screensaver also has
|
||||
`useTextTitleForLogo`, a fallback for logos too dark to read — the detail hero has no
|
||||
equivalent, so a dark logo is invisible on the near-black scrim.
|
||||
|
||||
### 13. Two different 4K thresholds
|
||||
|
||||
The badge fires at video width ≥ 3800 (`HomeComponents.kt:1184`); the `(4K)` suffix at
|
||||
≥ 3400 (`DetailFacts.kt:190`). A 3600-wide file is 4K in the spec row and not on the badge.
|
||||
|
||||
### 14. HDR10+ is named in `dynamicRangeLabel` but collapses to plain "HDR" in `mediaBadges`
|
||||
|
||||
### 15. One `FocusRequester` attached to two live nodes
|
||||
|
||||
`informationPane` is attached by the Overview pane, the Cast & Details pane and the Episodes
|
||||
empty states (`ui/MediaDetailsOverlay.kt:202-216`, `ui/SeriesDetailsOverlay.kt:291-329`).
|
||||
`AnimatedContent`'s 80ms fade-out keeps the outgoing pane composed, so a Down press landing
|
||||
in that window can request focus on a pane that is disappearing.
|
||||
|
||||
### 16. A series can never show format badges
|
||||
|
||||
`ui/SeriesDetailsOverlay.kt:248` passes `badges = emptyList()` where movies pass real ones.
|
||||
Related: `DetailFactRow` does `badges.take(3)`, so on a 4K/HDR/HEVC movie the airing badge
|
||||
appended in `ui/MediaDetailsOverlay.kt:147` is silently dropped.
|
||||
|
||||
### 17. Home row headers do not align
|
||||
|
||||
`MediaRow` and `MyShowsStrip` lead with a 28dp icon chip plus 10dp; `RecentSearchesRow`
|
||||
(`ui/MainActivity.kt:2084`) has no chip, so its title starts 38dp further left. It also uses
|
||||
9dp header spacing against everyone else's 6dp and gives its `LazyRow` no vertical padding,
|
||||
so focus-scaled chips have no room to grow.
|
||||
|
||||
### 18. The mini hero labels are positional fiction
|
||||
|
||||
`ui/HomeMovieHero.kt:124`: `listOf("POPULAR", "NEW RELEASE", "TRENDING")[index]`. But
|
||||
`selectHomeHeroMovies` interleaves new releases and popular picks and then falls back to
|
||||
every movie in the response. In the current screenshot a 2025 title is labelled NEW RELEASE
|
||||
and a 2026 one POPULAR.
|
||||
|
||||
### 19. A dead second home implementation
|
||||
|
||||
`ui/MainActivity.kt:2752-2900`: `HomeHero`, `HomeRow`, `ContentCard`, `HomeRowData` and
|
||||
`HomeRowSkeleton` are unreferenced (only the gateway *model* named `HomeRow` is in use).
|
||||
They carry a competing 48dp gutter and SemiBold header style — a live-looking template for
|
||||
the wrong conventions.
|
||||
|
||||
### 20. Tab content sits entirely below the fold with no affordance
|
||||
|
||||
By design per the code comments, but nothing on screen tells the viewer that Down reveals
|
||||
anything.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
`CLAUDE.md`'s detail-page section no longer describes this code. It documents a poster-left
|
||||
layout with Play hanging off the poster's bottom-right corner, "nothing scrolls vertically",
|
||||
a tab list of "Overview, Episodes, Cast, Details", and a `DetailReasonStrip` of several
|
||||
short phrases. The code is a full-bleed scrolling hero with Overview / Episodes / More Like
|
||||
This / Cast & Details and a single reason line (`ui/DetailPageComponents.kt:375-385`).
|
||||
Worth correcting before it misleads the next change.
|
||||
Reference in New Issue
Block a user