change (room avatar) : use TextAvatar from InitialLetterAvatar
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user