Merge pull request #7520 from element-hq/feature/bma/updateFonts

Update fonts on the headings
This commit is contained in:
Benoit Marty
2026-08-26 09:51:50 +02:00
committed by GitHub
77 changed files with 355 additions and 192 deletions
@@ -53,8 +53,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
@@ -80,7 +78,7 @@ import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGra
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.text.AdaptativeTitle
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -141,15 +139,21 @@ fun HomeTopBar(
else -> stringResource(selectedNavigationItem.labelRes)
}
}
HomeNavigationBarItem.Spaces -> stringResource(selectedNavigationItem.labelRes)
HomeNavigationBarItem.Spaces -> null
}
displayTitle?.let {
val style = when (spaceFiltersState) {
// Space name
is SpaceFiltersState.Selected -> ElementTheme.typography.fontHeadingSmMedium
// "Chats"
else -> ElementTheme.typography.fontHeadingLgBold
}
AdaptativeTitle(
title = displayTitle,
style = style,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
Text(
modifier = Modifier.semantics {
heading()
},
style = ElementTheme.typography.aliasScreenTitle,
text = displayTitle,
)
},
navigationIcon = {
NavigationIcon(
@@ -179,7 +183,9 @@ fun HomeTopBar(
TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) {
RoomListFiltersView(
state = filtersState,
modifier = Modifier.padding(bottom = 16.dp).padding(contentPadding)
modifier = Modifier
.padding(bottom = 16.dp)
.padding(contentPadding)
)
}
}
@@ -348,45 +354,45 @@ private fun AccountIcon(
),
contentAlignment = Alignment.Center,
) {
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
val statusEmoji = matrixUser.displayedStatus?.toEmojiText()
val avatarModifier = if (statusEmoji != null) {
Modifier.eraseStatusEmojiBackground(
parentSize = AvatarSize.CurrentUserTopBar.dp,
layoutDirection = LocalLayoutDirection.current,
)
} else {
Modifier
}
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
modifier = avatarModifier,
contentDescription = if (isCurrentAccount) {
if (showAvatarIndicator) {
stringResource(CommonStrings.a11y_settings_with_required_action)
} else {
stringResource(CommonStrings.common_settings)
}
} else {
null
},
}
val statusEmoji = matrixUser.displayedStatus?.toEmojiText()
val avatarModifier = if (statusEmoji != null) {
Modifier.eraseStatusEmojiBackground(
parentSize = AvatarSize.CurrentUserTopBar.dp,
layoutDirection = LocalLayoutDirection.current,
)
if (statusEmoji != null) {
StatusEmojiBadge(
emoji = statusEmoji,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
} else {
Modifier
}
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
modifier = avatarModifier,
contentDescription = if (isCurrentAccount) {
if (showAvatarIndicator) {
stringResource(CommonStrings.a11y_settings_with_required_action)
} else {
stringResource(CommonStrings.common_settings)
}
} else {
null
},
)
if (statusEmoji != null) {
StatusEmojiBadge(
emoji = statusEmoji,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
}
}
@@ -143,7 +143,7 @@ private fun EmptySpaceHomeView(
)
Text(
text = stringResource(CommonStrings.screen_space_list_empty_state_title),
style = ElementTheme.typography.fontHeadingLgBold,
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
@@ -10,6 +10,7 @@ package io.element.android.libraries.designsystem.atomic.atoms
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import io.element.android.compound.theme.ElementTheme
@@ -20,11 +21,12 @@ fun RoomPreviewTitleAtom(
title: String,
modifier: Modifier = Modifier,
fontStyle: FontStyle? = null,
style: TextStyle = ElementTheme.typography.fontHeadingLgBold
) {
Text(
modifier = modifier,
text = title,
style = ElementTheme.typography.fontHeadingLgBold,
style = style,
textAlign = TextAlign.Center,
fontStyle = fontStyle,
color = ElementTheme.colors.textPrimary,
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.designsystem.text
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Text
/**
* Render the title of a screen, but adapt the style if it does not fit on a single line,
* so that a wrapped title does not look oversized.
*
* @param title the title to render.
* @param style the style to use to render the [title].
* @param twoLinesStyle the style to use instead of [style] when the [title] does not fit on a single line with [style],
* so that a wrapped title does not look oversized.
* @param modifier the [Modifier] to apply to this layout.
*/
@Composable
fun AdaptativeTitle(
title: String,
style: TextStyle,
twoLinesStyle: TextStyle,
modifier: Modifier = Modifier,
) {
// If style and twoLinesStyle are the same, we don't need to measure the text.
if (style == twoLinesStyle) {
Text(
modifier = modifier.semantics {
heading()
},
style = style,
maxLines = 2,
text = title,
)
} else {
val textMeasurer = rememberTextMeasurer()
BoxWithConstraints(modifier = modifier) {
// Measure the title before rendering it, so that the style is picked in a single layout pass.
val fitsOnASingleLine = textMeasurer.measure(
text = title,
style = style,
constraints = constraints,
).lineCount == 1
Text(
modifier = Modifier.semantics {
heading()
},
style = if (fitsOnASingleLine) style else twoLinesStyle,
maxLines = 2,
text = title,
)
}
}
}
@PreviewsDayNight
@Composable
internal fun AdaptativeTitlePreview() = ElementPreview {
@Composable
fun AdaptativeTitleItem(
text: String,
content: @Composable () -> Unit,
) {
Column(
modifier = Modifier
.width(180.dp)
.background(ElementTheme.colors.bgSubtlePrimary),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = text,
style = ElementTheme.typography.fontBodyXsRegular,
color = ElementTheme.colors.textSecondary,
)
content()
}
}
Column(
modifier = Modifier.padding(4.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// Fits on a single line: the large style is kept.
AdaptativeTitleItem("Adaptative, 1 line:") {
AdaptativeTitle(
title = "Chats",
style = ElementTheme.typography.fontHeadingLgBold,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
// Would be rendered on two lines with the large style: the smaller style is used instead.
AdaptativeTitleItem("Adaptative, 2 lines:") {
AdaptativeTitle(
title = "Tutte le conversazioni:",
style = ElementTheme.typography.fontHeadingLgBold,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
// Would be rendered on two lines with the large style: the smaller style is used instead
// and it's finally rendered on a single line with the smaller style.
AdaptativeTitleItem("Adaptative, but 1 line:") {
AdaptativeTitle(
title = "Tous les chats",
style = ElementTheme.typography.fontHeadingLgBold,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
// Short title, with small font, 1 line.
AdaptativeTitleItem("Always small, 1 line:") {
AdaptativeTitle(
title = "My space",
style = ElementTheme.typography.fontHeadingSmMedium,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
// Space name, which can still be rendered on two lines.
AdaptativeTitleItem("Always small, 2 lines:") {
AdaptativeTitle(
title = "A very long space name",
style = ElementTheme.typography.fontHeadingSmMedium,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
}
}
@@ -46,7 +46,7 @@ fun SpaceHeaderRootView(
)
Text(
text = stringResource(CommonStrings.screen_space_list_title),
style = ElementTheme.typography.fontHeadingLgBold,
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
@@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewAliasAtom
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom
import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom
@@ -68,11 +69,15 @@ fun SpaceHeaderView(
},
title = {
if (name != null) {
RoomPreviewTitleAtom(title = name)
RoomPreviewTitleAtom(
title = name,
style = ElementTheme.typography.fontHeadingMdBold,
)
} else {
RoomPreviewTitleAtom(
title = stringResource(id = CommonStrings.common_no_space_name),
fontStyle = FontStyle.Italic
fontStyle = FontStyle.Italic,
style = ElementTheme.typography.fontHeadingMdBold,
)
}
},