diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt index 2684c8334d..afc6ef0bde 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults +import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap @@ -219,6 +220,7 @@ private fun RoomNameWithAvatar( ) { UnsavedAvatar( avatarUri = avatarUri, + avatarType = AvatarType.Room(), modifier = Modifier.clickable(onClick = onAvatarClick), ) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index b63bdff347..7f51773c45 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -53,6 +53,7 @@ import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.SuperButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog @@ -92,7 +93,11 @@ fun JoinRoomView( vertical = 32.dp ), topBar = { - JoinRoomTopBar(contentState = state.contentState, onBackClick = onBackClick) + JoinRoomTopBar( + contentState = state.contentState, + hideAvatarImage = state.hideAvatarsImages, + onBackClick = onBackClick, + ) }, content = { JoinRoomContent( @@ -490,7 +495,11 @@ private fun DefaultLoadedContent( RoomPreviewOrganism( modifier = modifier, avatar = { - Avatar(contentState.avatarData(AvatarSize.RoomHeader), hideImage = hideAvatarImage) + Avatar( + contentState.avatarData(AvatarSize.RoomHeader), + hideImage = hideAvatarImage, + avatarType = AvatarType.Room(), + ) }, title = { if (contentState.name != null) { @@ -545,6 +554,7 @@ private fun DefaultLoadedContent( @Composable private fun JoinRoomTopBar( contentState: ContentState, + hideAvatarImage: Boolean, onBackClick: () -> Unit, ) { TopAppBar( @@ -561,7 +571,11 @@ private fun JoinRoomTopBar( modifier = titleModifier, verticalAlignment = Alignment.CenterVertically ) { - Avatar(avatarData = contentState.avatarData(AvatarSize.TimelineRoom)) + Avatar( + avatarData = contentState.avatarData(AvatarSize.TimelineRoom), + hideImage = hideAvatarImage, + avatarType = AvatarType.Room(), + ) Text( modifier = Modifier.padding(horizontal = 8.dp), text = contentState.name, 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 a772015787..66fe8723bb 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,8 +83,9 @@ 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.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.RoomAvatar +import io.element.android.libraries.designsystem.components.avatar.AvatarType 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 @@ -563,10 +564,12 @@ private fun RoomAvatarAndNameRow( modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { - RoomAvatar( + Avatar( avatarData = roomAvatar, - heroes = heroes, - isTombstoned = isTombstoned, + avatarType = AvatarType.Room( + 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 cc8f536f5f..bb2e2c7f4d 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.AvatarType 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 @@ -98,6 +99,11 @@ private fun SuggestionItemView( is ResolvedSuggestion.Member -> suggestion.roomMember.getAvatarData(avatarSize) is ResolvedSuggestion.Alias -> suggestion.getAvatarData(avatarSize) } + val avatarType = when (suggestion) { + is ResolvedSuggestion.Alias -> AvatarType.Room() + ResolvedSuggestion.AtRoom, + is ResolvedSuggestion.Member -> AvatarType.User + } val title = when (suggestion) { is ResolvedSuggestion.AtRoom -> stringResource(R.string.screen_room_mentions_at_room_title) is ResolvedSuggestion.Member -> suggestion.roomMember.displayName @@ -109,7 +115,12 @@ private fun SuggestionItemView( is ResolvedSuggestion.Alias -> suggestion.roomAlias.value } - Avatar(avatarData = avatarData, modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp)) + Avatar( + avatarData = avatarData, + avatarType = avatarType, + modifier = Modifier.padding(start = 16.dp, top = 12.dp, bottom = 12.dp), + ) + Column( modifier = Modifier 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 b9a001e812..f38e59355c 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,8 @@ 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.RoomAvatar +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarType 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,9 +98,11 @@ fun EditDefaultNotificationSettingView( Text(text = subtitle) }, leadingContent = ListItemContent.Custom { - RoomAvatar( + Avatar( avatarData = summary.avatarData, - heroes = summary.heroesAvatar, + avatarType = AvatarType.Room( + heroes = summary.heroesAvatar, + ), ) }, onClick = { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt index 5b52f83b66..dc03b593c7 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt @@ -32,6 +32,7 @@ import io.element.android.features.preferences.impl.R import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap import io.element.android.libraries.designsystem.preview.ElementPreview @@ -101,6 +102,7 @@ fun EditUserProfileView( displayName = state.displayName, avatarUrl = state.userAvatarUrl, avatarSize = AvatarSize.EditProfileDetails, + avatarType = AvatarType.User, onAvatarClick = { onAvatarClick() }, modifier = Modifier.align(Alignment.CenterHorizontally), ) 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 d093fd7e58..02a6a637e3 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,10 +48,11 @@ import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.atomic.atoms.MatrixBadgeAtom import io.element.android.libraries.designsystem.atomic.molecules.MatrixBadgeRowMolecule import io.element.android.libraries.designsystem.components.ClickableLinkText +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.AvatarType 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 @@ -399,12 +400,14 @@ private fun RoomHeaderSection( .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - RoomAvatar( + Avatar( avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader), - heroes = heroes.map { user -> - user.getAvatarData(size = AvatarSize.RoomHeader) - }.toPersistentList(), - isTombstoned = isTombstoned, + avatarType = AvatarType.Room( + 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/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt index 5031d94d80..b77ea44001 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt @@ -34,6 +34,7 @@ import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap import io.element.android.libraries.designsystem.preview.ElementPreview @@ -103,6 +104,7 @@ fun RoomDetailsEditView( displayName = state.roomRawName, avatarUrl = state.roomAvatarUrl, avatarSize = AvatarSize.EditRoomDetails, + avatarType = AvatarType.Room(), onAvatarClick = ::onAvatarClick, modifier = Modifier.fillMaxWidth(), ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index fc7f68d506..5c6383ba3b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -40,6 +40,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.features.roomdirectory.impl.R import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType 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 @@ -248,7 +249,8 @@ private fun RoomDirectoryRoomRow( ) { Avatar( avatarData = roomDescription.avatarData(AvatarSize.RoomDirectoryItem), - modifier = Modifier.align(Alignment.CenterVertically) + avatarType = AvatarType.Room(), + modifier = Modifier.align(Alignment.CenterVertically), ) Column( modifier = Modifier 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 1be57d631a..d6ad371beb 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,8 @@ 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.RoomAvatar +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarType 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 @@ -184,11 +185,13 @@ private fun RoomSummaryScaffoldRow( .padding(horizontal = 16.dp, vertical = 11.dp) .height(IntrinsicSize.Min), ) { - RoomAvatar( + Avatar( avatarData = room.avatarData, - heroes = room.heroes, - isTombstoned = room.isTombstoned, - hideAvatarImage = hideAvatarImage, + avatarType = AvatarType.Room( + heroes = room.heroes, + isTombstoned = room.isTombstoned, + ), + hideImage = hideAvatarImage, ) Spacer(modifier = Modifier.width(16.dp)) Column( 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 106e52d350..4f691c9f0d 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 @@ -10,7 +10,6 @@ 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.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState @@ -37,15 +36,50 @@ import timber.log.Timber fun Avatar( avatarData: AvatarData, modifier: Modifier = Modifier, + avatarType: AvatarType = AvatarType.User, contentDescription: String? = null, // If not null, will be used instead of the size from avatarData forcedAvatarSize: Dp? = null, // If true, will show initials even if avatarData.url is not null hideImage: Boolean = false, +) { + when (avatarType) { + is AvatarType.Room -> RoomAvatar( + avatarData = avatarData, + avatarType = avatarType, + modifier = modifier, + hideAvatarImage = hideImage, + contentDescription = contentDescription, + ) + AvatarType.User -> UserAvatar( + avatarData = avatarData, + modifier = modifier, + contentDescription = contentDescription, + forcedAvatarSize = forcedAvatarSize, + hideImage = hideImage, + ) + is AvatarType.Space -> SpaceAvatar( + avatarData = avatarData, + avatarType = avatarType, + modifier = modifier, + hideAvatarImage = hideImage, + contentDescription = contentDescription, + ) + } +} + +@Composable +private fun UserAvatar( + avatarData: AvatarData, + modifier: Modifier = Modifier, + contentDescription: String? = null, + forcedAvatarSize: Dp? = null, + hideImage: Boolean = false, ) { if (avatarData.url.isNullOrBlank() || hideImage) { InitialLetterAvatar( avatarData = avatarData, + avatarType = AvatarType.User, forcedAvatarSize = forcedAvatarSize, modifier = modifier, contentDescription = contentDescription, @@ -53,6 +87,7 @@ fun Avatar( } else { ImageAvatar( avatarData = avatarData, + avatarType = AvatarType.User, forcedAvatarSize = forcedAvatarSize, modifier = modifier, contentDescription = contentDescription, @@ -61,8 +96,9 @@ fun Avatar( } @Composable -private fun ImageAvatar( +internal fun ImageAvatar( avatarData: AvatarData, + avatarType: AvatarType, forcedAvatarSize: Dp?, modifier: Modifier = Modifier, contentDescription: String? = null, @@ -74,7 +110,7 @@ private fun ImageAvatar( contentScale = ContentScale.Crop, modifier = modifier .size(size) - .clip(CircleShape) + .clip(avatarShape(avatarType)) ) { val collectedState by painter.state.collectAsState() when (val state = collectedState) { @@ -85,12 +121,14 @@ private fun ImageAvatar( } InitialLetterAvatar( avatarData = avatarData, + avatarType = avatarType, forcedAvatarSize = forcedAvatarSize, contentDescription = contentDescription, ) } else -> InitialLetterAvatar( avatarData = avatarData, + avatarType = avatarType, forcedAvatarSize = forcedAvatarSize, contentDescription = contentDescription, ) @@ -99,8 +137,9 @@ private fun ImageAvatar( } @Composable -private fun InitialLetterAvatar( +internal fun InitialLetterAvatar( avatarData: AvatarData, + avatarType: AvatarType, forcedAvatarSize: Dp?, contentDescription: String?, modifier: Modifier = Modifier, @@ -109,6 +148,7 @@ private fun InitialLetterAvatar( TextAvatar( text = avatarData.initialLetter, size = forcedAvatarSize ?: avatarData.size.dp, + avatarType = avatarType, colors = avatarColors, contentDescription = contentDescription, modifier = modifier 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 9c7efa864d..8406c951af 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 @@ -31,9 +31,10 @@ import kotlin.math.sin private const val MAX_AVATAR_COUNT = 4 @Composable -fun AvatarCluster( +internal fun AvatarCluster( avatars: ImmutableList, modifier: Modifier = Modifier, + avatarType: AvatarType = AvatarType.User, hideAvatarImages: Boolean = false, contentDescription: String? = null, ) { @@ -50,6 +51,7 @@ fun AvatarCluster( 1 -> { Avatar( avatarData = limitedAvatars[0], + avatarType = avatarType, modifier = modifier, contentDescription = contentDescription, hideImage = hideAvatarImages @@ -78,10 +80,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,15 +91,16 @@ 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, forcedAvatarSize = heroAvatarSize, + avatarType = avatarType, hideImage = hideAvatarImages, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt new file mode 100644 index 0000000000..dff82a5a3d --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt @@ -0,0 +1,24 @@ +/* + * 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.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Shape + +@Composable +fun avatarShape( + avatarType: AvatarType, +): Shape { + return when (avatarType) { + is AvatarType.Space -> RoundedCornerShape(avatarType.cornerSize) + is AvatarType.Room, + AvatarType.User -> CircleShape + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt new file mode 100644 index 0000000000..d210de954b --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2023, 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.ui.unit.Dp +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +sealed interface AvatarType { + data object User : AvatarType + + data class Room( + val isTombstoned: Boolean = false, + val heroes: ImmutableList = persistentListOf(), + ) : AvatarType + + data class Space( + val cornerSize: Dp, + ) : AvatarType +} 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 44d1c9b491..f01d71e41d 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 @@ -9,36 +9,46 @@ 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( +internal fun RoomAvatar( avatarData: AvatarData, - heroes: ImmutableList, + avatarType: AvatarType.Room, modifier: Modifier = Modifier, - isTombstoned: Boolean = false, hideAvatarImage: Boolean = false, contentDescription: String? = null, ) { when { - isTombstoned -> { + avatarType.isTombstoned -> { TombstonedRoomAvatar( size = avatarData.size, modifier = modifier, + avatarType = avatarType, contentDescription = contentDescription ) } - avatarData.url != null || heroes.isEmpty() -> { - Avatar( - avatarData = avatarData, - modifier = modifier, - contentDescription = contentDescription, - hideImage = hideAvatarImage - ) + avatarData.url != null || avatarType.heroes.isEmpty() -> { + if (avatarData.url.isNullOrBlank() || hideAvatarImage) { + InitialLetterAvatar( + avatarData = avatarData, + avatarType = avatarType, + modifier = modifier, + contentDescription = contentDescription, + forcedAvatarSize = null, + ) + } else { + ImageAvatar( + avatarData = avatarData, + avatarType = avatarType, + forcedAvatarSize = null, + modifier = modifier, + contentDescription = contentDescription, + ) + } } else -> { AvatarCluster( - avatars = heroes, + avatars = avatarType.heroes, modifier = modifier, hideAvatarImages = hideAvatarImage, contentDescription = contentDescription diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/SpaceAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/SpaceAvatar.kt new file mode 100644 index 0000000000..76f57d159e --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/SpaceAvatar.kt @@ -0,0 +1,74 @@ +/* + * 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.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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 io.element.android.libraries.designsystem.utils.CommonDrawables + +@Composable +fun SpaceAvatar( + avatarData: AvatarData, + avatarType: AvatarType.Space, + modifier: Modifier = Modifier, + isTombstoned: Boolean = false, + hideAvatarImage: Boolean = false, + contentDescription: String? = null, +) { + when { + isTombstoned -> TombstonedRoomAvatar( + size = avatarData.size, + avatarType = avatarType, + modifier = modifier, + contentDescription = contentDescription, + ) + avatarData.url.isNullOrBlank() || hideAvatarImage -> InitialLetterAvatar( + avatarData = avatarData, + avatarType = avatarType, + modifier = modifier, + contentDescription = contentDescription, + forcedAvatarSize = null, + ) + else -> ImageAvatar( + avatarData = avatarData, + avatarType = avatarType, + forcedAvatarSize = null, + modifier = modifier, + contentDescription = contentDescription, + ) + } +} + +@Preview(group = PreviewGroup.Avatars) +@Composable +internal fun SpaceAvatarPreview() = + ElementThemedPreview( + drawableFallbackForImages = CommonDrawables.sample_avatar, + ) { + Row( + modifier = Modifier.padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + SpaceAvatar( + avatarData = anAvatarData(), + avatarType = AvatarType.Space(cornerSize = 16.dp), + ) + SpaceAvatar( + avatarData = anAvatarData(), + avatarType = AvatarType.Space(cornerSize = 16.dp), + isTombstoned = true, + ) + } + } 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 65ebba3366..0af6aa4841 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 @@ -8,9 +8,11 @@ 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.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -34,12 +36,13 @@ internal fun TextAvatar( size: Dp, colors: AvatarColors, contentDescription: String?, + avatarType: AvatarType, modifier: Modifier = Modifier, ) { Box( modifier .size(size) - .clip(CircleShape) + .clip(avatarShape(avatarType)) .background(color = colors.background) ) { val fontSize = size.toSp() / 2 @@ -64,13 +67,25 @@ internal fun TextAvatar( @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, - ) + Row( + modifier = Modifier.padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + listOf( + AvatarType.User, + AvatarType.Room(), + AvatarType.Space(8.dp), + ).forEach { avatarType -> + TextAvatar( + text = "AB", + size = 40.dp, + colors = AvatarColors( + background = ElementTheme.colors.bgSubtlePrimary, + foreground = ElementTheme.colors.iconPrimary, + ), + avatarType = avatarType, + contentDescription = null, + ) + } } +} 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 fe09be909d..7da0401b25 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 @@ -16,9 +16,10 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewGroup @Composable -fun TombstonedRoomAvatar( +internal fun TombstonedRoomAvatar( size: AvatarSize, modifier: Modifier = Modifier, + avatarType: AvatarType, contentDescription: String? = null, ) { TextAvatar( @@ -29,7 +30,8 @@ fun TombstonedRoomAvatar( foreground = ElementTheme.colors.iconTertiary ), modifier = modifier, - contentDescription = contentDescription + avatarType = avatarType, + contentDescription = contentDescription, ) } @@ -38,6 +40,7 @@ fun TombstonedRoomAvatar( internal fun TombstonedRoomAvatarPreview() = ElementPreview { TombstonedRoomAvatar( size = AvatarSize.RoomListItem, + avatarType = AvatarType.Room(), contentDescription = null, ) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt index eecacda543..8412c18826 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt @@ -35,6 +35,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons 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.AvatarType 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 @@ -49,6 +50,7 @@ fun EditableAvatarView( displayName: String?, avatarUrl: Uri?, avatarSize: AvatarSize, + avatarType: AvatarType, onAvatarClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -74,12 +76,14 @@ fun EditableAvatarView( null, "mxc" -> { Avatar( avatarData = AvatarData(matrixId, displayName, avatarUrl?.toString(), size = avatarSize), + avatarType = avatarType, modifier = Modifier.fillMaxSize(), ) } else -> { UnsavedAvatar( avatarUri = avatarUrl, + avatarType = avatarType, modifier = Modifier.fillMaxSize(), ) } @@ -116,6 +120,7 @@ internal fun EditableAvatarViewPreview( displayName = "A room", avatarUrl = uri, avatarSize = AvatarSize.EditRoomDetails, + avatarType = AvatarType.User, onAvatarClick = {}, ) } 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 06cead3526..4a672624ab 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 @@ -28,8 +28,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter 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.Avatar 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.AvatarType 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,10 +54,12 @@ fun SelectedRoom( Column( horizontalAlignment = Alignment.CenterHorizontally, ) { - RoomAvatar( + Avatar( avatarData = roomInfo.getAvatarData(AvatarSize.SelectedRoom), - heroes = roomInfo.heroes.map { it.getAvatarData(AvatarSize.SelectedRoom) }.toImmutableList(), - isTombstoned = roomInfo.isTombstoned, + avatarType = AvatarType.Room( + 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/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt index 3a31bf916e..161bffbab9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt @@ -9,10 +9,11 @@ package io.element.android.libraries.matrix.ui.components import android.net.Uri 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.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AddAPhoto import androidx.compose.material3.MaterialTheme @@ -27,6 +28,8 @@ import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import coil3.request.ImageRequest import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.components.avatar.avatarShape +import io.element.android.libraries.designsystem.components.avatar.AvatarType 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 @@ -40,11 +43,12 @@ import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial @Composable fun UnsavedAvatar( avatarUri: Uri?, + avatarType: AvatarType, modifier: Modifier = Modifier, ) { val commonModifier = modifier .size(70.dp) - .clip(CircleShape) + .clip(avatarShape(avatarType)) if (avatarUri != null) { val context = LocalContext.current @@ -75,8 +79,13 @@ fun UnsavedAvatar( @PreviewsDayNight @Composable internal fun UnsavedAvatarPreview() = ElementPreview { - Row { - UnsavedAvatar(null) - UnsavedAvatar(Uri.EMPTY) + Row( + modifier = Modifier.padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + UnsavedAvatar(null, AvatarType.User) + UnsavedAvatar(Uri.EMPTY, AvatarType.User) + UnsavedAvatar(null, AvatarType.Space(8.dp)) + UnsavedAvatar(Uri.EMPTY, AvatarType.Space(8.dp)) } } 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 10769b9aec..f9a95ac4b8 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 @@ -32,8 +32,9 @@ import androidx.compose.ui.text.style.TextOverflow 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.Avatar 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.AvatarType 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,12 +215,14 @@ private fun RoomSummaryView( .heightIn(56.dp), verticalAlignment = Alignment.CenterVertically ) { - RoomAvatar( + Avatar( avatarData = roomInfo.getAvatarData(size = AvatarSize.RoomSelectRoomListItem), - heroes = roomInfo.heroes.map { user -> - user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem) - }.toPersistentList(), - isTombstoned = roomInfo.isTombstoned, + avatarType = AvatarType.Room( + heroes = roomInfo.heroes.map { user -> + user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem) + }.toPersistentList(), + isTombstoned = roomInfo.isTombstoned, + ), ) Column( modifier = Modifier