From 4e62aad638ab1b491dd4e4a529f53279fd2a883c Mon Sep 17 00:00:00 2001 From: justinzeus Date: Thu, 5 Nov 2026 18:37:58 +0100 Subject: [PATCH] Reuse baseline-aligned headers with source subtitle typography --- wolfskin-design/shell-tokens.json | 1 + .../wolfskin/design/DiscoverScreen.kt | 12 +----------- .../wolfskin/design/LibraryScreen.kt | 17 +++++++++++------ .../wolfskin/design/WolfskinTokens.kt | 1 + wolfskin-design/tokens.css | 1 + wolfskin-design/tokens.sha256 | 6 +++--- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/wolfskin-design/shell-tokens.json b/wolfskin-design/shell-tokens.json index 70bda98a..4583ffe7 100644 --- a/wolfskin-design/shell-tokens.json +++ b/wolfskin-design/shell-tokens.json @@ -357,6 +357,7 @@ "badgeIcon": {"$type":"dimension","px":28,"dp":14,"source":"index.html:142"}, "seeAllGap": {"$type":"dimension","px":8,"dp":4,"source":"index.html:146"}, "tailDescription": {"$type":"typography","px":24,"sp":12,"weight":500,"$value":{"fontWeight":500},"source":"index.html:301"}, + "headerSubtitle": {"$type":"typography","px":30,"sp":15,"weight":400,"$value":{"fontWeight":400},"source":"index.html:201"}, "imageReads": {"$type":"number","$value":4,"source":"spec 4.9, tokens.json image.concurrentReads"}, "unknownCriticOpacity": {"$type":"number","$value":0.55,"source":"spec 4.7, index.html:161"}, "fallbackPadding": {"$type":"dimension","px":22,"dp":11,"source":"index.html:130"}, diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt index 6b2f41cf..8873dcde 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt @@ -94,7 +94,7 @@ fun DiscoverScreen(state: DiscoverState, modifier: Modifier = Modifier) { if (state.hasHero && state.service == null && list.firstVisibleItemIndex == 0) state.hero?.let { DiscoverBackdrop(it, modifier = Modifier.graphicsLayer { translationY = -list.firstVisibleItemScrollOffset.toFloat() }) } LazyColumn(state = list, contentPadding = PaddingValues(bottom = (WolfskinTokens.Shell.stageHeight - L.pinnedLine - heights.last()).coerceAtLeast(D.frameBottomMin)), modifier = Modifier.fillMaxSize()) { item(key = "discover-header", contentType = if (state.hasHero && state.service == null) "hero" else "header") { - if (state.hasHero && state.service == null) DiscoverHero(state) else DiscoverHeader(state.shell.person, state.service?.name ?: "Discover", if (state.service != null) "General interest" else "") + if (state.hasHero && state.service == null) DiscoverHero(state) else LibraryHeader(state.shell.person, state.service?.name ?: "Discover", if (state.service != null) "General interest" else "") } item(key = "discover-chips", contentType = "chips") { if (state.snapshot.status !in setOf(DiscoverStatus.NeedsSignIn, DiscoverStatus.Unavailable, DiscoverStatus.Loading)) DiscoverChips(state) @@ -116,16 +116,6 @@ fun DiscoverScreen(state: DiscoverState, modifier: Modifier = Modifier) { } } -/** Header and type chips use the same safe area as the shell. */ -@Composable -private fun DiscoverHeader(person: ShellPerson, title: String, subtitle: String) { - Row(Modifier.fillMaxWidth().height(L.row_lhead).padding(start = L.safeSide, top = D.headerTop), horizontalArrangement = Arrangement.spacedBy(D.headerGap), verticalAlignment = Alignment.CenterVertically) { - Avatar(person, WolfskinTokens.Shell.settingsHeaderAvatar, WolfskinTokens.Type.avatarLetterLibHeader, letterColor = C.muted) - Text(title, color = C.fg, style = WolfskinTokens.Type.screenTitle) - if (subtitle.isNotEmpty()) Text(subtitle, color = C.muted, style = WolfskinTokens.Type.nav) - } -} - @Composable private fun DiscoverHero(state: DiscoverState) { val title = state.hero ?: return diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt index f8157eb6..99a60ada 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt @@ -48,6 +48,7 @@ fun LibraryScreen( val active = !shell.navFocused && !shell.pickerVisible val items = grid.items val focused = grid.focusedTitle + val count = if (grid.catalogue) items.size else grid.source.size var plateKey by remember { mutableStateOf(null) } DisposableEffect(shell, grid, registerFocus) { if (registerFocus) shell.contentFocus = grid @@ -104,7 +105,11 @@ fun LibraryScreen( } } }, contentPadding = PaddingValues(bottom = WolfskinTokens.Shell.stageHeight - L.pinnedLine - rowHeight)) { - item(key = "header", contentType = "header") { LibraryHeader(grid, shell.person) } + item(key = "header", contentType = "header") { + LibraryHeader(shell.person, grid.title, + if (grid.status != LibraryStatus.Ready) "" else "$count ${if (grid.catalogue) "titles" else if (grid.title == "Movies") if (count == 1) "film" else "films" else "series"}", + grid.savedAt) + } if (headerCount == 2) item(key = "toolbar", contentType = "chips") { LibraryToolbar(grid, active) } when (grid.status) { LibraryStatus.Loading -> items(3, key = { "skeleton-$it" }, contentType = { "grid" }) { @@ -157,15 +162,15 @@ fun LibraryScreen( } } +/** Library, catalogue and service headers share one baseline and person treatment. */ @Composable -private fun LibraryHeader(grid: LibraryGridState, person: ShellPerson) { - val count = if (grid.catalogue) grid.items.size else grid.source.size +fun LibraryHeader(person: ShellPerson, title: String, subtitle: String = "", savedAt: String? = null) { Box(Modifier.fillMaxWidth().height(L.row_lhead)) { Row(Modifier.offset(L.safeSide, L.navBand), horizontalArrangement = Arrangement.spacedBy(WolfskinTokens.Shell.settingsHeaderGap), verticalAlignment = Alignment.CenterVertically) { Avatar(person, WolfskinTokens.Shell.settingsHeaderAvatar, WolfskinTokens.Type.avatarLetterLibHeader, letterColor = C.muted) - Text(grid.title, modifier = Modifier.alignByBaseline(), color = C.fg, style = WolfskinTokens.Type.screenTitle) - Text(if (grid.status != LibraryStatus.Ready) "" else "$count ${if (grid.catalogue) "titles" else if (grid.title == "Movies") if (count == 1) "film" else "films" else "series"}", modifier = Modifier.alignByBaseline(), color = C.muted, style = WolfskinTokens.Type.nav.copy(fontWeight = FontWeight.Normal)) - grid.savedAt?.let { Text("Saved $it", color = C.warn, style = WolfskinTokens.Type.caption) } + Text(title, modifier = Modifier.alignByBaseline(), color = C.fg, style = WolfskinTokens.Type.screenTitle) + if (subtitle.isNotEmpty()) Text(subtitle, modifier = Modifier.alignByBaseline(), color = C.muted, style = G.headerSubtitle) + savedAt?.let { Text("Saved $it", color = C.warn, style = WolfskinTokens.Type.caption) } } } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt index 73ce70a6..d967b876 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt @@ -325,6 +325,7 @@ object WolfskinTokens { val badgeIcon = 14.0.dp val seeAllGap = 4.0.dp val tailDescription = TextStyle(fontSize = 12.0.sp, fontWeight = FontWeight(500), lineHeight = 15.600000000000001.sp, letterSpacing = 0.0.em) + val headerSubtitle = TextStyle(fontSize = 15.0.sp, fontWeight = FontWeight(400), lineHeight = 19.5.sp, letterSpacing = 0.0.em) const val imageReads = 4.0f const val unknownCriticOpacity = 0.55f val fallbackPadding = 11.0.dp diff --git a/wolfskin-design/tokens.css b/wolfskin-design/tokens.css index 72925b9c..844e6d93 100644 --- a/wolfskin-design/tokens.css +++ b/wolfskin-design/tokens.css @@ -206,6 +206,7 @@ --library-badgeIcon: 28.0px; --library-seeAllGap: 8.0px; --library-tailDescription: 24.0px; + --library-headerSubtitle: 30.0px; --library-fallbackPadding: 22.0px; --library-fallbackIcon: 44.0px; --discover-sheetPaddingX: 64.0px; diff --git a/wolfskin-design/tokens.sha256 b/wolfskin-design/tokens.sha256 index 9afb3e1d..0a4f6be4 100644 --- a/wolfskin-design/tokens.sha256 +++ b/wolfskin-design/tokens.sha256 @@ -1,6 +1,6 @@ ffc070ec958ece59f2e8eecd1144f43cdaf7fdb53fe51b178f1b17457ea4151a discover-tokens.json fcff646d59fb39e859c34c05037e05a7fb7583de2fe07315ec0176cf4d597980 generate_tokens.py -17c45415eb8e334a31a8b187ad2e2c960c35b58e565b19fbbcc9cb5e10717a3a shell-tokens.json -9359aaa6f81650ccfa46c3f0a76d128ea229ff16d79573b342804cc4cb12330e src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt -b230411658c4b82e7f9e1677842c60bbf61939567958075fff277bc0561e00ec tokens.css +050823bdd2bb71181a7b7c849e412129f312a150a27c2e5b9b3fbc1f5ccece0f shell-tokens.json +ab20caec74fcbedadfbc59449b227a11a62cf79e8bc862d5f95ec95d1b8574db src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt +4b741830cda8b905d71f97bf88ee8d6cca3e1cf0f881bc06c5525cbf05378808 tokens.css 5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53 tokens.json