Use heroes to render room avatars

This commit is contained in:
Benoit Marty 2024-06-20 13:10:25 +02:00
parent 99c43a175d
commit 11e4ee34ed
11 changed files with 79 additions and 10 deletions

View file

@ -25,9 +25,9 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.preferences.impl.R 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.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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferencePage
@ -114,7 +114,17 @@ fun EditDefaultNotificationSettingView(
Text(text = subtitle) Text(text = subtitle)
}, },
leadingContent = ListItemContent.Custom { leadingContent = ListItemContent.Custom {
Avatar(avatarData = avatarData) CompositeAvatar(
avatarData = avatarData,
heroes = summary.details.heroes.map { user ->
AvatarData(
id = user.userId.value,
name = user.displayName,
url = user.avatarUrl,
size = AvatarSize.CustomRoomNotificationSetting,
)
}
)
}, },
onClick = { onClick = {
openRoomNotificationSettings(summary.details.roomId) openRoomNotificationSettings(summary.details.roomId)

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.libraries.matrix.ui.room.isOwnUserAdmin
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -151,6 +152,7 @@ class RoomDetailsPresenter @Inject constructor(
isFavorite = isFavorite, isFavorite = isFavorite,
displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin, displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin,
isPublic = isPublic, isPublic = isPublic,
heroes = roomInfo?.heroes.orEmpty().toPersistentList(),
eventSink = ::handleEvents, 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.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember 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.room.RoomNotificationSettings
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class RoomDetailsState( data class RoomDetailsState(
val roomId: RoomId, val roomId: RoomId,
@ -43,6 +45,7 @@ data class RoomDetailsState(
val isFavorite: Boolean, val isFavorite: Boolean,
val displayRolesAndPermissionsSettings: Boolean, val displayRolesAndPermissionsSettings: Boolean,
val isPublic: Boolean, val isPublic: Boolean,
val heroes: ImmutableList<MatrixUser>,
val eventSink: (RoomDetailsEvent) -> Unit val eventSink: (RoomDetailsEvent) -> Unit
) )

View file

@ -28,6 +28,8 @@ 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.RoomMembershipState
import io.element.android.libraries.matrix.api.room.RoomNotificationMode 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.room.RoomNotificationSettings
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.toPersistentList
open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState> { open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState> {
override val values: Sequence<RoomDetailsState> override val values: Sequence<RoomDetailsState>
@ -99,6 +101,7 @@ fun aRoomDetailsState(
isFavorite: Boolean = false, isFavorite: Boolean = false,
displayAdminSettings: Boolean = false, displayAdminSettings: Boolean = false,
isPublic: Boolean = true, isPublic: Boolean = true,
heroes: List<MatrixUser> = emptyList(),
eventSink: (RoomDetailsEvent) -> Unit = {}, eventSink: (RoomDetailsEvent) -> Unit = {},
) = RoomDetailsState( ) = RoomDetailsState(
roomId = roomId, roomId = roomId,
@ -119,6 +122,7 @@ fun aRoomDetailsState(
isFavorite = isFavorite, isFavorite = isFavorite,
displayRolesAndPermissionsSettings = displayAdminSettings, displayRolesAndPermissionsSettings = displayAdminSettings,
isPublic = isPublic, isPublic = isPublic,
heroes = heroes.toPersistentList(),
eventSink = eventSink eventSink = eventSink
) )

View file

@ -54,9 +54,9 @@ import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.ClickableLinkText 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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.BackButton
import io.element.android.libraries.designsystem.components.button.MainActionButton import io.element.android.libraries.designsystem.components.button.MainActionButton
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
@ -81,6 +81,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.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode 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.room.getBestName
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -127,6 +128,7 @@ fun RoomDetailsView(
roomAlias = state.roomAlias, roomAlias = state.roomAlias,
isEncrypted = state.isEncrypted, isEncrypted = state.isEncrypted,
isPublic = state.isPublic, isPublic = state.isPublic,
heroes = state.heroes,
openAvatarPreview = { avatarUrl -> openAvatarPreview = { avatarUrl ->
openAvatarPreview(state.roomName, avatarUrl) openAvatarPreview(state.roomName, avatarUrl)
}, },
@ -324,6 +326,7 @@ private fun RoomHeaderSection(
roomAlias: RoomAlias?, roomAlias: RoomAlias?,
isEncrypted: Boolean, isEncrypted: Boolean,
isPublic: Boolean, isPublic: Boolean,
heroes: List<MatrixUser> = emptyList(),
openAvatarPreview: (url: String) -> Unit, openAvatarPreview: (url: String) -> Unit,
) { ) {
Column( Column(
@ -332,8 +335,16 @@ private fun RoomHeaderSection(
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Avatar( CompositeAvatar(
avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader), avatarData = AvatarData(roomId.value, roomName, avatarUrl, AvatarSize.RoomHeader),
heroes = heroes.map { user ->
AvatarData(
id = user.userId.value,
name = user.displayName,
url = user.avatarUrl,
size = AvatarSize.RoomHeader
)
},
modifier = Modifier modifier = Modifier
.size(70.dp) .size(70.dp)
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }

View file

@ -27,6 +27,8 @@ 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.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.toInviteSender import io.element.android.libraries.matrix.ui.model.toInviteSender
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import javax.inject.Inject import javax.inject.Inject
class RoomListRoomSummaryFactory @Inject constructor( class RoomListRoomSummaryFactory @Inject constructor(
@ -54,6 +56,7 @@ class RoomListRoomSummaryFactory @Inject constructor(
inviteSender = null, inviteSender = null,
isDm = false, isDm = false,
canonicalAlias = null, canonicalAlias = null,
heroes = persistentListOf(),
) )
} }
} }
@ -90,7 +93,15 @@ class RoomListRoomSummaryFactory @Inject constructor(
RoomSummaryDisplayType.INVITE RoomSummaryDisplayType.INVITE
} else { } else {
RoomSummaryDisplayType.ROOM RoomSummaryDisplayType.ROOM
} },
heroes = roomSummary.details.heroes.map {
AvatarData(
id = it.userId.value,
name = it.displayName,
url = it.avatarUrl,
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.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.ui.model.InviteSender import io.element.android.libraries.matrix.ui.model.InviteSender
import kotlinx.collections.immutable.ImmutableList
@Immutable @Immutable
data class RoomListRoomSummary( data class RoomListRoomSummary(
@ -43,6 +44,7 @@ data class RoomListRoomSummary(
val isDm: Boolean, val isDm: Boolean,
val isFavorite: Boolean, val isFavorite: Boolean,
val inviteSender: InviteSender?, val inviteSender: InviteSender?,
val heroes: ImmutableList<AvatarData>,
) { ) {
val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE && val isHighlighted = userDefinedNotificationMode != RoomNotificationMode.MUTE &&
(numberOfUnreadNotifications > 0 || numberOfUnreadMentions > 0) || (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.core.UserId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.ui.model.InviteSender import io.element.android.libraries.matrix.ui.model.InviteSender
import kotlinx.collections.immutable.toImmutableList
open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> { open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSummary> {
override val values: Sequence<RoomListRoomSummary> override val values: Sequence<RoomListRoomSummary>
@ -142,6 +143,7 @@ internal fun aRoomListRoomSummary(
inviteSender: InviteSender? = null, inviteSender: InviteSender? = null,
displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM, displayType: RoomSummaryDisplayType = RoomSummaryDisplayType.ROOM,
canonicalAlias: RoomAlias? = null, canonicalAlias: RoomAlias? = null,
heroes: List<AvatarData> = emptyList(),
) = RoomListRoomSummary( ) = RoomListRoomSummary(
id = id, id = id,
roomId = RoomId(id), roomId = RoomId(id),
@ -161,4 +163,5 @@ internal fun aRoomListRoomSummary(
inviteSender = inviteSender, inviteSender = inviteSender,
displayType = displayType, displayType = displayType,
canonicalAlias = canonicalAlias, canonicalAlias = canonicalAlias,
heroes = heroes.toImmutableList(),
) )

View file

@ -53,7 +53,7 @@ fun CompositeAvatar(
} }
when (numberOfHeroes) { when (numberOfHeroes) {
0 -> { 0 -> {
// Cannot happen error("Unsupported number of heroes: 0")
} }
1 -> { 1 -> {
Avatar(heroes[0], modifier, contentDescription) Avatar(heroes[0], modifier, contentDescription)

View file

@ -36,9 +36,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons 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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight 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.Icon
@ -60,7 +60,22 @@ fun SelectedRoom(
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Avatar(AvatarData(roomSummary.roomId.value, roomSummary.name, roomSummary.avatarUrl, AvatarSize.SelectedRoom)) CompositeAvatar(
avatarData = AvatarData(
roomSummary.roomId.value,
roomSummary.name,
roomSummary.avatarUrl,
AvatarSize.SelectedRoom,
),
heroes = roomSummary.heroes.map {
AvatarData(
id = it.userId.value,
name = it.displayName,
url = it.avatarUrl,
size = AvatarSize.SelectedRoom
)
}
)
Text( Text(
// If name is null, we do not have space to render "No room name", so just use `#` here. // If name is null, we do not have space to render "No room name", so just use `#` here.
text = roomSummary.name ?: "#", text = roomSummary.name ?: "#",

View file

@ -41,9 +41,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme 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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize 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.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -221,13 +221,21 @@ private fun RoomSummaryView(
.heightIn(56.dp), .heightIn(56.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Avatar( CompositeAvatar(
avatarData = AvatarData( avatarData = AvatarData(
id = summary.roomId.value, id = summary.roomId.value,
name = summary.name, name = summary.name,
url = summary.avatarUrl, url = summary.avatarUrl,
size = AvatarSize.RoomSelectRoomListItem, size = AvatarSize.RoomSelectRoomListItem,
), ),
heroes = summary.heroes.map {
AvatarData(
it.userId.value,
it.displayName,
it.avatarUrl,
AvatarSize.RoomSelectRoomListItem,
)
}
) )
Column( Column(
modifier = Modifier modifier = Modifier