Merge pull request #3058 from element-hq/feature/bma/dmColor

Let Dms use other member color.
This commit is contained in:
Benoit Marty 2024-06-21 09:44:38 +02:00 committed by GitHub
commit 981dad71a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 478 additions and 131 deletions

View file

@ -86,12 +86,14 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.room.canCall
import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState
import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -151,6 +153,9 @@ class MessagesPresenter @AssistedInject constructor(
val roomAvatar: AsyncData<AvatarData> by remember {
derivedStateOf { roomInfo?.avatarData()?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
}
val heroes by remember {
derivedStateOf { roomInfo?.heroes().orEmpty().toPersistentList() }
}
var hasDismissedInviteDialog by rememberSaveable {
mutableStateOf(false)
@ -217,6 +222,7 @@ class MessagesPresenter @AssistedInject constructor(
roomId = room.roomId,
roomName = roomName,
roomAvatar = roomAvatar,
heroes = heroes,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
userHasPermissionToRedactOther = userHasPermissionToRedactOther,
@ -250,6 +256,12 @@ class MessagesPresenter @AssistedInject constructor(
)
}
private fun MatrixRoomInfo.heroes(): List<AvatarData> {
return heroes.map { user ->
user.getAvatarData(size = AvatarSize.TimelineRoom)
}
}
private fun CoroutineScope.handleTimelineAction(
action: TimelineItemAction,
targetEvent: TimelineItem.Event,

View file

@ -29,12 +29,14 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class MessagesState(
val roomId: RoomId,
val roomName: AsyncData<String>,
val roomAvatar: AsyncData<AvatarData>,
val heroes: ImmutableList<AvatarData>,
val userHasPermissionToSendMessage: Boolean,
val userHasPermissionToRedactOwn: Boolean,
val userHasPermissionToRedactOther: Boolean,

View file

@ -99,8 +99,8 @@ fun aMessagesState(
userHasPermissionToSendReaction: Boolean = true,
composerState: MessageComposerState = aMessageComposerState(
textEditorState = TextEditorState.Rich(aRichTextEditorState(initialText = "Hello", initialFocus = true)),
isFullScreen = false,
mode = MessageComposerMode.Normal,
isFullScreen = false,
mode = MessageComposerMode.Normal,
),
voiceMessageComposerState: VoiceMessageComposerState = aVoiceMessageComposerState(),
timelineState: TimelineState = aTimelineState(
@ -121,6 +121,7 @@ fun aMessagesState(
roomId = RoomId("!id:domain"),
roomName = roomName,
roomAvatar = roomAvatar,
heroes = persistentListOf(),
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedactOwn = userHasPermissionToRedactOwn,
userHasPermissionToRedactOther = userHasPermissionToRedactOther,

View file

@ -83,9 +83,9 @@ import io.element.android.libraries.androidutils.ui.hideKeyboard
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.ProgressDialogType
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.CompositeAvatar
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
@ -187,6 +187,7 @@ fun MessagesView(
MessagesViewTopBar(
roomName = state.roomName.dataOrNull(),
roomAvatar = state.roomAvatar.dataOrNull(),
heroes = state.heroes,
callState = state.callState,
onBackClick = {
// Since the textfield is now based on an Android view, this is no longer done automatically.
@ -442,6 +443,7 @@ private fun MessagesViewComposerBottomSheetContents(
private fun MessagesViewTopBar(
roomName: String?,
roomAvatar: AvatarData?,
heroes: ImmutableList<AvatarData>,
callState: RoomCallState,
onRoomDetailsClick: () -> Unit,
onJoinCallClick: () -> Unit,
@ -457,6 +459,7 @@ private fun MessagesViewTopBar(
RoomAvatarAndNameRow(
roomName = roomName,
roomAvatar = roomAvatar,
heroes = heroes,
modifier = titleModifier
)
} else {
@ -500,13 +503,17 @@ private fun CallMenuItem(
private fun RoomAvatarAndNameRow(
roomName: String,
roomAvatar: AvatarData,
heroes: ImmutableList<AvatarData>,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Avatar(roomAvatar)
CompositeAvatar(
avatarData = roomAvatar,
heroes = heroes,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = roomName,

View file

@ -93,14 +93,14 @@ private fun RoomMemberSuggestionItemView(
modifier: Modifier = Modifier,
) {
Row(modifier = modifier.clickable { onSelectSuggestion(memberSuggestion) }, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
val avatarSize = AvatarSize.TimelineRoom
val avatarData = when (memberSuggestion) {
is ResolvedMentionSuggestion.AtRoom -> roomAvatar?.copy(size = avatarSize) ?: AvatarData(roomId, roomName, null, avatarSize)
is ResolvedMentionSuggestion.AtRoom -> roomAvatar?.copy(size = AvatarSize.Suggestion)
?: AvatarData(roomId, roomName, null, AvatarSize.Suggestion)
is ResolvedMentionSuggestion.Member -> AvatarData(
memberSuggestion.roomMember.userId.value,
memberSuggestion.roomMember.displayName,
memberSuggestion.roomMember.avatarUrl,
avatarSize,
id = memberSuggestion.roomMember.userId.value,
name = memberSuggestion.roomMember.displayName,
url = memberSuggestion.roomMember.avatarUrl,
size = AvatarSize.Suggestion,
)
}
val title = when (memberSuggestion) {

View file

@ -25,9 +25,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.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.CompositeAvatar
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
@ -37,7 +36,9 @@ import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toPersistentList
/**
* A view that allows a user to edit the default notification setting for rooms. This can be set separately
@ -96,12 +97,6 @@ fun EditDefaultNotificationSettingView(
RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute)
null -> ""
}
val avatarData = AvatarData(
id = summary.identifier(),
name = summary.details.name,
url = summary.details.avatarUrl,
size = AvatarSize.CustomRoomNotificationSetting,
)
ListItem(
headlineContent = {
val roomName = summary.details.name
@ -114,7 +109,12 @@ fun EditDefaultNotificationSettingView(
Text(text = subtitle)
},
leadingContent = ListItemContent.Custom {
Avatar(avatarData = avatarData)
CompositeAvatar(
avatarData = summary.details.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting),
heroes = summary.details.heroes.map { user ->
user.getAvatarData(size = AvatarSize.CustomRoomNotificationSetting)
}.toPersistentList()
)
},
onClick = {
openRoomNotificationSettings(summary.details.roomId)

View file

@ -109,7 +109,7 @@ fun EditUserProfileView(
matrixId = state.userId.value,
displayName = state.displayName,
avatarUrl = state.userAvatarUrl,
avatarSize = AvatarSize.RoomHeader,
avatarSize = AvatarSize.EditProfileDetails,
onAvatarClick = { onAvatarClick() },
modifier = Modifier.align(Alignment.CenterHorizontally),
)

View file

@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -151,6 +152,7 @@ class RoomDetailsPresenter @Inject constructor(
isFavorite = isFavorite,
displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin,
isPublic = isPublic,
heroes = roomInfo?.heroes.orEmpty().toPersistentList(),
eventSink = ::handleEvents,
)
}

View file

@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class RoomDetailsState(
val roomId: RoomId,
@ -43,6 +45,7 @@ data class RoomDetailsState(
val isFavorite: Boolean,
val displayRolesAndPermissionsSettings: Boolean,
val isPublic: Boolean,
val heroes: ImmutableList<MatrixUser>,
val eventSink: (RoomDetailsEvent) -> Unit
)

View file

@ -28,6 +28,9 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import kotlinx.collections.immutable.toPersistentList
open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState> {
override val values: Sequence<RoomDetailsState>
@ -48,6 +51,7 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
),
aRoomDetailsState(canCall = false, canInvite = false),
aRoomDetailsState(isPublic = false),
aRoomDetailsState(heroes = aMatrixUserList()),
// Add other state here
)
}
@ -99,6 +103,7 @@ fun aRoomDetailsState(
isFavorite: Boolean = false,
displayAdminSettings: Boolean = false,
isPublic: Boolean = true,
heroes: List<MatrixUser> = emptyList(),
eventSink: (RoomDetailsEvent) -> Unit = {},
) = RoomDetailsState(
roomId = roomId,
@ -119,6 +124,7 @@ fun aRoomDetailsState(
isFavorite = isFavorite,
displayRolesAndPermissionsSettings = displayAdminSettings,
isPublic = isPublic,
heroes = heroes.toPersistentList(),
eventSink = eventSink
)

View file

@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@ -54,9 +53,9 @@ import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
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.CompositeAvatar
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
@ -81,9 +80,13 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@Composable
fun RoomDetailsView(
@ -127,6 +130,7 @@ fun RoomDetailsView(
roomAlias = state.roomAlias,
isEncrypted = state.isEncrypted,
isPublic = state.isPublic,
heroes = state.heroes,
openAvatarPreview = { avatarUrl ->
openAvatarPreview(state.roomName, avatarUrl)
},
@ -324,6 +328,7 @@ private fun RoomHeaderSection(
roomAlias: RoomAlias?,
isEncrypted: Boolean,
isPublic: Boolean,
heroes: ImmutableList<MatrixUser>,
openAvatarPreview: (url: String) -> Unit,
) {
Column(
@ -332,10 +337,12 @@ private fun RoomHeaderSection(
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(
CompositeAvatar(
avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader),
heroes = heroes.map { user ->
user.getAvatarData(size = AvatarSize.RoomHeader)
}.toPersistentList(),
modifier = Modifier
.size(70.dp)
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
.testTag(TestTags.roomDetailAvatar)
)

View file

@ -67,7 +67,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.room.toMatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@ -276,11 +276,7 @@ private fun RoomMemberListItem(
}
MatrixUserRow(
modifier = modifier.clickable(onClick = onClick),
matrixUser = MatrixUser(
userId = roomMember.userId,
displayName = roomMember.displayName,
avatarUrl = roomMember.avatarUrl,
),
matrixUser = roomMember.toMatrixUser(),
avatarSize = AvatarSize.UserListItem,
trailingContent = roleText?.let {
@Composable {

View file

@ -45,7 +45,6 @@ import io.element.android.libraries.designsystem.components.async.AsyncIndicator
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
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.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent
@ -59,6 +58,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.launch
@ -217,12 +217,7 @@ private fun RoomMemberActionsBottomSheet(
modifier = Modifier.padding(vertical = 16.dp)
) {
Avatar(
avatarData = AvatarData(
id = roomMember.userId.value,
name = roomMember.displayName,
url = roomMember.avatarUrl,
size = AvatarSize.RoomListManageUser,
),
avatarData = roomMember.getAvatarData(size = AvatarSize.RoomListManageUser),
modifier = Modifier
.padding(bottom = 28.dp)
.align(Alignment.CenterHorizontally)

View file

@ -81,6 +81,7 @@ import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.api.room.toMatrixUser
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@ -327,7 +328,7 @@ private fun ListMemberItem(
}
MemberRow(
modifier = Modifier.clickable(enabled = canToggle, onClick = { onToggleSelection(roomMember) }),
avatarData = AvatarData(roomMember.userId.value, roomMember.displayName, roomMember.avatarUrl, AvatarSize.UserListItem),
avatarData = roomMember.getAvatarData(size = AvatarSize.UserListItem),
name = roomMember.getBestName(),
userId = roomMember.userId.value.takeIf { roomMember.displayName?.isNotBlank() == true },
isPending = roomMember.membership == RoomMembershipState.INVITE,

View file

@ -126,6 +126,7 @@ class RoomDetailsViewTest {
}
}
@Config(qualifiers = "h1024dp")
@Test
fun `click on add topic emit expected event`() {
ensureCalledOnceWithParam<RoomDetailsAction>(RoomDetailsAction.AddTopic) { callback ->

View file

@ -53,7 +53,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.Avatar
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
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
@ -158,7 +158,10 @@ private fun RoomSummaryScaffoldRow(
.padding(horizontal = 16.dp, vertical = 11.dp)
.height(IntrinsicSize.Min),
) {
Avatar(room.avatarData)
CompositeAvatar(
avatarData = room.avatarData,
heroes = room.heroes,
)
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier.fillMaxWidth(),

View file

@ -26,7 +26,11 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.toInviteSender
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject
class RoomListRoomSummaryFactory @Inject constructor(
@ -54,43 +58,45 @@ class RoomListRoomSummaryFactory @Inject constructor(
inviteSender = null,
isDm = false,
canonicalAlias = null,
heroes = persistentListOf(),
)
}
}
fun create(roomSummary: RoomSummary.Filled): RoomListRoomSummary {
val roomIdentifier = roomSummary.identifier()
val avatarData = AvatarData(
id = roomIdentifier,
name = roomSummary.details.name,
url = roomSummary.details.avatarUrl,
size = AvatarSize.RoomListItem,
)
return create(roomSummary.details)
}
private fun create(details: RoomSummaryDetails): RoomListRoomSummary {
val avatarData = details.getAvatarData(size = AvatarSize.RoomListItem)
return RoomListRoomSummary(
id = roomIdentifier,
roomId = RoomId(roomIdentifier),
name = roomSummary.details.name,
numberOfUnreadMessages = roomSummary.details.numUnreadMessages,
numberOfUnreadMentions = roomSummary.details.numUnreadMentions,
numberOfUnreadNotifications = roomSummary.details.numUnreadNotifications,
isMarkedUnread = roomSummary.details.isMarkedUnread,
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
lastMessage = roomSummary.details.lastMessage?.let { message ->
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
id = details.roomId.value,
roomId = details.roomId,
name = details.name,
numberOfUnreadMessages = details.numUnreadMessages,
numberOfUnreadMentions = details.numUnreadMentions,
numberOfUnreadNotifications = details.numUnreadNotifications,
isMarkedUnread = details.isMarkedUnread,
timestamp = lastMessageTimestampFormatter.format(details.lastMessageTimestamp),
lastMessage = details.lastMessage?.let { message ->
roomLastMessageFormatter.format(message.event, details.isDirect)
}.orEmpty(),
avatarData = avatarData,
userDefinedNotificationMode = roomSummary.details.userDefinedNotificationMode,
hasRoomCall = roomSummary.details.hasRoomCall,
isDirect = roomSummary.details.isDirect,
isFavorite = roomSummary.details.isFavorite,
inviteSender = roomSummary.details.inviter?.toInviteSender(),
isDm = roomSummary.details.isDm,
canonicalAlias = roomSummary.details.canonicalAlias,
displayType = if (roomSummary.details.currentUserMembership == CurrentUserMembership.INVITED) {
userDefinedNotificationMode = details.userDefinedNotificationMode,
hasRoomCall = details.hasRoomCall,
isDirect = details.isDirect,
isFavorite = details.isFavorite,
inviteSender = details.inviter?.toInviteSender(),
isDm = details.isDm,
canonicalAlias = details.canonicalAlias,
displayType = if (details.currentUserMembership == CurrentUserMembership.INVITED) {
RoomSummaryDisplayType.INVITE
} else {
RoomSummaryDisplayType.ROOM
}
},
heroes = details.heroes.map { user ->
user.getAvatarData(size = AvatarSize.RoomListItem)
}.toImmutableList(),
)
}
}

View file

@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.ui.model.InviteSender
import kotlinx.collections.immutable.ImmutableList
@Immutable
data class RoomListRoomSummary(
@ -43,6 +44,7 @@ data class RoomListRoomSummary(
val isDm: Boolean,
val isFavorite: Boolean,
val inviteSender: InviteSender?,
val heroes: ImmutableList<AvatarData>,
) {
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) ||

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.ui.model.InviteSender
import kotlinx.collections.immutable.toImmutableList
open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> {
override val values: Sequence<RoomListRoomSummary>
@ -142,6 +143,7 @@ internal fun aRoomListRoomSummary(
inviteSender: InviteSender? = null,
displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM,
canonicalAlias: RoomAlias? = null,
heroes: List<AvatarData> = emptyList(),
) = RoomListRoomSummary(
id = id,
roomId = RoomId(id),
@ -161,4 +163,5 @@ internal fun aRoomListRoomSummary(
inviteSender = inviteSender,
displayType = displayType,
canonicalAlias = canonicalAlias,
heroes = heroes.toImmutableList(),
)

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import kotlinx.collections.immutable.toPersistentList
import org.junit.Test
class RoomListRoomSummaryTest {
@ -91,6 +92,7 @@ internal fun createRoomListRoomSummary(
userDefinedNotificationMode: RoomNotificationMode? = null,
isFavorite: Boolean = false,
displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM,
heroes: List<AvatarData> = emptyList(),
) = RoomListRoomSummary(
id = A_ROOM_ID.value,
roomId = A_ROOM_ID,
@ -110,4 +112,5 @@ internal fun createRoomListRoomSummary(
canonicalAlias = null,
inviteSender = null,
isDm = false,
heroes = heroes.toPersistentList(),
)

View file

@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.clearAndSetSemantics
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 coil.compose.AsyncImage
@ -52,18 +53,22 @@ fun Avatar(
avatarData: AvatarData,
modifier: Modifier = Modifier,
contentDescription: String? = null,
// If not null, will be used instead of the size from avatarData
forcedAvatarSize: Dp? = null,
) {
val commonModifier = modifier
.size(avatarData.size.dp)
.size(forcedAvatarSize ?: avatarData.size.dp)
.clip(CircleShape)
if (avatarData.url.isNullOrBlank()) {
InitialsAvatar(
avatarData = avatarData,
forcedAvatarSize = forcedAvatarSize,
modifier = commonModifier,
)
} else {
ImageAvatar(
avatarData = avatarData,
forcedAvatarSize = forcedAvatarSize,
modifier = commonModifier,
contentDescription = contentDescription,
)
@ -73,6 +78,7 @@ fun Avatar(
@Composable
private fun ImageAvatar(
avatarData: AvatarData,
forcedAvatarSize: Dp?,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
@ -98,9 +104,15 @@ private fun ImageAvatar(
SideEffect {
Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}")
}
InitialsAvatar(avatarData = avatarData)
InitialsAvatar(
avatarData = avatarData,
forcedAvatarSize = forcedAvatarSize,
)
}
else -> InitialsAvatar(avatarData = avatarData)
else -> InitialsAvatar(
avatarData = avatarData,
forcedAvatarSize = forcedAvatarSize,
)
}
}
}
@ -109,13 +121,14 @@ private fun ImageAvatar(
@Composable
private fun InitialsAvatar(
avatarData: AvatarData,
forcedAvatarSize: Dp?,
modifier: Modifier = Modifier,
) {
val avatarColors = AvatarColorsProvider.provide(avatarData.id, ElementTheme.isLightTheme)
Box(
modifier.background(color = avatarColors.background)
) {
val fontSize = avatarData.size.dp.toSp() / 2
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

View file

@ -55,4 +55,8 @@ enum class AvatarSize(val dp: Dp) {
CustomRoomNotificationSetting(36.dp),
RoomDirectoryItem(36.dp),
EditProfileDetails(96.dp),
Suggestion(32.dp),
}

View file

@ -0,0 +1,137 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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<AvatarData>,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
if (avatarData.url != null || heroes.isEmpty()) {
Avatar(avatarData, modifier, contentDescription)
} 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(heroes[0], modifier, contentDescription)
}
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(
heroAvatar,
forcedAvatarSize = heroAvatarSize,
)
}
}
}
}
}
}
}
@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()
)

View file

@ -20,6 +20,7 @@ import androidx.compose.runtime.Immutable
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
@ -49,5 +50,6 @@ data class MatrixRoomInfo(
val notificationCount: Long,
val userDefinedNotificationMode: RoomNotificationMode?,
val hasRoomCall: Boolean,
val activeRoomCallParticipants: ImmutableList<String>
val activeRoomCallParticipants: ImmutableList<String>,
val heroes: ImmutableList<MatrixUser>,
)

View file

@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface RoomSummary {
data class Empty(val identifier: String) : RoomSummary
@ -52,6 +53,7 @@ data class RoomSummaryDetails(
val isDm: Boolean,
val isFavorite: Boolean,
val currentUserMembership: CurrentUserMembership,
val heroes: List<MatrixUser>,
) {
val lastMessageTimestamp = lastMessage?.originServerTs
}

View file

@ -22,10 +22,12 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap
import org.matrix.rustcomponents.sdk.RoomHero
import org.matrix.rustcomponents.sdk.Membership as RustMembership
import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
@ -55,7 +57,8 @@ class MatrixRoomInfoMapper {
notificationCount = it.notificationCount.toLong(),
userDefinedNotificationMode = it.userDefinedNotificationMode?.map(),
hasRoomCall = it.hasRoomCall,
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList()
activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList(),
heroes = it.elementHeroes().toImmutableList()
)
}
}
@ -72,6 +75,15 @@ fun RustRoomNotificationMode.map(): RoomNotificationMode = when (this) {
RustRoomNotificationMode.MUTE -> RoomNotificationMode.MUTE
}
/**
* Map a RoomHero to a MatrixUser. There is not need to create a RoomHero type on the application side.
*/
fun RoomHero.map(): MatrixUser = MatrixUser(
userId = UserId(userId),
displayName = displayName,
avatarUrl = avatarUrl
)
fun mapPowerLevels(powerLevels: Map<String, Long>): ImmutableMap<UserId, Long> {
return powerLevels.mapKeys { (key, _) -> UserId(key) }.toPersistentMap()
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.api.user.MatrixUser
import org.matrix.rustcomponents.sdk.RoomInfo
/**
* Extract the heroes from the room info.
* For now we only use heroes for direct rooms with 2 members.
* Also we keep the heroes only if there is one single hero.
*/
fun RoomInfo.elementHeroes(): List<MatrixUser> {
return heroes
.takeIf { isDirect && activeMembersCount.toLong() == 2L }
?.takeIf { it.size == 1 }
?.map { it.map() }
.orEmpty()
}

View file

@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.impl.notificationsettings.RoomNotificationSettingsMapper
import io.element.android.libraries.matrix.impl.room.elementHeroes
import io.element.android.libraries.matrix.impl.room.map
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
@ -49,6 +50,7 @@ class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFacto
isDm = roomInfo.isDirect && roomInfo.activeMembersCount.toLong() == 2L,
isFavorite = roomInfo.isFavourite,
currentUserMembership = roomInfo.membership.map(),
heroes = roomInfo.elementHeroes(),
)
}
}

View file

@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerL
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
@ -754,7 +755,8 @@ fun aRoomInfo(
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
userPowerLevels: ImmutableMap<UserId, Long> = persistentMapOf(),
activeRoomCallParticipants: List<String> = emptyList()
activeRoomCallParticipants: List<String> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
) = MatrixRoomInfo(
id = id,
name = name,
@ -779,6 +781,7 @@ fun aRoomInfo(
hasRoomCall = hasRoomCall,
userPowerLevels = userPowerLevels,
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(),
)
fun defaultRoomPowerLevels() = MatrixRoomPowerLevels(

View file

@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
@ -78,6 +79,7 @@ fun aRoomSummaryDetails(
isDm: Boolean = false,
isFavorite: Boolean = false,
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
heroes: List<MatrixUser> = emptyList(),
) = RoomSummaryDetails(
roomId = roomId,
name = name,
@ -95,6 +97,7 @@ fun aRoomSummaryDetails(
isDm = isDm,
isFavorite = isFavorite,
currentUserMembership = currentUserMembership,
heroes = heroes,
)
fun aRoomMessage(

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.api.user.MatrixUser
open class RoomSummaryDetailsProvider : PreviewParameterProvider<RoomSummaryDetails> {
override val values: Sequence<RoomSummaryDetails>
@ -50,6 +51,7 @@ fun aRoomSummaryDetails(
isMarkedUnread: Boolean = false,
isFavorite: Boolean = false,
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
heroes: List<MatrixUser> = emptyList(),
) = RoomSummaryDetails(
roomId = roomId,
name = name,
@ -67,4 +69,5 @@ fun aRoomSummaryDetails(
isMarkedUnread = isMarkedUnread,
isFavorite = isFavorite,
currentUserMembership = currentUserMembership,
heroes = heroes,
)

View file

@ -36,16 +36,17 @@ 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.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.CompositeAvatar
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
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toImmutableList
@Composable
fun SelectedRoom(
@ -60,7 +61,12 @@ fun SelectedRoom(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(AvatarData(roomSummary.roomId.value, roomSummary.name, roomSummary.avatarUrl, AvatarSize.SelectedRoom))
CompositeAvatar(
avatarData = roomSummary.getAvatarData(size = AvatarSize.SelectedRoom),
heroes = roomSummary.heroes.map { user ->
user.getAvatarData(size = AvatarSize.SelectedRoom)
}.toImmutableList()
)
Text(
// If name is null, we do not have space to render "No room name", so just use `#` here.
text = roomSummary.name ?: "#",

View file

@ -60,10 +60,5 @@ data class InviteSender(
fun RoomMember.toInviteSender() = InviteSender(
userId = userId,
displayName = displayName ?: "",
avatarData = AvatarData(
id = userId.value,
name = displayName,
url = avatarUrl,
size = AvatarSize.InviteSender,
),
avatarData = getAvatarData(size = AvatarSize.InviteSender),
)

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.ui.model
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.room.RoomMember
fun RoomMember.getAvatarData(size: AvatarSize) = AvatarData(
id = userId.value,
name = displayName,
url = avatarUrl,
size = size,
)

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.matrix.ui.model
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
fun RoomSummaryDetails.getAvatarData(size: AvatarSize) = AvatarData(
id = roomId.value,
name = name,
url = avatarUrl,
size = size,
)

View file

@ -41,9 +41,8 @@ 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.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.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -59,9 +58,11 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.ui.components.SelectedRoom
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.roomselect.api.RoomSelectMode
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -221,13 +222,11 @@ private fun RoomSummaryView(
.heightIn(56.dp),
verticalAlignment = Alignment.CenterVertically
) {
Avatar(
avatarData = AvatarData(
id = summary.roomId.value,
name = summary.name,
url = summary.avatarUrl,
size = AvatarSize.RoomSelectRoomListItem,
),
CompositeAvatar(
avatarData = summary.getAvatarData(size = AvatarSize.RoomSelectRoomListItem),
heroes = summary.heroes.map { user ->
user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem)
}.toPersistentList()
)
Column(
modifier = Modifier

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4744d8211b2afaa0e45d018e618296be417db72976a7fe248044e69dc419747
size 46405
oid sha256:b66bfca625a2c5abbb99eb63840c99d941331f74da6741203f382ee49fec29fa
size 45772

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b070a8b1d40d9b944a35f1ca9d034dac4d8f75fcd2ce29ca90a6c0d02eefe567
size 34637
oid sha256:5e1320dbc665a15434e9ea46bc8f085cb0a1dfe1a328d41139401ebd8ef23f84
size 34975

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d6975f45047b144416e394c61d50b2c67296e075ab87521b2bd19190e688b2db
size 44684
oid sha256:24d1b09fbf025b954711e4bd8392d32675568154bbd06ce8c69f4a8deb91a958
size 43695

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06ecef645a1770f028a0be5b519d9fa058896599764b679d431302530dfe5bfd
size 43593
oid sha256:05d47c7317111c9e7ac34f66705a8344114501c35d05d630f2146e128300077d
size 42587

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e5491a9525166428c8b1ce94aaf3ef56aa5415cab19f8bcd99be128c265925e9
size 45669

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8d89dc9226478c87a95f381474dc952372506c59f5797b1ebc06a73f79cda7d4
size 36723
oid sha256:42590cba46dc5f9e0d4e9504d1ea854c5f5f3fdf3acaddca2604988164153beb
size 37065

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fee91322d75a86cf574c0bae99fc7534a7a41f0aa9925554c2cecdd1f67616a2
size 35594
oid sha256:30e9d873c187481341222a986741061eb84162127d7e0d086d3c957d9ef2d2d8
size 35945

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb91e113a89b29e43529fee0140b04c9d45aa6dee95b636e8b02cb697f8eaec3
size 42854
oid sha256:189e30f39a33924a6da727d4caa4ce117df66cc9dd1857c4909649cf8db51fdb
size 43191

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dcb14bafbaeb398a7351528b9756888190cb3614b64ced30604525d33042d0b7
size 45983
oid sha256:a0f12af0b98d2d145d5443554a0657cd0699fe27f86447873630a189cbce615a
size 45008

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27f9fd42938231cbedef25c60c0bf5c38445ed4ae3c43076a3d0d1de781c1571
size 44882
oid sha256:db2babbe88dd81723ecf8f1c83fa73042efb590d04f1b12aaabf102f7f576dd9
size 43883

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d851ccac1aea76e629d3b8ef381595e734e8d02789ebea3a28b5f40dfb07040f
size 44838
oid sha256:53056291448931a702d761557712626259f04a3969f128553a7bc7a172d74d5b
size 43840

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a077772fbbcd2e5096e9fa5907705e5bb3d04692c597510f825102ce31afb6a
size 47451
oid sha256:d69018133b9a3ebf36e7819031df2c8b8bc19f1e7632cfeabc02ca6bcf76ffce
size 46744

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e60750a386993ee7e14a2f1e9afcbfb898d6212f9f043d67431017991b53a3c4
size 35470
oid sha256:374166110a04cee8f63d1d330601976970e5abcd92947cfb284e61613cf47f51
size 35727

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7d805eed0dbdf07599c836af16e904ec5a6ebe4adb0b9ebfa5200f2065630cfa
size 45596
oid sha256:04a0838ac192efeacb821d23ed8b3ce8305b8e8084beb0e5a0fbc56cfa1ce964
size 44435

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c04bd5c13058d696364295110fc4b323b424766bda0ba42c13b2ed772e8f6520
size 44474
oid sha256:6d760e5015257bd8d608749c1926c6177e4d0ef70d5d5f32764e970f4c80cb43
size 43331

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63d2319dc4617d805f0b652983e4bb7b6e4aa7c2e9c6ec37398fce2e2413a79d
size 46441

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eb96628c10622b9f39b9723b4c3a7b1a7e38a643753a42159af63e8f00a2f51c
size 37610
oid sha256:8c3dec8246e5605c61a9132cea048c5afaad35751246ffd81160c33782c26c93
size 37861

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f84aea29dfb13dbe42c6e466b3afd7258742bf2c0411039ab93e554803945333
size 36229
oid sha256:c8491d21c160851bbe3a0362edae05e46cf9cb072ca4bf41ad569a3a6c57097c
size 36458

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8e7e580cd945db94a37cfaa162ab709ade3a830f20a42bb98a28fd7650114a90
size 43740
oid sha256:f7f08cac8eecb3ae6067f756411d0ec5607d3207832cfe69858ef1189f18ee45
size 43996

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:14b22efc30f883ca6812f9e87bf8eaecc3d37479fa5421026fcaf7b01a11e952
size 46986
oid sha256:ea0cfd0f222862a8becdd4b4a7f2e47a4a7d83ecb1ff761889c46631d7d43e97
size 45861

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:45a3856e5d802c02302318caf584b4356122c3412bea177944ec8edab0c809c0
size 45846
oid sha256:cd7aa71f1d24d4cacafb4aaea8802d1493e317604d5ea35f0d9fac1a304d7f19
size 44718

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd29438c4d913cd920e2f19e85f9098d6a486a401fcd8e14a9f7a323dd5c334c
size 45773
oid sha256:234e65b2caa1f23f9e4d0e01c142d22df0d407851e554a7db3e0b09c57acf47a
size 44626

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6be06e6a243feb8096a2f121e5cadbd5d82351807b537c938e0e7cbfdf52739
size 21115

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:94607803d470f7f5772cbf421b3e56a845556773b3545cae5b42314a1804a02a
size 18858

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f3a7f0a4944f35fd726d5090eab08140b57332e6f7d8eb475fc6bf9ef37bcdf
size 26033

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61d634e424c3858902d03a50a128ecbba4c62766ebba97cd04f6eb147f41f71d
size 15042

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4fea2725865c6277387d8f9d4f1bc980548b671f2946c89dbb9f4cad34be80d7
size 14267

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e88e6991bca9f0c99c28b6126b10135d0ba8de1faa7bc6174e6f66bc11b2ce03
size 16794

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fa80768048c3247ede26c831a9d16a2f9c27774b5d9b75e70cb31e0f26b4d411
size 27620