change (room avatar) : use TextAvatar from InitialLetterAvatar
This commit is contained in:
parent
08bbd7219c
commit
f6dc8da6ad
4 changed files with 24 additions and 44 deletions
|
|
@ -370,7 +370,7 @@ fun Modifier.avatarBloom(
|
||||||
val initialsBitmap = initialsBitmap(
|
val initialsBitmap = initialsBitmap(
|
||||||
width = BloomDefaults.ENCODE_SIZE_PX.toDp(),
|
width = BloomDefaults.ENCODE_SIZE_PX.toDp(),
|
||||||
height = BloomDefaults.ENCODE_SIZE_PX.toDp(),
|
height = BloomDefaults.ENCODE_SIZE_PX.toDp(),
|
||||||
text = avatarData.initial,
|
text = avatarData.initialLetter,
|
||||||
textColor = avatarColors.foreground,
|
textColor = avatarColors.foreground,
|
||||||
backgroundColor = avatarColors.background,
|
backgroundColor = avatarColors.background,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,9 @@
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.components.avatar
|
package io.element.android.libraries.designsystem.components.avatar
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Row
|
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.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
|
|
@ -21,21 +19,16 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.layout.ContentScale
|
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.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.AsyncImagePainter
|
||||||
import coil3.compose.SubcomposeAsyncImage
|
import coil3.compose.SubcomposeAsyncImage
|
||||||
import coil3.compose.SubcomposeAsyncImageContent
|
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.colors.AvatarColorsProvider
|
||||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
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.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -50,21 +43,18 @@ fun Avatar(
|
||||||
// If true, will show initials even if avatarData.url is not null
|
// If true, will show initials even if avatarData.url is not null
|
||||||
hideImage: Boolean = false,
|
hideImage: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val commonModifier = modifier
|
|
||||||
.size(forcedAvatarSize ?: avatarData.size.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
if (avatarData.url.isNullOrBlank() || hideImage) {
|
if (avatarData.url.isNullOrBlank() || hideImage) {
|
||||||
InitialsAvatar(
|
InitialLetterAvatar(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
forcedAvatarSize = forcedAvatarSize,
|
forcedAvatarSize = forcedAvatarSize,
|
||||||
modifier = commonModifier,
|
modifier = modifier,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ImageAvatar(
|
ImageAvatar(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
forcedAvatarSize = forcedAvatarSize,
|
forcedAvatarSize = forcedAvatarSize,
|
||||||
modifier = commonModifier,
|
modifier = modifier,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -77,11 +67,14 @@ private fun ImageAvatar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
contentDescription: String? = null,
|
contentDescription: String? = null,
|
||||||
) {
|
) {
|
||||||
|
val size = forcedAvatarSize ?: avatarData.size.dp
|
||||||
SubcomposeAsyncImage(
|
SubcomposeAsyncImage(
|
||||||
model = avatarData,
|
model = avatarData,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.requiredSize(size)
|
||||||
|
.clip(CircleShape)
|
||||||
) {
|
) {
|
||||||
val collectedState by painter.state.collectAsState()
|
val collectedState by painter.state.collectAsState()
|
||||||
when (val state = collectedState) {
|
when (val state = collectedState) {
|
||||||
|
|
@ -90,13 +83,13 @@ private fun ImageAvatar(
|
||||||
SideEffect {
|
SideEffect {
|
||||||
Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}")
|
Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}")
|
||||||
}
|
}
|
||||||
InitialsAvatar(
|
InitialLetterAvatar(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
forcedAvatarSize = forcedAvatarSize,
|
forcedAvatarSize = forcedAvatarSize,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> InitialsAvatar(
|
else -> InitialLetterAvatar(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
forcedAvatarSize = forcedAvatarSize,
|
forcedAvatarSize = forcedAvatarSize,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
|
|
@ -106,33 +99,20 @@ private fun ImageAvatar(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InitialsAvatar(
|
private fun InitialLetterAvatar(
|
||||||
avatarData: AvatarData,
|
avatarData: AvatarData,
|
||||||
forcedAvatarSize: Dp?,
|
forcedAvatarSize: Dp?,
|
||||||
contentDescription: String?,
|
contentDescription: String?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val avatarColors = AvatarColorsProvider.provide(avatarData.id)
|
val avatarColors = AvatarColorsProvider.provide(avatarData.id)
|
||||||
Box(
|
TextAvatar(
|
||||||
modifier.background(color = avatarColors.background)
|
text = avatarData.initialLetter,
|
||||||
) {
|
size = forcedAvatarSize ?: avatarData.size.dp,
|
||||||
val fontSize = (forcedAvatarSize ?: avatarData.size.dp).toSp() / 2
|
colors = avatarColors,
|
||||||
val originalFont = ElementTheme.typography.fontHeadingMdBold
|
contentDescription = contentDescription,
|
||||||
val ratio = fontSize.value / originalFont.fontSize.value
|
modifier = modifier
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(group = PreviewGroup.Avatars)
|
@Preview(group = PreviewGroup.Avatars)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ data class AvatarData(
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
val size: AvatarSize,
|
val size: AvatarSize,
|
||||||
) {
|
) {
|
||||||
val initial by lazy {
|
val initialLetter by lazy {
|
||||||
// For roomIds, use "#" as initial
|
// For roomIds, use "#" as initial
|
||||||
(name?.takeIf { it.isNotBlank() } ?: id.takeIf { !it.startsWith("!") } ?: "#")
|
(name?.takeIf { it.isNotBlank() } ?: id.takeIf { !it.startsWith("!") } ?: "#")
|
||||||
.let { dn ->
|
.let { dn ->
|
||||||
|
|
|
||||||
|
|
@ -14,30 +14,30 @@ class AvatarDataTest {
|
||||||
@Test
|
@Test
|
||||||
fun `initial with text should get the first char, uppercased`() {
|
fun `initial with text should get the first char, uppercased`() {
|
||||||
val data = AvatarData("id", "test", null, AvatarSize.InviteSender)
|
val data = AvatarData("id", "test", null, AvatarSize.InviteSender)
|
||||||
assertThat(data.initial).isEqualTo("T")
|
assertThat(data.initialLetter).isEqualTo("T")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial with leading whitespace should get the first non-whitespace char, uppercased`() {
|
fun `initial with leading whitespace should get the first non-whitespace char, uppercased`() {
|
||||||
val data = AvatarData("id", " test", null, AvatarSize.InviteSender)
|
val data = AvatarData("id", " test", null, AvatarSize.InviteSender)
|
||||||
assertThat(data.initial).isEqualTo("T")
|
assertThat(data.initialLetter).isEqualTo("T")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial with long emoji should get the full emoji`() {
|
fun `initial with long emoji should get the full emoji`() {
|
||||||
val data = AvatarData("id", "\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08 Test", null, AvatarSize.InviteSender)
|
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
|
@Test
|
||||||
fun `initial with short emoji should get the emoji`() {
|
fun `initial with short emoji should get the emoji`() {
|
||||||
val data = AvatarData("id", "✂ Test", null, AvatarSize.InviteSender)
|
val data = AvatarData("id", "✂ Test", null, AvatarSize.InviteSender)
|
||||||
assertThat(data.initial).isEqualTo("✂")
|
assertThat(data.initialLetter).isEqualTo("✂")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial with a single letter should take that letter`() {
|
fun `initial with a single letter should take that letter`() {
|
||||||
val data = AvatarData("id", "T", null, AvatarSize.InviteSender)
|
val data = AvatarData("id", "T", null, AvatarSize.InviteSender)
|
||||||
assertThat(data.initial).isEqualTo("T")
|
assertThat(data.initialLetter).isEqualTo("T")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue