Skip to content

[TV] Align Android TV UI with the Figma design-system tokens - #5701

Open
sztomek wants to merge 15 commits into
mainfrom
feat/tv-design-system-tokens
Open

[TV] Align Android TV UI with the Figma design-system tokens#5701
sztomek wants to merge 15 commits into
mainfrom
feat/tv-design-system-tokens

Conversation

@sztomek

@sztomek sztomek commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

Aligns the Android TV UI with the Figma design system by extracting reusable color and text-style tokens and re-pointing every TV screen/component to them.

  • TvColors now defines the full Figma palette: TextPrimary/Secondary/Tertiary/Disabled (+ their *Active variants) TextPrimary70, and backgrounds BackgroundSunken/Surface/Base/Overlay/Active (+ BackgroundActive50/20).
  • TvTextStyles gains the Figma type ramp Title1…Caption2 (+ Body) (Figma px ÷ 1.5 per the TV module convention; system font kept, FontWeight(510)).
  • ~40 files re-pointed to the new tokens; legacy tokens (Dark, DarkGray, Gray, LightGray, TextPrimary20, BgActive, BgActive20, Divider) and the now-unused per-component text styles removed.

Where existing styling didn't map cleanly, the closest token was used and the gap logged (see below). Stacked on feat/tv-deep-destination-top-bar (base of this PR).

Decisions / discrepancies for design review

  • Color changes: Gray #3C3E42 → BackgroundOverlay #323538; focus/active backgrounds #D4D6DBBackgroundActive #FBFBFC (bg-active).
  • Resting translucent button backgrounds kept at 20% (BackgroundActive20) — Figma only defines bg-active-50; 50% would make the white label low-contrast. Needs a design call.
  • All per-component text styles eliminated — the styles previously kept below the ramp were re-pointed to their confirmed Figma style names (Caption1, Body, Title1, Title2, Headline); a Body token (19.33sp / 400) was added to the ramp. TvTextStyles now contains only ramp tokens.

A full discrepancy list is in the PR comments.

Testing Instructions

  1. Build & run the TV app on an Android TV device/emulator.
  2. Navigate Home, Your Podcasts, Playlists, Up Next; open podcast/playlist detail screens, the episode-actions and episode-info modals, and the profile modal.
  3. Verify tab bar, tiles, rows, buttons, and modals render correctly and that focus states stay legible (focused rows/cards/tabs show a white background with dark text).
  4. Sign-out onboarding (welcome, QR sign-in, create-account) screens render correctly.

Screenshots or Screencast

Screen_recording_20260805_181413.mp4

Checklist

  • If this is a user-facing change, I have added an entry in CHANGELOG.md
  • Ensure the linter passes (./gradlew spotlessApply to automatically apply formatting/linting)
  • I have considered whether it makes sense to add tests for my changes
  • All strings that need to be localized are in modules/services/localization/src/main/res/values/strings.xml (no string changes)
  • Any jetpack compose components I added or changed are covered by compose previews
  • I have updated (or requested that someone edit) the Event Horizon schema to reflect any new or changed analytics. (no analytics changes)

I have tested any UI changes...

  • with different themes
  • with a landscape orientation
  • with the device set to have a large display and font size
  • for accessibility with TalkBack

@sztomek

sztomek commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

TV design-system alignment — discrepancies to review

The Android TV UI was re-pointed onto the Figma design-system tokens (new TvColors

  • TvTextStyles entries). Where existing styling did not map cleanly, the closest
    token was used (agreed "full re-point + log gaps"); everything non-obvious is listed
    here for your review.

Policy applied

  • Colors: fully re-pointed. Value/alpha changes flagged below.
  • Text sizes: the Figma ramp is Figma-px ÷ 1.5 (TV convention). Styles were re-pointed
    where a token is a genuine match (size within a few sp AND compatible weight). Styles that
    fall below the ramp (sub-15sp) or use a weight the ramp lacks (400-weight body, Bold
    display) were kept as-is rather than forced onto the nearest token (which would overflow
    layouts or bold body text). Those are the candidates for new tokens.
  • Color.White text (#FFFFFF) was re-pointed to TextPrimary (#FBFBFC). Functional
    whites (QR-code backgrounds, focus-ring borders, gradient/scrim effects) were left literal.
  • SF Pro not ported: Figma specifies FontFamily(R.font.sf_pro) (iOS). No such resource
    exists in the module; tokens keep the system default + FontWeight(510).

Colors — value / alpha changes to verify

Legacy Legacy value New token New value Effect
Gray #3C3E42 BackgroundOverlay #323538 Focused tile, top-bar icon button, avatar placeholder bg slightly darker. No exact Figma match.
LightGray #D4D6DB BackgroundActive #FBFBFC Focused/active backgrounds (playlist card, episode row, dropdown item) become pure white. Matches bg-active.
BgActive #D4D6DB BackgroundActive #FBFBFC Filled-button focused container, same as above.
TextPrimary20 #FBFBFC @ .2 BackgroundActive20 #FBFBFC @ .2 Resting filled/tile-button backgrounds kept at 20% (no visual change). See note.
BgActive20 #FBFBFC @ .2 BackgroundActive20 #FBFBFC @ .2 Icon-button default container & QR-code dots kept at 20% (no visual change). See note.
Color.White (button active bg) #FFFFFF BackgroundActive #FBFBFC prominent/borderless/tile buttons; imperceptible off-white shift.
Color.Black (tile-button selected text) #000000 TextPrimaryActive #161718 Selected tile-button label; near-black, tiny shift.
Color.White @ .1 (borderless focus fill) #FFFFFF BackgroundActive @ .1 #FBFBFC base swapped to token; alpha unchanged.

Removed legacy token with no Figma equivalent (was unused): Divider #4A4D51 (equals
text-disabled; the system has no dedicated divider token).

Resting translucent-white note: buttons use a 20%-white resting background paired with a
white label. Figma only defines bg-active (100%) and bg-active-50 (50%); a 20% variant was
added as BackgroundActive20 and kept at 20% because bumping to 50% would leave the white label
low-contrast on a light pill. Please confirm the intended resting-button treatment (a real
bg-active-20, or resting = bg-active-50 with a dark label).

New tokens defined but not yet consumed anywhere: BackgroundSurface, BackgroundActive50,
TextTertiary, TextTertiaryActive, TextDisabled, TextDisabledActive — included so the
palette is complete; wire up as designs call for them. (TextPrimary70 is used by the featured
tile's sponsored label and description.)

Text styles — re-pointed (non-exact matches)

Legacy style size / weight New token token Note
ScreenTitle 28 / SemiBold Title3 32 / 510 +4sp, lighter weight (screen / empty-state / pane titles)
PlaylistCardTitle 24 / SemiBold Headline 25.33 / 510 +1.3sp, lighter
ModalTitle 24 / SemiBold, center Headline (+center) 25.33 / 510 +1.3sp, lighter
ModalEmail 25 / 510, center Headline (+center) 25.33 / 510 near-exact
EpisodeRowTitle 19 / Medium Callout 19.33 / 510 near-exact
Caption 17 / 510 Caption1 16.67 / 510 near-exact
TabLabel 17 / 510, center Caption1 16.67 / 510 near-exact
ModalButtonLabel 17 / 510, center Caption1 (+center where full-width) 16.67 / 510 near-exact
ModalFootnote 17 / 510, center Caption1 16.67 / 510 near-exact
PlaylistCardCaption 15 / 400 Caption2 15.33 / 510 caption gains 510 weight
FolderCardTitle 14 / SemiBold, center Caption2 (+center) 15.33 / 510 +1.3sp, lighter

Text styles — formerly "kept as-is", now re-pointed (Figma style names confirmed)

All per-component styles have been eliminated; TvTextStyles now holds only the Figma ramp,
plus a new Body token (19.33sp / 24sp line height, Figma Body 29/36px ÷ 1.5). Figma specifies
weight 510, but that would make Body identical to Callout and bold all body copy — the token
deliberately stays at regular 400; flagging for design confirmation.

Former style was Figma style now
VideoTilePodcastTitle 12 / 400 Caption1 16.67 / 510
VideoTileEpisodeTitle 13 / SemiBold Caption1 16.67 / 510
FeaturedTileSponsoredLabel 14 / 400 Body 19.33 / 400
FeaturedTileTitle 24 / Bold Title2 38 / 510
FeaturedTileDescription 16 / 400 Body 19.33 / 400
WelcomeTitle 27 / Medium, letterSpacing, center Title1 (+center) 50.67 / 510
WelcomeSubtitle 16 / Medium, letterSpacing, center Headline (+center) 25.33 / 510
SignInSubtitle 12 / Medium, center Body (+center) 19.33 / 400
ModalBody 16 / 400, center Body (+center) 19.33 / 400

The welcome styles' custom negative letter-spacing was dropped (not on the ramp); centering is
preserved at the call sites. The episode details and podcast info modals both use Caption2/TextSecondary
body text (matching their label/value metadata rows) with Headline titles.

Also left as Material typography (no ramp match): empty-state / error subtitles use
MaterialTheme.typography.bodyLarge in TvEmptyState, TvHomeScreen, TvUpNextScreen,
TvPlaylistDetailsScreen, TvPodcastDetailsScreen.

Misc

  • TvRow header uses a bespoke animated TextStyle (font size animates 17→21sp on focus)
    that can't be a static token; only its color was re-pointed to TextPrimary.
  • TvOverlayBorderColor = Color.White @ .12 (modal/menu hairline) kept as a literal effect.

@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown

Claude finished @sztomek's task in 5m 29s —— View job


Code review

Nice, disciplined re-point — the commit split (tokens → tiles → rows → modals → nav → onboarding → cleanup) makes it reviewable, and the discrepancy table is genuinely useful. I verified no dangling references to the removed tokens remain, so the module still compiles. I found one blocking issue and a handful of things worth deciding before merge. Detailed notes are in inline comments; summary below.


🔴 Blocking — spotlessCheck will fail on unused imports

Removing the last Color.White / Color.Black reference from a file left import androidx.compose.ui.graphics.Color orphaned in 11 files. ktlint's standard no-unused-imports rule is active (build.gradle.kts:141-147 disables only function-expression-body, multiline-expression-wrapping, backing-property-naming), so ./gradlew spotlessCheck should reject these. TvHomeScreen.kt and TvEpisodeInfoModal.kt do drop the import, so this looks like an oversight in the later commits rather than an intentional choice:

File Line
component/TvEmptyState.kt 18
upnext/TvUpNextScreen.kt 25
onboarding/createaccount/TvCreateAccountModal.kt 17
onboarding/welcome/TvWelcomeScreen.kt 23
onboarding/signin/TvSignInScreen.kt 22
onboarding/signin/TvSyncingScreen.kt 31
playlists/TvPlaylistsScreen.kt 39
playlists/details/TvPlaylistDetailsScreen.kt 29
podcasts/TvFolderDetailScreen.kt 20
podcasts/TvPodcastDetailsScreen.kt 34
podcasts/TvYourPodcastsScreen.kt 20

./gradlew spotlessApply clears all 11. The PR checklist has the linter box ticked, so it's worth re-running — I wasn't able to execute Gradle in this environment (no permission for ./gradlew), so this is from static analysis: each of these files has zero remaining Color-prefixed references and no : Color type usage.

Fix this →


🟡 Worth fixing in this PR

1. Contradictory textAlign in TvCreateAccountModal.kt:68-69Headline.copy(textAlign = TextAlign.Center) is immediately overridden by the textAlign = TextAlign.Start parameter. The Text param wins, so behaviour is unchanged, but it's dead code that reads as a bug. This header is deliberately left-aligned; the Center shouldn't have been carried over from ModalTitle. (inline)

2. TextTertiaryActive == TextTertiary (TvColors.kt:14) — both #7A7D82. Every other *Active pair inverts for light surfaces; a mid-grey that's unchanged on both #161718 and #FBFBFC reads like a copy/paste slip. Also only ~3.9:1 on bg-active, so it'd fail AA if wired up. Please re-check against Figma. (inline)

3. Ramp tokens no longer pin letterSpacing — the removed TabLabel set letterSpacing = 0.sp; Caption1 doesn't. tv.material3.Text does LocalTextStyle.current.merge(style), and merge only takes the argument's value when specified, so ambient Material tracking now leaks into the tab labels. Small visual delta, but it also makes all eight ramp tokens ambient-dependent, which undercuts the point of having canonical tokens. (inline)

4. Title1, Title2, Subtitle1 have no call sites — the description accounts for the unused colours explicitly, but not these; Subtitle1 doesn't appear in any table. If they're deliberate placeholders, a one-line KDoc on TvTextStyles saying so (plus documenting the Figma-px ÷ 1.5 convention and the SF Pro omission) would prevent both "dead code" review comments and someone later "tidying" the fractional sp values.


🟢 Nits / follow-ups

  • Token semantics for non-text foregrounds. LoadingView(color = TvColors.TextPrimary) appears in 9 places, and iconButtonColors uses TextPrimary/TextPrimaryActive for icon tints. Values are right, names aren't — a ForegroundPrimary alias (or the Figma foreground token, if one exists) would keep the mapping honest. (inline)
  • .copy(textAlign = Center) per recomposition in TvFolderCard.kt:85, TvEpisodeActionsModal.kt:118, TvProfileModal.kt:86/:178, TvDownloadAppModal.kt:59. TextStyle has a proper equals, so Text still skips — it's just a needless allocation per cell per recomposition. Since Caption1.copy(textAlign = Center) is repeated in three files, a shared TvTextStyles.Caption1Centered would be tidier than five local .copy calls. (inline)
  • TvFolderCard truncation. Caption2 is 15.33sp vs the old 14sp — ~10% wider glyphs on a maxLines = 1, softWrap = false label in a fixed-width card. Worth checking a long folder name; the preview uses a short one.
  • TvTopBar.kt:75 still uses literal Color.White for the focus-ring border while the container it wraps is now BackgroundActive (#FBFBFC). Imperceptible, but since the border exists specifically to reinforce the focused container, TvColors.BackgroundActive would be more consistent than leaving it as a "functional white".
  • Focused + selected tile buttons lose their white pill (focused* colours ignore isSelected), so focusing a selected item reads as deselecting it, and TextSecondary on 20% white is ~4:1. Pre-existing, but adjacent to this PR's stated focus-legibility goal. (inline)
  • Title3 is +4sp on every screen title (ScreenTitle 28 → 32). It lands in a Row with weight(1f) next to TvArchivedFilterButton in TvPodcastDetailsScreen.kt:295, and above fixed paddings in TvPodcastGridScaffold/TvPlaylistsScreen/TvUpNextScreen. Probably fine, but this is the change most likely to shift layout — the "screenshots to be attached" would settle it.

Things I checked and found clean

  • No dangling references to any removed token (Dark, DarkGray, Gray, LightGray, TextPrimary20, BgActive, BgActive20, Divider, or the 11 removed text styles) — the module compiles.
  • Alpha encodings are exact: TextPrimary70 0xB3 = 0.702 ✓ matches the old .copy(alpha = 0.7f); BackgroundActive50 0x80 = 0.502 ✓; BackgroundActive20 0x33 = 0.2 ✓ (so the resting-button backgrounds really are a no-op, as the description claims).
  • Every previously-centered style that got re-pointed has centering restored where the label is full-width, and correctly omitted where it isn't (TvDownloadAppModal.kt:87 — the Done button isn't fillMaxWidth, so Caption1 without Center is right).
  • Focused-state contrast on the changed surfaces holds up: TextPrimaryActive #161718 and TextSecondaryActive #3D4044 on BackgroundActive #FBFBFC are ~18:1 and ~10:1.
  • No security or data-handling surface in this diff — it's colours and text styles only. No analytics, strings, or DB changes, consistent with the checklist.

Note the two unchecked UI-testing boxes (large display / font size, TalkBack). Large-font is the relevant one here: the ramp uses sp throughout, so a +4sp base on screen titles compounds under font scaling.
· branch feat/tv-design-system-tokens

@dangermattic

dangermattic commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

Comment thread tv/src/main/java/au/com/shiftyjelly/pocketcasts/theme/TvColors.kt
@sztomek sztomek added this to the 8.19 milestone Aug 5, 2026
@sztomek sztomek added [Type] Enhancement Improve an existing feature. [Area] TV labels Aug 5, 2026
@sztomek sztomek changed the title Align Android TV UI with the Figma design-system tokens [TV] Align Android TV UI with the Figma design-system tokens Aug 5, 2026
@sztomek
sztomek force-pushed the feat/tv-design-system-tokens branch 2 times, most recently from 446584f to 3809384 Compare August 5, 2026 17:48
@sztomek
sztomek marked this pull request as ready for review August 5, 2026 17:52
@sztomek
sztomek requested a review from a team as a code owner August 5, 2026 17:52
@sztomek
sztomek requested review from geekygecko and removed request for a team August 5, 2026 17:52
@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown

Claude finished @sztomek's task in 5m 9s —— View job


Code review

Re-reviewed after the Address PR comments / Fine tune text styles commits. Last round's blocker and both token-hygiene points are genuinely fixed. What's left is mostly one theme: the "fine tune" pass moved several styles off what the PR description says, and a few of those moves look like drift rather than decisions.

  • Read the diff vs feat/tv-deep-destination-top-bar
  • Verify the previous review's findings were addressed
  • Check token definitions + call sites for correctness
  • Post review (5 inline comments)

✅ Fixed since last round

  • Unused Color imports — checked all 40 changed files: 17 still import androidx.compose.ui.graphics.Color and every one has a real remaining use. The 11 orphans are gone. TvUpNextScreen's newly-unused Column/Spacer/height/MaterialTheme are all still used too, so no new orphans were introduced by the TvEmptyState reuse.
  • letterSpacing = 0.sp now pinned on all 8 ramp tokens — the tab-label tracking leak is closed and the tokens are self-contained.
  • KDoc on TvTextStyles documents the ÷1.5 convention, the SF Pro omission, and why unused steps exist. That resolves the Title1/Title2/Subtitle1 dead-code question.
  • TvCreateAccountModal contradictory textAlign — the .copy(Center) is gone. (Line 68's explicit Start is now redundant but harmless.)

🟡 Worth resolving before merge

1. Featured tile hierarchy is inverted. The Sponsored eyebrow is now Body 19.33sp while the description below it is Caption2 15.33sp — it was 14sp vs 16sp. A secondary label went from 2sp below the description to 4sp above it. (inline)

2. Show notes / podcast description are on the smallest ramp step in secondary grey. TvEpisodeInfoModal.kt:184 and TvPodcastInfoModal.kt:186 went from 16sp Color.White to Caption2 15.33sp TextSecondary — smaller and lower contrast, on the two longest-form reading surfaces in the app. Meanwhile Body — the token named for body copy — is used for one-line subtitles, error strings, and that sponsored eyebrow, but for neither description pane. (inline)

3. Onboarding titles are +88%, unverified in a long locale. WelcomeTitle 27sp → Title1 50.67sp, subtitle 16sp → Headline 25.33sp, across TvWelcomeScreen, TvSyncingScreen, TvSignInScreen (×2) and TvCreateAccountScreen. On 960×540dp with horizontal = 48.dp padding, English fits; longer translations wrap at 64sp line height and push the button row toward the bottom-anchored button. Both large-font and theme checklist boxes are unticked and the screencast is English-only. (inline)

4. TvSignInQrContent.kt:105 centres left-aligned step text next to a numbered bubble — inherited from SignInSubtitle, invisible at 12sp because it never wrapped, visible now at 19.33sp. Lines 98 and 126 also still hold raw fontSize = 14.sp/18.sp literals, so "all per-component text styles eliminated" isn't quite accurate. (inline)

5. TextTertiaryActive == TextTertiary (TvColors.kt:12) — unchanged from last round. Every other *Active pair inverts; ~3.9:1 on bg-active. Unused today, so a wrong value here won't surface until someone wires it up. (inline)

6. The PR description tables no longer match the code. Since they're what design will review against, they're worth refreshing:

Description says Code actually does
FeaturedTileTitle → Title2 (38sp) Headline 25.33sp (TvFeaturedTile.kt:130)
FeaturedTileDescription → Body Caption2 (:138)
ModalBody → Body (description panes) Caption2 + TextSecondary
"TextPrimary70 is used… Title1 unlisted" both used; unused set is Title2, Subtitle1, BackgroundSurface, BackgroundActive50, TextTertiary/Active, TextDisabled/Active

🟢 Nits / carried over

  • .copy(textAlign = Center) is now at 20 call sites across 10 files — the largest remaining duplication, and a TextStyle allocation per recomposition each. Passing textAlign to Text instead is free; centred token siblings would suit the repeated ones. (inline)
  • TvColors has no KDoc while TvTextStyles now does, despite having the same unused-token set to explain.
  • Still open from last round, all fine as follow-ups: LoadingView(color = TvColors.TextPrimary) naming across 9 sites (a ForegroundPrimary alias), TvTopBar.kt:85's literal Color.White focus ring against a BackgroundActive container, and tileButtonColors' focused colours ignoring isSelected.

Things I checked and found clean

  • Every removed token (Dark, DarkGray, Gray, LightGray, TextPrimary20, BgActive, BgActive20, Divider) and all 11 removed text styles have zero remaining references.
  • TvUpNextScreen's UpNextEmptyTvEmptyState swap is behaviour-preserving: TvEmptyState is the same Box(contentAlignment = Center) + centred Column, the caller passes Modifier.fillMaxSize(), and autoFocusAction defaults to false matching the old un-focus-requested button. Net −26 lines.
  • Alpha encodings exact: TextPrimary70 0xB3 = 0.702 ✓, BackgroundActive50 0x80 = 0.502 ✓, BackgroundActive20 0x33 = 0.2 ✓ — the resting-button backgrounds really are a no-op.
  • Focused-state contrast holds: TextPrimaryActive #161718 and TextSecondaryActive #3D4044 on BackgroundActive #FBFBFC are ~18:1 and ~10:1.
  • No security or data-handling surface — colours and text styles only, no analytics/strings/DB changes, consistent with the checklist.

Caveat: I couldn't run Gradle in this environment (no permission for ./gradlew), so spotlessCheck, lint and the build are unverified here — the import analysis above is static. Buildkite's Danger run passed but the compile/lint jobs aren't visible from GitHub Actions.
· branch feat/tv-design-system-tokens

Comment thread tv/src/main/java/au/com/shiftyjelly/pocketcasts/component/TvFeaturedTile.kt Outdated
Comment thread tv/src/main/java/au/com/shiftyjelly/pocketcasts/theme/TvColors.kt

@geekygecko geekygecko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making this change!

* convention), hence the fractional `sp`. Figma's SF Pro family is not ported (no such resource in
* this module), so the tokens use the system default at [FontWeight] 510. The full ramp is defined
* even where a step has no call site yet, so the palette stays complete.
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it helps but there is a page in Figma related to the text styles for Google.

Image

Text(
text = pluralStringResource(LR.plurals.episode_count, episodeCount, episodeCount),
style = TvTextStyles.PlaylistCardCaption,
style = TvTextStyles.Caption2,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

These don't match the text style from the Figma document, but maybe we can do this separately later, as this PR is already quite large. This change would also make the text bigger, and it really seems quite large for this card.

Base automatically changed from feat/tv-deep-destination-top-bar to main August 6, 2026 10:05
@sztomek
sztomek force-pushed the feat/tv-design-system-tokens branch from 3809384 to fc3bc34 Compare August 6, 2026 10:52
@claude

claude Bot commented Aug 6, 2026

Copy link
Copy Markdown

Claude finished @sztomek's task in 4m 37s —— View job


Code review

Re-reviewed after fc3bc34 Address PR comments. Four of last round's five points are fixed; one substantive item and a few notes remain. @geekygecko's Figma screenshot also lets me verify something I'd previously only assumed, and it corrects one claim in the KDoc.

  • Diff the new commits against the previously-reviewed state
  • Verify last round's findings were addressed
  • Check token definitions + call sites
  • Post review (3 inline comments)

✅ Fixed since last round

Finding Status
Featured-tile hierarchy inverted (Sponsored eyebrow larger than its description) Fixed — eyebrow is Caption2 (TvFeaturedTile.kt:121), now below the Headline title and level with the description
TvSignInQrContent centring left-aligned step text Fixed — .copy(textAlign = Center) dropped at :104, and the orphaned TextAlign import went with it
TvColors missing the KDoc that TvTextStyles got Fixed — the "defined even where unused" note is now on both objects
TvCreateAccountModal contradictory textAlign Fixed in the prior commit (:68's explicit Start is redundant but harmless)

📐 Figma cross-check (from @geekygecko's screenshot)

The Google TV styles → Text Styles page is a useful confirmation, and worth folding back into the code:

  • The ÷1.5 derivation is correct. Figma's Caption 1 reads 25 / 32 / 0%; this file's Caption1 is 16.67sp / 21.33sp / 0.sp — exactly 25÷1.5 and 32÷1.5. That's the whole ramp validated against a real design value rather than inferred.
  • The family is Google Sans Medium, not SF Pro. The KDoc's "Figma's SF Pro family is not ported" looks like it came from an iOS page. The conclusion still holds (neither font ships here — modules/services/ui/src/main/res/font/ has only roboto_serif and dm_sans), but the KDoc points the next reader at the wrong font, and FontWeight(510) is the SF Pro reading of what Figma calls Medium (500). No visual delta on Roboto; suggestion inline. (inline)

🟡 One thing I'd still resolve before merge

Show notes / podcast description are on the smallest ramp step in secondary grey. TvEpisodeInfoModal.kt:184 and TvPodcastInfoModal.kt:186 are Caption2 15.33sp + TextSecondary, down from 16sp + Color.White — smaller and lower contrast, on the two longest-form reading surfaces in the app. Body is meanwhile spent on one-line subtitles, error strings and toasts. Since the PR description still lists ModalBody → Body, this reads as drift from the "Fine tune text styles" pass rather than a decision. (inline)

🟢 Carried over — fine as follow-ups

  • TextTertiaryActive == TextTertiary (TvColors.kt:17, both #7A7D82) — every other *Active pair inverts; ~3.9:1 on bg-active. Unused today, so a wrong value won't surface until someone wires it up. One line to check against Figma.
  • Onboarding Title1 is +88% (WelcomeTitle 27sp → 50.67sp) across TvWelcomeScreen:91, TvSyncingScreen:99, TvSignInScreen:94/:170, TvCreateAccountScreen:67. English fits at 960×540dp; a de/ru screenshot at 1.3× font scale would settle it. Both relevant checklist boxes are still unticked.
  • .copy(textAlign = Center) at 19 sites across 10 files — down one, still the largest duplication in the diff. Passing textAlign to Text is free.
  • TvPlaylistCard captions are Caption2 where Figma says Caption 1 — agreed with @geekygecko that this is follow-up material; I added the exact numbers (inline).
  • LoadingView(color = TvColors.TextPrimary) at 9 sites (a ForegroundPrimary alias), TvTopBar.kt:85's literal Color.White focus ring, tileButtonColors ignoring isSelected when focused, and the two raw fontSize = 14.sp/18.sp badge glyphs at TvSignInQrContent.kt:97/:125.
  • The PR description tables no longer match the codeFeaturedTileTitle → Title2, FeaturedTileDescription → Body, ModalBody → Body are all stale, and the unused set is now just Title2, Subtitle1, BackgroundSurface, BackgroundActive50, TextTertiary/Active, TextDisabled/Active. Worth a refresh since design will review against them.

Correcting my round-1 review

I flagged unused Color imports as blocking on spotlessCheck. That was overstated: TvPlaylistsScreen.kt:38 carries an unused import androidx.compose.ui.text.style.TextAlign that is also present on origin/main, so ktlint's no-unused-imports clearly isn't rejecting these in this configuration. The cleanup was still worth doing, but it was never a merge blocker. (That stray import is pre-existing and out of scope here.)

Things I checked and found clean

  • Every removed token (Dark, DarkGray, Gray, LightGray, TextPrimary20, BgActive, BgActive20, Divider) and all 11 removed text styles have zero remaining references.
  • All 9 ramp tokens pin letterSpacing = 0.sp and includeFontPadding = false — self-contained, no ambient Material tracking leak.
  • Alpha encodings exact: TextPrimary70 0xB3 = 0.702, BackgroundActive50 0x80 = 0.502, BackgroundActive20 0x33 = 0.2 — the resting-button backgrounds really are a no-op.
  • Focused-state contrast holds: TextPrimaryActive #161718 and TextSecondaryActive #3D4044 on BackgroundActive #FBFBFC are ~18:1 and ~10:1.
  • No security or data-handling surface — colours and text styles only, no analytics/strings/DB changes.

Caveat: ./gradlew is not permitted in this environment, so the build, spotlessCheck and lint are unverified here — all import and reference analysis above is static.
· branch feat/tv-design-system-tokens

Comment on lines +9 to +12
* The Figma type ramp for TV. Sizes are the Figma px values divided by 1.5 (the 1920x1080 → dp
* convention), hence the fractional `sp`. Figma's SF Pro family is not ported (no such resource in
* this module), so the tokens use the system default at [FontWeight] 510. The full ramp is defined
* even where a step has no call site yet, so the palette stays complete.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Figma page @geekygecko linked (Google TV styles → Text Styles) settles two of this KDoc's open questions, and contradicts one of them:

  • The ÷1.5 convention is confirmed. The selected Caption 1 in that screenshot reads 25 / 32 / 0%, and this file's Caption1 is 16.67sp / 21.33sp / 0.sp — exactly 25÷1.5 and 32÷1.5. Nice to have that verified rather than assumed.
  • The family is Google Sans, not SF Pro. The Typography panel shows Google Sans · Medium · 25 / 32. The PR description's "Figma specifies FontFamily(R.font.sf_pro) (iOS)" appears to have come from a different (iOS) page; the TV page is Google Sans. The conclusion is unchanged — neither font ships in this repo (modules/services/ui/src/main/res/font/ has only roboto_serif and dm_sans), so the system default is still the right fallback — but the KDoc currently sends the next reader looking for the wrong font.
  • FontWeight(510) came from the SF Pro reading. Google Sans Medium is FontWeight(500). Both round to the same Roboto face on Android TV so there's no visual delta, but 500 is the value the design actually specifies and it drops the "why 510?" question.
Suggested change
* The Figma type ramp for TV. Sizes are the Figma px values divided by 1.5 (the 1920x1080 → dp
* convention), hence the fractional `sp`. Figma's SF Pro family is not ported (no such resource in
* this module), so the tokens use the system default at [FontWeight] 510. The full ramp is defined
* even where a step has no call site yet, so the palette stays complete.
* The Figma type ramp for TV (Google TV styles → Text Styles). Sizes are the Figma px values
* divided by 1.5 (the 1920x1080 → dp convention), hence the fractional `sp`. Figma's Google Sans
* family is not ported (no such resource in this repo), so the tokens use the system default at
* [FontWeight] 500, matching Google Sans Medium. The full ramp is defined even where a step has no
* call site yet, so the palette stays complete.

(If you take the KDoc change, the FontWeight(510) on the nine tokens below wants to become FontWeight(500) to match.)

text = remember(showNotes.html) { AnnotatedString.fromHtml(showNotes.html) },
style = TvTextStyles.FeaturedTileDescription,
color = Color.White,
style = TvTextStyles.Caption2,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Carried over from last round and still the one substantive thing I'd change before merge — the other four points from that review are now fixed, so flagging it once more rather than letting it slip through on the approval.

Show notes are the longest-form reading surface in the TV app, and they're on the smallest ramp step in secondary grey: Caption2 15.33sp / #B0B3B8, down from 16sp / Color.White. Same on the podcast description at TvPodcastInfoModal.kt:186. Meanwhile Body (19.33sp) — the token literally named for body copy — is used for one-line subtitles and error strings (TvCreateAccountScreen.kt:73, TvSignInScreen.kt:127, TvDownloadAppModal.kt:64, TvToast.kt:101) but for neither description pane. At 10-foot viewing distance the scroll-heavy panes are where the extra 4sp matters most.

The PR description also still lists ModalBody → Body, which is what makes this read as drift from the "Fine tune text styles" pass rather than a deliberate call.

Suggested change
style = TvTextStyles.Caption2,
style = TvTextStyles.Body,
color = TvColors.TextPrimary,

Text(
text = pluralStringResource(LR.plurals.episode_count, episodeCount, episodeCount),
style = TvTextStyles.PlaylistCardCaption,
style = TvTextStyles.Caption2,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the concrete numbers to @geekygecko's point, since they make the "defer it" call easy to justify in writing rather than from memory:

Figma has the episode count at Caption 1 · 25/32, i.e. 16.67sp / 21.33sp after the ÷1.5 — this is Caption2 (15.33sp / 20sp). So the gap is 1.34sp, about 9%. The Smart Playlist caption on line 117 is the same token and presumably the same Figma style.

Worth noting for whoever picks up the follow-up: this card puts both captions in a Column with a weight(1f) spacer between them, and the title above is maxLines = 2 Headline (25.33sp, 30.67sp line height). In the fixed-height card that's ~61dp of title plus two caption lines, so a 1.34sp bump on each caption is ~3dp of extra vertical demand — tight but not obviously overflowing. Cheap to verify with a two-line playlist name.

Agreed it doesn't need to block this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants