From 08bbd7219c41a80a595598693373d83f4599ec3b Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:12:57 +0200 Subject: [PATCH 01/13] change (room avatar) : introduce TextAvatar component --- .../components/avatar/TextAvatar.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt new file mode 100644 index 0000000000..b63b99678e --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2025 New Vector 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.components.avatar + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.element.android.compound.theme.AvatarColors +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +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 + +@Composable +internal fun TextAvatar( + text: String, + size: Dp, + colors: AvatarColors, + contentDescription: String?, + modifier: Modifier = Modifier, +) { + Box( + modifier + .requiredSize(size) + .clip(CircleShape) + .background(color = colors.background) + ) { + val fontSize = size.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 = text, + style = originalFont.copy(fontSize = fontSize, lineHeight = lineHeight, letterSpacing = 0.sp), + color = colors.foreground, + ) + } +} + +@Preview(group = PreviewGroup.Avatars) +@Composable +internal fun TextAvatarPreview() = ElementPreview { + TextAvatar( + text = "AB", + size = 40.dp, + colors = AvatarColors( + background = ElementTheme.colors.bgSubtlePrimary, + foreground = ElementTheme.colors.iconPrimary, + ), + contentDescription = null, + ) + } From f6dc8da6ad78634999f6c08e4788d9fbb917c1f2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:34:55 +0200 Subject: [PATCH 02/13] change (room avatar) : use TextAvatar from InitialLetterAvatar --- .../designsystem/components/Bloom.kt | 2 +- .../designsystem/components/avatar/Avatar.kt | 54 ++++++------------- .../components/avatar/AvatarData.kt | 2 +- .../components/avatar/AvatarDataTest.kt | 10 ++-- 4 files changed, 24 insertions(+), 44 deletions(-) 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") } } From 45174030389f450c2a2b4653fa1934d8e4c896ee Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:35:47 +0200 Subject: [PATCH 03/13] change (room avatar) : use TextAvatar from TombstonedRoomAvatar --- .../components/avatar/TombstonedRoomAvatar.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt new file mode 100644 index 0000000000..9b33e7f1ed --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2025 New Vector 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.components.avatar + +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import io.element.android.compound.theme.AvatarColors +import io.element.android.compound.theme.ElementTheme + +@Composable +fun TombstonedRoomAvatar( + size: AvatarSize, + modifier: Modifier = Modifier, + contentDescription: String? = null, +) { + TextAvatar( + text = "!", + size = size.dp, + colors = AvatarColors( + background = ElementTheme.colors.bgSubtlePrimary, + foreground = ElementTheme.colors.iconTertiary + ), + modifier = modifier + .size(size.dp) + .clip(CircleShape), + contentDescription = contentDescription + ) +} From 1080417dd8898612e4042e5862bd585d95495ab4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:36:12 +0200 Subject: [PATCH 04/13] change (room avatar) : introduce RoomAvatar and AvatarCluster to replace CompositeAvatar --- .../components/avatar/AvatarCluster.kt | 127 ++++++++++++++++++ .../components/avatar/RoomAvatar.kt | 48 +++++++ 2 files changed, 175 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt new file mode 100644 index 0000000000..077e941a10 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2024 New Vector 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.components.avatar + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList +import java.util.Collections +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +private const val MAX_AVATAR_COUNT = 4 + +@Composable +fun AvatarCluster( + avatars: ImmutableList, + modifier: Modifier = Modifier, + hideAvatarImages: Boolean = false, + contentDescription: String? = null, +) { + val limitedAvatars = avatars.take(MAX_AVATAR_COUNT) + val numberOfAvatars = limitedAvatars.size + if (numberOfAvatars == 4) { + // Swap 2 and 3 so that the 4th avatar is at the bottom right + Collections.swap(limitedAvatars, 2, 3) + } + when (numberOfAvatars) { + 0 -> { + error("Unsupported number of avatars: 0") + } + 1 -> { + Avatar( + avatarData = limitedAvatars[0], + modifier = modifier, + contentDescription = contentDescription, + hideImage = hideAvatarImages + ) + } + else -> { + val size = limitedAvatars.first().size + val angle = 2 * Math.PI / numberOfAvatars + val offsetRadius = when (numberOfAvatars) { + 2 -> size.dp.value / 4.2 + 3 -> size.dp.value / 4.0 + 4 -> size.dp.value / 3.1 + else -> error("Unsupported number of heroes: $numberOfAvatars") + } + val heroAvatarSize = when (numberOfAvatars) { + 2 -> size.dp / 2.2f + 3 -> size.dp / 2.4f + 4 -> size.dp / 2.2f + else -> error("Unsupported number of heroes: $numberOfAvatars") + } + val angleOffset = when (numberOfAvatars) { + 2 -> PI + 3 -> 7 * PI / 6 + 4 -> 13 * PI / 4 + else -> error("Unsupported number of heroes: $numberOfAvatars") + } + Box( + modifier = modifier + .size(size.dp) + .semantics { + this.contentDescription = contentDescription.orEmpty() + }, + contentAlignment = Alignment.Center, + ) { + limitedAvatars.forEachIndexed { index, heroAvatar -> + val xOffset = (offsetRadius * cos(angle * index.toDouble() + angleOffset)).dp + val yOffset = (offsetRadius * sin(angle * index.toDouble() + angleOffset)).dp + Box( + modifier = Modifier + .size(heroAvatarSize) + .offset( + x = xOffset, + y = yOffset, + ) + ) { + Avatar( + avatarData = heroAvatar, + forcedAvatarSize = heroAvatarSize, + hideImage = hideAvatarImages, + ) + } + } + } + } + } +} + +@Preview(group = PreviewGroup.Avatars) +@Composable +internal fun AvatarClusterPreview() = ElementThemedPreview { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + repeat(6) { ngOfAvatars -> + AvatarCluster( + avatars = List(ngOfAvatars) { anAvatarData(it) }.toPersistentList(), + ) + } + } +} + +private fun anAvatarData(i: Int) = anAvatarData( + id = ('A' + i).toString(), + name = ('A' + i).toString() +) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt new file mode 100644 index 0000000000..1c618a54f0 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2025 New Vector 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.components.avatar + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import kotlinx.collections.immutable.ImmutableList + +@Composable +fun RoomAvatar( + avatarData: AvatarData, + heroes: ImmutableList, + modifier: Modifier = Modifier, + isTombstoned: Boolean = false, + hideAvatarImage: Boolean = false, + contentDescription: String? = null, +) { + when { + isTombstoned -> { + TombstonedRoomAvatar( + size = avatarData.size, + modifier = modifier, + contentDescription = contentDescription + ) + } + avatarData.url != null || heroes.isEmpty() -> { + Avatar( + avatarData = avatarData, + modifier = modifier, + contentDescription = contentDescription, + hideImage = hideAvatarImage + ) + } + else -> { + AvatarCluster( + avatars = heroes, + modifier = modifier, + hideAvatarImages = hideAvatarImage, + contentDescription = contentDescription + ) + } + } +} From d6be3b5a1daeede42803cadcb5d4c980c99c5fad Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:39:51 +0200 Subject: [PATCH 05/13] change (room avatar) : expose isTombstone where we need to display the RoomAvatar --- .../android/features/messages/impl/MessagesState.kt | 4 +++- .../features/roomdetails/impl/RoomDetailsPresenter.kt | 1 + .../android/features/roomdetails/impl/RoomDetailsState.kt | 1 + .../features/roomdetails/impl/RoomDetailsStateProvider.kt | 3 +++ .../roomlist/impl/datasource/RoomListRoomSummaryFactory.kt | 1 + .../features/roomlist/impl/model/RoomListRoomSummary.kt | 1 + .../roomlist/impl/model/RoomListRoomSummaryProvider.kt | 7 +++++++ .../roomlist/impl/model/RoomListBaseRoomSummaryTest.kt | 2 ++ .../matrix/ui/components/SelectRoomInfoProvider.kt | 2 ++ .../android/libraries/matrix/ui/model/SelectRoomInfo.kt | 2 ++ 10 files changed, 23 insertions(+), 1 deletion(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 5247cb2f5e..e838757aee 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -59,4 +59,6 @@ data class MessagesState( val roomMemberModerationState: RoomMemberModerationState, val successorRoom: SuccessorRoom?, val eventSink: (MessagesEvents) -> Unit -) +){ + val isTombstoned = successorRoom != null +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 505751c412..3e34f8d62b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -210,6 +210,7 @@ class RoomDetailsPresenter @Inject constructor( canShowSecurityAndPrivacy = canShowSecurityAndPrivacy, hasMemberVerificationViolations = hasMemberVerificationViolations, canReportRoom = canReportRoom, + isTombstoned = roomInfo.successorRoom != null, eventSink = ::handleEvents, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 5addbf3db8..e589eaaed9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -49,6 +49,7 @@ data class RoomDetailsState( val canShowSecurityAndPrivacy: Boolean, val hasMemberVerificationViolations: Boolean, val canReportRoom: Boolean, + val isTombstoned: Boolean, val eventSink: (RoomDetailsEvent) -> Unit ) { val roomBadges = buildList { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index d2cb4c5944..64f9976841 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -53,6 +53,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider aRoomDetailsState(knockRequestsCount = null, canShowKnockRequests = true), aRoomDetailsState(knockRequestsCount = 4, canShowKnockRequests = true), aRoomDetailsState(hasMemberVerificationViolations = true), + aRoomDetailsState(isTombstoned = true), aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFIED), aDmRoomDetailsState(dmRoomMemberVerificationState = UserProfileVerificationState.VERIFICATION_VIOLATION), // Add other state here @@ -118,6 +119,7 @@ fun aRoomDetailsState( canShowSecurityAndPrivacy: Boolean = true, hasMemberVerificationViolations: Boolean = false, canReportRoom: Boolean = true, + isTombstoned: Boolean = false, eventSink: (RoomDetailsEvent) -> Unit = {}, ) = RoomDetailsState( roomId = roomId, @@ -148,6 +150,7 @@ fun aRoomDetailsState( canShowSecurityAndPrivacy = canShowSecurityAndPrivacy, hasMemberVerificationViolations = hasMemberVerificationViolations, canReportRoom = canReportRoom, + isTombstoned = isTombstoned, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt index d1581f545b..98b7f8d853 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/RoomListRoomSummaryFactory.kt @@ -67,6 +67,7 @@ class RoomListRoomSummaryFactory @Inject constructor( heroes = roomInfo.heroes.map { user -> user.getAvatarData(size = AvatarSize.RoomListItem) }.toImmutableList(), + isTombstoned = roomInfo.successorRoom != null, ) } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt index a1c1156c2a..4b2c1e7afc 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt @@ -36,6 +36,7 @@ data class RoomListRoomSummary( val isDm: Boolean, val isFavorite: Boolean, val inviteSender: InviteSender?, + val isTombstoned: Boolean, val heroes: ImmutableList, ) { val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE && diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt index e71c8849b7..111c4bc86b 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummaryProvider.kt @@ -110,6 +110,11 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider = emptyList(), + isTombstoned: Boolean = false, ) = RoomListRoomSummary( id = id, roomId = RoomId(id), @@ -165,4 +171,5 @@ internal fun aRoomListRoomSummary( displayType = displayType, canonicalAlias = canonicalAlias, heroes = heroes.toImmutableList(), + isTombstoned = isTombstoned, ) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListBaseRoomSummaryTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListBaseRoomSummaryTest.kt index 4d5ef13d0b..e6bfb1a9cb 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListBaseRoomSummaryTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/model/RoomListBaseRoomSummaryTest.kt @@ -84,6 +84,7 @@ internal fun createRoomListRoomSummary( displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM, heroes: List = emptyList(), timestamp: String? = null, + isTombstoned: Boolean = false, ) = RoomListRoomSummary( id = A_ROOM_ID.value, roomId = A_ROOM_ID, @@ -104,4 +105,5 @@ internal fun createRoomListRoomSummary( inviteSender = null, isDm = false, heroes = heroes.toPersistentList(), + isTombstoned = isTombstoned, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt index 6d85cc28cb..f6ad30fae1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt @@ -30,10 +30,12 @@ fun aSelectRoomInfo( canonicalAlias: RoomAlias? = null, avatarUrl: String? = null, heroes: ImmutableList = persistentListOf(), + isTombstoned: Boolean = false, ) = SelectRoomInfo( roomId = roomId, name = name, canonicalAlias = canonicalAlias, avatarUrl = avatarUrl, heroes = heroes, + isTombstoned = isTombstoned, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt index 8ca5dcd326..63a0b1cc77 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt @@ -21,6 +21,7 @@ data class SelectRoomInfo( val canonicalAlias: RoomAlias?, val avatarUrl: String?, val heroes: ImmutableList, + val isTombstoned: Boolean, ) { fun getAvatarData(size: AvatarSize) = AvatarData( id = roomId.value, @@ -36,4 +37,5 @@ fun RoomSummary.toSelectRoomInfo() = SelectRoomInfo( avatarUrl = info.avatarUrl, heroes = info.heroes, canonicalAlias = info.canonicalAlias, + isTombstoned = info.successorRoom != null, ) From a5145279dadd67eb80a70c07da42eb65c9169408 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:40:22 +0200 Subject: [PATCH 06/13] change (room avatar) : use the new RoomAvatar with the isTombstoned param --- .../features/messages/impl/MessagesView.kt | 9 ++++++-- .../suggestions/SuggestionsPickerView.kt | 5 +++-- .../messages/impl/MessagesViewTest.kt | 2 +- .../EditDefaultNotificationSettingView.kt | 4 ++-- .../roomdetails/impl/RoomDetailsView.kt | 7 ++++-- .../impl/components/RoomSummaryRow.kt | 22 +++++++++++-------- .../matrix/ui/components/SelectedRoom.kt | 5 +++-- .../roomselect/impl/RoomSelectView.kt | 7 +++--- 8 files changed, 38 insertions(+), 23 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 02e9f77768..51b03c4a3c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -86,7 +86,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertM import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview @@ -196,6 +196,7 @@ fun MessagesView( MessagesViewTopBar( roomName = state.roomName.dataOrNull(), roomAvatar = state.roomAvatar.dataOrNull(), + isTombstoned = state.isTombstoned, heroes = state.heroes, roomCallState = state.roomCallState, dmUserIdentityState = state.dmUserVerificationState, @@ -492,6 +493,7 @@ private fun MessagesViewComposerBottomSheetContents( private fun MessagesViewTopBar( roomName: String?, roomAvatar: AvatarData?, + isTombstoned: Boolean, heroes: ImmutableList, roomCallState: RoomCallState, dmUserIdentityState: IdentityState?, @@ -517,6 +519,7 @@ private fun MessagesViewTopBar( RoomAvatarAndNameRow( roomName = roomName, roomAvatar = roomAvatar, + isTombstoned = isTombstoned, heroes = heroes, modifier = titleModifier ) @@ -562,15 +565,17 @@ private fun RoomAvatarAndNameRow( roomName: String, roomAvatar: AvatarData, heroes: ImmutableList, + isTombstoned: Boolean, modifier: Modifier = Modifier ) { Row( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { - CompositeAvatar( + RoomAvatar( avatarData = roomAvatar, heroes = heroes, + isTombstoned = isTombstoned, ) Text( modifier = Modifier.padding(horizontal = 8.dp), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt index 9d774d5841..cc8f536f5f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.R import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.anAvatarData import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.HorizontalDivider @@ -45,7 +46,7 @@ import kotlinx.collections.immutable.persistentListOf fun SuggestionsPickerView( roomId: RoomId, roomName: String?, - roomAvatarData: AvatarData?, + roomAvatarData: AvatarData, suggestions: ImmutableList, onSelectSuggestion: (ResolvedSuggestion) -> Unit, modifier: Modifier = Modifier, @@ -155,7 +156,7 @@ internal fun SuggestionsPickerViewPreview() { SuggestionsPickerView( roomId = RoomId("!room:matrix.org"), roomName = "Room", - roomAvatarData = null, + roomAvatarData = anAvatarData(), suggestions = persistentListOf( ResolvedSuggestion.AtRoom, ResolvedSuggestion.Member(roomMember), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 5ff6b7e874..51f5640ece 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -110,7 +110,7 @@ class MessagesViewTest { state = state, onRoomDetailsClick = callback, ) - rule.onNodeWithText(state.roomName.dataOrNull().orEmpty(), useUnmergedTree = true).performClick() + rule.onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick() } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index 6a94670c7d..b9a001e812 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.preferences.impl.R import io.element.android.libraries.designsystem.components.async.AsyncActionView -import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferencePage @@ -97,7 +97,7 @@ fun EditDefaultNotificationSettingView( Text(text = subtitle) }, leadingContent = ListItemContent.Custom { - CompositeAvatar( + RoomAvatar( avatarData = summary.avatarData, heroes = summary.heroesAvatar, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 8f7502dee5..e7fb070424 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -48,7 +48,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRow import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.avatar.DmAvatars import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.MainActionButton @@ -138,6 +138,7 @@ fun RoomDetailsView( roomName = state.roomName, roomAlias = state.roomAlias, heroes = state.heroes, + isTombstoned = state.isTombstoned, openAvatarPreview = { avatarUrl -> openAvatarPreview(state.roomName, avatarUrl) }, @@ -380,6 +381,7 @@ private fun RoomHeaderSection( roomName: String, roomAlias: RoomAlias?, heroes: ImmutableList, + isTombstoned: Boolean, openAvatarPreview: (url: String) -> Unit, onSubtitleClick: (String) -> Unit, ) { @@ -389,11 +391,12 @@ private fun RoomHeaderSection( .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - CompositeAvatar( + RoomAvatar( avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader), heroes = heroes.map { user -> user.getAvatarData(size = AvatarSize.RoomHeader) }.toPersistentList(), + isTombstoned = isTombstoned, modifier = Modifier .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .testTag(TestTags.roomDetailAvatar) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index b1c959d86d..1be57d631a 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -44,7 +44,7 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvid import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType import io.element.android.libraries.core.extensions.orEmpty import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom -import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button @@ -121,7 +121,7 @@ internal fun RoomSummaryRow( timestamp = room.timestamp, isHighlighted = room.isHighlighted ) - LastMessageAndIndicatorRow(room = room) + MessagePreviewAndIndicatorRow(room = room) } } RoomSummaryDisplayType.KNOCKED -> { @@ -184,10 +184,11 @@ private fun RoomSummaryScaffoldRow( .padding(horizontal = 16.dp, vertical = 11.dp) .height(IntrinsicSize.Min), ) { - CompositeAvatar( + RoomAvatar( avatarData = room.avatarData, heroes = room.heroes, - hideAvatarImages = hideAvatarImage, + isTombstoned = room.isTombstoned, + hideAvatarImage = hideAvatarImage, ) Spacer(modifier = Modifier.width(16.dp)) Column( @@ -255,7 +256,7 @@ private fun InviteSubtitle( } @Composable -private fun LastMessageAndIndicatorRow( +private fun MessagePreviewAndIndicatorRow( room: RoomListRoomSummary, modifier: Modifier = Modifier, ) { @@ -263,12 +264,15 @@ private fun LastMessageAndIndicatorRow( modifier = modifier.fillMaxWidth(), horizontalArrangement = spacedBy(28.dp) ) { - // Last Message - val attributedLastMessage = room.lastMessage as? AnnotatedString - ?: AnnotatedString(room.lastMessage.orEmpty().toString()) + val messagePreview = if (room.isTombstoned) { + stringResource(R.string.screen_roomlist_tombstoned_room_description) + } else { + room.lastMessage.orEmpty() + } + val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.toString()) Text( modifier = Modifier.weight(1f), - text = attributedLastMessage, + text = annotatedMessagePreview, color = ElementTheme.roomListRoomMessage(), style = ElementTheme.typography.fontBodyMdRegular, minLines = 2, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt index 11f6b74b95..06cead3526 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt @@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -53,9 +53,10 @@ fun SelectedRoom( Column( horizontalAlignment = Alignment.CenterHorizontally, ) { - CompositeAvatar( + RoomAvatar( avatarData = roomInfo.getAvatarData(AvatarSize.SelectedRoom), heroes = roomInfo.heroes.map { it.getAvatarData(AvatarSize.SelectedRoom) }.toImmutableList(), + isTombstoned = roomInfo.isTombstoned, ) Text( // If name is null, we do not have space to render "No room name", so just use `#` here. diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt index 1f13346b3a..10769b9aec 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -214,11 +214,12 @@ private fun RoomSummaryView( .heightIn(56.dp), verticalAlignment = Alignment.CenterVertically ) { - CompositeAvatar( + RoomAvatar( avatarData = roomInfo.getAvatarData(size = AvatarSize.RoomSelectRoomListItem), heroes = roomInfo.heroes.map { user -> user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem) - }.toPersistentList() + }.toPersistentList(), + isTombstoned = roomInfo.isTombstoned, ) Column( modifier = Modifier From 935a715cf6620620065c9dda6164a2477cdff092 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Jun 2025 18:40:33 +0200 Subject: [PATCH 07/13] change (room avatar) : delete CompositeAvatar --- .../components/avatar/CompositeAvatar.kt | 140 ------------------ 1 file changed, 140 deletions(-) delete mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt deleted file mode 100644 index c696050f23..0000000000 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2024 New Vector 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.components.avatar - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.ElementThemedPreview -import io.element.android.libraries.designsystem.preview.PreviewGroup -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toPersistentList -import java.util.Collections -import kotlin.math.PI -import kotlin.math.cos -import kotlin.math.sin - -@Composable -fun CompositeAvatar( - avatarData: AvatarData, - heroes: ImmutableList, - modifier: Modifier = Modifier, - hideAvatarImages: Boolean = false, - contentDescription: String? = null, -) { - if (avatarData.url != null || heroes.isEmpty()) { - Avatar( - avatarData = avatarData, - modifier = modifier, - contentDescription = contentDescription, - hideImage = hideAvatarImages - ) - } else { - val limitedHeroes = heroes.take(4) - val numberOfHeroes = limitedHeroes.size - if (numberOfHeroes == 4) { - // Swap 2 and 3 so that the 4th hero is at the bottom right - Collections.swap(limitedHeroes, 2, 3) - } - when (numberOfHeroes) { - 0 -> { - error("Unsupported number of heroes: 0") - } - 1 -> { - Avatar( - avatarData = heroes[0], - modifier = modifier, - contentDescription = contentDescription, - hideImage = hideAvatarImages - ) - } - else -> { - val angle = 2 * Math.PI / numberOfHeroes - val offsetRadius = when (numberOfHeroes) { - 2 -> avatarData.size.dp.value / 4.2 - 3 -> avatarData.size.dp.value / 4.0 - 4 -> avatarData.size.dp.value / 3.1 - else -> error("Unsupported number of heroes: $numberOfHeroes") - } - val heroAvatarSize = when (numberOfHeroes) { - 2 -> avatarData.size.dp / 2.2f - 3 -> avatarData.size.dp / 2.4f - 4 -> avatarData.size.dp / 2.2f - else -> error("Unsupported number of heroes: $numberOfHeroes") - } - val angleOffset = when (numberOfHeroes) { - 2 -> PI - 3 -> 7 * PI / 6 - 4 -> 13 * PI / 4 - else -> error("Unsupported number of heroes: $numberOfHeroes") - } - Box( - modifier = modifier - .size(avatarData.size.dp) - .semantics { - this.contentDescription = contentDescription.orEmpty() - }, - contentAlignment = Alignment.Center, - ) { - limitedHeroes.forEachIndexed { index, heroAvatar -> - val xOffset = (offsetRadius * cos(angle * index.toDouble() + angleOffset)).dp - val yOffset = (offsetRadius * sin(angle * index.toDouble() + angleOffset)).dp - Box( - modifier = Modifier - .size(heroAvatarSize) - .offset( - x = xOffset, - y = yOffset, - ) - ) { - Avatar( - avatarData = heroAvatar, - forcedAvatarSize = heroAvatarSize, - hideImage = hideAvatarImages, - ) - } - } - } - } - } - } -} - -@Preview(group = PreviewGroup.Avatars) -@Composable -internal fun CompositeAvatarPreview() = ElementThemedPreview { - val mainAvatar = anAvatarData( - id = "Zac", - name = "Zac", - size = AvatarSize.RoomListItem, - ) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - repeat(6) { nbOfHeroes -> - CompositeAvatar( - avatarData = mainAvatar, - heroes = List(nbOfHeroes) { aHeroAvatarData(it) }.toPersistentList(), - ) - } - } -} - -private fun aHeroAvatarData(i: Int) = anAvatarData( - id = ('A' + i).toString(), - name = ('A' + i).toString() -) From f3614ab67e27eefee88c257cff3c347bf10155bf Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 17 Jun 2025 20:37:41 +0200 Subject: [PATCH 08/13] change (room avatar) : clean code related to room name/avatar --- .../messages/impl/MessagesPresenter.kt | 11 ++--- .../features/messages/impl/MessagesState.kt | 6 +-- .../messages/impl/MessagesStateProvider.kt | 13 +++--- .../features/messages/impl/MessagesView.kt | 44 ++++++++----------- .../roomdetails/impl/RoomDetailsView.kt | 2 +- .../components/avatar/RoomAvatar.kt | 2 +- 6 files changed, 32 insertions(+), 46 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index aa0d55e29b..3390b55d5e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -152,11 +152,8 @@ class MessagesPresenter @AssistedInject constructor( val userEventPermissions by userEventPermissions(syncUpdateFlow.value) - val roomName: AsyncData by remember { - derivedStateOf { roomInfo.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } - } - val roomAvatar: AsyncData by remember { - derivedStateOf { AsyncData.Success(roomInfo.avatarData()) } + val roomAvatar by remember { + derivedStateOf { roomInfo.avatarData() } } val heroes by remember { derivedStateOf { roomInfo.heroes().toPersistentList() } @@ -245,7 +242,7 @@ class MessagesPresenter @AssistedInject constructor( return MessagesState( roomId = room.roomId, - roomName = roomName, + roomName = roomInfo.name, roomAvatar = roomAvatar, heroes = heroes, composerState = composerState, @@ -292,7 +289,7 @@ class MessagesPresenter @AssistedInject constructor( return AvatarData( id = id.value, name = name, - url = avatarUrl ?: room.info().avatarUrl, + url = avatarUrl, size = AvatarSize.TimelineRoom ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index e838757aee..bedce147dc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -32,8 +32,8 @@ import kotlinx.collections.immutable.ImmutableList @Immutable data class MessagesState( val roomId: RoomId, - val roomName: AsyncData, - val roomAvatar: AsyncData, + val roomName: String?, + val roomAvatar: AvatarData, val heroes: ImmutableList, val userEventPermissions: UserEventPermissions, val composerState: MessageComposerState, @@ -59,6 +59,6 @@ data class MessagesState( val roomMemberModerationState: RoomMemberModerationState, val successorRoom: SuccessorRoom?, val eventSink: (MessagesEvents) -> Unit -){ +) { val isTombstoned = successorRoom != null } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 62dd6a15d7..8fa539dd94 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -58,10 +58,7 @@ open class MessagesStateProvider : PreviewParameterProvider { aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)), aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)), aMessagesState(showReinvitePrompt = true), - aMessagesState( - roomName = AsyncData.Uninitialized, - roomAvatar = AsyncData.Uninitialized, - ), + aMessagesState(roomName = null), aMessagesState(composerState = aMessageComposerState(showTextFormatting = true)), aMessagesState( enableVoiceMessages = true, @@ -86,15 +83,15 @@ open class MessagesStateProvider : PreviewParameterProvider { currentPinnedMessageIndex = 0, ), ), - aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.Verified), - aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.VerificationViolation), + aMessagesState(roomName = "A DM with a very looong name", dmUserVerificationState = IdentityState.Verified), + aMessagesState(roomName = "A DM with a very looong name", dmUserVerificationState = IdentityState.VerificationViolation), aMessagesState(successorRoom = SuccessorRoom(RoomId("!id:domain"), null)), ) } fun aMessagesState( - roomName: AsyncData = AsyncData.Success("Room name"), - roomAvatar: AsyncData = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)), + roomName: String? = "Room name", + roomAvatar: AvatarData = AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom), userEventPermissions: UserEventPermissions = aUserEventPermissions(), composerState: MessageComposerState = aMessageComposerState( textEditorState = aTextEditorStateRich(initialText = "Hello", initialFocus = true), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 51b03c4a3c..a772015787 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -83,9 +83,7 @@ import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorVi import io.element.android.features.roomcall.api.RoomCallState import io.element.android.libraries.androidutils.ui.hideKeyboard import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule -import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog @@ -194,8 +192,8 @@ fun MessagesView( Column { ConnectivityIndicatorView(isOnline = state.hasNetworkConnection) MessagesViewTopBar( - roomName = state.roomName.dataOrNull(), - roomAvatar = state.roomAvatar.dataOrNull(), + roomName = state.roomName, + roomAvatar = state.roomAvatar, isTombstoned = state.isTombstoned, heroes = state.heroes, roomCallState = state.roomCallState, @@ -451,8 +449,8 @@ private fun MessagesViewComposerBottomSheetContents( } }), roomId = state.roomId, - roomName = state.roomName.dataOrNull(), - roomAvatarData = state.roomAvatar.dataOrNull(), + roomName = state.roomName, + roomAvatarData = state.roomAvatar, suggestions = state.composerState.suggestions, onSelectSuggestion = { state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it)) @@ -492,7 +490,7 @@ private fun MessagesViewComposerBottomSheetContents( @Composable private fun MessagesViewTopBar( roomName: String?, - roomAvatar: AvatarData?, + roomAvatar: AvatarData, isTombstoned: Boolean, heroes: ImmutableList, roomCallState: RoomCallState, @@ -515,20 +513,13 @@ private fun MessagesViewTopBar( verticalAlignment = Alignment.CenterVertically, ) { val titleModifier = Modifier.weight(1f, fill = false) - if (roomName != null && roomAvatar != null) { - RoomAvatarAndNameRow( - roomName = roomName, - roomAvatar = roomAvatar, - isTombstoned = isTombstoned, - heroes = heroes, - modifier = titleModifier - ) - } else { - IconTitlePlaceholdersRowMolecule( - iconSize = AvatarSize.TimelineRoom.dp, - modifier = titleModifier - ) - } + RoomAvatarAndNameRow( + roomName = roomName, + roomAvatar = roomAvatar, + isTombstoned = isTombstoned, + heroes = heroes, + modifier = titleModifier + ) when (dmUserIdentityState) { IdentityState.Verified -> { @@ -562,7 +553,7 @@ private fun MessagesViewTopBar( @Composable private fun RoomAvatarAndNameRow( - roomName: String, + roomName: String?, roomAvatar: AvatarData, heroes: ImmutableList, isTombstoned: Boolean, @@ -579,8 +570,9 @@ private fun RoomAvatarAndNameRow( ) Text( modifier = Modifier.padding(horizontal = 8.dp), - text = roomName, + text = roomName ?: stringResource(CommonStrings.common_no_room_name), style = ElementTheme.typography.fontBodyLgMedium, + fontStyle = FontStyle.Italic.takeIf { roomName == null }, maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -591,9 +583,9 @@ private fun RoomAvatarAndNameRow( private fun CantSendMessageBanner() { Row( modifier = Modifier - .fillMaxWidth() - .background(ElementTheme.colors.bgSubtleSecondary) - .padding(16.dp), + .fillMaxWidth() + .background(ElementTheme.colors.bgSubtleSecondary) + .padding(16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index e7fb070424..254c31023d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -48,8 +48,8 @@ import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRow import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.avatar.DmAvatars +import io.element.android.libraries.designsystem.components.avatar.RoomAvatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.MainActionButton import io.element.android.libraries.designsystem.components.list.ListItemContent diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt index 1c618a54f0..44d1c9b491 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/RoomAvatar.kt @@ -20,7 +20,7 @@ fun RoomAvatar( hideAvatarImage: Boolean = false, contentDescription: String? = null, ) { - when { + when { isTombstoned -> { TombstonedRoomAvatar( size = avatarData.size, From da371797d3a7c839f4d67d765c5db6528c7ac9a3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Jun 2025 12:29:47 +0200 Subject: [PATCH 09/13] change (room avatar) : add preview for TombstonedRoomAvatar --- .../components/avatar/TombstonedRoomAvatar.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt index 9b33e7f1ed..783f65fa5a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt @@ -12,8 +12,12 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import io.element.android.compound.theme.AvatarColors import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewGroup @Composable fun TombstonedRoomAvatar( @@ -34,3 +38,12 @@ fun TombstonedRoomAvatar( contentDescription = contentDescription ) } + +@Preview(group = PreviewGroup.Avatars) +@Composable +internal fun TombstonedRoomAvatarPreview() = ElementPreview { + TombstonedRoomAvatar( + size = AvatarSize.RoomListItem, + contentDescription = null, + ) +} From 21129d0c0bb653709030ddd7993fc7c6d08917f4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Jun 2025 12:53:27 +0200 Subject: [PATCH 10/13] change (room avatar) : fix AvatarClusterPreview --- .../components/avatar/AvatarCluster.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt index 077e941a10..9c7efa864d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarCluster.kt @@ -78,10 +78,10 @@ fun AvatarCluster( } Box( modifier = modifier - .size(size.dp) - .semantics { - this.contentDescription = contentDescription.orEmpty() - }, + .size(size.dp) + .semantics { + this.contentDescription = contentDescription.orEmpty() + }, contentAlignment = Alignment.Center, ) { limitedAvatars.forEachIndexed { index, heroAvatar -> @@ -89,11 +89,11 @@ fun AvatarCluster( val yOffset = (offsetRadius * sin(angle * index.toDouble() + angleOffset)).dp Box( modifier = Modifier - .size(heroAvatarSize) - .offset( - x = xOffset, - y = yOffset, - ) + .size(heroAvatarSize) + .offset( + x = xOffset, + y = yOffset, + ) ) { Avatar( avatarData = heroAvatar, @@ -113,7 +113,7 @@ internal fun AvatarClusterPreview() = ElementThemedPreview { Row( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - repeat(6) { ngOfAvatars -> + for (ngOfAvatars in 1..5) { AvatarCluster( avatars = List(ngOfAvatars) { anAvatarData(it) }.toPersistentList(), ) From 438a22bcec45b0303aaaffbcf8ef20638f61d9af Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Jun 2025 13:39:46 +0200 Subject: [PATCH 11/13] change (room avatar) : replace modifier.requiredSize by modifier.size --- .../libraries/designsystem/components/avatar/Avatar.kt | 4 ++-- .../designsystem/components/avatar/TextAvatar.kt | 4 ++-- .../components/avatar/TombstonedRoomAvatar.kt | 8 +------- 3 files changed, 5 insertions(+), 11 deletions(-) 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 6b69a9dfec..106e52d350 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 @@ -9,7 +9,7 @@ package io.element.android.libraries.designsystem.components.avatar import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -73,7 +73,7 @@ private fun ImageAvatar( contentDescription = contentDescription, contentScale = ContentScale.Crop, modifier = modifier - .requiredSize(size) + .size(size) .clip(CircleShape) ) { val collectedState by painter.state.collectAsState() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt index b63b99678e..65ebba3366 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TextAvatar.kt @@ -9,7 +9,7 @@ package io.element.android.libraries.designsystem.components.avatar import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -38,7 +38,7 @@ internal fun TextAvatar( ) { Box( modifier - .requiredSize(size) + .size(size) .clip(CircleShape) .background(color = colors.background) ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt index 783f65fa5a..fe09be909d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/TombstonedRoomAvatar.kt @@ -7,13 +7,9 @@ package io.element.android.libraries.designsystem.components.avatar -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import io.element.android.compound.theme.AvatarColors import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview @@ -32,9 +28,7 @@ fun TombstonedRoomAvatar( background = ElementTheme.colors.bgSubtlePrimary, foreground = ElementTheme.colors.iconTertiary ), - modifier = modifier - .size(size.dp) - .clip(CircleShape), + modifier = modifier, contentDescription = contentDescription ) } From 5b448dd1918f1618e12e8ec4b434c3eab0107ef1 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 18 Jun 2025 11:50:44 +0000 Subject: [PATCH 12/13] Update screenshots --- ...agecomposer.suggestions_SuggestionsPickerView_Day_0_en.png | 4 ++-- ...ecomposer.suggestions_SuggestionsPickerView_Night_0_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_14_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Day_5_en.png | 4 ++-- .../features.messages.impl_MessagesView_Night_14_en.png | 4 ++-- .../images/features.messages.impl_MessagesView_Night_5_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_17_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_18_en.png | 4 ++-- .../features.roomdetails.impl_RoomDetailsDark_19_en.png | 3 +++ .../images/features.roomdetails.impl_RoomDetails_17_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_18_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_19_en.png | 3 +++ ...ures.roomlist.impl.components_RoomSummaryRow_Day_34_en.png | 3 +++ ...es.roomlist.impl.components_RoomSummaryRow_Night_34_en.png | 3 +++ ...esignsystem.components.avatar_AvatarCluster_Avatars_en.png | 3 +++ ...ignsystem.components.avatar_CompositeAvatar_Avatars_en.png | 3 --- ...s.designsystem.components.avatar_TextAvatar_Avatars_en.png | 3 +++ ...stem.components.avatar_TombstonedRoomAvatar_Avatars_en.png | 3 +++ 18 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_19_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_19_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_34_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_34_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_AvatarCluster_Avatars_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TextAvatar_Avatars_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TombstonedRoomAvatar_Avatars_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png index 052885e97e..8b6135c4fa 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19f6f6dcdf49e9ffd2fbb0ea81c3fe0374faf79cecccc812ea1e1d8c05578671 -size 22344 +oid sha256:8823f1cca4a240779ade39486854332689d114efe2d2213a9ab0720113c7f04b +size 22353 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png index 1e43118d49..226dc55779 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12266ee1fc3477aec0f47e731294ccd2f93ecf4a3a1fada6cf4cb8ba6f7430ac -size 22661 +oid sha256:49916050cd93d9b4aa4a90aa3ecd19bec6095e8b7cb472a3543867c0ce6f9ffa +size 22783 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_14_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_14_en.png index c766a093d8..38e26d3deb 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79e6ab4ac13eeb03cb02843a122b519a6410aeaad40e5f272d0b09f2547af6e7 -size 62973 +oid sha256:45ead771fc133b1c992d090f35c93f0de4166b52966b818d8a401e75e5b63caf +size 62982 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png index dbd685291f..5b6906e54a 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62475d0e6b37cff42980135438faeccebe74bc110f517c12616351be8469c448 -size 55208 +oid sha256:4493799d889522a540435c10f0bf3642a438c38d6b219491afff4a72a169ce1c +size 58416 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_14_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_14_en.png index b21fe7cf0b..2b750a8c5c 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3241f342c62665d1a343d085ec9174e0e6555219ce51e74f4199bea74f641183 -size 65429 +oid sha256:5864ca4671dee1caef50d0444ffdeb0c7d2a5d9ed28ebc64937efbc469f6031a +size 65288 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png index 012203b274..e58339319e 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4209f327f3a7cb3fbb0de25230604857e9d0e39b7ca0b2bec948656f08b2e08 -size 54804 +oid sha256:b44213fdccb6b194466d3daa7474b77378f96ab9b35559c0ba392dde49ecf175 +size 58055 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png index 4e7871b133..d3792f6a0a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60571dc08867db9ad2f6528e95cc65df0eb57b9840bf8e96e6e1fbd802ad3a26 -size 38778 +oid sha256:f3cc4924fbbb2fea5937bf481b1322da14e1d2c8ea2cf4351fd6d0d222357738 +size 41304 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png index fcb6f77177..4e7871b133 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69d9aaf46305802861df8be0341abd73068376893bfd7aef43d7b8f3251350dd -size 38735 +oid sha256:60571dc08867db9ad2f6528e95cc65df0eb57b9840bf8e96e6e1fbd802ad3a26 +size 38778 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_19_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_19_en.png new file mode 100644 index 0000000000..fcb6f77177 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_19_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69d9aaf46305802861df8be0341abd73068376893bfd7aef43d7b8f3251350dd +size 38735 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png index e60ef55c42..c9734faef5 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_17_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cabfc7c5bf6f9b612e933cad211b8261b236cd981488cfa081e0226940626339 -size 39499 +oid sha256:63c75fdca8960f015caaa11bbb970c6e7621e87afbe7761d23db875664c55cd1 +size 42360 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png index 2e3298b2ff..e60ef55c42 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf96db08ac0382185c34863b38603fe55eb3aab691fa3c42b6d73e4167ce8f82 -size 39383 +oid sha256:cabfc7c5bf6f9b612e933cad211b8261b236cd981488cfa081e0226940626339 +size 39499 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_19_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_19_en.png new file mode 100644 index 0000000000..2e3298b2ff --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_19_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf96db08ac0382185c34863b38603fe55eb3aab691fa3c42b6d73e4167ce8f82 +size 39383 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_34_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_34_en.png new file mode 100644 index 0000000000..dd6fcb47f4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Day_34_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:26ec46ce726cb5a36accb280cc019a1a93d065bc6f6c74027d42efb4f7709f6a +size 14566 diff --git a/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_34_en.png b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_34_en.png new file mode 100644 index 0000000000..b66b1007fa --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomlist.impl.components_RoomSummaryRow_Night_34_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc7cf18c82801dd7aea6383a0f17fdbba11e92f9e4cead38ac609d7a2238837f +size 14203 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_AvatarCluster_Avatars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_AvatarCluster_Avatars_en.png new file mode 100644 index 0000000000..72b14d9cc3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_AvatarCluster_Avatars_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6afcae51ee69dcdb9dbf60c8f72e55b9b07e4feb72dfba0a40ffeebed6ab681 +size 25730 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en.png deleted file mode 100644 index d14745b783..0000000000 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_CompositeAvatar_Avatars_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:374b810a6ef7011ec1270b7d4faddc8feacef10d167a5b2fc2c920d87f7d8e2c -size 27831 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TextAvatar_Avatars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TextAvatar_Avatars_en.png new file mode 100644 index 0000000000..c0cc7d6d18 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TextAvatar_Avatars_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0645d454bcaac9e38fe9b6400347ee0fc92a71ef531f72b9ec527f644e050862 +size 5488 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TombstonedRoomAvatar_Avatars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TombstonedRoomAvatar_Avatars_en.png new file mode 100644 index 0000000000..c812b2a714 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_TombstonedRoomAvatar_Avatars_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50abd66f2c58d8cc18a96c73120a779882cd121049e473646518ff6841e25e30 +size 5106 From 5d95e3f7cf05e45c24332b8f3a3c4c85c0dba066 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Jun 2025 14:41:18 +0200 Subject: [PATCH 13/13] change (room avatar) : fix test after refactor --- .../android/features/messages/impl/MessagesPresenterTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 2271f6a7d1..f24175c33e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -119,9 +119,9 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { val initialState = consumeItemsUntilTimeout().last() assertThat(initialState.roomId).isEqualTo(A_ROOM_ID) - assertThat(initialState.roomName).isEqualTo(AsyncData.Success("")) + assertThat(initialState.roomName).isEqualTo("") assertThat(initialState.roomAvatar) - .isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))) + .isEqualTo(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)) assertThat(initialState.userEventPermissions.canSendMessage).isTrue() assertThat(initialState.userEventPermissions.canRedactOwn).isTrue() assertThat(initialState.hasNetworkConnection).isTrue()