Introduce AvatarType to be able to render space avatars
This commit is contained in:
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,9 +31,10 @@ import kotlin.math.sin
|
||||
private const val MAX_AVATAR_COUNT = 4
|
||||
|
||||
@Composable
|
||||
fun AvatarCluster(
|
||||
internal fun AvatarCluster(
|
||||
avatars: ImmutableList<AvatarData>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<AvatarData> = persistentListOf(),
|
||||
) : AvatarType
|
||||
|
||||
data class Space(
|
||||
val cornerSize: Dp,
|
||||
) : AvatarType
|
||||
}
|
||||
@@ -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<AvatarData>,
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user