Adopt Wix Madefor Text and share app card identity.

This commit is contained in:
franzap
2026-09-16 14:37:01 -03:00
parent 141c4409f3
commit d8aaec91c6
23 changed files with 149 additions and 79 deletions
+3 -1
View File
@@ -37,6 +37,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
object ZapMarkdown {
@@ -86,9 +87,10 @@ object ZapMarkdown {
val level = match.groupValues[1].length
withStyle(
SpanStyle(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.ExtraBold,
fontSize = headingSize(level),
letterSpacing = (-0.016).em,
),
) {
appendInline(match.groupValues[2], onOpenUrl)
+37 -25
View File
@@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
@@ -97,16 +98,21 @@ object ZapRadius {
}
// ---- Type ----
// One workhorse grotesque (Figtree) carries everything; hierarchy comes from size and weight.
// One workhorse grotesque (Wix Madefor Text) carries everything; hierarchy comes from size and weight.
val FigtreeFontFamily = FontFamily(
Font(R.font.figtree_light, FontWeight.Light),
Font(R.font.figtree_regular, FontWeight.Normal),
Font(R.font.figtree_medium, FontWeight.Medium),
Font(R.font.figtree_semibold, FontWeight.SemiBold),
Font(R.font.figtree_bold, FontWeight.Bold),
Font(R.font.figtree_extra_bold, FontWeight.ExtraBold),
Font(R.font.figtree_black, FontWeight.Black),
private val ZapLetterSpacing = (-0.016).em
val WixMadeforTextFontFamily = FontFamily(
Font(R.font.wix_madefor_text_regular, FontWeight.Normal),
Font(R.font.wix_madefor_text_italic, FontWeight.Normal, FontStyle.Italic),
Font(R.font.wix_madefor_text_medium, FontWeight.Medium),
Font(R.font.wix_madefor_text_medium_italic, FontWeight.Medium, FontStyle.Italic),
Font(R.font.wix_madefor_text_semibold, FontWeight.SemiBold),
Font(R.font.wix_madefor_text_semibold_italic, FontWeight.SemiBold, FontStyle.Italic),
Font(R.font.wix_madefor_text_bold, FontWeight.Bold),
Font(R.font.wix_madefor_text_bold_italic, FontWeight.Bold, FontStyle.Italic),
Font(R.font.wix_madefor_text_extrabold, FontWeight.ExtraBold),
Font(R.font.wix_madefor_text_extrabold_italic, FontWeight.ExtraBold, FontStyle.Italic),
)
private val ZapColorScheme = darkColorScheme(
@@ -137,85 +143,91 @@ private val ZapColorScheme = darkColorScheme(
private val ZapTypography = Typography(
// Display — hero headline only.
displayLarge = Typography().displayLarge.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 36.sp,
lineHeight = 38.sp,
letterSpacing = (-0.04).em,
letterSpacing = ZapLetterSpacing,
),
// Heading 1 — section and screen titles.
displaySmall = Typography().displaySmall.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 26.sp,
lineHeight = 30.sp,
letterSpacing = (-0.025).em,
letterSpacing = ZapLetterSpacing,
),
// Heading 2 — sheet titles, welcome card.
headlineSmall = Typography().headlineSmall.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = (-0.015).em,
letterSpacing = ZapLetterSpacing,
),
titleLarge = Typography().titleLarge.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = (-0.015).em,
letterSpacing = ZapLetterSpacing,
),
// Heading 3 — app names in detail heads and rows.
titleMedium = Typography().titleMedium.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 17.sp,
lineHeight = 24.sp,
letterSpacing = ZapLetterSpacing,
),
// Body — 16sp on Android.
bodyLarge = Typography().bodyLarge.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = ZapLetterSpacing,
),
// Body small — descriptions in rows and cards.
bodyMedium = Typography().bodyMedium.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 15.sp,
lineHeight = 22.sp,
letterSpacing = ZapLetterSpacing,
),
// Caption — timestamps, secondary facts.
bodySmall = Typography().bodySmall.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 13.sp,
lineHeight = 18.sp,
letterSpacing = ZapLetterSpacing,
),
// Button label — 14sp/600.
labelLarge = Typography().labelLarge.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = ZapLetterSpacing,
),
// Evidence — verifiable values (versions, hashes, package IDs); tabular figures.
labelMedium = Typography().labelMedium.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 13.sp,
lineHeight = 17.sp,
letterSpacing = ZapLetterSpacing,
fontFeatureSettings = "tnum",
),
// Label — list-group headers, table headers, nav groups. Uppercase at call sites.
labelSmall = Typography().labelSmall.copy(
fontFamily = FigtreeFontFamily,
fontFamily = WixMadeforTextFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.08.em,
letterSpacing = ZapLetterSpacing,
),
)
@@ -89,29 +89,17 @@ fun AppCard(
.clickable(onClick = onClick)
.padding(ZapSpacing.space4),
) {
BoxWithConstraints {
val iconSize = (maxWidth * 0.21f).coerceIn(50.dp, 68.dp)
Row(verticalAlignment = Alignment.Top) {
AppIcon(
title = app.name,
iconUrl = app.iconUrl,
size = iconSize,
)
Spacer(Modifier.width(ZapSpacing.space3))
Column(Modifier.weight(1f)) {
AppNameWithByline(
name = app.name,
authorName = authorName,
authorProfile = authorProfile,
authorPubkey = authorPubkey,
version = release?.version,
onAuthorClick = onProfileClick,
authorTestTag = authorPubkey?.let { "profile:$it" },
showAuthor = showAuthor,
)
}
}
}
AppIdentityRow(
name = app.name,
iconUrl = app.iconUrl,
authorName = authorName,
authorProfile = authorProfile,
authorPubkey = authorPubkey,
version = release?.version,
onAuthorClick = onProfileClick,
authorTestTag = authorPubkey?.let { "profile:$it" },
showAuthor = showAuthor,
)
if (app.summary.isNotBlank()) {
Spacer(Modifier.height(ZapSpacing.space2))
Text(
@@ -125,6 +113,43 @@ fun AppCard(
}
}
@Composable
fun AppIdentityRow(
name: String,
iconUrl: String?,
authorName: String?,
authorProfile: ProfileInfo?,
authorPubkey: String?,
version: String?,
modifier: Modifier = Modifier,
onAuthorClick: (() -> Unit)? = null,
authorTestTag: String? = null,
showAuthor: Boolean = true,
) {
BoxWithConstraints(modifier = modifier.fillMaxWidth()) {
val iconSize = (maxWidth * 0.21f).coerceIn(50.dp, 68.dp)
Row(verticalAlignment = Alignment.Top) {
AppIcon(
title = name,
iconUrl = iconUrl,
size = iconSize,
)
Spacer(Modifier.width(ZapSpacing.space3))
AppNameWithByline(
name = name,
authorName = authorName,
authorProfile = authorProfile,
authorPubkey = authorPubkey,
version = version,
onAuthorClick = onAuthorClick,
authorTestTag = authorTestTag,
showAuthor = showAuthor,
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
fun AppNameWithByline(
name: String,
@@ -137,18 +162,20 @@ fun AppNameWithByline(
authorTestTag: String? = null,
showAuthor: Boolean = true,
) {
Column(modifier = modifier) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Text(
text = name,
style = MaterialTheme.typography.titleMedium.copy(
fontSize = 19.sp,
lineHeight = 23.sp,
fontSize = 23.sp,
lineHeight = 28.sp,
fontWeight = FontWeight.Black,
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Spacer(Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
@@ -218,8 +245,8 @@ fun VersionPill(
fun StackCard(
stack: StackInfo,
appsByAddress: Map<String, AppInfo>,
onClick: () -> Unit,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
) {
Column(
modifier = modifier
@@ -227,7 +254,7 @@ fun StackCard(
.clip(RoundedCornerShape(ZapRadius.lg))
.background(ZapSurface1)
.border(1.dp, ZapLine, RoundedCornerShape(ZapRadius.lg))
.clickable(onClick = onClick)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
.padding(ZapSpacing.space4),
) {
Text(
@@ -57,7 +57,6 @@ import java.text.DateFormat
import java.text.NumberFormat
import java.util.Date
private val BigIconSize = 84.dp
private val SmallIconSize = 26.dp
private val SmallHeaderHeight = 32.dp
@@ -350,29 +349,16 @@ private fun RegularAppHeader(
authorProfile: ProfileInfo?,
onProfileClick: () -> Unit,
) {
// Reuses the same icon+title+version+byline block as the search/home AppCard, so the
// header reads as the same component, just bigger — not a bespoke hero layout.
val authorName = authorPubkey?.let { profileDisplayName(authorProfile, it) }
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.spacedBy(ZapSpacing.space3),
) {
AppIcon(
title = app.name,
iconUrl = app.iconUrl,
size = BigIconSize,
)
AppNameWithByline(
name = app.name,
authorName = authorName,
authorProfile = authorProfile,
authorPubkey = authorPubkey,
version = release?.version,
onAuthorClick = onProfileClick,
modifier = Modifier.weight(1f),
)
}
AppIdentityRow(
name = app.name,
iconUrl = app.iconUrl,
authorName = authorName,
authorProfile = authorProfile,
authorPubkey = authorPubkey,
version = release?.version,
onAuthorClick = onProfileClick,
)
}
@Composable
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font
android:font="@font/wix_madefor_text_regular"
android:fontStyle="normal"
android:fontWeight="400" />
<font
android:font="@font/wix_madefor_text_italic"
android:fontStyle="italic"
android:fontWeight="400" />
<font
android:font="@font/wix_madefor_text_medium"
android:fontStyle="normal"
android:fontWeight="500" />
<font
android:font="@font/wix_madefor_text_medium_italic"
android:fontStyle="italic"
android:fontWeight="500" />
<font
android:font="@font/wix_madefor_text_semibold"
android:fontStyle="normal"
android:fontWeight="600" />
<font
android:font="@font/wix_madefor_text_semibold_italic"
android:fontStyle="italic"
android:fontWeight="600" />
<font
android:font="@font/wix_madefor_text_bold"
android:fontStyle="normal"
android:fontWeight="700" />
<font
android:font="@font/wix_madefor_text_bold_italic"
android:fontStyle="italic"
android:fontWeight="700" />
<font
android:font="@font/wix_madefor_text_extrabold"
android:fontStyle="normal"
android:fontWeight="800" />
<font
android:font="@font/wix_madefor_text_extrabold_italic"
android:fontStyle="italic"
android:fontWeight="800" />
</font-family>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
<resources>
<style name="AppTheme" parent="android:style/Theme.Material.NoActionBar">
<item name="android:fontFamily">@font/figtree_regular</item>
<item name="android:fontFamily">@font/wix_madefor_text</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>