Merge pull request #6929 from element-hq/feature/bma/reorderRoomDetailItems

Reorder room detail items
This commit is contained in:
Benoit Marty
2026-06-01 22:39:41 +02:00
committed by GitHub
53 changed files with 203 additions and 180 deletions
@@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDis
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.room.JoinedRoom
@@ -117,7 +118,7 @@ class RoomDetailsPresenter(
val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias } }
val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } }
val dmMember by room.getDirectRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember?.userId)
val roomType = getRoomType(dmMember)
val roomCallState = roomCallStatePresenter.present()
val joinedMemberCount by remember { derivedStateOf { roomInfo.joinedMembersCount } }
@@ -174,7 +175,7 @@ class RoomDetailsPresenter(
}
}
val roomMemberDetailsState = roomMemberDetailsPresenter?.present()
val dmOtherMemberDetailsState = roomMemberDetailsPresenter?.present()
val hasMemberVerificationViolations by produceState(false) {
room.roomMemberIdentityStateChange(waitForEncryption = true)
@@ -196,7 +197,7 @@ class RoomDetailsPresenter(
canEdit = roomType == RoomDetailsType.Room && permissions.editDetailsPermissions.hasAny,
roomCallState = roomCallState,
roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState,
dmOtherMemberDetailsState = dmOtherMemberDetailsState,
leaveRoomState = leaveRoomState,
roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(),
isFavorite = isFavorite,
@@ -209,7 +210,7 @@ class RoomDetailsPresenter(
knockRequestsCount = knockRequestsCount,
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
hasMemberVerificationViolations = hasMemberVerificationViolations,
canReportRoom = canReportRoom,
canReportRoom = !isDm && canReportRoom,
isTombstoned = roomInfo.successorRoom != null,
showDebugInfo = isDeveloperModeEnabled,
roomVersion = roomInfo.roomVersion,
@@ -220,9 +221,9 @@ class RoomDetailsPresenter(
}
@Composable
private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) {
dmMemberState?.let { roomMember ->
roomMembersDetailsPresenterFactory.create(roomMember.userId)
private fun roomMemberDetailsPresenter(userId: UserId?) = remember(userId) {
userId?.let { userId ->
roomMembersDetailsPresenterFactory.create(userId)
}
}
@@ -31,7 +31,7 @@ data class RoomDetailsState(
val memberCount: Long,
val isEncrypted: Boolean,
val roomType: RoomDetailsType,
val roomMemberDetailsState: UserProfileState?,
val dmOtherMemberDetailsState: UserProfileState?,
val canEdit: Boolean,
val canInvite: Boolean,
val roomCallState: RoomCallState,
@@ -106,7 +106,7 @@ fun aRoomDetailsState(
canEdit: Boolean = false,
roomCallState: RoomCallState = aStandByCallState(),
roomType: RoomDetailsType = RoomDetailsType.Room,
roomMemberDetailsState: UserProfileState? = null,
dmOtherMemberDetailsState: UserProfileState? = null,
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
roomNotificationSettings: RoomNotificationSettings = aRoomNotificationSettings(),
isFavorite: Boolean = false,
@@ -137,7 +137,7 @@ fun aRoomDetailsState(
canEdit = canEdit,
roomCallState = roomCallState,
roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState,
dmOtherMemberDetailsState = dmOtherMemberDetailsState,
leaveRoomState = leaveRoomState,
roomNotificationSettings = roomNotificationSettings,
isFavorite = isFavorite,
@@ -184,7 +184,7 @@ fun aDmRoomDetailsState(
isEncrypted = isEncrypted,
canInvite = true,
roomType = RoomDetailsType.Dm(otherMember = aDmRoomMember(isIgnored = isDmMemberIgnored)),
roomMemberDetailsState = aUserProfileState(
dmOtherMemberDetailsState = aUserProfileState(
isBlocked = AsyncData.Success(isDmMemberIgnored),
verificationState = dmRoomMemberVerificationState,
)
@@ -43,6 +43,7 @@ import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.roomcall.api.hasPermissionToJoin
import io.element.android.features.userprofile.api.UserProfileState
import io.element.android.features.userprofile.api.UserProfileVerificationState
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
@@ -64,7 +65,7 @@ import io.element.android.libraries.designsystem.modifiers.niceClickable
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.preview.PreviewWithExtraLargeHeight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
@@ -96,6 +97,9 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
/**
* Ref: https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=21-120385
*/
@Composable
fun RoomDetailsView(
state: RoomDetailsState,
@@ -229,66 +233,11 @@ fun RoomDetailsView(
)
}
}
// Room content
PreferenceCategory {
if (state.roomNotificationSettings != null) {
NotificationItem(
isDefaultMode = state.roomNotificationSettings.isDefault,
openRoomNotificationSettings = openRoomNotificationSettings
)
}
FavoriteItem(
isFavorite = state.isFavorite,
onFavoriteChanges = {
state.eventSink(RoomDetailsEvent.SetFavorite(it))
}
MediaGalleryItem(
onClick = openMediaGallery
)
if (state.canShowSecurityAndPrivacy) {
SecurityAndPrivacyItem(
onClick = onSecurityAndPrivacyClick
)
}
}
state.roomMemberDetailsState?.let { dmMemberDetails ->
if (state.canInvite) {
PreferenceCategory {
InviteItem(onClick = invitePeople)
}
}
PreferenceCategory {
ProfileItem(
verificationState = dmMemberDetails.verificationState,
onClick = { onProfileClick(dmMemberDetails.userId) }
)
}
}
if (state.roomType is RoomDetailsType.Room) {
PreferenceCategory {
MembersItem(
memberCount = state.memberCount,
hasVerificationViolations = state.hasMemberVerificationViolations,
openRoomMemberList = openRoomMemberList,
)
if (state.canShowKnockRequests) {
KnockRequestsItem(
knockRequestsCount = state.knockRequestsCount,
onKnockRequestsClick = onKnockRequestsClick
)
}
if (state.displayRolesAndPermissionsSettings) {
ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_details_roles_and_permissions)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())),
onClick = openAdminSettings,
)
}
}
}
PreferenceCategory {
PinnedMessagesItem(
pinnedMessagesCount = state.pinnedMessagesCount,
onPinnedMessagesClick = onPinnedMessagesClick
@@ -296,23 +245,87 @@ fun RoomDetailsView(
PollsItem(
openPollHistory = openPollHistory
)
MediaGalleryItem(
onClick = openMediaGallery
}
when (state.roomType) {
is RoomDetailsType.Room -> {
PreferenceCategory {
MembersItem(
memberCount = state.memberCount,
hasVerificationViolations = state.hasMemberVerificationViolations,
openRoomMemberList = openRoomMemberList,
)
if (state.canShowKnockRequests) {
KnockRequestsItem(
knockRequestsCount = state.knockRequestsCount,
onKnockRequestsClick = onKnockRequestsClick
)
}
if (state.displayRolesAndPermissionsSettings) {
ListItem(
headlineContent = { Text(stringResource(R.string.screen_room_details_roles_and_permissions)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())),
onClick = openAdminSettings,
)
}
}
}
is RoomDetailsType.Dm -> {
if (state.canInvite) {
// Note: for rooms the invite action is a Main action
PreferenceCategory {
InviteItem(onClick = invitePeople)
}
}
state.dmOtherMemberDetailsState?.let { dmMemberDetails ->
PreferenceCategory {
ProfileItem(
verificationState = dmMemberDetails.verificationState,
onClick = { onProfileClick(dmMemberDetails.userId) }
)
}
}
}
}
PreferenceCategory {
if (state.roomNotificationSettings != null) {
NotificationItem(
isDefaultMode = state.roomNotificationSettings.isDefault,
openRoomNotificationSettings = openRoomNotificationSettings
)
}
FavoriteItem(
isFavorite = state.isFavorite,
onFavoriteChanges = {
state.eventSink(RoomDetailsEvent.SetFavorite(it))
}
)
if (state.canShowSecurityAndPrivacy && state.roomType is RoomDetailsType.Room) {
SecurityAndPrivacyItem(
onClick = onSecurityAndPrivacyClick
)
}
}
if (state.roomType is RoomDetailsType.Dm && state.roomMemberDetailsState != null) {
val roomMemberState = state.roomMemberDetailsState
BlockUserSection(roomMemberState)
BlockUserDialogs(roomMemberState)
if (state.isEncrypted) {
PreferenceCategory(
title = stringResource(R.string.screen_room_details_security_title)
) {
ListItem(
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
headlineContent = {
Text(stringResource(id = R.string.screen_room_details_encryption_enabled_title))
},
supportingContent = {
Text(stringResource(id = R.string.screen_room_details_encryption_enabled_subtitle))
},
)
}
}
OtherActionsSection(
dmOtherMemberDetailsState = state.dmOtherMemberDetailsState,
canReportRoom = state.canReportRoom,
onReportRoomClick = onReportRoomClick,
onLeaveRoomClick = { state.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = true)) }
)
if (state.showDebugInfo) {
DebugInfoSection(
roomId = state.roomId,
@@ -803,8 +816,13 @@ private fun OtherActionsSection(
canReportRoom: Boolean,
onReportRoomClick: () -> Unit,
onLeaveRoomClick: () -> Unit,
dmOtherMemberDetailsState: UserProfileState?,
) {
PreferenceCategory(showTopDivider = true) {
PreferenceCategory {
if (dmOtherMemberDetailsState != null) {
BlockUserSection(dmOtherMemberDetailsState)
BlockUserDialogs(dmOtherMemberDetailsState)
}
if (canReportRoom) {
ListItem(
headlineContent = {
@@ -832,7 +850,7 @@ private fun DebugInfoSection(
roomVersion: String?,
) {
val context = LocalContext.current
PreferenceCategory(showTopDivider = true) {
PreferenceCategory {
val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard)
ListItem(
headlineContent = {
@@ -870,17 +888,17 @@ private fun DebugInfoSection(
}
}
@PreviewWithLargeHeight
@PreviewWithExtraLargeHeight
@Composable
internal fun RoomDetailsPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =
ElementPreviewLight { ContentToPreview(state) }
@PreviewWithLargeHeight
@PreviewWithExtraLargeHeight
@Composable
internal fun RoomDetailsDarkPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =
ElementPreviewDark { ContentToPreview(state) }
@PreviewWithLargeHeight
@PreviewWithExtraLargeHeight
@Composable
internal fun RoomDetailsA11yPreview() = ElementPreview {
ContentToPreview(
@@ -334,7 +334,8 @@ class RoomDetailsViewTest {
setRoomDetailView(
state = aRoomDetailsState(
eventSink = EventsRecorder(expectEvents = false),
roomMemberDetailsState = aUserProfileState(userId = A_USER_ID),
roomType = RoomDetailsType.Dm(aDmRoomMember(userId = A_USER_ID)),
dmOtherMemberDetailsState = aUserProfileState(userId = A_USER_ID),
),
onProfileClick = callback,
)
@@ -352,7 +353,7 @@ class RoomDetailsViewTest {
roomType = RoomDetailsType.Dm(
aDmRoomMember(userId = UserId("@other:local.org")),
),
roomMemberDetailsState = aUserProfileState(userId = A_USER_ID),
dmOtherMemberDetailsState = aUserProfileState(userId = A_USER_ID),
canInvite = true,
),
invitePeople = callback,