From 8298404630bcb005e72c076dde84ef5a1fc247b4 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 29 Jul 2025 16:07:16 +0200 Subject: [PATCH] Adapt 'change roles' screens to the new creator/owner role (#5076) * Replace `RoomMember.Role.CREATOR` with `RoomMember.Role.Owner` - Make `RoomMember.Role` a sealed interface instead * Adapt room member role mapping to include the power level to distinguish between admins and owners * Use new `RoomMember.Role` sealed interface through the app * Change how `MembersByRole` groups members to add owners to the admins section * Adapt the `ChangeRoles` screen to the new roles: - Owners can't modify other owner's roles. - They can modify the roles of any other user, without confirmation. * Adapt 'roles and permissions' screen: - Owners can't demote themselves. - The admin count also counts owners. * Add more tests and screenshots * Add owners to its own section in the 'change roles' screen * Update screenshots --------- Co-authored-by: ElementBot --- .../suggestions/SuggestionsPickerView.kt | 2 +- .../impl/RoomDetailsStateProvider.kt | 2 +- .../impl/analytics/AnalyticUtils.kt | 8 +- .../members/RoomMemberListStateProvider.kt | 6 +- .../impl/members/RoomMemberListView.kt | 14 +- .../RolesAndPermissionsNode.kt | 2 +- .../RolesAndPermissionsPresenter.kt | 16 +- .../RolesAndPermissionsState.kt | 2 + .../RolesAndPermissionsStateProvider.kt | 7 +- .../RolesAndPermissionsView.kt | 24 ++- .../changeroles/ChangeRolesNode.kt | 4 +- .../changeroles/ChangeRolesPresenter.kt | 61 +++++--- .../changeroles/ChangeRolesState.kt | 10 +- .../changeroles/ChangeRolesStateProvider.kt | 51 ++++++- .../changeroles/ChangeRolesView.kt | 61 +++++--- .../ChangeRoomPermissionsStateProvider.kt | 16 +- .../permissions/ChangeRoomPermissionsView.kt | 13 +- .../impl/src/main/res/values/localazy.xml | 3 + .../RolesAndPermissionPresenterTest.kt | 4 +- .../RolesAndPermissionsViewTest.kt | 23 ++- .../changeroles/ChangeRolesPresenterTest.kt | 142 ++++++++++++++++-- .../changeroles/ChangeRolesViewTest.kt | 22 ++- .../changeroles/MembersByRoleTest.kt | 45 ++++-- .../ChangeBaseRoomPermissionsPresenterTest.kt | 74 ++++----- .../ChangeBaseRoomPermissionsViewTest.kt | 6 +- .../impl/RoomMemberModerationPresenterTest.kt | 18 +-- .../libraries/matrix/api/room/RoomInfo.kt | 2 +- .../libraries/matrix/api/room/RoomMember.kt | 41 ++--- .../api/room/powerlevels/RoomPowerLevels.kt | 17 ++- .../libraries/matrix/impl/RustMatrixClient.kt | 2 +- .../matrix/impl/room/RustBaseRoom.kt | 6 +- .../impl/room/member/RoomMemberMapper.kt | 45 +++--- .../RoomPowerLevelsValuesMapper.kt | 2 +- .../matrix/impl/fixtures/fakes/FakeFfiRoom.kt | 6 + .../matrix/impl/room/RustBaseRoomTest.kt | 29 ++++ .../impl/room/member/RoomMemberMapperTest.kt | 16 +- .../matrix/test/room/RoomMemberFixture.kt | 2 +- .../matrix/ui/model/RoomInfoExtension.kt | 8 +- .../matrix/ui/room/MatrixRoomState.kt | 4 +- ....changeroles_ChangeRolesView_Day_10_en.png | 4 +- ....changeroles_ChangeRolesView_Day_11_en.png | 3 + ...s.changeroles_ChangeRolesView_Day_1_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_2_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_3_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_4_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_5_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_6_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_7_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_8_en.png | 4 +- ...s.changeroles_ChangeRolesView_Day_9_en.png | 4 +- ...hangeroles_ChangeRolesView_Night_10_en.png | 4 +- ...hangeroles_ChangeRolesView_Night_11_en.png | 3 + ...changeroles_ChangeRolesView_Night_1_en.png | 4 +- ...changeroles_ChangeRolesView_Night_2_en.png | 4 +- ...changeroles_ChangeRolesView_Night_3_en.png | 4 +- ...changeroles_ChangeRolesView_Night_4_en.png | 4 +- ...changeroles_ChangeRolesView_Night_5_en.png | 4 +- ...changeroles_ChangeRolesView_Night_6_en.png | 4 +- ...changeroles_ChangeRolesView_Night_7_en.png | 4 +- ...changeroles_ChangeRolesView_Night_8_en.png | 4 +- ...changeroles_ChangeRolesView_Night_9_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_1_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_2_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_3_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_4_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_5_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_6_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_7_en.png | 4 +- ...sions_RolesAndPermissionsView_Day_8_en.png | 3 + ...ons_RolesAndPermissionsView_Night_1_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_2_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_3_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_4_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_5_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_6_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_7_en.png | 4 +- ...ons_RolesAndPermissionsView_Night_8_en.png | 3 + 77 files changed, 663 insertions(+), 301 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_11_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_11_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt index 93703377eb..99a72e06a2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt @@ -157,7 +157,7 @@ internal fun SuggestionsPickerViewPreview() { powerLevel = 0L, normalizedPowerLevel = 0L, isIgnored = false, - role = RoomMember.Role.USER, + role = RoomMember.Role.User, membershipChangeReason = null, ) val anAlias = remember { RoomAlias("#room:domain.org") } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index a5e81040e8..097cb2e219 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -69,7 +69,7 @@ fun aDmRoomMember( powerLevel: Long = 0, normalizedPowerLevel: Long = powerLevel, isIgnored: Boolean = false, - role: RoomMember.Role = RoomMember.Role.USER, + role: RoomMember.Role = RoomMember.Role.User, membershipChangeReason: String? = null, ) = RoomMember( userId = userId, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt index d28e87b58c..09d57af034 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt @@ -13,10 +13,10 @@ import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsV import io.element.android.services.analytics.api.AnalyticsService internal fun RoomMember.Role.toAnalyticsMemberRole(): RoomModeration.Role = when (this) { - RoomMember.Role.CREATOR -> RoomModeration.Role.Administrator // TODO - distinguish creator from admin - RoomMember.Role.ADMIN -> RoomModeration.Role.Administrator - RoomMember.Role.MODERATOR -> RoomModeration.Role.Moderator - RoomMember.Role.USER -> RoomModeration.Role.User + is RoomMember.Role.Owner -> RoomModeration.Role.Administrator // TODO - distinguish creator from admin + RoomMember.Role.Admin -> RoomModeration.Role.Administrator + RoomMember.Role.Moderator -> RoomModeration.Role.Moderator + RoomMember.Role.User -> RoomModeration.Role.User } internal fun analyticsMemberRoleForPowerLevel(powerLevel: Long): RoomModeration.Role { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index 09a7d9c910..52acb6ebd6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -150,7 +150,7 @@ fun aRoomMember( powerLevel: Long = 0L, normalizedPowerLevel: Long = 0L, isIgnored: Boolean = false, - role: RoomMember.Role = RoomMember.Role.USER, + role: RoomMember.Role = RoomMember.Role.User, membershipChangeReason: String? = null, ) = RoomMember( userId = userId, @@ -178,8 +178,8 @@ fun aRoomMemberList() = persistentListOf( aWalter(), ) -fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.ADMIN) -fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.MODERATOR) +fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin) +fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator) fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 81dc638d91..c28cdda62c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -61,7 +61,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.getBestName -import io.element.android.libraries.matrix.api.room.isOwner import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.ui.components.MatrixUserRow import io.element.android.libraries.ui.strings.CommonStrings @@ -295,14 +294,11 @@ private fun RoomMemberListItem( modifier: Modifier = Modifier, ) { val member = roomMemberWithIdentity.roomMember - val roleText = if (member.isOwner()) { - stringResource(R.string.screen_room_member_list_role_owner) - } else { - when (member.role) { - RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_member_list_role_administrator) - RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_member_list_role_moderator) - else -> null - } + val roleText = when (member.role) { + RoomMember.Role.Admin -> stringResource(R.string.screen_room_member_list_role_administrator) + RoomMember.Role.Moderator -> stringResource(R.string.screen_room_member_list_role_moderator) + is RoomMember.Role.Owner -> stringResource(R.string.screen_room_member_list_role_owner) + else -> null } MatrixUserRow( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt index a46ba8000e..80e2d007a3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt @@ -61,7 +61,7 @@ class RolesAndPermissionsNode @AssistedInject constructor( room.roomInfoFlow .filter { info -> val role = info.roleOf(room.sessionId) - role != RoomMember.Role.ADMIN && role != RoomMember.Role.CREATOR + role != RoomMember.Role.Admin && role !is RoomMember.Role.Owner } .take(1) .onEach { navigateUp() } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt index be8b77a07e..df62dbbbc0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.activeRoomMembers import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange +import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -50,14 +51,23 @@ class RolesAndPermissionsPresenter @Inject constructor( } val moderatorCount by remember { derivedStateOf { - roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.MODERATOR) + roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Moderator) } } val adminCount by remember { derivedStateOf { - roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.ADMIN) + val admins = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Admin) + val ownersCount = if (roomInfo.privilegedCreatorRole) { + val superAdmins = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Owner(isCreator = false)) + val creators = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Owner(isCreator = true)) + superAdmins + creators + } else { + 0 + } + admins + ownersCount } } + val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } } val changeOwnRoleAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val resetPermissionsAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } @@ -83,8 +93,10 @@ class RolesAndPermissionsPresenter @Inject constructor( } return RolesAndPermissionsState( + roomSupportsOwnerRole = roomInfo.privilegedCreatorRole, adminCount = adminCount, moderatorCount = moderatorCount, + canDemoteSelf = canDemoteSelf.value, changeOwnRoleAction = changeOwnRoleAction.value, resetPermissionsAction = resetPermissionsAction.value, eventSink = { handleEvent(it) }, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt index 535786be67..24f645a309 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt @@ -10,8 +10,10 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions import io.element.android.libraries.architecture.AsyncAction data class RolesAndPermissionsState( + val roomSupportsOwnerRole: Boolean, val adminCount: Int, val moderatorCount: Int, + val canDemoteSelf: Boolean, val changeOwnRoleAction: AsyncAction, val resetPermissionsAction: AsyncAction, val eventSink: (RolesAndPermissionsEvents) -> Unit, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt index d353812c9b..211a16d7d1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt @@ -13,7 +13,7 @@ import io.element.android.libraries.architecture.AsyncAction class RolesAndPermissionsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aRolesAndPermissionsState(), + aRolesAndPermissionsState(roomSupportsOwners = false), aRolesAndPermissionsState(adminCount = 1, moderatorCount = 2), aRolesAndPermissionsState( adminCount = 1, @@ -45,17 +45,22 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, resetPermissionsAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (RolesAndPermissionsEvents) -> Unit = {}, ) = RolesAndPermissionsState( + roomSupportsOwnerRole = roomSupportsOwners, adminCount = adminCount, + canDemoteSelf = canDemoteSelf, moderatorCount = moderatorCount, changeOwnRoleAction = changeOwnRoleAction, resetPermissionsAction = resetPermissionsAction, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt index 2e388d2f39..536b0f8b18 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt @@ -55,8 +55,14 @@ fun RolesAndPermissionsView( onBackClick = rolesAndPermissionsNavigator::onBackClick, ) { ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_roles_header), hasDivider = false) + + val adminsTitle = if (state.roomSupportsOwnerRole) { + stringResource(R.string.screen_room_roles_and_permissions_admins_and_owners) + } else { + stringResource(R.string.screen_room_roles_and_permissions_admins) + } ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_admins)) }, + headlineContent = { Text(adminsTitle) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), trailingContent = ListItemContent.Text("${state.adminCount}"), onClick = { rolesAndPermissionsNavigator.openAdminList() }, @@ -67,11 +73,13 @@ fun RolesAndPermissionsView( trailingContent = ListItemContent.Text("${state.moderatorCount}"), onClick = { rolesAndPermissionsNavigator.openModeratorList() }, ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) }, - onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit())) - ) + if (state.canDemoteSelf) { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) }, + onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit())) + ) + } ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true) ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_room_details)) }, @@ -170,7 +178,7 @@ private fun ChangeOwnRoleBottomSheet( 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)) + eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) } }, style = ListItemStyle.Destructive, @@ -179,7 +187,7 @@ private fun ChangeOwnRoleBottomSheet( 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)) + eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User)) } }, style = ListItemStyle.Destructive, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt index 81ca9c0903..b4750a96ee 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt @@ -44,8 +44,8 @@ class ChangeRolesNode @AssistedInject constructor( private val presenter = presenterFactory.run { val role = when (inputs.listType) { - is ListType.Admins -> RoomMember.Role.ADMIN - is ListType.Moderators -> RoomMember.Role.MODERATOR + is ListType.Admins -> RoomMember.Role.Admin + is ListType.Moderators -> RoomMember.Role.Moderator } create(role) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt index 20256cb2ba..b5e5b63854 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt @@ -75,18 +75,17 @@ class ChangeRolesPresenter @AssistedInject constructor( val exitState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val saveState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val usersWithRole = produceState(initialValue = persistentListOf()) { - room.usersWithRole(role) - .map { members -> members.map { it.toMatrixUser() } } - .onEach { users -> - val previous: PersistentList = value - value = users.toPersistentList() - // Users who were selected but didn't have the role, so their role change was pending - val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } } - // Users who no longer have the role - val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet() - selectedUsers.value = (users + toAdd - toRemove).toImmutableList() - } - .launchIn(this) + room.usersWithRole(role).map { members -> members.map { it.toMatrixUser() } } + .onEach { users -> + val previous: PersistentList = value + value = users.toPersistentList() + // Users who were selected but didn't have the role, so their role change was pending + val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } } + // Users who no longer have the role + val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet() + selectedUsers.value = (users + toAdd - toRemove).toImmutableList() + } + .launchIn(this) } val roomMemberState by room.membersStateFlow.collectAsState() @@ -97,7 +96,6 @@ class ChangeRolesPresenter @AssistedInject constructor( .search(query.orEmpty()) .groupedByRole() - println(results) searchResults = if (results.isEmpty()) { SearchBarResultState.NoResultsFound() } else { @@ -109,9 +107,10 @@ class ChangeRolesPresenter @AssistedInject constructor( val roomInfo by room.roomInfoFlow.collectAsState() fun canChangeMemberRole(userId: UserId): Boolean { - // An admin can't remove or demote another admin - val role = roomInfo.roleOf(userId) - return role !in listOf(RoomMember.Role.ADMIN, RoomMember.Role.CREATOR) + // This is used to group the + val currentUserRole = roomInfo.roleOf(room.sessionId) + val otherUserRole = roomInfo.roleOf(userId) + return currentUserRole.powerLevel > otherUserRole.powerLevel } fun handleEvent(event: ChangeRolesEvent) { @@ -133,11 +132,21 @@ class ChangeRolesPresenter @AssistedInject constructor( selectedUsers.value = newList.toImmutableList() } is ChangeRolesEvent.Save -> { - if (role == RoomMember.Role.ADMIN && selectedUsers != usersWithRole && !saveState.value.isConfirming()) { - // Confirm adding admin - saveState.value = AsyncAction.ConfirmingNoParams - } else if (!saveState.value.isLoading()) { - coroutineScope.save(usersWithRole.value, selectedUsers, saveState) + val currentUserIsAdmin = roomInfo.roleOf(room.sessionId) == RoomMember.Role.Admin + val isModifyingAdmins = role == RoomMember.Role.Admin + val hasChanges = selectedUsers != usersWithRole + val isConfirming = saveState.value.isConfirming() + + val needsConfirmation = currentUserIsAdmin && isModifyingAdmins && hasChanges && !isConfirming + + when { + needsConfirmation -> { + // Confirm modifying users + saveState.value = AsyncAction.ConfirmingNoParams + } + !saveState.value.isLoading() -> { + coroutineScope.save(usersWithRole.value, selectedUsers, saveState) + } } } is ChangeRolesEvent.ClearError -> { @@ -175,10 +184,12 @@ class ChangeRolesPresenter @AssistedInject constructor( } private fun List.groupedByRole(): MembersByRole { + val groupedMembers = MembersByRole(this) return MembersByRole( - admins = filter { it.role == RoomMember.Role.ADMIN }.sorted(), - moderators = filter { it.role == RoomMember.Role.MODERATOR }.sorted(), - members = filter { it.role == RoomMember.Role.USER }.sorted(), + owners = groupedMembers.owners.sorted(), + admins = groupedMembers.admins.sorted(), + moderators = groupedMembers.moderators.sorted(), + members = groupedMembers.members.sorted(), ) } @@ -203,7 +214,7 @@ class ChangeRolesPresenter @AssistedInject constructor( } for (selectedUser in toRemove) { analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User)) - add(UserRoleChange(selectedUser.userId, RoomMember.Role.USER)) + add(UserRoleChange(selectedUser.userId, RoomMember.Role.User)) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt index d829997afa..445b5aa347 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt @@ -30,17 +30,19 @@ data class ChangeRolesState( ) data class MembersByRole( + val owners: ImmutableList, val admins: ImmutableList, val moderators: ImmutableList, val members: ImmutableList, ) { constructor(members: List) : this( - admins = members.filter { it.role == RoomMember.Role.ADMIN }.sorted(), - moderators = members.filter { it.role == RoomMember.Role.MODERATOR }.sorted(), - members = members.filter { it.role == RoomMember.Role.USER }.sorted(), + owners = members.filter { it.role is RoomMember.Role.Owner }.sorted(), + admins = members.filter { it.role == RoomMember.Role.Admin }.sorted(), + moderators = members.filter { it.role == RoomMember.Role.Moderator }.sorted(), + members = members.filter { it.role == RoomMember.Role.User }.sorted(), ) - fun isEmpty() = admins.isEmpty() && moderators.isEmpty() && members.isEmpty() + fun isEmpty() = owners.isEmpty() && admins.isEmpty() && moderators.isEmpty() && members.isEmpty() } private fun Iterable.sorted(): ImmutableList { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt index 3aa1becd46..2b62508d97 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.aRoomMemberList import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -15,6 +16,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -24,7 +26,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aChangeRolesState(), - aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.MODERATOR), + aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.Moderator), aChangeRolesStateWithSelectedUsers().copy(hasPendingChanges = false), aChangeRolesStateWithSelectedUsers(), aChangeRolesStateWithSelectedUsers().copy( @@ -41,11 +43,12 @@ class ChangeRolesStateProvider : PreviewParameterProvider { aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading), aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(Unit)), aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))), + aChangeRolesStateWithOwners(), ) } internal fun aChangeRolesState( - role: RoomMember.Role = RoomMember.Role.ADMIN, + role: RoomMember.Role = RoomMember.Role.Admin, query: String? = null, isSearchActive: Boolean = false, searchResults: SearchBarResultState = SearchBarResultState.NoResultsFound(), @@ -84,3 +87,47 @@ internal fun aChangeRolesStateWithSelectedUsers() = aChangeRolesState( hasPendingChanges = true, canRemoveMember = { it != UserId("@alice:server.org") }, ) + +internal fun aChangeRolesStateWithOwners() = aChangeRolesState( + role = RoomMember.Role.Admin, + searchResults = SearchBarResultState.Results( + MembersByRole( + members = persistentListOf( + aRoomMember( + userId = UserId("@alice:server.org"), + displayName = "Alice", + role = RoomMember.Role.Owner(isCreator = true), + ), + aRoomMember( + userId = UserId("@bob:server.org"), + displayName = "Bob", + role = RoomMember.Role.Owner(isCreator = false), + ), + aRoomMember( + userId = UserId("@carol:server.org"), + displayName = "Carol", + role = RoomMember.Role.Admin, + ), + aRoomMember( + userId = UserId("@david:server.org"), + displayName = "David", + role = RoomMember.Role.User, + ), + ) + ), + ), + canRemoveMember = { userId -> + when (userId) { + UserId("@alice:server.org") -> false // Owner - creator + UserId("@bob:server.org") -> false // Owner - super admin + UserId("@carol:server.org") -> true // Admin + UserId("@david:server.org") -> true // User + else -> false + } + }, + selectedUsers = persistentListOf( + aMatrixUser(id = "@alice:server.org", displayName = "Alice"), + aMatrixUser(id = "@bob:server.org", displayName = "Bob"), + aMatrixUser(id = "@carol:server.org", displayName = "Carol"), + ) +) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt index 7a993b6fc6..2fdf1dcf1b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt @@ -57,6 +57,7 @@ import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Checkbox +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.SearchBar import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -96,9 +97,9 @@ fun ChangeRolesView( AnimatedVisibility(visible = !state.isSearchActive) { TopAppBar( titleStr = when (state.role) { - RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_change_role_administrators_title) - RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_change_role_moderators_title) - RoomMember.Role.CREATOR, RoomMember.Role.USER -> error("This should never be reached") + RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_role_administrators_title) + RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_role_moderators_title) + is RoomMember.Role.Owner, RoomMember.Role.User -> error("This should never be reached") }, navigationIcon = { BackButton(onClick = { state.eventSink(ChangeRolesEvent.Exit) }) @@ -187,7 +188,7 @@ fun ChangeRolesView( when (state.savingState) { is AsyncAction.Confirming -> { - if (state.role == RoomMember.Role.ADMIN) { + if (state.role == RoomMember.Role.Admin) { // Confirm adding new admins dialogs ConfirmationDialog( title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title), @@ -234,10 +235,30 @@ private fun SearchResultsList( item { selectedUsersList(selectedUsers) } + if (searchResults.owners.isNotEmpty()) { + stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_owners)) } + item { + Text( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 8.dp), + text = stringResource(R.string.screen_room_change_role_moderators_owner_section_footer), + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodySmRegular, + ) + } + items(searchResults.owners, key = { it.userId }) { roomMember -> + ListMemberItem( + roomMember = roomMember, + canRemoveMember = canRemoveMember, + onToggleSelection = onToggleSelection, + selectedUsers = selectedUsers + ) + } + } if (searchResults.admins.isNotEmpty()) { stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_admins)) } // Add a footer for the admin section in change role to moderator screen - if (currentRole == RoomMember.Role.MODERATOR) { + if (currentRole == RoomMember.Role.Moderator) { item { Text( modifier = Modifier @@ -303,20 +324,24 @@ private fun ListMemberItem( ) { val canToggle = canRemoveMember(roomMember.userId) val trailingContent: @Composable (() -> Unit) = { - Checkbox( - checked = selectedUsers.any { it.userId == roomMember.userId }, - onCheckedChange = { onToggleSelection(roomMember) }, - enabled = canToggle, - ) + if (canToggle) { + Checkbox( + checked = selectedUsers.any { it.userId == roomMember.userId }, + onCheckedChange = { onToggleSelection(roomMember) }, + ) + } + } + Column { + MemberRow( + modifier = Modifier.clickable(enabled = canToggle, onClick = { onToggleSelection(roomMember) }), + avatarData = roomMember.getAvatarData(size = AvatarSize.UserListItem), + name = roomMember.getBestName(), + userId = roomMember.userId.value.takeIf { roomMember.displayName?.isNotBlank() == true }, + isPending = roomMember.membership == RoomMembershipState.INVITE, + trailingContent = trailingContent, + ) + HorizontalDivider() } - MemberRow( - modifier = Modifier.clickable(enabled = canToggle, onClick = { onToggleSelection(roomMember) }), - avatarData = roomMember.getAvatarData(size = AvatarSize.UserListItem), - name = roomMember.getBestName(), - userId = roomMember.userId.value.takeIf { roomMember.displayName?.isNotBlank() == true }, - isPending = roomMember.membership == RoomMembershipState.INVITE, - trailingContent = trailingContent, - ) } @Composable diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt index 4210d117c7..7af850230e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt @@ -55,15 +55,15 @@ internal fun aChangeRoomPermissionsState( private fun previewPermissions(): RoomPowerLevelsValues { return RoomPowerLevelsValues( // MembershipModeration section - invite = RoomMember.Role.ADMIN.powerLevel, - kick = RoomMember.Role.MODERATOR.powerLevel, - ban = RoomMember.Role.USER.powerLevel, + invite = RoomMember.Role.Admin.powerLevel, + kick = RoomMember.Role.Moderator.powerLevel, + ban = RoomMember.Role.User.powerLevel, // MessagesAndContent section - redactEvents = RoomMember.Role.MODERATOR.powerLevel, - sendEvents = RoomMember.Role.ADMIN.powerLevel, + redactEvents = RoomMember.Role.Moderator.powerLevel, + sendEvents = RoomMember.Role.Admin.powerLevel, // RoomDetails section - roomName = RoomMember.Role.ADMIN.powerLevel, - roomAvatar = RoomMember.Role.MODERATOR.powerLevel, - roomTopic = RoomMember.Role.USER.powerLevel, + roomName = RoomMember.Role.Admin.powerLevel, + roomAvatar = RoomMember.Role.Moderator.powerLevel, + roomTopic = RoomMember.Role.User.powerLevel, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt index 48984b4ed7..6acf4935dc 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt @@ -80,21 +80,21 @@ fun ChangeRoomPermissionsView( ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) SelectRoleItem( permissionsItem = permissionItem, - role = RoomMember.Role.ADMIN, + role = RoomMember.Role.Admin, currentPermissions = state.currentPermissions ) { item, role -> state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) } SelectRoleItem( permissionsItem = permissionItem, - role = RoomMember.Role.MODERATOR, + role = RoomMember.Role.Moderator, currentPermissions = state.currentPermissions ) { item, role -> state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) } SelectRoleItem( permissionsItem = permissionItem, - role = RoomMember.Role.USER, + role = RoomMember.Role.User, currentPermissions = state.currentPermissions ) { item, role -> state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) @@ -135,9 +135,10 @@ private fun SelectRoleItem( onClick: (RoomPermissionType, RoomMember.Role) -> Unit ) { val title = when (role) { - RoomMember.Role.ADMIN, RoomMember.Role.CREATOR -> stringResource(R.string.screen_room_change_permissions_administrators) - RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_change_permissions_moderators) - RoomMember.Role.USER -> stringResource(R.string.screen_room_change_permissions_everyone) + RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_permissions_administrators) + RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_permissions_moderators) + RoomMember.Role.User -> stringResource(R.string.screen_room_change_permissions_everyone) + else -> error("Unsupported role selected: $role") } ListItem( headlineContent = { Text(text = title) }, diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 6cea0c26a9..ebd421f958 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -28,6 +28,7 @@ "%1$s (Pending)" "(Pending)" "Admins automatically have moderator privileges" + "Owners automatically have admin privileges." "Edit Moderators" "Admins" "Moderators" @@ -99,12 +100,14 @@ "Mentions and Keywords only" "In this room, notify me for" "Admins" + "Admins and owners" "Change my role" "Demote to member" "Demote to moderator" "Member moderation" "Messages and content" "Moderators" + "Owners" "Permissions" "Reset permissions" "Once you reset permissions, you will lose the current settings." diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt index 718c618484..734cf4696a 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt @@ -66,7 +66,7 @@ class RolesAndPermissionPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR)) + initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) runCurrent() assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading) @@ -87,7 +87,7 @@ class RolesAndPermissionPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR)) + initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) runCurrent() assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt index 8aded9f698..4aa68bc047 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt @@ -47,12 +47,30 @@ class RolesAndPermissionsViewTest { fun `tapping on Admins opens admin list`() { ensureCalledOnce { callback -> rule.setRolesAndPermissionsView( + aRolesAndPermissionsState( + roomSupportsOwners = false, + eventSink = EventsRecorder(expectEvents = false) + ), openAdminList = callback, ) rule.clickOn(R.string.screen_room_roles_and_permissions_admins) } } + @Test + fun `tapping on Admins and Owners opens admin list`() { + ensureCalledOnce { callback -> + rule.setRolesAndPermissionsView( + aRolesAndPermissionsState( + roomSupportsOwners = true, + eventSink = EventsRecorder(expectEvents = false) + ), + openAdminList = callback, + ) + rule.clickOn(R.string.screen_room_roles_and_permissions_admins_and_owners) + } + } + @Test fun `tapping on Moderators opens moderators list`() { ensureCalledOnce { callback -> @@ -126,7 +144,7 @@ class RolesAndPermissionsViewTest { ) rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator) rule.mainClock.advanceTimeBy(1_000L) - recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR)) + recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) } @Test @@ -140,7 +158,7 @@ class RolesAndPermissionsViewTest { ) rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member) rule.mainClock.advanceTimeBy(1_000L) - recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.USER)) + recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User)) } @Test @@ -160,6 +178,7 @@ class RolesAndPermissionsViewTest { private fun AndroidComposeTestRule.setRolesAndPermissionsView( state: RolesAndPermissionsState = aRolesAndPermissionsState( + roomSupportsOwners = false, eventSink = EventsRecorder(expectEvents = false), ), goBack: () -> Unit = EnsureNeverCalled(), diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt index 18ddf2ae04..e9c48cb209 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt @@ -12,6 +12,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration +import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.aRoomMemberList import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -23,6 +24,7 @@ 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.A_USER_ID_2 +import io.element.android.libraries.matrix.test.A_USER_ID_3 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 @@ -43,7 +45,7 @@ class ChangeRolesPresenterTest { presenter.present() }.test { with(awaitItem()) { - assertThat(role).isEqualTo(RoomMember.Role.ADMIN) + assertThat(role).isEqualTo(RoomMember.Role.Admin) assertThat(query).isNull() assertThat(isSearchActive).isFalse() assertThat(searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) @@ -70,6 +72,76 @@ class ChangeRolesPresenterTest { } } + @Test + fun `present - canChangeRole of users with lower power level unless they are owners`() = runTest { + val creatorUserId = UserId("@creator:matrix.org") + val superAdminUserId = UserId("@super_admin:matrix.org") + + val room = FakeJoinedRoom().apply { + // User is a creator, so they can change roles of other members. So is `creatorUserId`. + givenRoomInfo( + aRoomInfo( + roomCreators = listOf(sessionId, creatorUserId), + roomPowerLevels = RoomPowerLevels( + defaultRoomPowerLevelValues(), + users = persistentMapOf( + // bob is Admin + A_USER_ID_2 to RoomMember.Role.Admin.powerLevel, + // carol is Moderator + A_USER_ID_3 to RoomMember.Role.Moderator.powerLevel, + // super_admin is Owner - Superadmin + superAdminUserId to RoomMember.Role.Owner(isCreator = false).powerLevel, + ) + ) + ) + ) + + val roomMemberList = aRoomMemberList() + listOf( + // Owner - superadmin + aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = true)), + // Owner - creator + aRoomMember(userId = creatorUserId, role = RoomMember.Role.Owner(isCreator = true)) + ) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList.toPersistentList())) + } + val presenter = createChangeRolesPresenter(room = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + awaitItem().run { + assertThat(canChangeMemberRole(A_USER_ID_2)).isTrue() // Admin + assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator + assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner + } + } + } + + @Test + fun `present - when modifying admins, creators are displayed too`() = runTest { + val room = FakeJoinedRoom().apply { + val creatorUserId = UserId("@creator:matrix.org") + val memberList = aRoomMemberList() + .plus(aRoomMember(displayName = "CREATOR", role = RoomMember.Role.Owner(isCreator = true), userId = creatorUserId)) + .toPersistentList() + givenRoomInfo(aRoomInfo(roomCreators = listOf(creatorUserId))) + givenRoomMembersState(RoomMembersState.Ready(memberList)) + } + val presenter = createChangeRolesPresenter(room = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + awaitItem().searchResults.run { + assertThat(this).isInstanceOf(SearchBarResultState.Results::class.java) + val results = (this as SearchBarResultState.Results).results + assertThat(results.admins).isNotEmpty() + assertThat(results.owners).isNotEmpty() + assertThat(results.owners.last().role).isEqualTo(RoomMember.Role.Owner(isCreator = true)) + } + } + } + @Test fun `present - ToggleSearchActive changes the value`() = runTest { val room = FakeJoinedRoom().apply { @@ -145,7 +217,7 @@ class ChangeRolesPresenterTest { fun `present - UserSelectionToggle adds and removes users from the selected user list`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } val presenter = createChangeRolesPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -167,7 +239,7 @@ class ChangeRolesPresenterTest { fun `present - hasPendingChanges is true when the initial selected users don't match the new ones`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } val presenter = createChangeRolesPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -196,7 +268,7 @@ class ChangeRolesPresenterTest { fun `present - Exit will display success if no pending changes`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } val presenter = createChangeRolesPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -216,7 +288,7 @@ class ChangeRolesPresenterTest { fun `present - CancelExit will remove exit confirmation`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } val presenter = createChangeRolesPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -242,7 +314,7 @@ class ChangeRolesPresenterTest { fun `present - Exit will display a confirmation dialog if there are pending changes, calling it again will actually exit`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } val presenter = createChangeRolesPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -273,9 +345,9 @@ class ChangeRolesPresenterTest { baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }), ).apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } - val presenter = createChangeRolesPresenter(role = RoomMember.Role.ADMIN, room = room) + val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -302,9 +374,9 @@ class ChangeRolesPresenterTest { fun `present - CancelSave will remove the confirmation dialog`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) } - val presenter = createChangeRolesPresenter(role = RoomMember.Role.ADMIN, room = room) + val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -331,10 +403,10 @@ class ChangeRolesPresenterTest { baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }), ).apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.MODERATOR))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Moderator))) } val presenter = createChangeRolesPresenter( - role = RoomMember.Role.MODERATOR, + role = RoomMember.Role.Moderator, room = room, analyticsService = analyticsService ) @@ -358,15 +430,55 @@ class ChangeRolesPresenterTest { } } + @Test + fun `present - Save will just save the changes if the current user is a room creator and the selected users are not`() = runTest { + val analyticsService = FakeAnalyticsService() + val room = FakeJoinedRoom( + updateUserRoleResult = { Result.success(Unit) }, + baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }), + ).apply { + givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + givenRoomInfo( + aRoomInfo( + roomCreators = listOf(sessionId), + roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Admin, userId = A_USER_ID_2) + ) + ) + } + val presenter = createChangeRolesPresenter( + role = RoomMember.Role.Admin, + room = room, + analyticsService = analyticsService + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.selectedUsers).hasSize(1) + + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) + + awaitItem().eventSink(ChangeRolesEvent.Save) + + val loadingState = awaitItem() + assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) + skipItems(1) + + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(Unit)) + assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User)) + } + } + @Test fun `present - Save can handle failures and ClearError clears them`() = runTest { val room = FakeJoinedRoom( updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) } ).apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.MODERATOR))) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Moderator, userId = A_USER_ID))) } - val presenter = createChangeRolesPresenter(role = RoomMember.Role.MODERATOR, room = room) + val presenter = createChangeRolesPresenter(role = RoomMember.Role.Moderator, room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -399,7 +511,7 @@ class ChangeRolesPresenterTest { } private fun TestScope.createChangeRolesPresenter( - role: RoomMember.Role = RoomMember.Role.ADMIN, + role: RoomMember.Role = RoomMember.Role.Admin, room: FakeJoinedRoom = FakeJoinedRoom(), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt index 6f2179d4d4..e5aedece1e 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt @@ -41,11 +41,25 @@ class ChangeRolesViewTest { @get:Rule val rule = createAndroidComposeRule() @Test - fun `passing a 'USER' role throws an exception`() { + fun `passing a 'User' role throws an exception`() { val exception = runCatchingExceptions { rule.setChangeRolesContent( state = aChangeRolesState( - role = RoomMember.Role.USER, + role = RoomMember.Role.User, + eventSink = EnsureNeverCalledWithParam(), + ), + ) + }.exceptionOrNull() + + assertThat(exception).isNotNull() + } + + @Test + fun `passing an 'Owner' role throws an exception`() { + val exception = runCatchingExceptions { + rule.setChangeRolesContent( + state = aChangeRolesState( + role = RoomMember.Role.Owner(isCreator = true), eventSink = EnsureNeverCalledWithParam(), ), ) @@ -166,7 +180,7 @@ class ChangeRolesViewTest { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( - role = RoomMember.Role.ADMIN, + role = RoomMember.Role.Admin, isSearchActive = true, savingState = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder, @@ -183,7 +197,7 @@ class ChangeRolesViewTest { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( - role = RoomMember.Role.ADMIN, + role = RoomMember.Role.Admin, isSearchActive = true, savingState = AsyncAction.ConfirmingNoParams, eventSink = eventsRecorder, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/MembersByRoleTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/MembersByRoleTest.kt index f788624ccc..8241f4b781 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/MembersByRoleTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/MembersByRoleTest.kt @@ -14,6 +14,8 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_4 import io.element.android.libraries.matrix.test.A_USER_ID_5 +import io.element.android.libraries.matrix.test.A_USER_ID_6 +import io.element.android.libraries.matrix.test.A_USER_ID_7 import io.element.android.libraries.matrix.test.room.aRoomMember import kotlinx.collections.immutable.persistentListOf import org.junit.Test @@ -22,22 +24,28 @@ class MembersByRoleTest { @Test fun `constructor - with single member list categorizes and sorts members`() { val members = listOf( - aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.ADMIN), - aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.ADMIN), - aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER), - aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.USER), - aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.USER), + aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.Admin), + aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.Admin), + aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.User), + aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.User), + aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.User), + aRoomMember(A_USER_ID_6, displayName = "Justin", role = RoomMember.Role.Owner(isCreator = true)), + aRoomMember(A_USER_ID_7, displayName = "Mallory", role = RoomMember.Role.Owner(isCreator = false)), ) val membersByRole = MembersByRole(members = members) + assertThat(membersByRole.owners).containsExactly( + aRoomMember(A_USER_ID_6, displayName = "Justin", role = RoomMember.Role.Owner(isCreator = true)), + aRoomMember(A_USER_ID_7, displayName = "Mallory", role = RoomMember.Role.Owner(isCreator = false)), + ) assertThat(membersByRole.admins).containsExactly( - aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.ADMIN), - aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.ADMIN), + aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.Admin), + aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.Admin), ) assertThat(membersByRole.moderators).isEmpty() assertThat(membersByRole.members).containsExactly( - aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER), - aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.USER), - aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.USER), + aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.User), + aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.User), + aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.User), ) } @@ -46,24 +54,35 @@ class MembersByRoleTest { val emptyMembersByRole = MembersByRole(emptyList()) assertThat(emptyMembersByRole.isEmpty()).isTrue() + val membersByRoleWithOwners = MembersByRole( + owners = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Admin)), + admins = persistentListOf(), + moderators = persistentListOf(), + members = persistentListOf(), + ) + assertThat(membersByRoleWithOwners.isEmpty()).isFalse() + val membersByRoleWithAdmins = MembersByRole( - admins = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.ADMIN)), + owners = persistentListOf(), + admins = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Admin)), moderators = persistentListOf(), members = persistentListOf(), ) assertThat(membersByRoleWithAdmins.isEmpty()).isFalse() val membersByRoleWithModerators = MembersByRole( + owners = persistentListOf(), admins = persistentListOf(), - moderators = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.MODERATOR)), + moderators = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Moderator)), members = persistentListOf(), ) assertThat(membersByRoleWithModerators.isEmpty()).isFalse() val membersByRoleWithMembers = MembersByRole( + owners = persistentListOf(), admins = persistentListOf(), moderators = persistentListOf(), - members = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.USER)), + members = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.User)), ) assertThat(membersByRoleWithMembers.isEmpty()).isFalse() } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt index 70770a6c5c..c59931140a 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt @@ -15,9 +15,9 @@ 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.Role.ADMIN -import io.element.android.libraries.matrix.api.room.RoomMember.Role.MODERATOR -import io.element.android.libraries.matrix.api.room.RoomMember.Role.USER +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.RoomMember.Role.User import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom @@ -100,13 +100,13 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) + assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) assertThat(hasChanges).isTrue() } } @@ -120,28 +120,28 @@ class ChangeBaseRoomPermissionsPresenterTest { }.test { val state = awaitUpdatedItem() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator)) val items = cancelAndConsumeRemainingEvents() (items.last() as? Event.Item)?.value?.run { assertThat(currentPermissions).isEqualTo( RoomPowerLevelsValues( - invite = MODERATOR.powerLevel, - kick = MODERATOR.powerLevel, - ban = MODERATOR.powerLevel, - redactEvents = MODERATOR.powerLevel, - sendEvents = MODERATOR.powerLevel, - roomName = MODERATOR.powerLevel, - roomAvatar = MODERATOR.powerLevel, - roomTopic = MODERATOR.powerLevel, + invite = Moderator.powerLevel, + kick = Moderator.powerLevel, + ban = Moderator.powerLevel, + redactEvents = Moderator.powerLevel, + sendEvents = Moderator.powerLevel, + roomName = Moderator.powerLevel, + roomAvatar = Moderator.powerLevel, + roomTopic = Moderator.powerLevel, ) ) } @@ -162,17 +162,17 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) + assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, USER)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, ADMIN)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, ADMIN)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, ADMIN)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin)) skipItems(7) assertThat(awaitItem().hasChanges).isTrue() @@ -181,7 +181,7 @@ class ChangeBaseRoomPermissionsPresenterTest { assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().hasChanges).isFalse() awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) } assertThat(analyticsService.capturedEvents).containsExactlyElementsIn( @@ -227,17 +227,17 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) + assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Save) assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) // Couldn't save the changes, so they're still pending assertThat(hasChanges).isTrue() assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -245,7 +245,7 @@ class ChangeBaseRoomPermissionsPresenterTest { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) assertThat(hasChanges).isTrue() } @@ -259,7 +259,7 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Exit) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt index 8ed659d31c..f4ab1ef1a9 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt @@ -115,9 +115,9 @@ class ChangeBaseRoomPermissionsViewTest { rule.onAllNodesWithText(users).onFirst().performClick() recorder.assertList( listOf( - ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.ADMIN), - ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.MODERATOR), - ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.USER), + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Admin), + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Moderator), + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.User), ) ) } 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 3496612c35..2d1bf77fe0 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 @@ -61,8 +61,8 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom( canBan = false, canKick = false, - myUserRole = RoomMember.Role.USER, - targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel) + myUserRole = RoomMember.Role.User, + targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.User.powerLevel) ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -81,7 +81,7 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom( canBan = true, canKick = true, - myUserRole = RoomMember.Role.ADMIN, + myUserRole = RoomMember.Role.Admin, targetRoomMember = null ) createRoomMemberModerationPresenter(room = room).test { @@ -103,8 +103,8 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom( canBan = true, canKick = true, - myUserRole = RoomMember.Role.ADMIN, - targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel) + myUserRole = RoomMember.Role.Admin, + targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.User.powerLevel) ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -125,8 +125,8 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom( canBan = true, canKick = true, - myUserRole = RoomMember.Role.MODERATOR, - targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.ADMIN.powerLevel) + myUserRole = RoomMember.Role.Moderator, + targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.Admin.powerLevel) ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -147,7 +147,7 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom( canBan = true, canKick = true, - myUserRole = RoomMember.Role.MODERATOR, + myUserRole = RoomMember.Role.Moderator, targetRoomMember = aRoomMember(userId = A_USER_ID, membership = RoomMembershipState.BAN) ) createRoomMemberModerationPresenter(room = room).test { @@ -321,7 +321,7 @@ class RoomMemberModerationPresenterTest { private fun aJoinedRoom( canKick: Boolean = false, canBan: Boolean = false, - myUserRole: RoomMember.Role = RoomMember.Role.USER, + myUserRole: RoomMember.Role = RoomMember.Role.User, kickUserResult: Result = Result.success(Unit), banUserResult: Result = Result.success(Unit), unBanUserResult: Result = Result.success(Unit), diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt index f3224d546b..48e549ab3f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt @@ -85,7 +85,7 @@ data class RoomInfo( * Returns the list of users with the given [role] in this room. */ fun usersWithRole(role: RoomMember.Role): List { - return if (role == RoomMember.Role.CREATOR) { + return if (role is RoomMember.Role.Owner && role.isCreator) { this.creators } else { this.roomPowerLevels?.usersWithRole(role).orEmpty().toList() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 74c5eef63d..c2db1e9ec5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -25,21 +25,34 @@ data class RoomMember( /** * Role of the RoomMember, based on its [powerLevel]. */ - enum class Role(val powerLevel: Long) { - CREATOR(Long.MAX_VALUE), - ADMIN(100L), - MODERATOR(50L), - USER(0L); + sealed interface Role { + data class Owner(val isCreator: Boolean) : Role + data object Admin : Role + data object Moderator : Role + data object User : Role + + val powerLevel: Long + get() = when (this) { + is Owner -> if (isCreator) CREATOR_POWERLEVEL else SUPERADMIN_POWERLEVEL + Admin -> ADMIN_POWERLEVEL + Moderator -> MODERATOR_POWERLEVEL + User -> USER_POWERLEVEL + } companion object { - const val SUPER_ADMIN_LEVEL = 150L + private const val CREATOR_POWERLEVEL = Long.MAX_VALUE + private const val SUPERADMIN_POWERLEVEL = 150L + private const val ADMIN_POWERLEVEL = 100L + private const val MODERATOR_POWERLEVEL = 50L + private const val USER_POWERLEVEL = 0L fun forPowerLevel(powerLevel: Long): Role { return when { - powerLevel > SUPER_ADMIN_LEVEL -> CREATOR - powerLevel >= ADMIN.powerLevel -> ADMIN - powerLevel >= MODERATOR.powerLevel -> MODERATOR - else -> USER + powerLevel == CREATOR_POWERLEVEL -> Owner(isCreator = true) + powerLevel >= SUPERADMIN_POWERLEVEL -> Owner(isCreator = false) + powerLevel >= ADMIN_POWERLEVEL -> Admin + powerLevel >= MODERATOR_POWERLEVEL -> Moderator + else -> User } } } @@ -87,11 +100,3 @@ fun RoomMember.toMatrixUser() = MatrixUser( displayName = displayName, avatarUrl = avatarUrl, ) - -/** - * Returns `true` if the [RoomMember] is an owner of the room. - * Owners are defined as members with either the [RoomMember.Role.CREATOR] role or a power level greater than or equal to [RoomMember.Role.SUPER_ADMIN_LEVEL]. - */ -fun RoomMember.isOwner(): Boolean { - return role == RoomMember.Role.CREATOR || powerLevel >= RoomMember.Role.SUPER_ADMIN_LEVEL -} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt index 2d8b415ae5..635a6cc1f4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt @@ -25,14 +25,23 @@ data class RoomPowerLevels( val values: RoomPowerLevelsValues, private val users: ImmutableMap, ) { + /** + * Returns the power level of the user in the room. + * + * If the user is not found, returns 0. + */ + fun powerLevelOf(userId: UserId): Long { + return users[userId] ?: 0L + } + /** * Returns the set of [UserId]s that have the given role in the room. * - * **WARNING**: This method must not be used with the [RoomMember.Role.CREATOR] role. It'll result in a runtime error. + * **WARNING**: This method must not be used with a creator role. It'll result in a runtime error. */ fun usersWithRole(role: RoomMember.Role): Set { - return if (role == RoomMember.Role.CREATOR) { - error("RoomPowerLevels.usersWithRole should not be used with CREATOR role, use roomInfo.creators instead") + return if (role is RoomMember.Role.Owner && role.isCreator) { + error("RoomPowerLevels.usersWithRole should not be used with a creator role, use roomInfo.creators instead") } else { users.filterValues { RoomMember.Role.forPowerLevel(it) == role }.keys } @@ -42,7 +51,7 @@ data class RoomPowerLevels( * Returns the role of the user in the room based on their power level. * If the user is not found, returns null. * - * **WARNING**: This method must not be used with the [RoomMember.Role.CREATOR] role, as it won't return any results. + * **WARNING**: This method must not be used with a creator role, as it won't return any results. */ fun roleOf(userId: UserId): RoomMember.Role? { return users[userId]?.let(RoomMember.Role::forPowerLevel) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 50b71d92c1..83369b4c59 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -343,7 +343,7 @@ class RustMatrixClient( powerLevelContentOverride = defaultRoomCreationPowerLevels.copy( invite = if (createRoomParams.joinRuleOverride == JoinRule.Knock) { // override the invite power level so it's the same as kick. - RoomMember.Role.MODERATOR.powerLevel.toInt() + RoomMember.Role.Moderator.powerLevel.toInt() } else { null } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt index 45aad80350..975185242b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt @@ -128,7 +128,11 @@ class RustBaseRoom( override suspend fun userRole(userId: UserId): Result = withContext(roomDispatcher) { runCatchingExceptions { - RoomMemberMapper.mapRole(innerRoom.suggestedRoleForUser(userId.value)) + val powerLevel = roomInfoFlow.value.roomPowerLevels?.powerLevelOf(userId) ?: 0L + RoomMemberMapper.mapRole( + role = innerRoom.suggestedRoleForUser(userId.value), + powerLevel = powerLevel, + ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt index fe49cdff83..9411ae3aab 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt @@ -16,25 +16,36 @@ import org.matrix.rustcomponents.sdk.MembershipState as RustMembershipState import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember object RoomMemberMapper { - fun map(roomMember: RustRoomMember): RoomMember = RoomMember( - userId = UserId(roomMember.userId), - displayName = roomMember.displayName, - avatarUrl = roomMember.avatarUrl, - membership = mapMembership(roomMember.membership), - isNameAmbiguous = roomMember.isNameAmbiguous, - powerLevel = roomMember.powerLevel.into(), - normalizedPowerLevel = roomMember.normalizedPowerLevel.into(), - isIgnored = roomMember.isIgnored, - role = mapRole(roomMember.suggestedRoleForPowerLevel), - membershipChangeReason = roomMember.membershipChangeReason - ) + fun map(roomMember: RustRoomMember): RoomMember { + val powerLevel = roomMember.powerLevel.into() + return RoomMember( + userId = UserId(roomMember.userId), + displayName = roomMember.displayName, + avatarUrl = roomMember.avatarUrl, + membership = mapMembership(roomMember.membership), + isNameAmbiguous = roomMember.isNameAmbiguous, + powerLevel = powerLevel, + normalizedPowerLevel = roomMember.normalizedPowerLevel.into(), + isIgnored = roomMember.isIgnored, + role = mapRole(roomMember.suggestedRoleForPowerLevel, powerLevel), + membershipChangeReason = roomMember.membershipChangeReason + ) + } - fun mapRole(role: RoomMemberRole): RoomMember.Role = + fun mapRole(role: RoomMemberRole, powerLevel: Long?): RoomMember.Role = when (role) { - RoomMemberRole.CREATOR -> RoomMember.Role.CREATOR - RoomMemberRole.ADMINISTRATOR -> RoomMember.Role.ADMIN - RoomMemberRole.MODERATOR -> RoomMember.Role.MODERATOR - RoomMemberRole.USER -> RoomMember.Role.USER + RoomMemberRole.CREATOR -> RoomMember.Role.Owner(isCreator = true) + RoomMemberRole.ADMINISTRATOR -> { + val superAdmin = RoomMember.Role.Owner(isCreator = false) + val powerLevelOrDefault = powerLevel ?: 0L + if (powerLevelOrDefault >= superAdmin.powerLevel) { + superAdmin + } else { + RoomMember.Role.Admin + } + } + RoomMemberRole.MODERATOR -> RoomMember.Role.Moderator + RoomMemberRole.USER -> RoomMember.Role.User } fun mapMembership(membershipState: RustMembershipState): RoomMembershipState = diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt index f48c055db0..9121d8bdf0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt @@ -28,6 +28,6 @@ object RoomPowerLevelsValuesMapper { } fun PowerLevel.into(): Long = when (this) { - PowerLevel.Infinite -> RoomMember.Role.CREATOR.powerLevel + PowerLevel.Infinite -> RoomMember.Role.Owner(isCreator = true).powerLevel is PowerLevel.Value -> this.value } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt index 45aa970658..41a0424991 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt @@ -16,6 +16,7 @@ import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomMembersIterator +import uniffi.matrix_sdk.RoomMemberRole class FakeFfiRoom( private val roomId: RoomId = A_ROOM_ID, @@ -23,6 +24,7 @@ class FakeFfiRoom( private val getMembersNoSync: () -> RoomMembersIterator = { lambdaError() }, private val leaveLambda: () -> Unit = { lambdaError() }, private val latestEventLambda: () -> EventTimelineItem? = { lambdaError() }, + private val suggestedRoleForUserLambda: (String) -> RoomMemberRole = { lambdaError() }, private val roomInfo: RoomInfo = aRustRoomInfo(id = roomId.value), ) : Room(NoPointer) { override fun id(): String { @@ -49,6 +51,10 @@ class FakeFfiRoom( return latestEventLambda() } + override suspend fun suggestedRoleForUser(userId: String): RoomMemberRole { + return suggestedRoleForUserLambda(userId) + } + override fun close() { // No-op } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt index f25f568c5a..e24eca53cb 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt @@ -12,20 +12,26 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomInfo +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +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.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoom import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.isActive import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import uniffi.matrix_sdk.RoomMemberRole class RustBaseRoomTest { @Test @@ -111,6 +117,29 @@ class RustBaseRoomTest { } } + @Test + fun `userRole loads and maps the role`() = runTest { + val rustBaseRoom = createRustBaseRoom( + initialRoomInfo = aRoomInfo( + roomPowerLevels = RoomPowerLevels( + values = RoomPowerLevelsValues(50, 50, 50, 50, 50, 50, 50, 50), + users = persistentMapOf(A_USER_ID to 100L) + ) + ), + innerRoom = FakeFfiRoom( + suggestedRoleForUserLambda = { userId -> + // Simulate the role suggestion based on power level + if (userId == A_USER_ID.value) RoomMemberRole.ADMINISTRATOR else RoomMemberRole.USER + } + ), + ) + val result = rustBaseRoom.userRole(A_USER_ID).getOrNull() + assertThat(result).isNotNull() + assertThat(result).isEqualTo(RoomMember.Role.Admin) + + rustBaseRoom.destroy() + } + private suspend fun TestScope.leaveRoomAndObserveMembershipChange( roomMembershipObserver: RoomMembershipObserver, rustBaseRoom: RustBaseRoom, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt index 7b26d13dbc..6c469c6eb8 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt @@ -17,9 +17,19 @@ import org.matrix.rustcomponents.sdk.MembershipState as RustMembershipState class RoomMemberMapperTest { @Test fun mapRole() { - assertThat(RoomMemberMapper.mapRole(RoomMemberRole.USER)).isEqualTo(RoomMember.Role.USER) - assertThat(RoomMemberMapper.mapRole(RoomMemberRole.MODERATOR)).isEqualTo(RoomMember.Role.MODERATOR) - assertThat(RoomMemberMapper.mapRole(RoomMemberRole.ADMINISTRATOR)).isEqualTo(RoomMember.Role.ADMIN) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.USER, 0L)).isEqualTo(RoomMember.Role.User) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.MODERATOR, 50L)).isEqualTo(RoomMember.Role.Moderator) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.ADMINISTRATOR, 100L)).isEqualTo(RoomMember.Role.Admin) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.ADMINISTRATOR, 150L)).isEqualTo(RoomMember.Role.Owner(isCreator = false)) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.CREATOR, Long.MAX_VALUE)).isEqualTo(RoomMember.Role.Owner(isCreator = true)) + + // `null` power level defaults to USER role + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.ADMINISTRATOR, null)).isEqualTo(RoomMember.Role.Admin) + + // Power level is only taken into account for ADMINISTRATOR role + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.USER, 123L)).isEqualTo(RoomMember.Role.User) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.MODERATOR, 1L)).isEqualTo(RoomMember.Role.Moderator) + assertThat(RoomMemberMapper.mapRole(RoomMemberRole.CREATOR, 0L)).isEqualTo(RoomMember.Role.Owner(isCreator = true)) } @Test diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt index f4f7f3d835..4c620dfd5a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt @@ -20,7 +20,7 @@ fun aRoomMember( powerLevel: Long = 0L, normalizedPowerLevel: Long = 0L, isIgnored: Boolean = false, - role: RoomMember.Role = RoomMember.Role.USER, + role: RoomMember.Role = RoomMember.Role.User, membershipChangeReason: String? = null, ) = RoomMember( userId = userId, 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 d6d53a3321..a6520d5580 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 @@ -22,14 +22,14 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData( /** * Returns the role of the user in the room. - * If the user is a creator, returns [RoomMember.Role.CREATOR]. + * If the user is a creator, returns [RoomMember.Role.Owner]. * Otherwise, checks the power levels and returns the corresponding role. - * If no specific power level is set for the user, defaults to [RoomMember.Role.USER]. + * If no specific power level is set for the user, defaults to [RoomMember.Role.User]. */ fun RoomInfo.roleOf(userId: UserId): RoomMember.Role { return if (creators.contains(userId)) { - RoomMember.Role.CREATOR + RoomMember.Role.Owner(isCreator = true) } else { - roomPowerLevels?.roleOf(userId) ?: RoomMember.Role.USER + roomPowerLevels?.roleOf(userId) ?: RoomMember.Role.User } } 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 index f4b372df5f..ad97d30cfe 100644 --- 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 @@ -99,7 +99,7 @@ fun BaseRoom.canHandleKnockRequestsAsState(updateKey: Long): State { fun BaseRoom.userPowerLevelAsState(updateKey: Long): State { return produceState(initialValue = 0, key1 = updateKey) { value = userRole(sessionId) - .getOrDefault(RoomMember.Role.USER) + .getOrDefault(RoomMember.Role.User) .powerLevel } } @@ -108,7 +108,7 @@ fun BaseRoom.userPowerLevelAsState(updateKey: Long): State { fun BaseRoom.isOwnUserAdmin(): Boolean { val roomInfo by roomInfoFlow.collectAsState() val role = roomInfo.roleOf(sessionId) - return role == RoomMember.Role.ADMIN || role == RoomMember.Role.CREATOR + return role == RoomMember.Role.Admin || role is RoomMember.Role.Owner } @Composable diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en.png index f0d82f9d09..5dec06b739 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5c71623f182b0e2aac51c276d3b61f039997c3dadf6c7bd9de6c4f2dfa352b8 -size 49837 +oid sha256:a1a33eba9c38eb8e518401f19536be0dedeb25e1c6fa805006d25b2354eea9ad +size 50289 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_11_en.png new file mode 100644 index 0000000000..d526d29dc4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01a7f9e4be6f064593c299e84f9bf3926dbeb5be1c243b7293889c178c593672 +size 53305 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en.png index 9c9ce4e803..ffa4748938 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:326e774bb0420b4d55cca0014bcb8aac69f2351e58e756c7d63de3905f5bb6d0 -size 66470 +oid sha256:f002866b7076f6541d2842009e7f0f2df1ab32981ca3d2bca0c8dddb260a3991 +size 65349 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en.png index 39c11d95e7..1275aa4578 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba30b7ffc1096fd5190fbf7e1881217dce20a5e0865800d49b26d82fd0aac0dd -size 60733 +oid sha256:95e9f4fb1fdce076c41091c8e629fb5e4540d6c820271464a0df62ec9c86d04b +size 60279 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en.png index 402673d64c..79b11504f2 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fa5719b287ebb77a79900ffdec8793d9a86f2a07920f32c50924872609b09d5 -size 60682 +oid sha256:9e24c761af34c2256845387f58fa09bd0200a5a325a7f263b1544db1f8109d55 +size 60230 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en.png index 35975f0208..f7b50c340d 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46cd3b9a7be00e4f41fbaab11e1cb6aa6528d6db578a84512f7abedf5d4b5492 -size 55610 +oid sha256:ebe33703e823b36249c9ce77c1bc40cd1bc76a7a184947b2b8dd01d04add1126 +size 55151 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en.png index 85bd84bb10..42b8106d12 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13c8b71ef7331e86d15200b8b1979688b21337237bcccd309ce70d866f9304f4 -size 12932 +oid sha256:128c073395c100b5fa4d61ce8549fac6db753dd1d8ab33475652b5a051130f6e +size 12485 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en.png index 71757e0f8d..8f18954e8f 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d992de8f70372d03ca2e701310ab261c1f5ddb5864810071a84f6b17ce5f6a4 -size 57531 +oid sha256:57509ab85d4f7feda8287a9a39353c8213633b0675a638b286523bb9cdc177fe +size 58002 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en.png index 901af608dd..df34183203 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b34ca16979d6e496ec13cec78250eace85e17616cfa162d131d2c8f797c6b1f -size 59946 +oid sha256:0ddf77c1776754186a16520834dd32458777cefd02b63e810eae46b85788e825 +size 60434 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en.png index 4ada35a715..d1d40d0ae3 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3dbd48f7c11b0b5881eedf939654e50b37bfc349c28c3c0e93d5ca5bda0717da -size 51691 +oid sha256:0ef2a7ada57b834c8cc062096e5793027ba0e5b86d5d43bef88d3a5c448d5bdb +size 51789 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en.png index 023f5af795..19d5f7b959 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83a9e031d5b928895778ac073d4374863a5e8a6b0ac9d99fee0a95879a0421e9 -size 64502 +oid sha256:699f12cc41c0d766c61db13ea980fb150b7027457526892da636fa020d3e6a2f +size 64046 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en.png index 1b3fa517d2..bd402d9bac 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a00224f7aea8db03d01c0dbf244b90eb5d2d3f4289ced45cda874dc655fbac9 -size 48320 +oid sha256:0b2908f2612fde0ac68aa872b039e39614004e9207119f34fd480bf9ab558e5f +size 48502 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_11_en.png new file mode 100644 index 0000000000..15668cbd4b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0907cb2655a7a02dd4cc00ffaabf564274eb54ccb739ac61d0b3c6f13a7dd86c +size 54081 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en.png index f5c9bf6755..b3c14a2192 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbca019f7bb7555bcfea4645ad3ec00445ec55a54cc98611b1e0d7f99bebeea4 -size 67131 +oid sha256:d05b4a8db64921114da702cbbbf3480037a5e95299b6be61b64882063bf3a8d6 +size 66013 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en.png index 06adaeeaa3..eb56e2372f 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa22afe652bae97bbe9e3504610f520e6fd7337f2757f79c39e9e0f6115902fe -size 61461 +oid sha256:def295811ae2872a5c882de15ced9613e6699d52d07a7263f2bee4f1cbaabbe8 +size 61093 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en.png index 0d8b2edc9a..92c500c146 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbb5c63809d556bcc60b11b9c23940e26bfac377ed4a5a0d97971e060365c6f3 -size 61346 +oid sha256:f87e5c5ee46d0d722d468c20b7403393a34eda7c555fc331e5d936fab6bbc5f6 +size 60981 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en.png index 4daeb638d8..bc2037d7f1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a15796338eaa5a8b7a5c7e5252fe2d9c392f2d0dd78ce9e0976dfd68fa6ad5 -size 55975 +oid sha256:e9721797718883f2130ba5a90037f1edf8d813a02b5b8329fd7c677a517b6657 +size 55601 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en.png index 31bb7dad85..0272e43931 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:562dc9be79006376346ab1b2635890615eecc381e82c15b16a9ebf1855741e24 -size 12817 +oid sha256:f9a601e883469969a5c585e44d2c3f5da53abf307a9334fa19c27f685d3c7731 +size 12436 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en.png index 8ae4205f0d..a240e4f154 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2023b53d3a7099f55bc36a738506c6fab2910f3c83ca24a1647b6ba35c29f351 -size 55807 +oid sha256:ca1a7ecdceabde82b8283bba218211a7a86c33a01887a6b1d021cdd0460fdf47 +size 55937 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en.png index 796180be45..1ed07aef66 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41c3b6a09bf3899a0a28ac6b9fc96b773687adeb40a4b11ab0f80dadc1139e1e -size 58176 +oid sha256:2865975bc3f091eead3fdab2870622aa8d8336d2bd0bde7110249e14d1bf5527 +size 58289 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en.png index 1509868c15..228944b78b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24389189d4955f584fb1ed4b390e6ec829622d457ee7f3205b0104f2bcd90bdd -size 50948 +oid sha256:7f15f8c3e7b04e73ef0af6b013e8b817946016e81c4cc788f3c9fd1bc5e8667e +size 50836 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en.png index c5fdad0d7d..a9f04fb2b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:971a027f6aaaa21b7d62af3f08b50eb7cf8369199076226ea92e1b4b30f74342 -size 64234 +oid sha256:f184247e1ed5f006dca11426d94498c12724475011b4509d7f8fd13dd95fe0f9 +size 63882 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png index fa54fe8bd0..81568f2c67 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67a08774600869ff76540d518c0d5f80c38f1f155a65af56374ccf362d5109d4 -size 40618 +oid sha256:8116875aca8d06c8450168555a8e591cb96b23aaa9f194f62456b6b369cfbc50 +size 42699 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png index 21bd18a2c0..a209564cf1 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc1246fbe273fafe61dfacfe4131aeb166a81a4c4befc35fbeb68cf85c0f355f -size 57070 +oid sha256:68af7df886052c7db027225c978b8c10f5bf69beab65834add7456500f2d5cc0 +size 59115 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png index 59f4a409b2..f31dc64e93 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f632403bc5aad7990c8f2a81eec9977db7bbf0fd8db33bbda61af5d25329ac7f -size 37776 +oid sha256:168e8ccfc1be8927e7bab6526e71044786aefc5f8d6f5594b971e75926196481 +size 39424 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png index b393d1eef1..d87ba2d721 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60df11189f82a42a51fddfcece2d0fdd88c55d06f2a1ee1e2069bae74e9c1378 -size 36410 +oid sha256:3e2fce3603a20cdda8ac969e15cbd0b636185f0ff63930d67795074e740cf807 +size 38175 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png index 286ddded30..410e22851e 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4cff66b4102160aeeb3db4bb5df1fef1428b5fadc7a12a62170d2335f506ad3b -size 46662 +oid sha256:79d359483e7bc03c3d5facf61bdc854711f685c1826fd909e8d599892eb28de3 +size 48333 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png index 59f4a409b2..f31dc64e93 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f632403bc5aad7990c8f2a81eec9977db7bbf0fd8db33bbda61af5d25329ac7f -size 37776 +oid sha256:168e8ccfc1be8927e7bab6526e71044786aefc5f8d6f5594b971e75926196481 +size 39424 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png index aa6320755c..bece8aca73 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:554d8d41ed47b34e71f6fff1689ee2c9488580d012d4ad2b867814399899c6e7 -size 36812 +oid sha256:e9be92dc8fbaebaa5c108754180eba18fdab8a31c5a239091b2779480bf89848 +size 38620 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png new file mode 100644 index 0000000000..9295b0a5a8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:620f0bc7825ebf1d0abcbc824c990162bf6a98afe9d3566b476c16dabaa17935 +size 39106 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png index 4115eb9688..65fb11c758 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:036944c74e2a5150d54e9ad4d64fe6b624fe8e889e411c226816398c65110f55 -size 39321 +oid sha256:6cd19bb1be2581e0f1bb50624111bc13717a60778d5141f06857e1edaa34ab69 +size 41325 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png index b87b3c6850..572cbee231 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24df22e86f0fe16ba9d3ce944105c0e2450bed08c8a9556f0d3da0cec32067b5 -size 54642 +oid sha256:79770d1b10c85dc12bf57f0a4cf5cb147aca1dc91841bf680349a4603b266310 +size 56490 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png index f4a6eb9f29..559cf71d76 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca39d5d1f2a24584a695f7e3741a82109870a41a289b72882b9cf476e0526de6 -size 35948 +oid sha256:33bda0d03e1f14113201df4187a0af99e5e415d2cf2cb0e7c24803d5685dbee8 +size 37586 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png index 3dc4557519..e5b7219d4b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4381d361278286530bbf89b79c56ae37edb722ec0a829ad20f98ebe1205aa1b9 -size 33990 +oid sha256:e7625469fb3ad71c44d84e3b0f55cb952f07c15a57117a5c5b369952c6dab089 +size 35681 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png index 00930872a0..5ca1da955b 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29863c9616f9ff6eedc59d9c8a78304e061f68448c51c217f8c43d7bb2b5bf69 -size 43568 +oid sha256:4c7bfeaa1a068be4ed2c21f659dedf996442ac4d0dbd27d8fa9c0d49944cc38f +size 45256 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png index f4a6eb9f29..559cf71d76 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca39d5d1f2a24584a695f7e3741a82109870a41a289b72882b9cf476e0526de6 -size 35948 +oid sha256:33bda0d03e1f14113201df4187a0af99e5e415d2cf2cb0e7c24803d5685dbee8 +size 37586 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png index b0418ba23b..f8c51f7f69 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dea918985f1cb9870d5da1e84ac2c7a43227f03aae4e4b7462bac5d6ba7343c2 -size 34480 +oid sha256:f65d83a0f0b106cd9738b94b99c1fe8ea75fcadb443377da65d8847e72adfd72 +size 36169 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png new file mode 100644 index 0000000000..52eca6f222 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd8ce7b96a775a029920f5274d4dbc7ed03eb34ed446299ba0043a57ee20c7eb +size 37978