diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt index a2b0049ad6..764a208a82 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Bloom.kt @@ -370,7 +370,7 @@ fun Modifier.avatarBloom( val initialsBitmap = initialsBitmap( width = BloomDefaults.ENCODE_SIZE_PX.toDp(), height = BloomDefaults.ENCODE_SIZE_PX.toDp(), - text = avatarData.initial, + text = avatarData.initialLetter, textColor = avatarColors.foreground, backgroundColor = avatarColors.background, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index 94ffd9c0fb..6b69a9dfec 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -7,11 +7,9 @@ package io.element.android.libraries.designsystem.components.avatar -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -21,21 +19,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.semantics.clearAndSetSemantics -import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import coil3.compose.AsyncImagePainter import coil3.compose.SubcomposeAsyncImage import coil3.compose.SubcomposeAsyncImageContent -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables import timber.log.Timber @@ -50,21 +43,18 @@ fun Avatar( // If true, will show initials even if avatarData.url is not null hideImage: Boolean = false, ) { - val commonModifier = modifier - .size(forcedAvatarSize ?: avatarData.size.dp) - .clip(CircleShape) if (avatarData.url.isNullOrBlank() || hideImage) { - InitialsAvatar( + InitialLetterAvatar( avatarData = avatarData, forcedAvatarSize = forcedAvatarSize, - modifier = commonModifier, + modifier = modifier, contentDescription = contentDescription, ) } else { ImageAvatar( avatarData = avatarData, forcedAvatarSize = forcedAvatarSize, - modifier = commonModifier, + modifier = modifier, contentDescription = contentDescription, ) } @@ -77,11 +67,14 @@ private fun ImageAvatar( modifier: Modifier = Modifier, contentDescription: String? = null, ) { + val size = forcedAvatarSize ?: avatarData.size.dp SubcomposeAsyncImage( model = avatarData, contentDescription = contentDescription, contentScale = ContentScale.Crop, modifier = modifier + .requiredSize(size) + .clip(CircleShape) ) { val collectedState by painter.state.collectAsState() when (val state = collectedState) { @@ -90,13 +83,13 @@ private fun ImageAvatar( SideEffect { Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") } - InitialsAvatar( + InitialLetterAvatar( avatarData = avatarData, forcedAvatarSize = forcedAvatarSize, contentDescription = contentDescription, ) } - else -> InitialsAvatar( + else -> InitialLetterAvatar( avatarData = avatarData, forcedAvatarSize = forcedAvatarSize, contentDescription = contentDescription, @@ -106,33 +99,20 @@ private fun ImageAvatar( } @Composable -private fun InitialsAvatar( +private fun InitialLetterAvatar( avatarData: AvatarData, forcedAvatarSize: Dp?, contentDescription: String?, modifier: Modifier = Modifier, ) { val avatarColors = AvatarColorsProvider.provide(avatarData.id) - Box( - modifier.background(color = avatarColors.background) - ) { - val fontSize = (forcedAvatarSize ?: avatarData.size.dp).toSp() / 2 - val originalFont = ElementTheme.typography.fontHeadingMdBold - val ratio = fontSize.value / originalFont.fontSize.value - val lineHeight = originalFont.lineHeight * ratio - Text( - modifier = Modifier - .clearAndSetSemantics { - contentDescription?.let { - this.contentDescription = it - } - } - .align(Alignment.Center), - text = avatarData.initial, - style = originalFont.copy(fontSize = fontSize, lineHeight = lineHeight, letterSpacing = 0.sp), - color = avatarColors.foreground, - ) - } + TextAvatar( + text = avatarData.initialLetter, + size = forcedAvatarSize ?: avatarData.size.dp, + colors = avatarColors, + contentDescription = contentDescription, + modifier = modifier + ) } @Preview(group = PreviewGroup.Avatars) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt index 12b6fe05a3..2e711ee6eb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt @@ -18,7 +18,7 @@ data class AvatarData( val url: String? = null, val size: AvatarSize, ) { - val initial by lazy { + val initialLetter by lazy { // For roomIds, use "#" as initial (name?.takeIf { it.isNotBlank() } ?: id.takeIf { !it.startsWith("!") } ?: "#") .let { dn -> diff --git a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataTest.kt b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataTest.kt index 6fe50e995b..01395eb9f1 100644 --- a/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataTest.kt +++ b/libraries/designsystem/src/test/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataTest.kt @@ -14,30 +14,30 @@ class AvatarDataTest { @Test fun `initial with text should get the first char, uppercased`() { val data = AvatarData("id", "test", null, AvatarSize.InviteSender) - assertThat(data.initial).isEqualTo("T") + assertThat(data.initialLetter).isEqualTo("T") } @Test fun `initial with leading whitespace should get the first non-whitespace char, uppercased`() { val data = AvatarData("id", " test", null, AvatarSize.InviteSender) - assertThat(data.initial).isEqualTo("T") + assertThat(data.initialLetter).isEqualTo("T") } @Test fun `initial with long emoji should get the full emoji`() { val data = AvatarData("id", "\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08 Test", null, AvatarSize.InviteSender) - assertThat(data.initial).isEqualTo("\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08") + assertThat(data.initialLetter).isEqualTo("\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08") } @Test fun `initial with short emoji should get the emoji`() { val data = AvatarData("id", "✂ Test", null, AvatarSize.InviteSender) - assertThat(data.initial).isEqualTo("✂") + assertThat(data.initialLetter).isEqualTo("✂") } @Test fun `initial with a single letter should take that letter`() { val data = AvatarData("id", "T", null, AvatarSize.InviteSender) - assertThat(data.initial).isEqualTo("T") + assertThat(data.initialLetter).isEqualTo("T") } }