Use just the other user's avatar for DM details (#6738)

* Use just the other user's avatar for DM details. Remove `DmAvatars` component and other no longer needed data.

* Improve selection indicator by clipping the avatar to a circle shape

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa 2026-05-08 11:17:30 +02:00 committed by GitHub
parent 245c30c18a
commit 4a4b3e07ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 128 additions and 286 deletions

View file

@ -47,7 +47,6 @@ import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@ -99,9 +98,8 @@ class RoomDetailsPresenter(
val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias } }
val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } }
val dmMember by room.getDirectRoomMember(membersState)
val currentMember by room.getCurrentRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType = getRoomType(dmMember, currentMember)
val roomType = getRoomType(dmMember)
val roomCallState = roomCallStatePresenter.present()
val joinedMemberCount by remember { derivedStateOf { roomInfo.joinedMembersCount } }
@ -210,15 +208,9 @@ class RoomDetailsPresenter(
}
@Composable
private fun getRoomType(
dmMember: RoomMember?,
currentMember: RoomMember?,
): RoomDetailsType = remember(dmMember, currentMember) {
if (dmMember != null && currentMember != null) {
RoomDetailsType.Dm(
me = currentMember,
otherMember = dmMember,
)
private fun getRoomType(dmMember: RoomMember?): RoomDetailsType = remember(dmMember) {
if (dmMember != null) {
RoomDetailsType.Dm(otherMember = dmMember)
} else {
RoomDetailsType.Room
}

View file

@ -77,10 +77,7 @@ data class RoomDetailsState(
@Immutable
sealed interface RoomDetailsType {
data object Room : RoomDetailsType
data class Dm(
val me: RoomMember,
val otherMember: RoomMember,
) : RoomDetailsType
data class Dm(val otherMember: RoomMember) : RoomDetailsType
}
@Immutable

View file

@ -13,7 +13,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.userprofile.api.UserProfileState
import io.element.android.features.userprofile.api.UserProfileVerificationState
import io.element.android.features.userprofile.shared.aUserProfileState
@ -179,10 +178,7 @@ fun aDmRoomDetailsState(
roomName = roomName,
isPublic = false,
isEncrypted = isEncrypted,
roomType = RoomDetailsType.Dm(
me = aRoomMember(),
otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored),
),
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
roomMemberDetailsState = aUserProfileState(
isBlocked = AsyncData.Success(isDmMemberIgnored),
verificationState = dmRoomMemberVerificationState,

View file

@ -21,6 +21,7 @@ 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.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@ -31,6 +32,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@ -52,7 +54,6 @@ 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.button.BackButton
import io.element.android.libraries.designsystem.components.button.MainActionButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
@ -91,6 +92,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.compose.LocalAnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Composable
@ -154,9 +156,9 @@ fun RoomDetailsView(
}
is RoomDetailsType.Dm -> {
DmHeaderSection(
me = state.roomType.me,
otherMember = state.roomType.otherMember,
roomName = state.roomName,
isTombstoned = state.isTombstoned,
openAvatarPreview = { name, avatarUrl ->
openAvatarPreview(name, avatarUrl)
},
@ -417,6 +419,7 @@ private fun RoomHeaderSection(
),
contentDescription = stringResource(CommonStrings.a11y_room_avatar),
modifier = Modifier
.clip(CircleShape)
.clickable(
enabled = avatarUrl != null,
onClickLabel = stringResource(CommonStrings.action_view),
@ -435,9 +438,9 @@ private fun RoomHeaderSection(
@Composable
private fun DmHeaderSection(
me: RoomMember,
otherMember: RoomMember,
roomName: String,
isTombstoned: Boolean,
openAvatarPreview: (name: String, url: String) -> Unit,
onSubtitleClick: (String) -> Unit,
modifier: Modifier = Modifier
@ -448,11 +451,24 @@ private fun DmHeaderSection(
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
DmAvatars(
userAvatarData = me.getAvatarData(size = AvatarSize.DmCluster),
otherUserAvatarData = otherMember.getAvatarData(size = AvatarSize.DmCluster),
openAvatarPreview = { url -> openAvatarPreview(me.getBestName(), url) },
openOtherAvatarPreview = { url -> openAvatarPreview(roomName, url) },
Avatar(
avatarData = AvatarData(otherMember.userId.value, roomName, otherMember.avatarUrl, AvatarSize.RoomDetailsHeader),
avatarType = AvatarType.Room(
heroes = persistentListOf(
otherMember.getAvatarData(size = AvatarSize.RoomDetailsHeader)
),
isTombstoned = isTombstoned,
),
contentDescription = stringResource(CommonStrings.a11y_room_avatar),
modifier = Modifier
.clip(CircleShape)
.clickable(
enabled = otherMember.avatarUrl != null,
onClickLabel = stringResource(CommonStrings.action_view),
) {
openAvatarPreview(otherMember.getBestName(), otherMember.avatarUrl!!)
}
.testTag(TestTags.roomDetailAvatar)
)
TitleAndSubtitle(
title = roomName,

View file

@ -206,12 +206,7 @@ class RoomDetailsPresenterTest {
val presenter = createRoomDetailsPresenter(room)
presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) {
val initialState = awaitItem()
assertThat(initialState.roomType).isEqualTo(
RoomDetailsType.Dm(
me = myRoomMember,
otherMember = otherRoomMember,
)
)
assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherMember = otherRoomMember))
cancelAndIgnoreRemainingEvents()
}
}

View file

@ -126,10 +126,7 @@ class RoomDetailsViewTest {
state = aRoomDetailsState(
eventSink = EventsRecorder(expectEvents = false),
canInvite = true,
roomType = RoomDetailsType.Dm(
aRoomMember(UserId("@me:local.org")),
aRoomMember(UserId("@other:local.org"))
),
roomType = RoomDetailsType.Dm(aRoomMember(UserId("@other:local.org"))),
),
onJoinCallClick = callback,
)
@ -232,10 +229,7 @@ class RoomDetailsViewTest {
fun `click on avatar test on DM`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<RoomDetailsEvent>(expectEvents = false)
val state = aRoomDetailsState(
roomType = RoomDetailsType.Dm(
aRoomMember(),
aDmRoomMember(avatarUrl = "an_avatar_url"),
),
roomType = RoomDetailsType.Dm(aDmRoomMember(avatarUrl = "an_avatar_url"),),
roomName = "Daniel",
eventSink = eventsRecorder,
)
@ -244,7 +238,7 @@ class RoomDetailsViewTest {
state = state,
openAvatarPreview = callback,
)
onNodeWithTag(TestTags.memberDetailAvatar.value).performClick()
onNodeWithTag(TestTags.roomDetailAvatar.value).performClick()
callback.assertSuccess()
}