From 59a682b407d6e75e5eaf4458ffa9f03a31463201 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 12 Mar 2024 15:45:06 +0100 Subject: [PATCH] Change a room's permissions power levels (#2525) * Change a room's permissions power levels * Make `currentPermissions` use a `MatrixRoomPowerLevels?` instance instead. * Update strings * Update screenshots --------- Co-authored-by: ElementBot --- changelog.d/2259.feature | 1 + .../impl/src/main/res/values/localazy.xml | 8 +- .../RolesAndPermissionsEvents.kt | 1 + .../RolesAndPermissionsFlowNode.kt | 24 ++ .../RolesAndPermissionsNode.kt | 29 +- .../RolesAndPermissionsPresenter.kt | 16 + .../RolesAndPermissionsState.kt | 1 + .../RolesAndPermissionsStateProvider.kt | 17 + .../RolesAndPermissionsView.kt | 57 +++- .../permissions/ChangeRoomPermissionsEvent.kt | 26 ++ .../permissions/ChangeRoomPermissionsNode.kt | 66 ++++ .../ChangeRoomPermissionsPresenter.kt | 145 +++++++++ .../permissions/ChangeRoomPermissionsState.kt | 42 +++ ...angeRoomPermissionsStatePreviewProvider.kt | 74 +++++ .../permissions/ChangeRoomPermissionsView.kt | 192 ++++++++++++ .../impl/src/main/res/values/localazy.xml | 8 +- .../RolesAndPermissionPresenterTests.kt | 30 ++ .../RolesAndPermissionsViewTests.kt | 109 ++++++- .../ChangeRoomPermissionsPresenterTests.kt | 294 ++++++++++++++++++ .../ChangeRoomPermissionsViewTests.kt | 201 ++++++++++++ .../impl/StateContentFormatter.kt | 2 +- .../impl/src/main/res/values/localazy.xml | 4 + .../DefaultRoomLastMessageFormatterTest.kt | 2 +- .../libraries/matrix/api/room/MatrixRoom.kt | 7 + .../room/powerlevels/MatrixRoomPowerLevels.kt | 11 + .../api/timeline/item/event/OtherState.kt | 2 +- .../matrix/impl/room/RustMatrixRoom.kt | 31 ++ .../room/powerlevels/RoomPowerLevelsMapper.kt | 35 +++ .../item/event/TimelineEventContentMapper.kt | 2 +- .../matrix/test/room/FakeMatrixRoom.kt | 38 +++ .../src/main/res/values/localazy.xml | 1 + .../tests/testutils/EnsureCalledOnce.kt | 19 ++ ...nticsNodeInteractionsProviderExtensions.kt | 11 + ...sView-Day-10_11_null_0,NEXUS_5,1.0,en].png | 3 + ...sView-Day-10_11_null_1,NEXUS_5,1.0,en].png | 3 + ...sView-Day-10_11_null_2,NEXUS_5,1.0,en].png | 3 + ...sView-Day-10_11_null_3,NEXUS_5,1.0,en].png | 3 + ...sView-Day-10_11_null_4,NEXUS_5,1.0,en].png | 3 + ...sView-Day-10_11_null_5,NEXUS_5,1.0,en].png | 3 + ...sView-Day-10_11_null_6,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_0,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_1,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_2,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_3,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_4,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_5,NEXUS_5,1.0,en].png | 3 + ...iew-Night-10_12_null_6,NEXUS_5,1.0,en].png | 3 + ...ionView-Day-8_9_null_0,NEXUS_5,1.0,en].png | 3 + ...ionView-Day-8_9_null_1,NEXUS_5,1.0,en].png | 3 + ...onView-Day-8_9_null_2,NEXUS_5,1.0,en].png} | 0 ...ionView-Day-8_9_null_3,NEXUS_5,1.0,en].png | 3 + ...ionView-Day-8_9_null_4,NEXUS_5,1.0,en].png | 3 + ...ionView-Day-8_9_null_5,NEXUS_5,1.0,en].png | 3 + ...ionView-Day-8_9_null_6,NEXUS_5,1.0,en].png | 3 + ...ionView-Day-8_9_null_7,NEXUS_5,1.0,en].png | 3 + ...View-Night-8_10_null_0,NEXUS_5,1.0,en].png | 3 + ...View-Night-8_10_null_1,NEXUS_5,1.0,en].png | 3 + ...iew-Night-8_10_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-8_10_null_3,NEXUS_5,1.0,en].png | 3 + ...View-Night-8_10_null_4,NEXUS_5,1.0,en].png | 3 + ...View-Night-8_10_null_5,NEXUS_5,1.0,en].png | 3 + ...View-Night-8_10_null_6,NEXUS_5,1.0,en].png | 3 + ...View-Night-8_10_null_7,NEXUS_5,1.0,en].png | 3 + ...ngsView-Day-8_9_null_0,NEXUS_5,1.0,en].png | 3 - ...ngsView-Day-8_9_null_1,NEXUS_5,1.0,en].png | 3 - ...ngsView-Day-8_9_null_3,NEXUS_5,1.0,en].png | 3 - ...ngsView-Day-8_9_null_4,NEXUS_5,1.0,en].png | 3 - ...View-Night-8_10_null_0,NEXUS_5,1.0,en].png | 3 - ...View-Night-8_10_null_1,NEXUS_5,1.0,en].png | 3 - ...View-Night-8_10_null_3,NEXUS_5,1.0,en].png | 3 - ...View-Night-8_10_null_4,NEXUS_5,1.0,en].png | 3 - 71 files changed, 1556 insertions(+), 58 deletions(-) create mode 100644 changelog.d/2259.feature create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStatePreviewProvider.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTests.kt create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsViewTests.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsMapper.kt create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_1,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_2,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_7,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_1,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_2,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_7,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_4,NEXUS_5,1.0,en].png diff --git a/changelog.d/2259.feature b/changelog.d/2259.feature new file mode 100644 index 0000000000..24a3b06b3e --- /dev/null +++ b/changelog.d/2259.feature @@ -0,0 +1 @@ +Change a room's permissions power levels. diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 61cafa9ac5..daebb8613b 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -36,10 +36,10 @@ "Messages and content" "Admins and moderators" "Remove people" - "Change Room Avatar" + "Change room avatar" "Room details" - "Change Room Name" - "Change Room Topic" + "Change room name" + "Change room topic" "Send messages" "Edit Admins" "You will not be able to undo this action. You are promoting the user to have the same power level as you." @@ -87,7 +87,7 @@ "Moderators" "Permissions" "Reset permissions" - "Once you reset permissions, you will lose your current settings." + "Once you reset permissions, you will lose the current settings." "Reset permissions?" "Roles" "Room details" diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt index 249d934816..f81fd94fb0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt @@ -21,5 +21,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RolesAndPermissionsEvents { data object ChangeOwnRole : RolesAndPermissionsEvents data class DemoteSelfTo(val role: RoomMember.Role) : RolesAndPermissionsEvents + data object ResetPermissions : RolesAndPermissionsEvents data object CancelPendingAction : RolesAndPermissionsEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt index 6c38551441..319de14309 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt @@ -28,6 +28,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.ChangeRolesNode +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsNode +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode @@ -55,6 +57,9 @@ class RolesAndPermissionsFlowNode @AssistedInject constructor( @Parcelize data object ModeratorList : NavTarget + + @Parcelize + data class ChangeRoomPermissions(val section: ChangeRoomPermissionsSection) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -68,6 +73,18 @@ class RolesAndPermissionsFlowNode @AssistedInject constructor( override fun openModeratorList() { backstack.push(NavTarget.ModeratorList) } + + override fun openEditRoomDetailsPermissions() { + backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.RoomDetails)) + } + + override fun openMessagesAndContentPermissions() { + backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MessagesAndContent)) + } + + override fun openModerationPermissions() { + backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MembershipModeration)) + } } createNode( buildContext = buildContext, @@ -88,6 +105,13 @@ class RolesAndPermissionsFlowNode @AssistedInject constructor( plugins = listOf(inputs), ) } + is NavTarget.ChangeRoomPermissions -> { + val inputs = ChangeRoomPermissionsNode.Inputs(navTarget.section) + createNode( + buildContext = buildContext, + plugins = listOf(inputs), + ) + } } } 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 1e78302146..0da156e54b 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 @@ -17,6 +17,7 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -46,17 +47,24 @@ class RolesAndPermissionsNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: RolesAndPermissionsPresenter, private val room: MatrixRoom, -) : Node(buildContext, plugins = plugins), RoomDetailsAdminSettingsNavigator { - interface Callback : Plugin { - fun openAdminList() - fun openModeratorList() +) : Node(buildContext, plugins = plugins), RolesAndPermissionsNavigator { + interface Callback : Plugin, RolesAndPermissionsNavigator { + override fun openAdminList() + override fun openModeratorList() + override fun openEditRoomDetailsPermissions() + override fun openMessagesAndContentPermissions() + override fun openModerationPermissions() + override fun onBackPressed() {} } private val callback = plugins().first() - override fun onBackPressed() = navigateUp() - override fun openAdminList() = callback.openAdminList() - override fun openModeratorList() = callback.openModeratorList() + @Stable + private val navigator = object : RolesAndPermissionsNavigator by callback { + override fun onBackPressed() { + navigateUp() + } + } override fun onBuilt() { super.onBuilt() @@ -88,14 +96,17 @@ class RolesAndPermissionsNode @AssistedInject constructor( val state = presenter.present() RolesAndPermissionsView( state = state, - roomDetailsAdminSettingsNavigator = this, + rolesAndPermissionsNavigator = navigator, modifier = modifier, ) } } -interface RoomDetailsAdminSettingsNavigator { +interface RolesAndPermissionsNavigator { fun onBackPressed() {} fun openAdminList() {} fun openModeratorList() {} + fun openEditRoomDetailsPermissions() {} + fun openMessagesAndContentPermissions() {} + fun openModerationPermissions() {} } 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 db67de66a4..9e6eb824cc 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 @@ -55,6 +55,7 @@ class RolesAndPermissionsPresenter @Inject constructor( } } val changeOwnRoleAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + val resetPermissionsAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } fun handleEvent(event: RolesAndPermissionsEvents) { when (event) { @@ -63,11 +64,17 @@ class RolesAndPermissionsPresenter @Inject constructor( } is RolesAndPermissionsEvents.CancelPendingAction -> { changeOwnRoleAction.value = AsyncAction.Uninitialized + resetPermissionsAction.value = AsyncAction.Uninitialized } is RolesAndPermissionsEvents.DemoteSelfTo -> coroutineScope.demoteSelfTo( role = event.role, changeOwnRoleAction = changeOwnRoleAction, ) + is RolesAndPermissionsEvents.ResetPermissions -> if (resetPermissionsAction.value.isConfirming()) { + coroutineScope.resetPermissions(resetPermissionsAction) + } else { + resetPermissionsAction.value = AsyncAction.Confirming + } } } @@ -75,6 +82,7 @@ class RolesAndPermissionsPresenter @Inject constructor( adminCount = adminCount, moderatorCount = moderatorCount, changeOwnRoleAction = changeOwnRoleAction.value, + resetPermissionsAction = resetPermissionsAction.value, eventSink = { handleEvent(it) }, ) } @@ -88,6 +96,14 @@ class RolesAndPermissionsPresenter @Inject constructor( } } + private fun CoroutineScope.resetPermissions( + resetPermissionsAction: MutableState>, + ) = launch(dispatchers.io) { + runUpdatingState(resetPermissionsAction) { + room.resetPowerLevels().map {} + } + } + private fun MatrixRoomInfo?.userCountWithRole(role: RoomMember.Role): Int { return if (this != null) { userPowerLevels.count { (_, level) -> RoomMember.Role.forPowerLevel(level) == role } 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 b1c2905ae8..5fab19fcef 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 @@ -22,5 +22,6 @@ data class RolesAndPermissionsState( val adminCount: Int, val moderatorCount: Int, 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 cde21c1e7e..9fa91879ba 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 @@ -39,6 +39,21 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, + resetPermissionsAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (RolesAndPermissionsEvents) -> Unit = {}, ) = RolesAndPermissionsState( adminCount = adminCount, moderatorCount = moderatorCount, changeOwnRoleAction = changeOwnRoleAction, + resetPermissionsAction = resetPermissionsAction, eventSink = eventSink, ) 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 087fa8c862..f97b069215 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 @@ -33,6 +33,8 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferencePage @@ -53,35 +55,72 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun RolesAndPermissionsView( state: RolesAndPermissionsState, - roomDetailsAdminSettingsNavigator: RoomDetailsAdminSettingsNavigator, + rolesAndPermissionsNavigator: RolesAndPermissionsNavigator, modifier: Modifier = Modifier, ) { PreferencePage( modifier = modifier, title = stringResource(R.string.screen_room_roles_and_permissions_title), - onBackPressed = roomDetailsAdminSettingsNavigator::onBackPressed, + onBackPressed = rolesAndPermissionsNavigator::onBackPressed, ) { ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_roles_header), hasDivider = false) ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_admins)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), trailingContent = ListItemContent.Text("${state.adminCount}"), - onClick = { roomDetailsAdminSettingsNavigator.openAdminList() }, + onClick = { rolesAndPermissionsNavigator.openAdminList() }, ) ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_moderators)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChatProblem())), trailingContent = ListItemContent.Text("${state.moderatorCount}"), - onClick = { roomDetailsAdminSettingsNavigator.openModeratorList() }, + 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())) ) + 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)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), + onClick = { rolesAndPermissionsNavigator.openEditRoomDetailsPermissions() }, + ) + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_messages_and_content)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Chat())), + onClick = { rolesAndPermissionsNavigator.openMessagesAndContentPermissions() }, + ) + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_member_moderation)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())), + onClick = { rolesAndPermissionsNavigator.openModerationPermissions() }, + ) HorizontalDivider() + ListItem( + headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) }, + onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, + style = ListItemStyle.Destructive, + ) } + AsyncActionView( + async = state.resetPermissionsAction, + confirmationDialog = { + ConfirmationDialog( + title = stringResource(R.string.screen_room_roles_and_permissions_reset_confirm_title), + content = stringResource(R.string.screen_room_roles_and_permissions_reset_confirm_description), + submitText = stringResource(CommonStrings.action_reset), + destructiveSubmit = true, + onSubmitClicked = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, + onDismiss = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) }, + ) + }, + onSuccess = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) }, + onErrorDismiss = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) } + ) + when (state.changeOwnRoleAction) { is AsyncAction.Confirming -> { ChangeOwnRoleBottomSheet( @@ -156,11 +195,7 @@ private fun ChangeOwnRoleBottomSheet( ) ListItem( headlineContent = { Text(stringResource(CommonStrings.action_cancel)) }, - onClick = { - sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.CancelPendingAction) - } - }, + onClick = ::dismiss, style = ListItemStyle.Primary, ) } @@ -168,11 +203,11 @@ private fun ChangeOwnRoleBottomSheet( @PreviewsDayNight @Composable -internal fun RoomDetailsAdminSettingsViewPreview(@PreviewParameter(RolesAndPermissionsStateProvider::class) state: RolesAndPermissionsState) { +internal fun RolesAndPermissionViewPreview(@PreviewParameter(RolesAndPermissionsStateProvider::class) state: RolesAndPermissionsState) { ElementPreview { RolesAndPermissionsView( state = state, - roomDetailsAdminSettingsNavigator = object : RoomDetailsAdminSettingsNavigator {}, + rolesAndPermissionsNavigator = object : RolesAndPermissionsNavigator {}, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt new file mode 100644 index 0000000000..003c4f7233 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions + +import io.element.android.libraries.matrix.api.room.RoomMember + +interface ChangeRoomPermissionsEvent { + data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: RoomMember.Role) : ChangeRoomPermissionsEvent + data object Save : ChangeRoomPermissionsEvent + data object Exit : ChangeRoomPermissionsEvent + data object ResetPendingActions : ChangeRoomPermissionsEvent +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt new file mode 100644 index 0000000000..18b369a38b --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.RoomScope +import kotlinx.parcelize.Parcelize + +@ContributesNode(RoomScope::class) +class ChangeRoomPermissionsNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: ChangeRoomPermissionsPresenter.Factory, +) : Node(buildContext, plugins = plugins) { + @Parcelize + data class Inputs( + val section: ChangeRoomPermissionsSection, + ) : NodeInputs, Parcelable + + private val inputs: Inputs = inputs() + + private val presenter = presenterFactory.run { + create(inputs.section) + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ChangeRoomPermissionsView( + modifier = modifier, + state = state, + onBackPressed = this::navigateUp, + ) + } +} + +@Parcelize +enum class ChangeRoomPermissionsSection : Parcelable { + RoomDetails, + MessagesAndContent, + MembershipModeration, +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt new file mode 100644 index 0000000000..37d5d351eb --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class ChangeRoomPermissionsPresenter @AssistedInject constructor( + @Assisted private val section: ChangeRoomPermissionsSection, + private val room: MatrixRoom, +) : Presenter { + companion object { + internal fun itemsForSection(section: ChangeRoomPermissionsSection) = when (section) { + ChangeRoomPermissionsSection.RoomDetails -> persistentListOf( + RoomPermissionType.ROOM_NAME, + RoomPermissionType.ROOM_AVATAR, + RoomPermissionType.ROOM_TOPIC, + ) + ChangeRoomPermissionsSection.MessagesAndContent -> persistentListOf( + RoomPermissionType.SEND_EVENTS, + RoomPermissionType.REDACT_EVENTS, + ) + ChangeRoomPermissionsSection.MembershipModeration -> persistentListOf( + RoomPermissionType.INVITE, + RoomPermissionType.KICK, + RoomPermissionType.BAN, + ) + } + } + @AssistedFactory + interface Factory { + fun create(section: ChangeRoomPermissionsSection): ChangeRoomPermissionsPresenter + } + + private val items: ImmutableList = itemsForSection(section) + + private var initialPermissions by mutableStateOf(null) + private var currentPermissions by mutableStateOf(null) + private var saveAction by mutableStateOf>(AsyncAction.Uninitialized) + private var confirmExitAction by mutableStateOf>(AsyncAction.Uninitialized) + + @Composable + override fun present(): ChangeRoomPermissionsState { + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + updatePermissions() + } + + val hasChanges by remember { + derivedStateOf { initialPermissions != currentPermissions } + } + + fun handleEvent(event: ChangeRoomPermissionsEvent) { + when (event) { + is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> { + currentPermissions = when (event.action) { + RoomPermissionType.BAN -> currentPermissions?.copy(ban = event.role.powerLevel) + RoomPermissionType.INVITE -> currentPermissions?.copy(invite = event.role.powerLevel) + RoomPermissionType.KICK -> currentPermissions?.copy(kick = event.role.powerLevel) + RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = event.role.powerLevel) + RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = event.role.powerLevel) + RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = event.role.powerLevel) + RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = event.role.powerLevel) + RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = event.role.powerLevel) + } + } + is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() + is ChangeRoomPermissionsEvent.Exit -> { + confirmExitAction = if (!hasChanges || confirmExitAction.isConfirming()) { + AsyncAction.Success(Unit) + } else { + AsyncAction.Confirming + } + } + is ChangeRoomPermissionsEvent.ResetPendingActions -> { + saveAction = AsyncAction.Uninitialized + confirmExitAction = AsyncAction.Uninitialized + } + } + } + return ChangeRoomPermissionsState( + section = section, + currentPermissions = currentPermissions, + items = items, + hasChanges = hasChanges, + saveAction = saveAction, + confirmExitAction = confirmExitAction, + eventSink = { handleEvent(it) } + ) + } + + private suspend fun updatePermissions() { + val powerLevels = room.powerLevels().getOrNull() ?: return + initialPermissions = powerLevels + currentPermissions = initialPermissions + } + + private fun CoroutineScope.save() = launch { + saveAction = AsyncAction.Loading + val updatedRoomPowerLevels = currentPermissions ?: run { + saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")) + return@launch + } + room.updatePowerLevels(updatedRoomPowerLevels) + .onSuccess { + initialPermissions = currentPermissions + saveAction = AsyncAction.Success(Unit) + } + .onFailure { + saveAction = AsyncAction.Failure(it) + } + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt new file mode 100644 index 0000000000..a80e7bb49f --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions + +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels +import kotlinx.collections.immutable.ImmutableList + +data class ChangeRoomPermissionsState( + val section: ChangeRoomPermissionsSection, + val currentPermissions: MatrixRoomPowerLevels?, + val items: ImmutableList, + val hasChanges: Boolean, + val saveAction: AsyncAction, + val confirmExitAction: AsyncAction, + val eventSink: (ChangeRoomPermissionsEvent) -> Unit, +) + +enum class RoomPermissionType { + BAN, + INVITE, + KICK, + SEND_EVENTS, + REDACT_EVENTS, + ROOM_NAME, + ROOM_AVATAR, + ROOM_TOPIC +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStatePreviewProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStatePreviewProvider.kt new file mode 100644 index 0000000000..8bb8073068 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStatePreviewProvider.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels +import kotlinx.collections.immutable.toPersistentList + +class ChangeRoomPermissionsStatePreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails), + aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MessagesAndContent), + aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MembershipModeration), + aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true), + aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Loading), + aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes")) + ), + aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, confirmExitAction = AsyncAction.Confirming), + ) +} + +internal fun aChangeRoomPermissionsState( + section: ChangeRoomPermissionsSection, + currentPermissions: MatrixRoomPowerLevels = previewPermissions(), + items: List = ChangeRoomPermissionsPresenter.itemsForSection(section), + hasChanges: Boolean = false, + saveAction: AsyncAction = AsyncAction.Uninitialized, + confirmExitAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, +) = ChangeRoomPermissionsState( + section = section, + currentPermissions = currentPermissions, + items = items.toPersistentList(), + hasChanges = hasChanges, + saveAction = saveAction, + confirmExitAction = confirmExitAction, + eventSink = eventSink, +) + +private fun previewPermissions(): MatrixRoomPowerLevels { + return MatrixRoomPowerLevels( + // MembershipModeration section + 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, + // RoomDetails section + 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 new file mode 100644 index 0000000000..1d57545a0b --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.roomdetails.impl.R +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.ListSectionHeader +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChangeRoomPermissionsView( + state: ChangeRoomPermissionsState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + BackHandler { + state.eventSink(ChangeRoomPermissionsEvent.Exit) + } + Scaffold( + modifier = modifier, + topBar = { + val title = when (state.section) { + ChangeRoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details) + ChangeRoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content) + ChangeRoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_change_permissions_member_moderation) + } + TopAppBar( + title = { Text(text = title, style = ElementTheme.typography.aliasScreenTitle) }, + navigationIcon = { + BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) + }, + actions = { + TextButton( + text = stringResource(CommonStrings.action_save), + onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + enabled = state.hasChanges, + ) + } + ) + } + ) { padding -> + Column(modifier = Modifier.padding(padding)) { + for ((index, permissionItem) in state.items.withIndex()) { + ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) + SelectRoleItem( + permissionsItem = permissionItem, + role = RoomMember.Role.ADMIN, + currentPermissions = state.currentPermissions + ) { item, role -> + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + } + SelectRoleItem( + permissionsItem = permissionItem, + role = RoomMember.Role.MODERATOR, + currentPermissions = state.currentPermissions + ) { item, role -> + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + } + SelectRoleItem( + permissionsItem = permissionItem, + role = RoomMember.Role.USER, + currentPermissions = state.currentPermissions + ) { item, role -> + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + } + } + } + } + + AsyncActionView( + async = state.saveAction, + onSuccess = { onBackPressed() }, + onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } + ) + + AsyncActionView( + async = state.confirmExitAction, + onSuccess = { onBackPressed() }, + confirmationDialog = { + ConfirmationDialog( + title = stringResource(R.string.screen_room_change_role_unsaved_changes_title), + content = stringResource(R.string.screen_room_change_role_unsaved_changes_description), + submitText = stringResource(CommonStrings.action_save), + cancelText = stringResource(CommonStrings.action_discard), + onSubmitClicked = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) } + ) + }, + onErrorDismiss = {}, + ) +} + +@Composable +private fun SelectRoleItem( + permissionsItem: RoomPermissionType, + role: RoomMember.Role, + currentPermissions: MatrixRoomPowerLevels?, + onClick: (RoomPermissionType, RoomMember.Role) -> Unit +) { + val title = when (role) { + 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) + } + ListItem( + headlineContent = { Text(text = title) }, + trailingContent = if (currentPermissions?.isSelected(permissionsItem, role).orFalse()) { + ListItemContent.Icon(IconSource.Vector(CompoundIcons.Check())) + } else { + null + }, + style = ListItemStyle.Primary, + onClick = { onClick(permissionsItem, role) }, + ) +} + +private fun MatrixRoomPowerLevels.isSelected(item: RoomPermissionType, role: RoomMember.Role): Boolean { + return when (item) { + RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(ban) == role + RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(invite) == role + RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(kick) == role + RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(sendEvents) == role + RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(redactEvents) == role + RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(roomName) == role + RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(roomAvatar) == role + RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(roomTopic) == role + } +} + +@Composable +private fun titleForSection(item: RoomPermissionType): String = when (item) { + RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people) + RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people) + RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people) + RoomPermissionType.SEND_EVENTS -> stringResource(R.string.screen_room_change_permissions_send_messages) + RoomPermissionType.REDACT_EVENTS -> stringResource(R.string.screen_room_change_permissions_delete_messages) + RoomPermissionType.ROOM_NAME -> stringResource(R.string.screen_room_change_permissions_room_name) + RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar) + RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic) +} + +@PreviewsDayNight +@Composable +internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermissionsStatePreviewProvider::class) state: ChangeRoomPermissionsState) { + ElementPreview { + ChangeRoomPermissionsView( + state = state, + onBackPressed = {}, + ) + } +} diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index cb5b7e75e2..9fca8935b0 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -18,10 +18,10 @@ "Messages and content" "Admins and moderators" "Remove people" - "Change Room Avatar" + "Change room avatar" "Room details" - "Change Room Name" - "Change Room Topic" + "Change room name" + "Change room topic" "Send messages" "Edit Admins" "You will not be able to undo this action. You are promoting the user to have the same power level as you." @@ -104,7 +104,7 @@ "Moderators" "Permissions" "Reset permissions" - "Once you reset permissions, you will lose your current settings." + "Once you reset permissions, you will lose the current settings." "Reset permissions?" "Roles" "Room details" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTests.kt index 0221a6851a..0c81a549b9 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTests.kt @@ -118,6 +118,36 @@ class RolesAndPermissionPresenterTests { } } + @Test + fun `present - ResetPermissions needs confirmation, then resets permissions`() = runTest { + val presenter = createRolesAndPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions) + // Confirmation + awaitItem().eventSink(RolesAndPermissionsEvents.ResetPermissions) + + assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Loading) + assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Success(Unit)) + } + } + + @Test + fun `present - ResetPermissions confirmation can be cancelled`() = runTest { + val presenter = createRolesAndPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions) + awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction) + + assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Uninitialized) + } + } + private fun TestScope.createRolesAndPermissionsPresenter( room: FakeMatrixRoom = FakeMatrixRoom(), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionsViewTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionsViewTests.kt index 6eb596ce83..be6411b373 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionsViewTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionsViewTests.kt @@ -21,19 +21,26 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.roomdetails.impl.R +import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsEvents +import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsNavigator import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsState import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsView -import io.element.android.features.roomdetails.impl.rolesandpermissions.RoomDetailsAdminSettingsNavigator import io.element.android.features.roomdetails.impl.rolesandpermissions.aRolesAndPermissionsState +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.ensureCalledTimes import io.element.android.tests.testutils.pressBack +import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.junit.runner.RunWith +import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class RolesAndPermissionsViewTests { @@ -68,6 +75,100 @@ class RolesAndPermissionsViewTests { rule.clickOn(R.string.screen_room_roles_and_permissions_moderators) } } + + @Test + @Config(qualifiers = "h640dp") + fun `tapping on any of the permission items open the change permissions screen`() { + ensureCalledTimes(3) { callback -> + rule.setRolesAndPermissionsView( + openPermissionScreens = callback, + ) + rule.clickOn(R.string.screen_room_roles_and_permissions_room_details) + rule.clickOn(R.string.screen_room_roles_and_permissions_messages_and_content) + rule.clickOn(R.string.screen_room_roles_and_permissions_member_moderation) + } + } + + @Test + @Config(qualifiers = "h640dp") + fun `tapping on reset permissions triggers ResetPermissions event`() { + val recorder = EventsRecorder() + rule.setRolesAndPermissionsView( + state = aRolesAndPermissionsState( + eventSink = recorder, + ), + ) + rule.clickOn(R.string.screen_room_roles_and_permissions_reset) + recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions) + } + + @Test + fun `tapping on Reset in the reset permissions confirmation dialog triggers ResetPermissions event`() { + val recorder = EventsRecorder() + rule.setRolesAndPermissionsView( + state = aRolesAndPermissionsState( + resetPermissionsAction = AsyncAction.Confirming, + eventSink = recorder, + ), + ) + rule.clickOn(CommonStrings.action_reset) + recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions) + } + + @Test + fun `tapping on Cancel in the reset permissions confirmation dialog triggers CancelPendingAction event`() { + val recorder = EventsRecorder() + rule.setRolesAndPermissionsView( + state = aRolesAndPermissionsState( + resetPermissionsAction = AsyncAction.Confirming, + eventSink = recorder, + ), + ) + rule.clickOn(CommonStrings.action_cancel) + recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction) + } + + @Test + fun `tapping on 'Demote to moderator' in the demote self bottom sheet triggers the right event`() { + val recorder = EventsRecorder() + rule.setRolesAndPermissionsView( + state = aRolesAndPermissionsState( + changeOwnRoleAction = AsyncAction.Confirming, + eventSink = recorder, + ), + ) + 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)) + } + + @Test + fun `tapping on 'Demote to member' in the demote self bottom sheet triggers the right event`() = runTest { + val recorder = EventsRecorder() + rule.setRolesAndPermissionsView( + state = aRolesAndPermissionsState( + changeOwnRoleAction = AsyncAction.Confirming, + eventSink = recorder, + ), + ) + 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)) + } + + @Test + fun `tapping on 'Cancel' in the demote self bottom sheet triggers the right event`() { + val recorder = EventsRecorder() + rule.setRolesAndPermissionsView( + state = aRolesAndPermissionsState( + changeOwnRoleAction = AsyncAction.Confirming, + eventSink = recorder, + ), + ) + rule.clickOn(CommonStrings.action_cancel) + rule.mainClock.advanceTimeBy(1_000L) + recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction) + } } private fun AndroidComposeTestRule.setRolesAndPermissionsView( @@ -77,14 +178,18 @@ private fun AndroidComposeTestRule.setRoles goBack: () -> Unit = EnsureNeverCalled(), openAdminList: () -> Unit = EnsureNeverCalled(), openModeratorList: () -> Unit = EnsureNeverCalled(), + openPermissionScreens: () -> Unit = EnsureNeverCalled(), ) { setContent { RolesAndPermissionsView( state = state, - roomDetailsAdminSettingsNavigator = object : RoomDetailsAdminSettingsNavigator { + rolesAndPermissionsNavigator = object : RolesAndPermissionsNavigator { override fun onBackPressed() = goBack() override fun openAdminList() = openAdminList() override fun openModeratorList() = openModeratorList() + override fun openEditRoomDetailsPermissions() = openPermissionScreens() + override fun openModerationPermissions() = openPermissionScreens() + override fun openMessagesAndContentPermissions() = openPermissionScreens() } ) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTests.kt new file mode 100644 index 0000000000..513438b710 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTests.kt @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.rolesandpermissions.permissions + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.Event +import app.cash.turbine.TurbineTestContext +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsEvent +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsPresenter +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsState +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.RoomPermissionType +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.powerlevels.MatrixRoomPowerLevels +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevels +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ChangeRoomPermissionsPresenterTests { + @Test + fun `present - initial state`() = runTest { + val section = ChangeRoomPermissionsSection.RoomDetails + val presenter = createChangeRoomPermissionsPresenter(section = section) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + // Initial state, no permissions loaded + awaitItem().run { + assertThat(this.section).isEqualTo(section) + assertThat(this.currentPermissions).isNull() + assertThat(this.items).isNotEmpty() + assertThat(this.hasChanges).isFalse() + assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized) + } + + // Updated state, permissions loaded + assertThat(awaitItem().currentPermissions).isEqualTo(defaultPermissions()) + } + } + + @Test + fun `present - RoomDetails section contains the right items`() = runTest { + val section = ChangeRoomPermissionsSection.RoomDetails + val presenter = createChangeRoomPermissionsPresenter(section = section) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitUpdatedItem().items).containsExactly( + RoomPermissionType.ROOM_NAME, + RoomPermissionType.ROOM_AVATAR, + RoomPermissionType.ROOM_TOPIC, + ) + } + } + + @Test + fun `present - MessagesAndContent section contains the right items`() = runTest { + val section = ChangeRoomPermissionsSection.MessagesAndContent + val presenter = createChangeRoomPermissionsPresenter(section = section) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitUpdatedItem().items).containsExactly( + RoomPermissionType.SEND_EVENTS, + RoomPermissionType.REDACT_EVENTS, + ) + } + } + + @Test + fun `present - MembershipModeration section contains the right items`() = runTest { + val section = ChangeRoomPermissionsSection.MembershipModeration + val presenter = createChangeRoomPermissionsPresenter(section = section) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitUpdatedItem().items).containsExactly( + RoomPermissionType.INVITE, + RoomPermissionType.KICK, + RoomPermissionType.BAN, + ) + } + } + + @Test + fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) + assertThat(state.hasChanges).isFalse() + + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) + + awaitItem().run { + assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(hasChanges).isTrue() + } + } + } + + @Test + fun `present - ChangeMinimumRoleForAction works for all actions`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.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)) + + val items = cancelAndConsumeRemainingEvents() + + (items.last() as? Event.Item)?.value?.run { + assertThat(currentPermissions).isEqualTo( + MatrixRoomPowerLevels( + invite = MODERATOR.powerLevel, + kick = MODERATOR.powerLevel, + ban = MODERATOR.powerLevel, + redactEvents = MODERATOR.powerLevel, + sendEvents = MODERATOR.powerLevel, + roomName = MODERATOR.powerLevel, + roomAvatar = MODERATOR.powerLevel, + roomTopic = MODERATOR.powerLevel, + ) + ) + } + } + } + + @Test + fun `present - Save updates the current permissions and resets hasChanges`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) + assertThat(state.hasChanges).isFalse() + + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) + assertThat(awaitItem().hasChanges).isTrue() + + state.eventSink(ChangeRoomPermissionsEvent.Save) + + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) + assertThat(awaitItem().hasChanges).isFalse() + awaitItem().run { + assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + } + } + } + + @Test + fun `present - Save will fail if there are not current permissions`() = runTest { + val room = FakeMatrixRoom().apply { + givenPowerLevelsResult(Result.failure(IllegalStateException("Failed to load power levels"))) + } + val presenter = createChangeRoomPermissionsPresenter(room = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitItem() + assertThat(state.currentPermissions).isNull() + + state.eventSink(ChangeRoomPermissionsEvent.Save) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + + @Test + fun `present - Save can handle failures and they can be cleared`() = runTest { + val room = FakeMatrixRoom().apply { + givenUpdatePowerLevelsResult(Result.failure(IllegalStateException("Failed to update power levels"))) + } + val presenter = createChangeRoomPermissionsPresenter(room = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) + assertThat(state.hasChanges).isFalse() + + 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) + // Couldn't save the changes, so they're still pending + assertThat(hasChanges).isTrue() + assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) + } + + state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) + awaitItem().run { + assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(hasChanges).isTrue() + } + } + } + + @Test + fun `present - Exit does not need a confirmation when there are no pending changes`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) + assertThat(awaitItem().hasChanges).isTrue() + + state.eventSink(ChangeRoomPermissionsEvent.Exit) + assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Confirming) + + state.eventSink(ChangeRoomPermissionsEvent.Exit) + assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) + } + } + + @Test + fun `present - Exit needs confirmation when there are pending changes`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + + state.eventSink(ChangeRoomPermissionsEvent.Exit) + + assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) + } + } + + private fun createChangeRoomPermissionsPresenter( + section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails, + room: FakeMatrixRoom = FakeMatrixRoom(), + ) = ChangeRoomPermissionsPresenter( + section = section, + room = room, + ) + + private fun defaultPermissions() = defaultRoomPowerLevels().run { + MatrixRoomPowerLevels( + invite = invite, + kick = kick, + ban = ban, + redactEvents = redactEvents, + sendEvents = sendEvents, + roomName = roomName, + roomAvatar = roomAvatar, + roomTopic = roomTopic, + ) + } + + private suspend fun TurbineTestContext.awaitUpdatedItem(): ChangeRoomPermissionsState { + skipItems(1) + return awaitItem() + } +} diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsViewTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsViewTests.kt new file mode 100644 index 0000000000..942b40ff7a --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsViewTests.kt @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.rolesandpermissions.permissions + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.roomdetails.impl.R +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsEvent +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsState +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsView +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.RoomPermissionType +import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.aChangeRoomPermissionsState +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.clickOnFirst +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ChangeRoomPermissionsViewTests { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `click on back icon invokes Exit`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + eventsRecorder = recorder, + ) + rule.pressBack() + recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) + } + + @Test + fun `click on back key invokes Exit`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + eventsRecorder = recorder, + ) + rule.pressBackKey() + recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) + } + + @Test + fun `when confirming exit with pending changes, using the back key actually exits`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + eventSink = recorder, + ), + eventsRecorder = recorder, + ) + rule.pressBackKey() + recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) + } + + @Test + fun `when confirming exit with pending changes, clicking on 'discard' button in the dialog actually exits`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + confirmExitAction = AsyncAction.Confirming, + eventSink = recorder, + ), + eventsRecorder = recorder, + ) + rule.clickOn(CommonStrings.action_discard) + recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) + } + + @Test + fun `when confirming exit with pending changes, clicking on 'save' button in the dialog saves the changes`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + confirmExitAction = AsyncAction.Confirming, + eventSink = recorder, + ), + eventsRecorder = recorder, + ) + rule.clickOnFirst(CommonStrings.action_save) + recorder.assertSingle(ChangeRoomPermissionsEvent.Save) + } + + @Test + fun `click on a role item triggers ChangeRole event`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + eventsRecorder = recorder, + ) + val admins = rule.activity.getText(R.string.screen_room_change_permissions_administrators).toString() + val moderators = rule.activity.getText(R.string.screen_room_change_permissions_moderators).toString() + val users = rule.activity.getText(R.string.screen_room_change_permissions_everyone).toString() + rule.onAllNodesWithText(admins).onFirst().performClick() + rule.onAllNodesWithText(moderators).onFirst().performClick() + 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), + ) + ) + } + + @Test + fun `click on the Save menu item triggers Save event`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + eventSink = recorder, + ), + eventsRecorder = recorder, + ) + rule.clickOn(CommonStrings.action_save) + recorder.assertSingle(ChangeRoomPermissionsEvent.Save) + } + + @Test + fun `a successful save exits the screen`() { + ensureCalledOnce { callback -> + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + saveAction = AsyncAction.Success(Unit), + ), + onBackPressed = callback + ) + rule.clickOn(CommonStrings.action_save) + } + } + + @Test + fun `click on the Ok option in save error dialog triggers ResetPendingAction event`() { + val recorder = EventsRecorder() + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + hasChanges = true, + saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")), + eventSink = recorder, + ), + eventsRecorder = recorder, + ) + rule.clickOn(CommonStrings.action_ok) + recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions) + } +} + +private fun AndroidComposeTestRule.setChangeRoomPermissionsRule( + eventsRecorder: EventsRecorder = EventsRecorder(expectEvents = false), + state: ChangeRoomPermissionsState = aChangeRoomPermissionsState( + section = ChangeRoomPermissionsSection.RoomDetails, + eventSink = eventsRecorder, + ), + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + ChangeRoomPermissionsView( + state = state, + onBackPressed = onBackPressed, + ) + } +} diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt index 857bc0b89e..2e86648f7b 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt @@ -170,7 +170,7 @@ class StateContentFormatter @Inject constructor( "RoomPinnedEvents" } } - is OtherState.RoomPowerLevels -> when (renderingMode) { + is OtherState.RoomUserPowerLevels -> when (renderingMode) { RenderingMode.RoomList -> { Timber.v("Filtering timeline item for room state change: $content") null diff --git a/libraries/eventformatter/impl/src/main/res/values/localazy.xml b/libraries/eventformatter/impl/src/main/res/values/localazy.xml index 525a9435fe..53589974fe 100644 --- a/libraries/eventformatter/impl/src/main/res/values/localazy.xml +++ b/libraries/eventformatter/impl/src/main/res/values/localazy.xml @@ -3,12 +3,16 @@ "(avatar was changed too)" "%1$s changed their avatar" "You changed your avatar" + "%1$s was demoted to member" + "%1$s was demoted to moderator" "%1$s changed their display name from %2$s to %3$s" "You changed your display name from %1$s to %2$s" "%1$s removed their display name (it was %2$s)" "You removed your display name (it was %1$s)" "%1$s set their display name to %2$s" "You set your display name to %1$s" + "%1$s was promoted to admin" + "%1$s was promoted to moderator" "%1$s changed the room avatar" "You changed the room avatar" "%1$s removed the room avatar" diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt index 5165e5dadf..b127911cc0 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt @@ -650,7 +650,7 @@ class DefaultRoomLastMessageFormatterTest { OtherState.RoomHistoryVisibility, OtherState.RoomJoinRules, OtherState.RoomPinnedEvents, - OtherState.RoomPowerLevels(emptyMap()), + OtherState.RoomUserPowerLevels(emptyMap()), OtherState.RoomServerAcl, OtherState.RoomTombstone, OtherState.SpaceChild, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 97285a345a..f049bd1deb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.ReceiptType @@ -97,6 +98,12 @@ interface MatrixRoom : Closeable { suspend fun unsubscribeFromSync() + suspend fun powerLevels(): Result + + suspend fun updatePowerLevels(matrixRoomPowerLevels: MatrixRoomPowerLevels): Result + + suspend fun resetPowerLevels(): Result + suspend fun userRole(userId: UserId): Result suspend fun updateUsersRoles(changes: List): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt index 2950788c68..f2577fcb6b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt @@ -20,6 +20,17 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.StateEventType +data class MatrixRoomPowerLevels( + val ban: Long, + val invite: Long, + val kick: Long, + val sendEvents: Long, + val redactEvents: Long, + val roomName: Long, + val roomAvatar: Long, + val roomTopic: Long, +) + /** * Shortcut for calling [MatrixRoom.canUserInvite] with our own user. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt index 90b30cc1f9..d963b71a63 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt @@ -33,7 +33,7 @@ sealed interface OtherState { data object RoomJoinRules : OtherState data class RoomName(val name: String?) : OtherState data object RoomPinnedEvents : OtherState - data class RoomPowerLevels(val users: Map) : OtherState + data class RoomUserPowerLevels(val users: Map) : OtherState data object RoomServerAcl : OtherState data class RoomThirdPartyInvite(val displayName: String?) : OtherState data object RoomTombstone : OtherState diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 26e0036415..9a85f25502 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.roomNotificationSettings import io.element.android.libraries.matrix.api.timeline.MatrixTimeline @@ -54,6 +55,7 @@ import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper +import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsMapper import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType import io.element.android.libraries.matrix.impl.util.mxCallbackFlow @@ -86,6 +88,7 @@ import org.matrix.rustcomponents.sdk.messageEventContentFromHtml import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown import org.matrix.rustcomponents.sdk.use import timber.log.Timber +import uniffi.matrix_sdk.RoomPowerLevelChanges import java.io.File import org.matrix.rustcomponents.sdk.Room as InnerRoom import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline @@ -253,6 +256,34 @@ class RustMatrixRoom( } } + override suspend fun powerLevels(): Result = withContext(roomDispatcher) { + runCatching { + RoomPowerLevelsMapper.map(innerRoom.getPowerLevels()) + } + } + + override suspend fun updatePowerLevels(matrixRoomPowerLevels: MatrixRoomPowerLevels): Result = withContext(roomDispatcher) { + runCatching { + val changes = RoomPowerLevelChanges( + ban = matrixRoomPowerLevels.ban, + invite = matrixRoomPowerLevels.invite, + kick = matrixRoomPowerLevels.kick, + redact = matrixRoomPowerLevels.redactEvents, + eventsDefault = matrixRoomPowerLevels.sendEvents, + roomName = matrixRoomPowerLevels.roomName, + roomAvatar = matrixRoomPowerLevels.roomAvatar, + roomTopic = matrixRoomPowerLevels.roomTopic, + ) + innerRoom.applyPowerLevelChanges(changes) + } + } + + override suspend fun resetPowerLevels(): Result = withContext(roomDispatcher) { + runCatching { + RoomPowerLevelsMapper.map(innerRoom.resetPowerLevels()) + } + } + override suspend fun userAvatarUrl(userId: UserId): Result = withContext(roomDispatcher) { runCatching { innerRoom.memberAvatarUrl(userId.value) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsMapper.kt new file mode 100644 index 0000000000..fdf8cdbf85 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsMapper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.room.powerlevels + +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels +import org.matrix.rustcomponents.sdk.RoomPowerLevels as RustRoomPowerLevels + +object RoomPowerLevelsMapper { + fun map(roomPowerLevels: RustRoomPowerLevels): MatrixRoomPowerLevels { + return MatrixRoomPowerLevels( + ban = roomPowerLevels.ban, + invite = roomPowerLevels.invite, + kick = roomPowerLevels.kick, + sendEvents = roomPowerLevels.eventsDefault, + redactEvents = roomPowerLevels.redact, + roomName = roomPowerLevels.roomName, + roomAvatar = roomPowerLevels.roomAvatar, + roomTopic = roomPowerLevels.roomTopic + ) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 0bef1744cc..c2f08f778e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -163,7 +163,7 @@ private fun RustOtherState.map(): OtherState { RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules is RustOtherState.RoomName -> OtherState.RoomName(name) RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents - is RustOtherState.RoomPowerLevels -> OtherState.RoomPowerLevels(users) + is RustOtherState.RoomPowerLevels -> OtherState.RoomUserPowerLevels(users) RustOtherState.RoomServerAcl -> OtherState.RoomServerAcl is RustOtherState.RoomThirdPartyInvite -> OtherState.RoomThirdPartyInvite(displayName) RustOtherState.RoomTombstone -> OtherState.RoomTombstone diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index a1e51b4f7b..56d63fe1d8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.location.AssetType +import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.ReceiptType @@ -125,6 +126,9 @@ class FakeMatrixRoom( private var canUserTriggerRoomNotificationResult: Result = Result.success(true) private var canUserJoinCallResult: Result = Result.success(true) private var setIsFavoriteResult = Result.success(Unit) + private var powerLevelsResult = Result.success(defaultRoomPowerLevels()) + private var updatePowerLevelsResult = Result.success(Unit) + private var resetPowerLevelsResult = Result.success(defaultRoomPowerLevels()) var sendMessageMentions = emptyList() val editMessageCalls = mutableListOf>() private val _typingRecord = mutableListOf() @@ -204,6 +208,17 @@ class FakeMatrixRoom( override suspend fun subscribeToSync() = Unit override suspend fun unsubscribeFromSync() = Unit + override suspend fun powerLevels(): Result { + return powerLevelsResult + } + + override suspend fun updatePowerLevels(matrixRoomPowerLevels: MatrixRoomPowerLevels): Result = simulateLongTask { + updatePowerLevelsResult + } + + override suspend fun resetPowerLevels(): Result = simulateLongTask { + resetPowerLevelsResult + } override fun destroy() = Unit @@ -676,6 +691,18 @@ class FakeMatrixRoom( fun givenRoomTypingMembers(typingMembers: List) { _roomTypingMembersFlow.tryEmit(typingMembers) } + + fun givenPowerLevelsResult(result: Result) { + powerLevelsResult = result + } + + fun givenUpdatePowerLevelsResult(result: Result) { + updatePowerLevelsResult = result + } + + fun givenResetPowerLevelsResult(result: Result) { + resetPowerLevelsResult = result + } } data class SendLocationInvocation( @@ -752,3 +779,14 @@ fun aRoomInfo( userPowerLevels = userPowerLevels, activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(), ) + +fun defaultRoomPowerLevels() = MatrixRoomPowerLevels( + ban = 50, + invite = 0, + kick = 50, + sendEvents = 0, + redactEvents = 50, + roomName = 100, + roomAvatar = 100, + roomTopic = 100 +) diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index c44401618f..c8ee3c3d08 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -49,6 +49,7 @@ "Decline" "Delete Poll" "Disable" + "Discard" "Done" "Edit" "Edit poll" diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt index 9789aff870..ba0b2dbf88 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt @@ -29,12 +29,31 @@ class EnsureCalledOnce : () -> Unit { } } +class EnsureCalledTimes(val times: Int) : () -> Unit { + private var counter = 0 + override fun invoke() { + counter++ + } + + fun assertSuccess() { + if (counter != times) { + throw AssertionError("Expected to be called $times, but was called $counter times") + } + } +} + fun ensureCalledOnce(block: (callback: () -> Unit) -> Unit) { val callback = EnsureCalledOnce() block(callback) callback.assertSuccess() } +fun ensureCalledTimes(times: Int, block: (callback: () -> Unit) -> Unit) { + val callback = EnsureCalledTimes(times) + block(callback) + callback.assertSuccess() +} + class EnsureCalledOnceWithParam( private val expectedParam: T, private val result: R, diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt index 1ebbd80acf..da6e67a91d 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.performClick import io.element.android.libraries.ui.strings.CommonStrings import org.junit.rules.TestRule @@ -34,6 +35,16 @@ fun AndroidComposeTestRule.clickOn(@StringR .performClick() } +fun AndroidComposeTestRule.clickOnFirst(@StringRes res: Int) { + val text = activity.getString(res) + onAllNodes(hasText(text) and hasClickAction()).onFirst().performClick() +} + +fun AndroidComposeTestRule.clickOnLast(@StringRes res: Int) { + val text = activity.getString(res) + onAllNodes(hasText(text) and hasClickAction()).onFirst().performClick() +} + /** * Press the back button in the app bar. */ diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c1fa064495 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cce394023e7c5bac40bcf234f9073196b1eb424893760f29d609eb329f62817f +size 42587 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..308abc0e92 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97eaf8930ebc7c127dddb12a1002161aa10b120f87ce88779ea96ff35790e344 +size 38372 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8428ee7714 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d6818023d37493825ba7b929470152847c397b5f19a5bd8ee862fc900dee690 +size 40755 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..52135a6c75 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86aa47d55841e5041bf56534852dd716b4546cc3657a30133b88c0c87998f16c +size 42464 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d7bdf55419 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:237125629b6449d6898391235b625248b71629b4d96415b80fb7ef5e773e86b2 +size 40484 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6c7a7c2b33 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:753eecb930bf0659671f2f1ecb2a64ae226aac96c5365ed549c635fd90c8c729 +size 40951 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..bca446759d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0b37566f62e30fcb4f16364a9ac2c008a98a5429c3fa5724d7ef6455e79cf34 +size 47995 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..35d6c4ad87 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff4c4a80156b2977da007d6258d1cd6cefc93bc709ec33bff0e1b484604cf0a +size 38506 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3e0e7b54fd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:635c8d6bdaafb07706d95d5a9cb9a4e828ef312c31b33a80f6deff1a1c3b206f +size 34554 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..97de47b574 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af5a843cc9cd5a51d986ba7dae7d2c6a088f862f46bac961667a1fb1d62b8a20 +size 36966 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e7363aa84b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:059e598dc973e8c01dba4ef49accf47ea2bd74377dfbfb441c3c213fc77a1cee +size 38158 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7f5fe9dbc4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c34415877b279b2018d02667cc98b4857f4ae5bfa68d169f3226a0c99ed4620b +size 36699 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..17ca2569d6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc02c16667b78ca5a9985da6816f17bbd0f7bb2307569aacaf3391ccd8431bd5 +size 36567 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9d7aa5c1be --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4faece23a4780f5465abe42587f56933ecded55bf3ae966a9e6b634e7df1e049 +size 42845 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6b04a5962e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee5c36535582ecb91b7315afa70bae38db2c7287760071293dbf5fac2b8000b4 +size 43609 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0676dcd329 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c5d2d8a8e672c19e1ed9cd37291a6355f815fcd55157fca7a5966bba0baa830 +size 43303 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ba495911fc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c63e9045666c863d13865f4d40563a57ee7ec2110de023455a16d400eb67d26 +size 41977 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..598e940ff8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:905960605341e3cf5987025bdc560e0141f1b5f1ff5e5b114d3f698712e3428d +size 39869 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c4eb3d509f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cd6b2f0b51fe273ee5479e77a4762d4addb8e59560edbbb4a3ce4520ceb4a75 +size 51493 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ba495911fc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c63e9045666c863d13865f4d40563a57ee7ec2110de023455a16d400eb67d26 +size 41977 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d3cc403fb5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c25b6b3d2699895a4b34a61864d2b57aad9f44d5488830f5f2e061fddf42e32 +size 40355 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3e3bf2effa --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09a844501c3aea35d5ec151415b25c5be8960b98697a99dba450c8a25fb69c8b +size 39847 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9d78e2116d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d262c2c0681ea12ce6920af395bd56aa58a97448ade03997cd22e921d1e2f77 +size 39564 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c11f365339 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1e7414cc8c9412c45e55ea77842b908a1979aa680a66609cbdfa6f5a91d87c9 +size 37925 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..bc035365a5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a08860391a1f9854c2354ede0236e0ea78bd1242b134121e2e6a1903e7b8a6b2 +size 35378 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..32e6921e00 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:865c6148937271e08a14f4820652b9c9109b81eca71b715711460c8fe14fcc97 +size 45654 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c11f365339 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1e7414cc8c9412c45e55ea77842b908a1979aa680a66609cbdfa6f5a91d87c9 +size 37925 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5159dcd623 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b9d5ca3bccf0fbca3ced8392f2ca42dd448c032d97d81291357dc3a1bb33ea2 +size 35806 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 3c15ca411d..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5c6410198fc2f7e74279e21013718bf8326bfd871fa7ffa244470725f7bf3ae5 -size 23045 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index a4329e47f8..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d6fce3b7fa9ac12e25f135ddeabb96dad4bba9422926e5451647255a712e481 -size 22772 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index 944ddb606f..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c788eab0495478d8253cbd11970c57c83670e2b761a675b413642db3c6ee57fa -size 25095 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index e3cefb87a5..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:98f81fcfa9cb5c7c02f8322ca4e444df32e81acb5f0d79f39841f9b71c436594 -size 26376 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index f6fa9fb31f..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dbc67150e8d533fd473204fbf4c9360751b8498831c435a06e9ed1525dfa1d7b -size 21637 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index 16255be86e..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09b42b37d3e05cfe60187512403139b305c39d4d3ff59d122d73762a548d01fa -size 21364 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index 4426f04cce..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:50b1f5c23b436ca2f6045c4d2a6d14c7902a5f10646ce59522a02c2edc297374 -size 22310 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index 06fd4deac9..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b7f91366d3576553c7dd2bc469290fa094dc4095510bb5e841fa0a9b179e85d -size 23085