From 2b12e22cdd298c1d7e2d68a02f21145d5bfaf005 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 18 Dec 2025 11:42:07 +0100 Subject: [PATCH 01/11] change(room permissions): remove change settings permissions --- .../impl/permissions/ChangeRoomPermissionsPresenter.kt | 2 -- .../impl/permissions/ChangeRoomPermissionsState.kt | 2 -- .../impl/permissions/ChangeRoomPermissionsView.kt | 1 - 3 files changed, 5 deletions(-) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt index d085be5920..0032af6b01 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt @@ -52,7 +52,6 @@ class ChangeRoomPermissionsPresenter( ) RoomPermissionsSection.ManageSpace -> persistentListOf( RoomPermissionType.SPACE_MANAGE_ROOMS, - RoomPermissionType.CHANGE_SETTINGS, ) } @@ -108,7 +107,6 @@ class ChangeRoomPermissionsPresenter( RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel) RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel) RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions?.copy(spaceChild = powerLevel) - RoomPermissionType.CHANGE_SETTINGS -> currentPermissions?.copy(stateDefault = powerLevel) } } is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt index 88309ea023..f538e0a36f 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt @@ -38,7 +38,6 @@ data class ChangeRoomPermissionsState( RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(currentPermissions.roomAvatar) RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(currentPermissions.roomTopic) RoomPermissionType.SPACE_MANAGE_ROOMS -> RoomMember.Role.forPowerLevel(currentPermissions.spaceChild) - RoomPermissionType.CHANGE_SETTINGS -> RoomMember.Role.forPowerLevel(currentPermissions.stateDefault) } return when (role) { is RoomMember.Role.Owner, @@ -84,5 +83,4 @@ enum class RoomPermissionType { ROOM_AVATAR, ROOM_TOPIC, SPACE_MANAGE_ROOMS, - CHANGE_SETTINGS, } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt index 5dea091be4..c9d9ed435f 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt @@ -127,7 +127,6 @@ private fun titleForType(type: RoomPermissionType): String = when (type) { RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar) RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic) RoomPermissionType.SPACE_MANAGE_ROOMS -> stringResource(R.string.screen_room_change_permissions_manage_space_rooms) - RoomPermissionType.CHANGE_SETTINGS -> stringResource(R.string.screen_room_change_permissions_change_settings) } @PreviewsDayNight From a0d6fddf73286ac22467ac6b1a41d513b0cd94fb Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 18 Dec 2025 21:36:55 +0100 Subject: [PATCH 02/11] change(member moderation): fix available moderation actions --- .../impl/RoomMemberModerationPresenter.kt | 28 ++++++++------- .../matrix/ui/model/RoomInfoExtension.kt | 21 +++++++++--- .../matrix/ui/room/MatrixRoomState.kt | 34 ------------------- 3 files changed, 32 insertions(+), 51 deletions(-) delete mode 100644 libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 16a11aee96..cfb9412394 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember @@ -35,7 +36,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState +import io.element.android.libraries.matrix.ui.model.powerLevelOf import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -56,11 +57,14 @@ class RoomMemberModerationPresenter( @Composable override fun present(): RoomMemberModerationState { val coroutineScope = rememberCoroutineScope() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val permissions by room.permissionsAsState(RoomMemberModerationPermissions.DEFAULT) { perms -> perms.roomMemberModerationPermissions() } - val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value) + val currentUserPowerLevel by remember { + room.roomInfoFlow.mapState { info -> + info.powerLevelOf(room.sessionId) + } + }.collectAsState() val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } @@ -83,7 +87,7 @@ class RoomMemberModerationPresenter( moderationActions.value = computeModerationActions( member = member, permissions = permissions, - currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, + currentUserPowerLevel = currentUserPowerLevel, ) } is RoomMemberModerationEvents.ProcessAction -> { @@ -148,26 +152,26 @@ class RoomMemberModerationPresenter( private fun computeModerationActions( member: RoomMember?, permissions: RoomMemberModerationPermissions, - currentUserMemberPowerLevel: Long, + currentUserPowerLevel: Long, ): ImmutableList { return buildList { add(ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true)) // Assume the member is a regular user when it's unknown val targetMemberPowerLevel = member?.powerLevel ?: 0 - val canModerateThisUser = currentUserMemberPowerLevel > targetMemberPowerLevel + val canModerateThisUser = currentUserPowerLevel > targetMemberPowerLevel // Assume the member is joined when it's unknown val membership = member?.membership ?: RoomMembershipState.JOIN if (permissions.canKick) { - val isKickEnabled = canModerateThisUser && membership.isActive() - add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled)) - } - if (permissions.canBan) { + // Unban requires kick permission instead of a dedicated unban permission if (membership == RoomMembershipState.BAN) { add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser)) - } else { - add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser)) + } else if (membership != RoomMembershipState.LEAVE) { + add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = canModerateThisUser)) } } + if (permissions.canBan && membership != RoomMembershipState.BAN) { + add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser)) + } }.toImmutableList() } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt index f9a86c9bd7..3a03d8329b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt @@ -21,6 +21,20 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData( size = size, ) +/** + * Returns the power level of the user in the room. + * If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns the power level of [RoomMember.Role.Owner]. + * Otherwise, checks the room's power levels for the user's power level. + * If no specific power level is set for the user, defaults to 0. + */ +fun RoomInfo.powerLevelOf(userId: UserId): Long { + return if (privilegedCreatorRole && creators.contains(userId)) { + RoomMember.Role.Owner(isCreator = true).powerLevel + } else { + roomPowerLevels?.powerLevelOf(userId = userId) ?: 0L + } +} + /** * Returns the role of the user in the room. * If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns [RoomMember.Role.Owner]. @@ -28,9 +42,6 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData( * If no specific power level is set for the user, defaults to [RoomMember.Role.User]. */ fun RoomInfo.roleOf(userId: UserId): RoomMember.Role { - return if (privilegedCreatorRole && creators.contains(userId)) { - RoomMember.Role.Owner(isCreator = true) - } else { - roomPowerLevels?.roleOf(userId) ?: RoomMember.Role.User - } + val powerLevel = powerLevelOf(userId = userId) + return RoomMember.Role.forPowerLevel(powerLevel) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt deleted file mode 100644 index d4e6127b69..0000000000 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.ui.room - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.ui.model.roleOf - -@Composable -fun BaseRoom.userPowerLevelAsState(updateKey: Long): State { - return produceState(initialValue = 0, key1 = updateKey) { - value = userRole(sessionId) - .getOrDefault(RoomMember.Role.User) - .powerLevel - } -} - -@Composable -fun BaseRoom.isOwnUserAdmin(): Boolean { - val roomInfo by roomInfoFlow.collectAsState() - val role = roomInfo.roleOf(sessionId) - return role == RoomMember.Role.Admin || role is RoomMember.Role.Owner -} From 4c2aa0ba33c3f564fd7490c1fb8579fc459a5b90 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 18 Dec 2025 21:37:56 +0100 Subject: [PATCH 03/11] change(room permissions): fix some role&permissions inconsistencies after last changes --- .../impl/roles/ChangeRolesPresenter.kt | 8 +++-- .../impl/root/RolesAndPermissionsPresenter.kt | 14 ++++++-- .../impl/root/RolesAndPermissionsState.kt | 14 ++++++-- .../root/RolesAndPermissionsStateProvider.kt | 7 ++-- .../impl/root/RolesAndPermissionsView.kt | 33 ++++++++----------- 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt index 4181fb6e2e..3989a76df3 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt @@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole 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.model.powerLevelOf import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator import io.element.android.services.analytics.api.AnalyticsService @@ -124,9 +125,10 @@ class ChangeRolesPresenter( val roomInfo by room.roomInfoFlow.collectAsState() fun canChangeMemberRole(userId: UserId): Boolean { - val currentUserRole = roomInfo.roleOf(room.sessionId) - val otherUserRole = roomInfo.roleOf(userId) - return currentUserRole.powerLevel > otherUserRole.powerLevel + val currentUserPowerLevel = roomInfo.powerLevelOf(room.sessionId) + val otherUserPowerLevel = roomInfo.powerLevelOf(userId) + return currentUserPowerLevel > otherUserPowerLevel && + currentUserPowerLevel >= role.powerLevel } fun handleEvent(event: ChangeRolesEvent) { diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt index bde20affd8..f80899fc0f 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.powerlevels.userCountWithRole import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -49,7 +50,16 @@ class RolesAndPermissionsPresenter( room.userCountWithRole { role -> role is RoomMember.Role.Admin || role is RoomMember.Role.Owner } }.collectAsState(null) - val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } } + val availableDemoteActions by remember { + derivedStateOf { + val currentRole = roomInfo.roleOf(room.sessionId) + when (currentRole) { + is RoomMember.Role.Admin -> persistentListOf(DemoteActions.ToModerator, DemoteActions.ToMember) + is RoomMember.Role.Moderator -> persistentListOf(DemoteActions.ToMember) + else -> persistentListOf() + } + } + } val changeOwnRoleAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val resetPermissionsAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } @@ -78,7 +88,7 @@ class RolesAndPermissionsPresenter( roomSupportsOwnerRole = roomInfo.privilegedCreatorRole, adminCount = adminCount, moderatorCount = moderatorCount, - canDemoteSelf = canDemoteSelf.value, + availableDemoteActions = availableDemoteActions, changeOwnRoleAction = changeOwnRoleAction.value, resetPermissionsAction = resetPermissionsAction.value, eventSink = ::handleEvent, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt index 90785d1b38..9064f559c6 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt @@ -8,14 +8,24 @@ package io.element.android.features.rolesandpermissions.impl.root +import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember +import kotlinx.collections.immutable.ImmutableList data class RolesAndPermissionsState( val roomSupportsOwnerRole: Boolean, val adminCount: Int?, val moderatorCount: Int?, - val canDemoteSelf: Boolean, + val availableDemoteActions: ImmutableList, val changeOwnRoleAction: AsyncAction, val resetPermissionsAction: AsyncAction, val eventSink: (RolesAndPermissionsEvents) -> Unit, -) +) { + val canDemoteSelf = availableDemoteActions.isNotEmpty() +} + +enum class DemoteActions(val role: RoomMember.Role, val titleRes: Int) { + ToModerator(RoomMember.Role.Moderator, R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator), + ToMember(RoomMember.Role.User, R.string.screen_room_roles_and_permissions_change_role_demote_to_member) +} diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt index 23448c0351..cf0163d0ba 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt @@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction +import kotlinx.collections.immutable.toImmutableList class RolesAndPermissionsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -46,7 +47,7 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider = listOf(DemoteActions.ToModerator, DemoteActions.ToMember), changeOwnRoleAction: AsyncAction = AsyncAction.Uninitialized, resetPermissionsAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (RolesAndPermissionsEvents) -> Unit = {}, ) = RolesAndPermissionsState( roomSupportsOwnerRole = roomSupportsOwners, adminCount = adminCount, - canDemoteSelf = canDemoteSelf, + availableDemoteActions = availableDemoteActions.toImmutableList(), moderatorCount = moderatorCount, changeOwnRoleAction = changeOwnRoleAction, resetPermissionsAction = resetPermissionsAction, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt index c0cb15983c..82b49f0d81 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt @@ -39,8 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.ListSectionHea import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.hide -import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList @Composable fun RolesAndPermissionsView( @@ -117,6 +117,7 @@ fun RolesAndPermissionsView( when (state.changeOwnRoleAction) { is AsyncAction.Confirming -> { ChangeOwnRoleBottomSheet( + availableDemoteActions = state.availableDemoteActions, eventSink = state.eventSink, ) } @@ -136,6 +137,7 @@ fun RolesAndPermissionsView( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ChangeOwnRoleBottomSheet( + availableDemoteActions: ImmutableList, eventSink: (RolesAndPermissionsEvents) -> Unit, ) { val coroutineScope = rememberCoroutineScope() @@ -164,24 +166,17 @@ private fun ChangeOwnRoleBottomSheet( style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textPrimary, ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) }, - onClick = { - sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) - } - }, - style = ListItemStyle.Destructive, - ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) }, - onClick = { - sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User)) - } - }, - style = ListItemStyle.Destructive, - ) + for (demoteAction in availableDemoteActions) { + ListItem( + headlineContent = { Text(stringResource(demoteAction.titleRes)) }, + onClick = { + sheetState.hide(coroutineScope) { + eventSink(RolesAndPermissionsEvents.DemoteSelfTo(demoteAction.role)) + } + }, + style = ListItemStyle.Destructive, + ) + } ListItem( headlineContent = { Text(stringResource(CommonStrings.action_cancel)) }, onClick = ::dismiss, From 74dd3f381e1e55a6c84bbe99f292665c82d24474 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 18 Dec 2025 21:45:49 +0100 Subject: [PATCH 04/11] quality: fix tests after changes --- .../impl/RoomMemberModerationPresenterTest.kt | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt index 3f59151eea..3397f3a40a 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt @@ -21,11 +21,14 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService @@ -33,6 +36,7 @@ import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -161,7 +165,6 @@ class RoomMemberModerationPresenterTest { assertThat(updatedState.selectedUser).isEqualTo(targetUser) assertThat(updatedState.actions).containsExactly( ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), - ModerationActionState(action = ModerationAction.KickUser, isEnabled = false), ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = true), ) } @@ -223,9 +226,11 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() room.baseRoom.givenUpdateMembersResult { // Simulate the member list being updated - room.givenRoomMembersState(RoomMembersState.Ready( - persistentListOf(aRoomMember()) - )) + room.givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(aRoomMember()) + ) + ) } createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -251,9 +256,11 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() room.baseRoom.givenUpdateMembersResult { // Simulate the member list being updated - room.givenRoomMembersState(RoomMembersState.Ready( - persistentListOf(aRoomMember()) - )) + room.givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(aRoomMember()) + ) + ) } createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -279,9 +286,11 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() room.baseRoom.givenUpdateMembersResult { // Simulate the member list being updated - room.givenRoomMembersState(RoomMembersState.Ready( - persistentListOf(aRoomMember()) - )) + room.givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(aRoomMember()) + ) + ) } createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -361,7 +370,13 @@ class RoomMemberModerationPresenterTest { canKick = canKick ), userRoleResult = { Result.success(myUserRole) }, - updateMembersResult = { Result.success(Unit) } + updateMembersResult = { Result.success(Unit) }, + initialRoomInfo = aRoomInfo( + roomPowerLevels = RoomPowerLevels( + values = defaultRoomPowerLevelValues(), + users = persistentMapOf(A_USER_ID to myUserRole.powerLevel) + ) + ) ), ).apply { val roomMembers = listOfNotNull(targetRoomMember).toImmutableList() From f13d9259c5baff59515769967deb6d55918f3405 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 19 Dec 2025 17:10:28 +0100 Subject: [PATCH 05/11] change(room permissions): user can edit only roles <= to his own role --- .../ChangeRoomPermissionsPresenter.kt | 8 ++++ .../permissions/ChangeRoomPermissionsState.kt | 47 ++++++++++++++----- .../ChangeRoomPermissionsStateProvider.kt | 3 ++ .../permissions/ChangeRoomPermissionsView.kt | 3 +- .../ChangeRoomPermissionsPresenterTest.kt | 38 ++++++++++++++- 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt index 0032af6b01..552f1d0ec6 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt @@ -10,6 +10,7 @@ package io.element.android.features.rolesandpermissions.impl.permissions import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -20,9 +21,11 @@ import dev.zacsweers.metro.Inject import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues +import io.element.android.libraries.matrix.ui.model.powerLevelOf import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableMap @@ -89,6 +92,10 @@ class ChangeRoomPermissionsPresenter( derivedStateOf { initialPermissions != currentPermissions } } + val ownPowerLevel by remember { + room.roomInfoFlow.mapState { it.powerLevelOf(room.sessionId) } + }.collectAsState() + fun handleEvent(event: ChangeRoomPermissionsEvent) { when (event) { is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> { @@ -123,6 +130,7 @@ class ChangeRoomPermissionsPresenter( } } return ChangeRoomPermissionsState( + ownPowerLevel = ownPowerLevel, currentPermissions = currentPermissions, itemsBySection = itemsBySection, hasChanges = hasChanges, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt index f538e0a36f..535f2b4a71 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt @@ -18,34 +18,55 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentListOf data class ChangeRoomPermissionsState( + private val ownPowerLevel: Long, val currentPermissions: RoomPowerLevelsValues?, val itemsBySection: ImmutableMap>, val hasChanges: Boolean, val saveAction: AsyncAction, val eventSink: (ChangeRoomPermissionsEvent) -> Unit, ) { + private val ownRole = RoomMember.Role.forPowerLevel(ownPowerLevel) + + // Roles that the user can select based on their own role + val selectableRoles: ImmutableList = when (ownRole) { + is RoomMember.Role.Owner, + RoomMember.Role.Admin -> persistentListOf(SelectableRole.Admin, SelectableRole.Moderator, SelectableRole.Everyone) + RoomMember.Role.Moderator -> persistentListOf(SelectableRole.Moderator, SelectableRole.Everyone) + RoomMember.Role.User -> persistentListOf(SelectableRole.Everyone) + } + fun selectedRoleForType(type: RoomPermissionType): SelectableRole? { - if (currentPermissions == null) return null - val role = when (type) { - RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(currentPermissions.ban) - RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(currentPermissions.invite) - RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(currentPermissions.kick) - RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.eventsDefault) - RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.redactEvents) - RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(currentPermissions.roomName) - RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(currentPermissions.roomAvatar) - RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(currentPermissions.roomTopic) - RoomPermissionType.SPACE_MANAGE_ROOMS -> RoomMember.Role.forPowerLevel(currentPermissions.spaceChild) - } - return when (role) { + val powerLevel = currentPowerLevelForType(type = type) ?: return null + return when (RoomMember.Role.forPowerLevel(powerLevel)) { is RoomMember.Role.Owner, RoomMember.Role.Admin -> SelectableRole.Admin RoomMember.Role.Moderator -> SelectableRole.Moderator RoomMember.Role.User -> SelectableRole.Everyone } } + + fun canChangePermission(type: RoomPermissionType): Boolean { + val currentPowerLevel = currentPowerLevelForType(type) ?: return false + return ownPowerLevel >= currentPowerLevel + } + + private fun currentPowerLevelForType(type: RoomPermissionType): Long? { + if (currentPermissions == null) return null + return when (type) { + RoomPermissionType.BAN -> currentPermissions.ban + RoomPermissionType.INVITE -> currentPermissions.invite + RoomPermissionType.KICK -> currentPermissions.kick + RoomPermissionType.SEND_EVENTS -> currentPermissions.eventsDefault + RoomPermissionType.REDACT_EVENTS -> currentPermissions.redactEvents + RoomPermissionType.ROOM_NAME -> currentPermissions.roomName + RoomPermissionType.ROOM_AVATAR -> currentPermissions.roomAvatar + RoomPermissionType.ROOM_TOPIC -> currentPermissions.roomTopic + RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions.spaceChild + } + } } enum class RoomPermissionsSection { diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt index 6c4e5aa379..2760272d8a 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt @@ -19,6 +19,7 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider get() = sequenceOf( aChangeRoomPermissionsState(), + aChangeRoomPermissionsState(ownPowerLevel = RoomMember.Role.Moderator.powerLevel), aChangeRoomPermissionsState(hasChanges = true), aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading), aChangeRoomPermissionsState( @@ -31,12 +32,14 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider> = ChangeRoomPermissionsPresenter.buildItems(false), hasChanges: Boolean = false, saveAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, ) = ChangeRoomPermissionsState( + ownPowerLevel = ownPowerLevel, currentPermissions = currentPermissions, itemsBySection = itemsBySection.toImmutableMap(), hasChanges = hasChanges, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt index c9d9ed435f..b7084acf57 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt @@ -74,7 +74,8 @@ fun ChangeRoomPermissionsView( PreferenceDropdown( title = titleForType(permissionType), selectedOption = state.selectedRoleForType(permissionType), - options = SelectableRole.entries.toImmutableList(), + options = state.selectableRoles, + enabled = state.canChangePermission(permissionType), onSelectOption = { role -> state.eventSink( ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction( diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt index 9d94c4831e..34b1ac0da5 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt @@ -16,13 +16,18 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.test.runTest import org.junit.Test @@ -70,6 +75,28 @@ class ChangeRoomPermissionsPresenterTest { } } + @Test + fun `present - check canChangePermissions and selectableOptions for moderator`() = runTest { + val room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + initialRoomInfo = initialRoomInfo(role = Moderator), + powerLevelsResult = { Result.success(defaultPermissions()) } + ), + ) + val presenter = createChangeRoomPermissionsPresenter(room = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + assertThat(state.selectableRoles).containsExactly(SelectableRole.Moderator, SelectableRole.Everyone) + for (sectionItems in state.itemsBySection.values) { + for (permissionType in sectionItems) { + assertThat(state.canChangePermission(permissionType)).isTrue() + } + } + } + } + @Test fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest { val presenter = createChangeRoomPermissionsPresenter() @@ -266,7 +293,9 @@ class ChangeRoomPermissionsPresenterTest { private fun createChangeRoomPermissionsPresenter( room: FakeJoinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }), + baseRoom = FakeBaseRoom( + initialRoomInfo = initialRoomInfo(), + powerLevelsResult = { Result.success(defaultPermissions()) }), ), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ) = ChangeRoomPermissionsPresenter( @@ -274,6 +303,13 @@ class ChangeRoomPermissionsPresenterTest { analyticsService = analyticsService, ) + private fun initialRoomInfo(role: RoomMember.Role = Admin) = aRoomInfo( + roomPowerLevels = RoomPowerLevels( + values = defaultPermissions(), + users = persistentMapOf(A_SESSION_ID to role.powerLevel), + ) + ) + private fun defaultPermissions() = defaultRoomPowerLevelValues() private suspend fun TurbineTestContext.awaitUpdatedItem(): ChangeRoomPermissionsState { From 044d027488fe26169e27f96d8a386217e046b1d0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 19 Dec 2025 17:35:22 +0100 Subject: [PATCH 06/11] quality: format code --- .../impl/permissions/ChangeRoomPermissionsView.kt | 1 - .../impl/permissions/ChangeRoomPermissionsPresenterTest.kt | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt index b7084acf57..529df0d50d 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt index 34b1ac0da5..ba7d47adb2 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt @@ -295,7 +295,8 @@ class ChangeRoomPermissionsPresenterTest { room: FakeJoinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( initialRoomInfo = initialRoomInfo(), - powerLevelsResult = { Result.success(defaultPermissions()) }), + powerLevelsResult = { Result.success(defaultPermissions()) } + ), ), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ) = ChangeRoomPermissionsPresenter( From 7de4b7d3af29cc669a5796b0ac135eeb04944836 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 22 Dec 2025 10:29:46 +0000 Subject: [PATCH 07/11] Update screenshots --- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png | 4 ++-- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png | 4 ++-- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png | 4 ++-- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png | 4 ++-- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_5_en.png | 4 ++-- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_6_en.png | 3 +++ ....impl.permissions_ChangeRoomPermissionsView_Night_1_en.png | 4 ++-- ....impl.permissions_ChangeRoomPermissionsView_Night_2_en.png | 4 ++-- ....impl.permissions_ChangeRoomPermissionsView_Night_3_en.png | 4 ++-- ....impl.permissions_ChangeRoomPermissionsView_Night_4_en.png | 4 ++-- ....impl.permissions_ChangeRoomPermissionsView_Night_5_en.png | 4 ++-- ....impl.permissions_ChangeRoomPermissionsView_Night_6_en.png | 3 +++ 12 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png index f3ef799045..a6d9a85284 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f82f648e9f5d67abfe2cf783df7e70568a74ff5dcae876e80b54995981a65886 -size 49650 +oid sha256:4eadce77750c8946f3d506a59925901fe3a44ac61ac08e4a23b6517f4596eece +size 48910 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png index 83b5995ef4..f3ef799045 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ebf86fc0ac80375edd1730caab4a669aa2cc545b2ea946981901fd360114320 -size 44555 +oid sha256:f82f648e9f5d67abfe2cf783df7e70568a74ff5dcae876e80b54995981a65886 +size 49650 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png index 0c1593bc85..83b5995ef4 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fae74539f53342110e50a077ad78b8422d08ba931b948b2b167d90f6cbe8ade6 -size 43535 +oid sha256:0ebf86fc0ac80375edd1730caab4a669aa2cc545b2ea946981901fd360114320 +size 44555 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png index 5a6d410ad7..0c1593bc85 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61bced1ea61f5952e83956b4c4447a0371fe99c874e2a20df431bc59e02dc838 -size 50316 +oid sha256:fae74539f53342110e50a077ad78b8422d08ba931b948b2b167d90f6cbe8ade6 +size 43535 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en.png index 16c2a363d8..5a6d410ad7 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73d9461a1d51964ba1a912376658e3e550e952821f7aa1d9bff765ed11deb920 -size 49123 +oid sha256:61bced1ea61f5952e83956b4c4447a0371fe99c874e2a20df431bc59e02dc838 +size 50316 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en.png new file mode 100644 index 0000000000..16c2a363d8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73d9461a1d51964ba1a912376658e3e550e952821f7aa1d9bff765ed11deb920 +size 49123 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png index cb24aa2578..43f63ee7f9 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:192fce45ea8d83d8f7aa40f9337b2dd92c3b1194da611fed1c42fd771d74fe0a -size 48534 +oid sha256:009e04296b2dbeb127e6d3b9306dce996fa35bf4443d34abbdf2b8cc14126053 +size 47615 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png index b16e4b3ba3..cb24aa2578 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3ed16d04a1106b82183c4fe6ae741b598448050e2ddae2fa842b0b01d475e0a -size 43230 +oid sha256:192fce45ea8d83d8f7aa40f9337b2dd92c3b1194da611fed1c42fd771d74fe0a +size 48534 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png index 7ae9ff70b6..b16e4b3ba3 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cb2f7d7cad98d111ed2c1c0b8120e5837f2586f845594e581b41b7cb96e00f2 -size 41555 +oid sha256:b3ed16d04a1106b82183c4fe6ae741b598448050e2ddae2fa842b0b01d475e0a +size 43230 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png index 1aa08a4e12..7ae9ff70b6 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0862da8d120e276afd6bdef6a9b247d3976ae70adab47077d8749600dd695884 -size 48280 +oid sha256:0cb2f7d7cad98d111ed2c1c0b8120e5837f2586f845594e581b41b7cb96e00f2 +size 41555 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en.png index 25c50a55cb..1aa08a4e12 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48e5f7460a13798d2f2963930e4e7a95c6a1ffa704d034ec21e33ba1bde6606c -size 48123 +oid sha256:0862da8d120e276afd6bdef6a9b247d3976ae70adab47077d8749600dd695884 +size 48280 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en.png new file mode 100644 index 0000000000..25c50a55cb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48e5f7460a13798d2f2963930e4e7a95c6a1ffa704d034ec21e33ba1bde6606c +size 48123 From a12c37d6d9544b772fb530db69d2ef2cee859829 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 22 Dec 2025 15:25:54 +0100 Subject: [PATCH 08/11] misc : add enabled param to ListItemContent.Custom View --- .../shared/blockuser/BlockUserSection.kt | 2 +- .../components/list/ListItemContent.kt | 4 ++-- .../preferences/PreferenceCheckbox.kt | 1 - .../preferences/PreferenceDropdown.kt | 24 +++++++++++++------ .../components/preferences/PreferenceSlide.kt | 1 - .../preferences/PreferenceSwitch.kt | 1 - .../preferences/components/PreferenceIcon.kt | 3 +-- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt index 7a73c60e4a..c3caffa7f3 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt @@ -70,7 +70,7 @@ private fun PreferenceBlockUser( isLoading: Boolean, eventSink: (UserProfileEvents) -> Unit, ) { - val loadingCurrentValue = @Composable { + val loadingCurrentValue = @Composable { _: Boolean -> CircularProgressIndicator( modifier = Modifier .progressSemantics() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt index 022100abe1..e8add0369f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt @@ -85,7 +85,7 @@ sealed interface ListItemContent { data class Text(val text: String) : ListItemContent /** Displays any custom content. */ - data class Custom(val content: @Composable () -> Unit) : ListItemContent + data class Custom(val content: @Composable (enabled: Boolean) -> Unit) : ListItemContent /** Displays a badge. */ data object Badge : ListItemContent @@ -131,7 +131,7 @@ sealed interface ListItemContent { is Counter -> { CounterAtom(count = count) } - is Custom -> content() + is Custom -> content(isItemEnabled) } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt index 5277aca1c7..6b4a8e1e0c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt @@ -43,7 +43,6 @@ fun PreferenceCheckbox( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt index 23fdec9942..5fcbcab475 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt @@ -40,7 +40,7 @@ import io.element.android.libraries.designsystem.theme.components.DropdownMenuIt import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.toEnabledColor +import io.element.android.libraries.designsystem.toIconSecondaryEnabledColor import io.element.android.libraries.designsystem.toSecondaryEnabledColor import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -64,7 +64,6 @@ fun PreferenceDropdown( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { @@ -72,7 +71,6 @@ fun PreferenceDropdown( style = ElementTheme.typography.fontBodyLgRegular, modifier = Modifier.fillMaxWidth(), text = title, - color = enabled.toEnabledColor(), ) }, supportingContent = supportingText?.let { @@ -80,22 +78,23 @@ fun PreferenceDropdown( Text( style = ElementTheme.typography.fontBodyMdRegular, text = it, - color = enabled.toSecondaryEnabledColor(), ) } }, trailingContent = ListItemContent.Custom( - content = { + content = { enabled -> DropdownTrailingContent( selectedOption = selectedOption, options = options, onSelectOption = onSelectOption, expanded = isDropdownExpanded, onExpandedChange = { isDropdownExpanded = it }, + enabled = enabled, modifier = Modifier.fillMaxSize(0.3f) ) } ), + enabled = enabled, onClick = { isDropdownExpanded = true }.takeIf { !isDropdownExpanded }, ) } @@ -118,6 +117,7 @@ private fun DropdownTrailingContent( expanded: Boolean, onExpandedChange: (Boolean) -> Unit, onSelectOption: (T) -> Unit, + enabled: Boolean, modifier: Modifier = Modifier, ) { Row( @@ -129,7 +129,7 @@ private fun DropdownTrailingContent( text = selectedOption?.getText().orEmpty(), maxLines = 1, style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textSecondary, + color = enabled.toSecondaryEnabledColor(), overflow = TextOverflow.Ellipsis, textAlign = TextAlign.End, modifier = Modifier.weight(1f), @@ -137,7 +137,7 @@ private fun DropdownTrailingContent( Icon( imageVector = CompoundIcons.ChevronDown(), contentDescription = null, - tint = ElementTheme.colors.iconSecondary, + tint = enabled.toIconSecondaryEnabledColor(), ) DropdownMenu( expanded = expanded, @@ -146,6 +146,7 @@ private fun DropdownTrailingContent( ) { options.forEach { option -> DropdownMenuItem( + enabled = enabled, text = { Text( text = option.getText(), @@ -206,5 +207,14 @@ internal fun PreferenceDropdownPreview() = ElementThemedPreview { options = options, onSelectOption = {}, ) + PreferenceDropdown( + title = "Dropdown", + supportingText = "Options for dropdown", + icon = CompoundIcons.Threads(), + selectedOption = options.first(), + options = options, + onSelectOption = {}, + enabled = false + ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt index a5092609e9..671eb5bf3f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt @@ -44,7 +44,6 @@ fun PreferenceSlide( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt index 404a26737f..4545dbdf3e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt @@ -42,7 +42,6 @@ fun PreferenceSwitch( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt index b3e1227525..59e818c18a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt @@ -34,11 +34,10 @@ fun preferenceIcon( @DrawableRes iconResourceId: Int? = null, showIconBadge: Boolean = false, tintColor: Color? = null, - enabled: Boolean = true, showIconAreaIfNoIcon: Boolean = false, ): ListItemContent.Custom? { return if (icon != null || iconResourceId != null || showIconAreaIfNoIcon) { - ListItemContent.Custom { + ListItemContent.Custom { enabled -> PreferenceIcon( icon = icon, iconResourceId = iconResourceId, From 401fc26b80666a276554bfc34cd5c6cfadb61764 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 22 Dec 2025 14:41:43 +0000 Subject: [PATCH 09/11] Update screenshots --- ...ns.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png | 4 ++-- ....impl.permissions_ChangeRoomPermissionsView_Night_1_en.png | 4 ++-- ...mponents.preferences_PreferenceDropdown_Preferences_en.png | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png index a6d9a85284..de8c905de2 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4eadce77750c8946f3d506a59925901fe3a44ac61ac08e4a23b6517f4596eece -size 48910 +oid sha256:12c69b646ec09afdbc6e90873ee831adbd62ea7c45679c54620f74a9bd74941c +size 48321 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png index 43f63ee7f9..b261774095 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:009e04296b2dbeb127e6d3b9306dce996fa35bf4443d34abbdf2b8cc14126053 -size 47615 +oid sha256:e72874066861ce76c76134dd84b9ab058ea3c7ca1c54176f8863c6bc1b8226fc +size 47273 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png index 1d7613430e..47d04e19bb 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00eb9c6756c353ac82144c391617a515d871940c32d0604d86203f8568e9e5e2 -size 32077 +oid sha256:ed643834f8fd2bc167dfd95113ab9098392f4d626ccdd58c9fae2cfd264f70c5 +size 41611 From 2dcfdf6e4fcee89e31eedc4a9e4a4dea0bb045de Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 22 Dec 2025 18:04:08 +0100 Subject: [PATCH 10/11] quality: rename enum after PR review --- .../impl/root/RolesAndPermissionsPresenter.kt | 6 +++--- .../impl/root/RolesAndPermissionsState.kt | 6 +++--- .../impl/root/RolesAndPermissionsStateProvider.kt | 6 +++--- .../impl/root/RolesAndPermissionsView.kt | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt index f80899fc0f..dd3a59b99e 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt @@ -54,8 +54,8 @@ class RolesAndPermissionsPresenter( derivedStateOf { val currentRole = roomInfo.roleOf(room.sessionId) when (currentRole) { - is RoomMember.Role.Admin -> persistentListOf(DemoteActions.ToModerator, DemoteActions.ToMember) - is RoomMember.Role.Moderator -> persistentListOf(DemoteActions.ToMember) + is RoomMember.Role.Admin -> persistentListOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember) + is RoomMember.Role.Moderator -> persistentListOf(SelfDemoteAction.ToMember) else -> persistentListOf() } } @@ -88,7 +88,7 @@ class RolesAndPermissionsPresenter( roomSupportsOwnerRole = roomInfo.privilegedCreatorRole, adminCount = adminCount, moderatorCount = moderatorCount, - availableDemoteActions = availableDemoteActions, + availableSelfDemoteActions = availableDemoteActions, changeOwnRoleAction = changeOwnRoleAction.value, resetPermissionsAction = resetPermissionsAction.value, eventSink = ::handleEvent, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt index 9064f559c6..626ad3b699 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt @@ -17,15 +17,15 @@ data class RolesAndPermissionsState( val roomSupportsOwnerRole: Boolean, val adminCount: Int?, val moderatorCount: Int?, - val availableDemoteActions: ImmutableList, + val availableSelfDemoteActions: ImmutableList, val changeOwnRoleAction: AsyncAction, val resetPermissionsAction: AsyncAction, val eventSink: (RolesAndPermissionsEvents) -> Unit, ) { - val canDemoteSelf = availableDemoteActions.isNotEmpty() + val canSelfDemote = availableSelfDemoteActions.isNotEmpty() } -enum class DemoteActions(val role: RoomMember.Role, val titleRes: Int) { +enum class SelfDemoteAction(val role: RoomMember.Role, val titleRes: Int) { ToModerator(RoomMember.Role.Moderator, R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator), ToMember(RoomMember.Role.User, R.string.screen_room_roles_and_permissions_change_role_demote_to_member) } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt index cf0163d0ba..45bd72db19 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt @@ -47,7 +47,7 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider = listOf(DemoteActions.ToModerator, DemoteActions.ToMember), + availableSelfDemoteActions: List = listOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember), changeOwnRoleAction: AsyncAction = AsyncAction.Uninitialized, resetPermissionsAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (RolesAndPermissionsEvents) -> Unit = {}, ) = RolesAndPermissionsState( roomSupportsOwnerRole = roomSupportsOwners, adminCount = adminCount, - availableDemoteActions = availableDemoteActions.toImmutableList(), + availableSelfDemoteActions = availableSelfDemoteActions.toImmutableList(), moderatorCount = moderatorCount, changeOwnRoleAction = changeOwnRoleAction, resetPermissionsAction = resetPermissionsAction, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt index 82b49f0d81..269fdee664 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt @@ -76,7 +76,7 @@ fun RolesAndPermissionsView( }, onClick = { rolesAndPermissionsNavigator.openModeratorList() }, ) - if (state.canDemoteSelf) { + if (state.canSelfDemote) { ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) }, onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) }, @@ -117,7 +117,7 @@ fun RolesAndPermissionsView( when (state.changeOwnRoleAction) { is AsyncAction.Confirming -> { ChangeOwnRoleBottomSheet( - availableDemoteActions = state.availableDemoteActions, + availableDemoteActions = state.availableSelfDemoteActions, eventSink = state.eventSink, ) } @@ -137,7 +137,7 @@ fun RolesAndPermissionsView( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ChangeOwnRoleBottomSheet( - availableDemoteActions: ImmutableList, + availableDemoteActions: ImmutableList, eventSink: (RolesAndPermissionsEvents) -> Unit, ) { val coroutineScope = rememberCoroutineScope() From f7248b87f0dd7e5aa8f7c30b98115dad8e2402c2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 22 Dec 2025 18:06:41 +0100 Subject: [PATCH 11/11] fix: display banned member list if permissions.canKick or permissions.canBan --- .../features/roomdetails/impl/members/RoomMemberListState.kt | 2 +- .../roommembermoderation/api/RoomMemberModerationPermissions.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 7c928fb27a..5a113bc3b2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -26,7 +26,7 @@ data class RoomMemberListState( val moderationState: RoomMemberModerationState, val eventSink: (RoomMemberListEvents) -> Unit, ) { - val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true + val showBannedSection: Boolean = moderationState.permissions.hasAny && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true } enum class SelectedSection { diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt index 223456de69..10ea6c4451 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt @@ -13,6 +13,8 @@ data class RoomMemberModerationPermissions( val canKick: Boolean, val canBan: Boolean, ) { + val hasAny = canKick || canBan + companion object { val DEFAULT = RoomMemberModerationPermissions( canKick = false,