diff --git a/CHANGES.md b/CHANGES.md index 1ac0cd1124..28e774a8ff 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,86 @@ +Changes in Element X v25.11.2 +============================= + + + +## What's Changed +### ✨ Features +* Enable access to security and privacy by @bmarty in https://github.com/element-hq/element-x-android/pull/5566 +* Add ability to forward a media from the media viewer and the gallery by @bmarty in https://github.com/element-hq/element-x-android/pull/5622 +* Split notifications for messages in threads by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5595 +### 🙌 Improvements +* Enable `SyncNotificationsWithWorkManager` in nightly and debug builds by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5573 +* Confirm exit without saving change in room details edit screen by @bmarty in https://github.com/element-hq/element-x-android/pull/5618 +* Space : add view members entry by @ganfra in https://github.com/element-hq/element-x-android/pull/5619 +* Update notification sound by @bmarty in https://github.com/element-hq/element-x-android/pull/5667 +* Use the new notification sound only on debug and nightly build by @bmarty in https://github.com/element-hq/element-x-android/pull/5673 +* Make sure we know the session verification state before showing the options to verify the session by @bmarty in https://github.com/element-hq/element-x-android/pull/5677 +### 🐛 Bugfixes +* Improve how brand color is applied. by @bmarty in https://github.com/element-hq/element-x-android/pull/5584 +* Improve wellknown retrieval API by @bmarty in https://github.com/element-hq/element-x-android/pull/5587 +* Clearing the room list search clears the search term too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5603 +* Delete pin code only when the last session is deleted by @bmarty in https://github.com/element-hq/element-x-android/pull/5600 +* Fix issues with WorkManager on Android 12 and below by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5606 +* Fix marking a room as read re-instantiates its timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5628 +* Display only valid emojis in recent emoji list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5612 +* Fix navigation issue. by @bmarty in https://github.com/element-hq/element-x-android/pull/5666 +* Fix forward events from media viewer from pinned media timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/5669 +* Try fixing 'Timeline Event object has already been destroyed' by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5675 +* Use the SDK Client to check whether a homeserver is compatible by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5664 +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5610 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5662 +### 🧱 Build +* Remove `@Inject`, not necessary anymore when class is annotated with `@ContributesBinding` by @bmarty in https://github.com/element-hq/element-x-android/pull/5589 +* Upgrade ktlint to 1.7.1 and ensure Renovate will upgrade the version by @bmarty in https://github.com/element-hq/element-x-android/pull/5638 +* Improve architecture around Nodes by @bmarty in https://github.com/element-hq/element-x-android/pull/5641 +* Move dependencies block out of the android block. by @bmarty in https://github.com/element-hq/element-x-android/pull/5674 +* Always use the handleEvent(s) function the same way. by @bmarty in https://github.com/element-hq/element-x-android/pull/5672 +### Dependency upgrades +* fix(deps): update metro to v0.7.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5576 +* fix(deps): update dependencyanalysis to v3.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5577 +* fix(deps): update dependency io.sentry:sentry-android to v8.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5586 +* fix(deps): update dependency androidx.work:work-runtime-ktx to v2.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5590 +* fix(deps): update dependency com.posthog:posthog-android to v3.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5594 +* fix(deps): update dependency com.google.crypto.tink:tink-android to v1.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5572 +* Update plugin sonarqube to v7.0.1.6134 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5605 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.28 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5620 +* fix(deps): update dependencyanalysis to v3.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5602 +* fix(deps): update dependency com.github.matrix-org:matrix-analytics-events to v0.29.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5621 +* fix(deps): update dependencyanalysis to v3.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5624 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.29 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5625 +* fix(deps): update dependency io.sentry:sentry-android to v8.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5629 +* fix(deps): update dependencyanalysis to v3.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5642 +* fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5644 +* chore(deps): update danger/danger-js action to v13.0.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5652 +* fix(deps): update dependency com.google.firebase:firebase-bom to v34.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5643 +* fix(deps): update firebaseappdistribution to v5.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5640 +* fix(deps): update metro to v0.7.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5663 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.31 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5657 +* Update GitHub Artifact Actions (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5609 +* Update dependency io.element.android:element-call-embedded to v0.16.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5598 +* Update roborazzi to v1.51.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5676 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.11.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5681 +* fix(deps): update metro to v0.7.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5683 +### Others +* Improve code around Element .well-known configuration by @bmarty in https://github.com/element-hq/element-x-android/pull/5565 +* misc: display offline banner for all LoggedIn screens by @ganfra in https://github.com/element-hq/element-x-android/pull/5574 +* Remove icon preview duplicate by @bmarty in https://github.com/element-hq/element-x-android/pull/5588 +* Remove application navigation state usage in the push module by @bmarty in https://github.com/element-hq/element-x-android/pull/5596 +* Design : update Home TopBar and RoomList Filters by @ganfra in https://github.com/element-hq/element-x-android/pull/5599 +* Add missing tests on the analytic modules by @bmarty in https://github.com/element-hq/element-x-android/pull/5604 +* design(space): let SpaceRoomItemView divider be full width by @ganfra in https://github.com/element-hq/element-x-android/pull/5597 +* Update notification style by @bmarty in https://github.com/element-hq/element-x-android/pull/5607 +* Improve how data is handled for the WorkManager. by @bmarty in https://github.com/element-hq/element-x-android/pull/5592 +* Revert "Make sure declining a call stops observing the ringing call state" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5615 +* Misc : space flow inject room by @ganfra in https://github.com/element-hq/element-x-android/pull/5614 +* Enable `SyncNotificationsWithWorkManager` by default in release mode apps too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5646 +* Revert "Update notification sound" by @bmarty in https://github.com/element-hq/element-x-android/pull/5671 +* Introduce new query to count accounts by @bmarty in https://github.com/element-hq/element-x-android/pull/5678 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.11.0...v25.11.2 + Changes in Element X v25.11.0 ============================= diff --git a/appconfig/build.gradle.kts b/appconfig/build.gradle.kts index 15b8bd51ba..166aafe2ff 100644 --- a/appconfig/build.gradle.kts +++ b/appconfig/build.gradle.kts @@ -32,7 +32,7 @@ android { value = if (isEnterpriseBuild) { BuildTimeConfig.BUG_REPORT_URL ?: "" } else { - "https://riot.im/bugreports/submit" + "https://rageshakes.element.io/api/submit" }, ) buildConfigFieldStr( diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index a41eb8d777..3189384f53 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -30,7 +30,6 @@ import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params import io.element.android.features.roomdirectory.api.RoomDescription -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs @@ -70,7 +69,6 @@ class RoomFlowNode( private val joinRoomEntryPoint: JoinRoomEntryPoint, private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint, private val membershipObserver: RoomMembershipObserver, - private val spaceEntryPoint: SpaceEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Loading, @@ -105,9 +103,6 @@ class RoomFlowNode( @Parcelize data class JoinedRoom(val roomId: RoomId) : NavTarget - - @Parcelize - data class JoinedSpace(val spaceId: RoomId) : NavTarget } override fun onBuilt() { @@ -209,15 +204,6 @@ class RoomFlowNode( ) createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback) } - is NavTarget.JoinedSpace -> { - val spaceCallback = plugins().single() - spaceEntryPoint.createNode( - parentNode = this, - buildContext = buildContext, - inputs = SpaceEntryPoint.Inputs(roomId = navTarget.spaceId), - callback = spaceCallback, - ) - } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index 419dc074fc..25c1f0664b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -197,10 +197,6 @@ class JoinedRoomLoadedFlowNode( callback.navigateToRoom(roomId, viaParameters) } - override fun navigateToRoomDetails() { - backstack.push(NavTarget.RoomDetails) - } - override fun navigateToRoomMemberList() { backstack.push(NavTarget.RoomMemberList) } diff --git a/fastlane/metadata/android/en-US/changelogs/202511020.txt b/fastlane/metadata/android/en-US/changelogs/202511020.txt new file mode 100644 index 0000000000..a4b397f1bb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202511020.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file diff --git a/features/call/impl/build.gradle.kts b/features/call/impl/build.gradle.kts index 9efe5ba75b..5c1b4ef0a6 100644 --- a/features/call/impl/build.gradle.kts +++ b/features/call/impl/build.gradle.kts @@ -93,6 +93,7 @@ dependencies { testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.matrixuiTest) testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.appnavstate.test) diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt index 0af482fc49..7ec5532e83 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt @@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.test.runTest diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt index 3d1c35df4d..9464af603f 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt @@ -33,9 +33,9 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder import io.element.android.libraries.push.api.notifications.ForegroundServiceType import io.element.android.libraries.push.api.notifications.NotificationIdProvider -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.services.appnavstate.test.FakeAppForegroundStateService @@ -415,6 +415,7 @@ class DefaultActiveCallManagerTest { verify { notificationManagerCompat.cancel(any()) } } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `IncomingCall - ignore expired ring lifetime`() = runTest { diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index 39b7c320d9..72f7640615 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -24,11 +24,12 @@ class FakeEnterpriseService( private val defaultHomeserverListResult: () -> List = { emptyList() }, private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() }, initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default, + initialBrandColor: Color? = null, private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() }, private val firebasePushGatewayResult: () -> String? = { lambdaError() }, private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() }, ) : EnterpriseService { - private val brandColorState = MutableStateFlow(null) + private val brandColorState = MutableStateFlow(initialBrandColor) private val semanticColorsState = MutableStateFlow(initialSemanticColors) override suspend fun isEnterpriseUser(sessionId: SessionId): Boolean = simulateLongTask { diff --git a/features/home/impl/build.gradle.kts b/features/home/impl/build.gradle.kts index 9a29532ef0..1ac8dcec72 100644 --- a/features/home/impl/build.gradle.kts +++ b/features/home/impl/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation(libs.haze) implementation(libs.haze.materials) implementation(projects.features.reportroom.api) - implementation(projects.features.changeroommemberroles.api) + implementation(projects.features.rolesandpermissions.api) implementation(projects.libraries.previewutils) api(projects.features.home.api) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt index 5380c51295..553a60a185 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt @@ -28,8 +28,6 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType import io.element.android.features.home.api.HomeEntryPoint import io.element.android.features.home.impl.components.RoomListMenuAction import io.element.android.features.home.impl.model.RoomListRoomSummary @@ -40,6 +38,8 @@ import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBl import io.element.android.features.leaveroom.api.LeaveRoomRenderer import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.features.reportroom.api.ReportRoomEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.appyx.launchMolecule @@ -93,10 +93,12 @@ class HomeFlowNode( changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy, -> commonLifecycle.coroutineScope.launch { - changeRoomMemberRolesNode.waitForRoleChanged() + val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion() withContext(NonCancellable) { backstack.pop() - onNewOwnersSelected(changeRoomMemberRolesNode.roomId) + if (isNewOwnerSelected) { + onNewOwnersSelected(changeRoomMemberRolesNode.roomId) + } } } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt index ce6a8682b6..3bb8c459e9 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt @@ -35,6 +35,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.poll.impl.R 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.dialogs.SaveChangesDialog 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 @@ -63,8 +64,7 @@ fun CreatePollView( val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) } BackHandler(onBack = navBack) if (state.showBackConfirmation) { - ConfirmationDialog( - content = stringResource(id = R.string.screen_create_poll_cancel_confirmation_content_android), + SaveChangesDialog( onSubmitClick = { state.eventSink(CreatePollEvents.NavBack) }, onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } ) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index bfcfcf424e..790ecdd092 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -77,7 +77,7 @@ class BugReportPresenter( val sendingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val formState: MutableState = remember { + val formState: MutableState = rememberSaveable { mutableStateOf(BugReportFormState.Default) } val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction) diff --git a/features/changeroommemberroles/api/build.gradle.kts b/features/rolesandpermissions/api/build.gradle.kts similarity index 87% rename from features/changeroommemberroles/api/build.gradle.kts rename to features/rolesandpermissions/api/build.gradle.kts index 655bd5683e..0ace6032d9 100644 --- a/features/changeroommemberroles/api/build.gradle.kts +++ b/features/rolesandpermissions/api/build.gradle.kts @@ -11,7 +11,7 @@ plugins { } android { - namespace = "io.element.android.features.changeroommemberroles.api" + namespace = "io.element.android.features.rolesandpermissions.api" } dependencies { diff --git a/features/changeroommemberroles/api/src/main/kotlin/io/element/android/features/changeroommemberroles/api/ChangeRoomMemberRolesEntryPoint.kt b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/ChangeRoomMemberRolesEntryPoint.kt similarity index 79% rename from features/changeroommemberroles/api/src/main/kotlin/io/element/android/features/changeroommemberroles/api/ChangeRoomMemberRolesEntryPoint.kt rename to features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/ChangeRoomMemberRolesEntryPoint.kt index 7905cdc0ae..5bc4bbfbdd 100644 --- a/features/changeroommemberroles/api/src/main/kotlin/io/element/android/features/changeroommemberroles/api/ChangeRoomMemberRolesEntryPoint.kt +++ b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/ChangeRoomMemberRolesEntryPoint.kt @@ -5,12 +5,11 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.api +package io.element.android.features.rolesandpermissions.api import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import io.element.android.libraries.architecture.FeatureEntryPoint -import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -24,11 +23,11 @@ fun interface ChangeRoomMemberRolesEntryPoint : FeatureEntryPoint { interface NodeProxy { val roomId: RoomId - suspend fun waitForRoleChanged() + suspend fun waitForCompletion(): Boolean } } -enum class ChangeRoomMemberRolesListType : NodeInputs { +enum class ChangeRoomMemberRolesListType { SelectNewOwnersWhenLeaving, Admins, Moderators diff --git a/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/RolesAndPermissionsEntryPoint.kt b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/RolesAndPermissionsEntryPoint.kt new file mode 100644 index 0000000000..4ed1fa5c90 --- /dev/null +++ b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/RolesAndPermissionsEntryPoint.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.api + +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint + +fun interface RolesAndPermissionsEntryPoint : SimpleFeatureEntryPoint diff --git a/features/changeroommemberroles/impl/build.gradle.kts b/features/rolesandpermissions/impl/build.gradle.kts similarity index 90% rename from features/changeroommemberroles/impl/build.gradle.kts rename to features/rolesandpermissions/impl/build.gradle.kts index be2bf17979..2cd0960c50 100644 --- a/features/changeroommemberroles/impl/build.gradle.kts +++ b/features/rolesandpermissions/impl/build.gradle.kts @@ -14,7 +14,7 @@ plugins { } android { - namespace = "io.element.android.features.changeroommemberroles.impl" + namespace = "io.element.android.features.rolesandpermissions.impl" testOptions { unitTests { @@ -26,7 +26,7 @@ android { setupDependencyInjection() dependencies { - api(projects.features.changeroommemberroles.api) + api(projects.features.rolesandpermissions.api) implementation(projects.appnav) implementation(projects.libraries.architecture) implementation(projects.libraries.core) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/DefaultRolesAndPermissionsEntryPoint.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/DefaultRolesAndPermissionsEntryPoint.kt new file mode 100644 index 0000000000..a889b8266c --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/DefaultRolesAndPermissionsEntryPoint.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.RoomScope + +@ContributesBinding(RoomScope::class) +class DefaultRolesAndPermissionsEntryPoint : RolesAndPermissionsEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } +} diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RolesAndPermissionsFlowNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RolesAndPermissionsFlowNode.kt new file mode 100644 index 0000000000..17453e4451 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RolesAndPermissionsFlowNode.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl + +import android.os.Parcelable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.coroutineScope +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop +import com.bumble.appyx.navmodel.backstack.operation.push +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode +import io.element.android.features.rolesandpermissions.impl.roles.ChangeRolesNode +import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.designsystem.components.async.AsyncIndicator +import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost +import io.element.android.libraries.designsystem.components.async.AsyncIndicatorState +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize + +@ContributesNode(RoomScope::class) +@AssistedInject +class RolesAndPermissionsFlowNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + + @Parcelize + data object ChangeAdmins : NavTarget + + @Parcelize + data object ChangeModerators : NavTarget + + @Parcelize + data object ChangeRoomPermissions : NavTarget + } + + private val asyncIndicatorState = AsyncIndicatorState() + + override fun onBuilt() { + super.onBuilt() + whenChildAttached { lifecycle, node: ChangeRolesNode -> + lifecycle.coroutineScope.launch { + val changesSaved = node.waitForCompletion() + onChangeComplete(changesSaved) + } + } + } + + private fun onChangeComplete(changesSaved: Boolean) { + backstack.pop() + if (changesSaved) { + asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) { + AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes)) + } + } + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + is NavTarget.Root -> { + val callback = object : RolesAndPermissionsNode.Callback { + override fun openAdminList() { + backstack.push(NavTarget.ChangeAdmins) + } + + override fun openModeratorList() { + backstack.push(NavTarget.ChangeModerators) + } + + override fun openEditPermissions() { + backstack.push(NavTarget.ChangeRoomPermissions) + } + } + createNode( + buildContext = buildContext, + plugins = listOf(callback), + ) + } + is NavTarget.ChangeAdmins -> { + val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Admins) + createNode(buildContext = buildContext, plugins = listOf(inputs)) + } + is NavTarget.ChangeModerators -> { + val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Moderators) + createNode(buildContext = buildContext, plugins = listOf(inputs)) + } + is NavTarget.ChangeRoomPermissions -> { + val callback = object : ChangeRoomPermissionsNode.Callback { + override fun onComplete(changesSaved: Boolean) { + onChangeComplete(changesSaved) + } + } + createNode(buildContext = buildContext, plugins = listOf(callback)) + } + } + } + + @Composable + override fun View(modifier: Modifier) { + Box(modifier = modifier) { + BackstackView() + AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState) + } + } +} diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/RoomMemberListDataSource.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RoomMemberListDataSource.kt similarity index 95% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/RoomMemberListDataSource.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RoomMemberListDataSource.kt index a27833122a..523b320ccd 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/RoomMemberListDataSource.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RoomMemberListDataSource.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/analytics/AnalyticUtils.kt similarity index 97% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/analytics/AnalyticUtils.kt index 09d57af034..a609b79087 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/analytics/AnalyticUtils.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.analytics +package io.element.android.features.rolesandpermissions.impl.analytics import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.libraries.matrix.api.room.RoomMember diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsEvent.kt similarity index 68% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsEvent.kt index a4c49c7ac3..ee2984205d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsEvent.kt @@ -5,12 +5,10 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import io.element.android.libraries.matrix.api.room.RoomMember +package io.element.android.features.rolesandpermissions.impl.permissions interface ChangeRoomPermissionsEvent { - data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: RoomMember.Role) : ChangeRoomPermissionsEvent + data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: SelectableRole) : 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/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsNode.kt similarity index 57% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsNode.kt index cebcc56e7f..81716ab8c4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsNode.kt @@ -5,9 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions -import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext @@ -16,25 +15,21 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.libraries.architecture.NodeInputs -import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope -import kotlinx.parcelize.Parcelize @ContributesNode(RoomScope::class) @AssistedInject class ChangeRoomPermissionsNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, - presenterFactory: ChangeRoomPermissionsPresenter.Factory, + private val presenter: ChangeRoomPermissionsPresenter, ) : Node(buildContext, plugins = plugins) { - @Parcelize - data class Inputs( - val section: ChangeRoomPermissionsSection, - ) : NodeInputs, Parcelable + interface Callback : Plugin { + fun onComplete(changesSaved: Boolean) + } - private val inputs: Inputs = inputs() - private val presenter = presenterFactory.create(inputs.section) + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -42,14 +37,7 @@ class ChangeRoomPermissionsNode( ChangeRoomPermissionsView( modifier = modifier, state = state, - onBackClick = this::navigateUp, + onComplete = callback::onComplete, ) } } - -@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/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt similarity index 70% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt index e9636978d5..b36d1c0bc9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -15,50 +15,60 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedFactory -import dev.zacsweers.metro.AssistedInject -import io.element.android.features.roomdetails.impl.analytics.trackPermissionChangeAnalytics +import dev.zacsweers.metro.Inject +import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.services.analytics.api.AnalyticsService -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -@AssistedInject +@Inject class ChangeRoomPermissionsPresenter( - @Assisted private val section: ChangeRoomPermissionsSection, private val room: JoinedRoom, private val analyticsService: AnalyticsService, ) : Presenter { companion object { - internal fun itemsForSection(section: ChangeRoomPermissionsSection) = when (section) { - ChangeRoomPermissionsSection.RoomDetails -> persistentListOf( + private fun itemsForSection(section: RoomPermissionsSection) = when (section) { + RoomPermissionsSection.SpaceDetails, + RoomPermissionsSection.RoomDetails -> persistentListOf( RoomPermissionType.ROOM_NAME, RoomPermissionType.ROOM_AVATAR, RoomPermissionType.ROOM_TOPIC, ) - ChangeRoomPermissionsSection.MessagesAndContent -> persistentListOf( + RoomPermissionsSection.MessagesAndContent -> persistentListOf( RoomPermissionType.SEND_EVENTS, RoomPermissionType.REDACT_EVENTS, ) - ChangeRoomPermissionsSection.MembershipModeration -> persistentListOf( + RoomPermissionsSection.MembershipModeration -> persistentListOf( RoomPermissionType.INVITE, RoomPermissionType.KICK, RoomPermissionType.BAN, ) } - } - @AssistedFactory - interface Factory { - fun create(section: ChangeRoomPermissionsSection): ChangeRoomPermissionsPresenter + + private fun RoomPermissionsSection.shouldShow(isSpace: Boolean): Boolean { + return when (this) { + RoomPermissionsSection.RoomDetails -> !isSpace + RoomPermissionsSection.MembershipModeration -> true + RoomPermissionsSection.MessagesAndContent -> !isSpace + RoomPermissionsSection.SpaceDetails -> isSpace + } + } + + internal fun buildItems(isSpace: Boolean) = + RoomPermissionsSection.entries + .filter { section -> section.shouldShow(isSpace) } + .associateWith { itemsForSection(it) } + .toImmutableMap() } - private val items: ImmutableList = itemsForSection(section) + private val itemsBySection = buildItems(isSpace = room.info().isSpace) private var initialPermissions by mutableStateOf(null) private var currentPermissions by mutableStateOf(null) @@ -80,15 +90,20 @@ class ChangeRoomPermissionsPresenter( fun handleEvent(event: ChangeRoomPermissionsEvent) { when (event) { is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> { + val powerLevel = when (event.role) { + SelectableRole.Admin -> RoomMember.Role.Admin.powerLevel + SelectableRole.Moderator -> RoomMember.Role.Moderator.powerLevel + SelectableRole.Everyone -> RoomMember.Role.User.powerLevel + } 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) + RoomPermissionType.BAN -> currentPermissions?.copy(ban = powerLevel) + RoomPermissionType.INVITE -> currentPermissions?.copy(invite = powerLevel) + RoomPermissionType.KICK -> currentPermissions?.copy(kick = powerLevel) + RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = powerLevel) + RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = powerLevel) + RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = powerLevel) + RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel) + RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel) } } is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() @@ -106,9 +121,8 @@ class ChangeRoomPermissionsPresenter( } } return ChangeRoomPermissionsState( - section = section, currentPermissions = currentPermissions, - items = items, + itemsBySection = itemsBySection, hasChanges = hasChanges, saveAction = saveAction, confirmExitAction = confirmExitAction, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt new file mode 100644 index 0000000000..c609b4a135 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.permissions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.res.stringResource +import io.element.android.features.rolesandpermissions.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.preferences.DropdownOption +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap + +data class ChangeRoomPermissionsState( + val currentPermissions: RoomPowerLevelsValues?, + val itemsBySection: ImmutableMap>, + val hasChanges: Boolean, + val saveAction: AsyncAction, + val confirmExitAction: AsyncAction, + val eventSink: (ChangeRoomPermissionsEvent) -> Unit, +) { + fun selectedRoleForType(type: RoomPermissionType): SelectableRole? { + if (currentPermissions == null) return null + val role = when (type) { + RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(currentPermissions.ban) + RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(currentPermissions.invite) + RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(currentPermissions.kick) + RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.sendEvents) + RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(currentPermissions.redactEvents) + RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(currentPermissions.roomName) + RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(currentPermissions.roomAvatar) + RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(currentPermissions.roomTopic) + } + return when (role) { + is RoomMember.Role.Owner, + RoomMember.Role.Admin -> SelectableRole.Admin + RoomMember.Role.Moderator -> SelectableRole.Moderator + RoomMember.Role.User -> SelectableRole.Everyone + } + } +} + +enum class RoomPermissionsSection { + SpaceDetails, + RoomDetails, + MessagesAndContent, + MembershipModeration, +} + +enum class SelectableRole : DropdownOption { + Admin { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(R.string.screen_room_member_list_role_administrator) + }, + Moderator { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(R.string.screen_room_member_list_role_moderator) + }, + Everyone { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(R.string.screen_room_change_permissions_everyone) + } +} + +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/ChangeRoomPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt similarity index 63% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt index 07d3455a90..2196c4aa46 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt @@ -5,47 +5,39 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.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.RoomPowerLevelsValues -import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableMap class ChangeRoomPermissionsStateProvider : 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(), + aChangeRoomPermissionsState(hasChanges = true), + aChangeRoomPermissionsState(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.ConfirmingNoParams, - ), + aChangeRoomPermissionsState(hasChanges = true, confirmExitAction = AsyncAction.ConfirmingNoParams), ) } internal fun aChangeRoomPermissionsState( - section: ChangeRoomPermissionsSection, currentPermissions: RoomPowerLevelsValues = previewPermissions(), - items: List = ChangeRoomPermissionsPresenter.itemsForSection(section), + itemsBySection: Map> = ChangeRoomPermissionsPresenter.buildItems(false), hasChanges: Boolean = false, saveAction: AsyncAction = AsyncAction.Uninitialized, confirmExitAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, ) = ChangeRoomPermissionsState( - section = section, currentPermissions = currentPermissions, - items = items.toImmutableList(), + itemsBySection = itemsBySection.toImmutableMap(), hasChanges = hasChanges, saveAction = saveAction, confirmExitAction = confirmExitAction, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt new file mode 100644 index 0000000000..165ebd0528 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt @@ -0,0 +1,143 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.permissions + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +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.features.rolesandpermissions.impl.R +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.preferences.PreferenceDropdown +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +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.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.toImmutableList + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChangeRoomPermissionsView( + state: ChangeRoomPermissionsState, + onComplete: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + BackHandler { + state.eventSink(ChangeRoomPermissionsEvent.Exit) + } + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + titleStr = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), + navigationIcon = { + BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) + }, + actions = { + TextButton( + text = stringResource(CommonStrings.action_save), + onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + enabled = state.hasChanges, + ) + } + ) + } + ) { padding -> + LazyColumn( + modifier = Modifier + .padding(padding) + .fillMaxSize() + ) { + state.itemsBySection.onEachIndexed { index, (section, items) -> + item { + ListSectionHeader(titleForSection(section), hasDivider = index > 0) + } + for (permissionType in items) { + item { + PreferenceDropdown( + title = titleForType(permissionType), + selectedOption = state.selectedRoleForType(permissionType), + options = SelectableRole.entries.toImmutableList(), + onSelectOption = { role -> + state.eventSink( + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction( + action = permissionType, + role = role + ) + ) + } + ) + } + } + } + } + } + + AsyncActionView( + async = state.saveAction, + onSuccess = { onComplete(true) }, + onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } + ) + + AsyncActionView( + async = state.confirmExitAction, + onSuccess = { onComplete(false) }, + 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), + onSubmitClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) } + ) + }, + onErrorDismiss = {}, + ) +} + +@Composable +private fun titleForSection(section: RoomPermissionsSection): String = when (section) { + RoomPermissionsSection.SpaceDetails -> stringResource(R.string.screen_room_roles_and_permissions_space_details) + RoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_roles_and_permissions_room_details) + RoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_roles_and_permissions_messages_and_content) + RoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_roles_and_permissions_member_moderation) +} + +@Composable +private fun titleForType(type: RoomPermissionType): String = when (type) { + 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(ChangeRoomPermissionsStateProvider::class) state: ChangeRoomPermissionsState) { + ElementPreview { + ChangeRoomPermissionsView( + state = state, + onComplete = {}, + ) + } +} diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesEvent.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt similarity index 85% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesEvent.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt index 56e1c50bcc..df924bc575 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesEvent.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt @@ -1,11 +1,11 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 2025 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import io.element.android.libraries.matrix.api.user.MatrixUser diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNode.kt similarity index 83% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNode.kt index 9edd20b549..01eb423fb7 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNode.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -17,10 +17,11 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.coroutines.flow.first @@ -40,17 +41,17 @@ class ChangeRolesNode( private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole()) private val stateFlow = launchMolecule { presenter.present() } - suspend fun waitForRoleChanged() { - stateFlow.first { it.savingState.isSuccess() } + suspend fun waitForCompletion(): Boolean { + val successState = stateFlow.first { it.savingState.isSuccess() } + return successState.savingState.dataOrNull().orFalse() } @Composable override fun View(modifier: Modifier) { val state by stateFlow.collectAsState() ChangeRolesView( - modifier = modifier, state = state, - navigateUp = this::navigateUp, + modifier = modifier, ) } } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt similarity index 96% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenter.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt index b6d865a66a..719fdee2c2 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -15,17 +15,18 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.RoomModeration +import io.element.android.features.rolesandpermissions.impl.RoomMemberListDataSource import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.di.annotations.RoomCoroutineScope import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember @@ -51,6 +52,7 @@ class ChangeRolesPresenter( private val room: JoinedRoom, private val dispatchers: CoroutineDispatchers, private val analyticsService: AnalyticsService, + @RoomCoroutineScope private val roomCoroutineScope: CoroutineScope, ) : Presenter { @AssistedFactory fun interface Factory { @@ -59,7 +61,6 @@ class ChangeRolesPresenter( @Composable override fun present(): ChangeRolesState { - val coroutineScope = rememberCoroutineScope() val dataSource = remember { RoomMemberListDataSource(room, dispatchers) } var query by rememberSaveable { mutableStateOf(null) } var searchActive by rememberSaveable { mutableStateOf(false) } @@ -103,7 +104,6 @@ class ChangeRolesPresenter( val roomInfo by room.roomInfoFlow.collectAsState() fun canChangeMemberRole(userId: UserId): Boolean { - // This is used to group the val currentUserRole = roomInfo.roleOf(room.sessionId) val otherUserRole = roomInfo.roleOf(userId) return currentUserRole.powerLevel > otherUserRole.powerLevel @@ -142,7 +142,7 @@ class ChangeRolesPresenter( saveState.value = AsyncAction.ConfirmingNoParams } !saveState.value.isLoading() -> { - coroutineScope.save(usersWithRole.value, selectedUsers, saveState) + roomCoroutineScope.save(usersWithRole.value, selectedUsers, saveState) } } } @@ -213,9 +213,9 @@ class ChangeRolesPresenter( saveState.value = AsyncAction.Failure(it) } .onSuccess { - saveState.value = AsyncAction.Success(true) // Asynchronously reload the room members launch { room.updateMembers() } + saveState.value = AsyncAction.Success(true) } } } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt similarity index 95% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesState.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt index e0b3e68a9e..17d7b0c3dd 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt @@ -1,11 +1,11 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 2025 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.theme.components.SearchBarResultState diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt similarity index 98% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesStateProvider.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt index b54347f73f..5b0cadd024 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt @@ -1,11 +1,11 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 2025 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt similarity index 94% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesView.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt index f9ebd75ca2..d065fc4169 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility @@ -29,8 +29,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -39,9 +37,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.async.AsyncActionView -import io.element.android.libraries.designsystem.components.async.AsyncIndicator import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -50,6 +48,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Checkbox @@ -76,10 +75,8 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun ChangeRolesView( state: ChangeRolesState, - navigateUp: () -> Unit, modifier: Modifier = Modifier, ) { - val latestNavigateUp by rememberUpdatedState(newValue = navigateUp) BackHandler(enabled = !state.isSearchActive) { state.eventSink(ChangeRolesEvent.Exit) } @@ -167,24 +164,13 @@ fun ChangeRolesView( val asyncIndicatorState = rememberAsyncIndicatorState() AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState) - AsyncActionView( async = state.savingState, - onSuccess = { changeSaved -> - if (changeSaved) { - asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) { - AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes)) - } - } else { - latestNavigateUp() - } - }, + onSuccess = {}, confirmationDialog = { confirming -> when (confirming) { is AsyncAction.ConfirmingCancellation -> { - ConfirmationDialog( - title = stringResource(CommonStrings.dialog_unsaved_changes_title), - content = stringResource(CommonStrings.dialog_unsaved_changes_description_android), + SaveChangesDialog( onSubmitClick = { state.eventSink(ChangeRolesEvent.Exit) }, onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) } ) @@ -415,8 +401,7 @@ private fun MemberRow( internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider::class) state: ChangeRolesState) { ElementPreview { ChangeRolesView( - state = state, - navigateUp = {}, + state = state ) } } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRoomMemberRolesRootNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRoomMemberRolesRootNode.kt similarity index 86% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRoomMemberRolesRootNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRoomMemberRolesRootNode.kt index a7556ef8ca..aad691809f 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRoomMemberRolesRootNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRoomMemberRolesRootNode.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import android.os.Parcelable import androidx.compose.runtime.Composable @@ -20,8 +20,8 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.RoomGraphFactory -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs @@ -70,7 +70,7 @@ class ChangeRoomMemberRolesRootNode( override val roomId: RoomId = inputs.joinedRoom.roomId - override suspend fun waitForRoleChanged() { - waitForChildAttached().waitForRoleChanged() + override suspend fun waitForCompletion(): Boolean { + return waitForChildAttached().waitForCompletion() } } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPoint.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPoint.kt similarity index 81% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPoint.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPoint.kt index e76333cda7..62ec729b14 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPoint.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPoint.kt @@ -5,13 +5,13 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.ContributesBinding -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.room.JoinedRoom diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt similarity index 88% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt index 8b98f78d97..3864f7997c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import io.element.android.libraries.matrix.api.room.RoomMember diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsNode.kt similarity index 88% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsNode.kt index 29394398d3..da06fd1eae 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsNode.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable @@ -39,9 +39,8 @@ class RolesAndPermissionsNode( interface Callback : Plugin, RolesAndPermissionsNavigator { override fun openAdminList() override fun openModeratorList() - override fun openEditRoomDetailsPermissions() - override fun openMessagesAndContentPermissions() - override fun openModerationPermissions() + override fun openEditPermissions() + override fun onBackClick() {} } @@ -85,7 +84,5 @@ interface RolesAndPermissionsNavigator { fun onBackClick() {} fun openAdminList() {} fun openModeratorList() {} - fun openEditRoomDetailsPermissions() {} - fun openMessagesAndContentPermissions() {} - fun openModerationPermissions() {} + fun openEditPermissions() {} } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt similarity index 98% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt index e7033d37be..d7a39c7b2e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt similarity index 88% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt index 24f645a309..8be6fcaf31 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import io.element.android.libraries.architecture.AsyncAction diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt similarity index 97% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt index 211a16d7d1..d134c6e222 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt similarity index 89% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt index b581554a92..87b03fef92 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -20,7 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp 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.features.rolesandpermissions.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 @@ -78,25 +78,16 @@ fun RolesAndPermissionsView( leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit())) ) } - ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true) + HorizontalDivider() 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() }, + headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_permissions_header)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Settings())), + onClick = { rolesAndPermissionsNavigator.openEditPermissions() }, ) HorizontalDivider() ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Delete())), onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, style = ListItemStyle.Destructive, ) diff --git a/features/changeroommemberroles/impl/src/main/res/values-be/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-be/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-be/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-be/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-bg/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-bg/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-bg/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-bg/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-cs/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-cs/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-cs/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-cs/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-cy/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-cy/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-cy/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-cy/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-da/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-da/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-da/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-da/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-de/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-de/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-de/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-de/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-el/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-el/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-el/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-el/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-es/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-es/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-es/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-es/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-et/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-et/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-et/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-et/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-eu/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-eu/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-eu/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-eu/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-fa/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-fa/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-fa/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-fa/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-fi/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-fi/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-fi/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-fi/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-fr/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-fr/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-fr/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-fr/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-hu/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-hu/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-in/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-in/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-in/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-it/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-it/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-it/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-it/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-ka/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ka/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-ka/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ka/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-ko/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ko/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-ko/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ko/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-lt/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-lt/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-lt/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-lt/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-nb/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-nb/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-nl/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-nl/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-nl/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-nl/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-pl/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pl/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-pl/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-pl/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-pt-rBR/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pt-rBR/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-pt-rBR/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-pt-rBR/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-pt/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-pt/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-ro/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ro/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-ro/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ro/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-ru/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-ru/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-sk/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-sk/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-sk/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-sk/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-sv/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-sv/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-sv/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-sv/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-tr/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-tr/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-tr/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-tr/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-uk/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-uk/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-uk/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-uk/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-ur/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ur/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-ur/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ur/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-uz/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-uz/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-uz/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-uz/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-zh-rTW/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-zh-rTW/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-zh-rTW/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-zh-rTW/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values-zh/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-zh/translations.xml similarity index 100% rename from features/changeroommemberroles/impl/src/main/res/values-zh/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-zh/translations.xml diff --git a/features/changeroommemberroles/impl/src/main/res/values/localazy.xml b/features/rolesandpermissions/impl/src/main/res/values/localazy.xml similarity index 97% rename from features/changeroommemberroles/impl/src/main/res/values/localazy.xml rename to features/rolesandpermissions/impl/src/main/res/values/localazy.xml index 43f6dc10f8..56c335f1c1 100644 --- a/features/changeroommemberroles/impl/src/main/res/values/localazy.xml +++ b/features/rolesandpermissions/impl/src/main/res/values/localazy.xml @@ -9,10 +9,10 @@ "Messages and content" "Admins and moderators" "Remove people and decline requests to join" - "Change room avatar" + "Change avatar" "Room details" - "Change room name" - "Change room topic" + "Change name" + "Change 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." @@ -66,5 +66,6 @@ "Reset permissions?" "Roles" "Room details" + "Space details" "Roles and permissions" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt similarity index 79% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt index c59931140a..e19291cd2b 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow @@ -17,7 +17,6 @@ import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator -import io.element.android.libraries.matrix.api.room.RoomMember.Role.User import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom @@ -26,19 +25,17 @@ import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test -class ChangeBaseRoomPermissionsPresenterTest { +class ChangeRoomPermissionsPresenterTest { @Test fun `present - initial state`() = runTest { - val section = ChangeRoomPermissionsSection.RoomDetails - val presenter = createChangeRoomPermissionsPresenter(section = section) + val presenter = createChangeRoomPermissionsPresenter() 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.itemsBySection).isNotEmpty() assertThat(this.hasChanges).isFalse() assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized) assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized) @@ -50,42 +47,22 @@ class ChangeBaseRoomPermissionsPresenterTest { } @Test - fun `present - RoomDetails section contains the right items`() = runTest { - val section = ChangeRoomPermissionsSection.RoomDetails - val presenter = createChangeRoomPermissionsPresenter(section = section) + fun `present - items by section are correct for room`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - assertThat(awaitUpdatedItem().items).containsExactly( + val itemsBySection = awaitUpdatedItem().itemsBySection + assertThat(itemsBySection[RoomPermissionsSection.RoomDetails]).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( + assertThat(itemsBySection[RoomPermissionsSection.MessagesAndContent]).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( + assertThat(itemsBySection[RoomPermissionsSection.MembershipModeration]).containsExactly( RoomPermissionType.INVITE, RoomPermissionType.KICK, RoomPermissionType.BAN, @@ -103,7 +80,7 @@ class ChangeBaseRoomPermissionsPresenterTest { assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator)) awaitItem().run { assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) @@ -120,18 +97,18 @@ class ChangeBaseRoomPermissionsPresenterTest { }.test { val state = awaitUpdatedItem() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, SelectableRole.Moderator)) - val items = cancelAndConsumeRemainingEvents() + val itemsBySection = cancelAndConsumeRemainingEvents() - (items.last() as? Event.Item)?.value?.run { + (itemsBySection.last() as? Event.Item)?.value?.run { assertThat(currentPermissions).isEqualTo( RoomPowerLevelsValues( invite = Moderator.powerLevel, @@ -165,14 +142,14 @@ class ChangeBaseRoomPermissionsPresenterTest { assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, SelectableRole.Everyone)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, SelectableRole.Admin)) skipItems(7) assertThat(awaitItem().hasChanges).isTrue() @@ -230,7 +207,7 @@ class ChangeBaseRoomPermissionsPresenterTest { assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator)) assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Save) @@ -259,7 +236,7 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator)) assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Exit) @@ -285,13 +262,11 @@ class ChangeBaseRoomPermissionsPresenterTest { } private fun createChangeRoomPermissionsPresenter( - section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails, room: FakeJoinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }), ), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ) = ChangeRoomPermissionsPresenter( - section = section, room = room, analyticsService = analyticsService, ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt similarity index 66% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt index f4ab1ef1a9..9627d03b7c 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt @@ -5,40 +5,40 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.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.rolesandpermissions.impl.R 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.EnsureNeverCalledWithParam 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.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class ChangeBaseRoomPermissionsViewTest { +class ChangeRoomPermissionsViewTest { @get:Rule val rule = createAndroidComposeRule() @Test fun `click on back icon invokes Exit`() { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( - eventsRecorder = recorder, + state = aChangeRoomPermissionsState( + eventSink = recorder + ) ) rule.pressBack() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -48,7 +48,9 @@ class ChangeBaseRoomPermissionsViewTest { fun `click on back key invokes Exit`() { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( - eventsRecorder = recorder, + state = aChangeRoomPermissionsState( + eventSink = recorder + ) ) rule.pressBackKey() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -59,11 +61,9 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, eventSink = recorder, ), - eventsRecorder = recorder, ) rule.pressBackKey() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -74,12 +74,10 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, confirmExitAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), - eventsRecorder = recorder, ) rule.clickOn(CommonStrings.action_discard) recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -90,12 +88,10 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, confirmExitAction = AsyncAction.ConfirmingNoParams, eventSink = recorder, ), - eventsRecorder = recorder, ) rule.clickOnFirst(CommonStrings.action_save) recorder.assertSingle(ChangeRoomPermissionsEvent.Save) @@ -105,21 +101,19 @@ class ChangeBaseRoomPermissionsViewTest { 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), + state = aChangeRoomPermissionsState( + itemsBySection = persistentMapOf( + // Makes sure there is only one item to click on + RoomPermissionsSection.RoomDetails to persistentListOf(RoomPermissionType.ROOM_NAME) + ), + eventSink = recorder, ) ) + rule.clickOn(R.string.screen_room_change_permissions_room_name) + rule.clickOn(R.string.screen_room_change_permissions_everyone) + recorder.assertSingle( + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Everyone), + ) } @Test @@ -127,11 +121,9 @@ class ChangeBaseRoomPermissionsViewTest { 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) @@ -139,14 +131,13 @@ class ChangeBaseRoomPermissionsViewTest { @Test fun `a successful save exits the screen`() { - ensureCalledOnce { callback -> + ensureCalledOnceWithParam(true) { callback -> rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Success(Unit), ), - onBackClick = callback + onComplete = callback ) rule.clickOn(CommonStrings.action_save) } @@ -157,12 +148,10 @@ class ChangeBaseRoomPermissionsViewTest { 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) @@ -170,17 +159,13 @@ class ChangeBaseRoomPermissionsViewTest { } private fun AndroidComposeTestRule.setChangeRoomPermissionsRule( - eventsRecorder: EventsRecorder = EventsRecorder(expectEvents = false), - state: ChangeRoomPermissionsState = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, - eventSink = eventsRecorder, - ), - onBackClick: () -> Unit = EnsureNeverCalled(), + state: ChangeRoomPermissionsState = aChangeRoomPermissionsState(), + onComplete: (Boolean) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { ChangeRoomPermissionsView( state = state, - onBackClick = onBackClick, + onComplete = onComplete, ) } } diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNodeTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNodeTest.kt similarity index 83% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNodeTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNodeTest.kt index 3da57d3380..e8a470b0ef 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNodeTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNodeTest.kt @@ -5,10 +5,10 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import com.google.common.truth.Truth.assertThat -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.matrix.api.room.RoomMember import org.junit.Test diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt similarity index 97% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenterTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt index a5e4cfaccb..4b3ed237cd 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow @@ -351,21 +351,15 @@ class ChangeRolesPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Save) val confirmingState = awaitItem() assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmingState.eventSink(ChangeRolesEvent.Save) - - val loadingState = awaitItem() - assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) - + assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true)) } } @@ -413,18 +407,12 @@ class ChangeRolesPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) - awaitItem().eventSink(ChangeRolesEvent.Save) - - val loadingState = awaitItem() - assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) - + assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true)) assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.Moderator)) } @@ -491,7 +479,7 @@ class ChangeRolesPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) @@ -501,8 +489,6 @@ class ChangeRolesPresenterTest { val loadingState = awaitItem() assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) - assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true)) assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User)) } @@ -520,7 +506,7 @@ class ChangeRolesPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - skipItems(1) + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) @@ -529,7 +515,6 @@ class ChangeRolesPresenterTest { awaitItem().eventSink(ChangeRolesEvent.Save) val loadingState = awaitItem() assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) val failedState = awaitItem() assertThat(failedState.savingState).isInstanceOf(AsyncAction.Failure::class.java) @@ -567,5 +552,6 @@ internal fun TestScope.createChangeRolesPresenter( room = room, dispatchers = dispatchers, analyticsService = analyticsService, + roomCoroutineScope = this, ) } diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt similarity index 98% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesViewTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt index 19d9cadfa8..d0e6d120bd 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn @@ -306,12 +305,10 @@ class ChangeRolesViewTest { private fun AndroidComposeTestRule.setChangeRolesContent( state: ChangeRolesState, - onBackClick: () -> Unit = EnsureNeverCalled(), ) { setContent { ChangeRolesView( state = state, - navigateUp = onBackClick, ) } } diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPointTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPointTest.kt similarity index 91% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPointTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPointTest.kt index 84e53c0cb6..3612479418 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPointTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPointTest.kt @@ -5,12 +5,12 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.test.ext.junit.runners.AndroidJUnit4 import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.tests.testutils.node.TestParentNode import kotlinx.coroutines.test.runTest diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/MembersByRoleTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/MembersByRoleTest.kt similarity index 97% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/MembersByRoleTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/MembersByRoleTest.kt index a2e134f807..101a0e2869 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/MembersByRoleTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/MembersByRoleTest.kt @@ -1,11 +1,11 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 2025 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.room.RoomMember diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt similarity index 98% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt index 734cf4696a..d5350b05f4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt similarity index 89% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt index 4aa68bc047..c9355d64d5 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt @@ -5,13 +5,13 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.activity.ComponentActivity 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.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.ui.strings.CommonStrings @@ -83,14 +83,12 @@ class RolesAndPermissionsViewTest { @Test @Config(qualifiers = "h640dp") - fun `tapping on any of the permission items open the change permissions screen`() { - ensureCalledTimes(3) { callback -> + fun `tapping permission item open the change permissions screen`() { + ensureCalledTimes(1) { callback -> rule.setRolesAndPermissionsView( - openPermissionScreens = callback, + openEditPermissions = 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) + rule.clickOn(R.string.screen_room_roles_and_permissions_permissions_header) } } @@ -184,7 +182,7 @@ private fun AndroidComposeTestRule.setRoles goBack: () -> Unit = EnsureNeverCalled(), openAdminList: () -> Unit = EnsureNeverCalled(), openModeratorList: () -> Unit = EnsureNeverCalled(), - openPermissionScreens: () -> Unit = EnsureNeverCalled(), + openEditPermissions: () -> Unit = EnsureNeverCalled(), ) { setSafeContent { RolesAndPermissionsView( @@ -193,9 +191,7 @@ private fun AndroidComposeTestRule.setRoles override fun onBackClick() = goBack() override fun openAdminList() = openAdminList() override fun openModeratorList() = openModeratorList() - override fun openEditRoomDetailsPermissions() = openPermissionScreens() - override fun openModerationPermissions() = openPermissionScreens() - override fun openMessagesAndContentPermissions() = openPermissionScreens() + override fun openEditPermissions() = openEditPermissions() } ) } diff --git a/features/changeroommemberroles/test/build.gradle.kts b/features/rolesandpermissions/test/build.gradle.kts similarity index 75% rename from features/changeroommemberroles/test/build.gradle.kts rename to features/rolesandpermissions/test/build.gradle.kts index 4d85d83c90..a5385b29d7 100644 --- a/features/changeroommemberroles/test/build.gradle.kts +++ b/features/rolesandpermissions/test/build.gradle.kts @@ -10,11 +10,11 @@ plugins { } android { - namespace = "io.element.android.features.changeroommemberroles.test" + namespace = "io.element.android.features.rolesandpermissions.test" } dependencies { - implementation(projects.features.changeroommemberroles.api) + implementation(projects.features.rolesandpermissions.api) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.tests.testutils) diff --git a/features/changeroommemberroles/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt similarity index 80% rename from features/changeroommemberroles/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt rename to features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt index 16b404ab5e..de447cef3c 100644 --- a/features/changeroommemberroles/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt +++ b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt @@ -9,8 +9,8 @@ package io.element.android.features.changeroommemberroles.test import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.tests.testutils.lambda.lambdaError diff --git a/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeRolesAndPermissionsEntryPoint.kt b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeRolesAndPermissionsEntryPoint.kt new file mode 100644 index 0000000000..489f1ad3b0 --- /dev/null +++ b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeRolesAndPermissionsEntryPoint.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.changeroommemberroles.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeRolesAndPermissionsEntryPoint : RolesAndPermissionsEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + lambdaError() + } +} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 896fc8dbdc..881510ff28 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation(projects.features.verifysession.api) implementation(projects.features.reportroom.api) implementation(projects.features.roommembermoderation.api) - implementation(projects.features.changeroommemberroles.api) + implementation(projects.features.rolesandpermissions.api) implementation(projects.features.invitepeople.api) testCommonDependencies(libs, true) @@ -69,7 +69,7 @@ dependencies { testImplementation(projects.libraries.usersearch.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.features.call.test) - testImplementation(projects.features.changeroommemberroles.test) + testImplementation(projects.features.rolesandpermissions.test) testImplementation(projects.features.knockrequests.test) testImplementation(projects.features.messages.test) testImplementation(projects.features.poll.test) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 5eee694786..35f50a4b1e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -26,19 +26,19 @@ import io.element.android.annotations.ContributesNode import io.element.android.appconfig.LearnMoreConfig import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.poll.api.history.PollHistoryEntryPoint import io.element.android.features.reportroom.api.ReportRoomEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode import io.element.android.features.roomdetails.impl.members.RoomMemberListNode import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode -import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode import io.element.android.features.userprofile.shared.UserProfileNodeHelper import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint @@ -82,6 +82,7 @@ class RoomDetailsFlowNode( private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint, private val reportRoomEntryPoint: ReportRoomEntryPoint, private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint, + private val rolesAndPermissionsEntryPoint: RolesAndPermissionsEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), @@ -156,10 +157,12 @@ class RoomDetailsFlowNode( changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy, -> commonLifecycle.coroutineScope.launch { - changeRoomMemberRolesNode.waitForRoleChanged() + val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion() withContext(NonCancellable) { backstack.pop() - roomDetailsNode.onNewOwnersSelected() + if (isNewOwnerSelected) { + roomDetailsNode.onNewOwnersSelected() + } } } } @@ -343,7 +346,7 @@ class RoomDetailsFlowNode( } is NavTarget.AdminSettings -> { - createNode(buildContext) + rolesAndPermissionsEntryPoint.createNode(this, buildContext) } NavTarget.PinnedMessagesList -> { val params = MessagesEntryPoint.Params( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt index 89b7812cb2..b1e39508bd 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt @@ -37,7 +37,7 @@ import io.element.android.libraries.designsystem.components.async.AsyncActionVie import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -152,9 +152,7 @@ fun RoomDetailsEditView( }, confirmationDialog = { if (state.saveAction == AsyncAction.ConfirmingCancellation) { - ConfirmationDialog( - title = stringResource(CommonStrings.dialog_unsaved_changes_title), - content = stringResource(CommonStrings.dialog_unsaved_changes_description_android), + SaveChangesDialog( onSubmitClick = { state.eventSink(RoomDetailsEditEvents.OnBackPress) }, onDismiss = { state.eventSink(RoomDetailsEditEvents.CloseDialog) } ) 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 deleted file mode 100644 index b2b7d46780..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions - -import android.os.Parcelable -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.lifecycle.coroutineScope -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.navmodel.backstack.BackStack -import com.bumble.appyx.navmodel.backstack.operation.pop -import com.bumble.appyx.navmodel.backstack.operation.push -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedInject -import io.element.android.annotations.ContributesNode -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroles.api.ChangeRoomMemberRolesListType -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 -import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.matrix.api.room.JoinedRoom -import kotlinx.coroutines.launch -import kotlinx.parcelize.Parcelize - -@ContributesNode(RoomScope::class) -@AssistedInject -class RolesAndPermissionsFlowNode( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint, - private val joinedRoom: JoinedRoom, -) : BaseFlowNode( - backstack = BackStack( - initialElement = NavTarget.AdminSettings, - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins, -) { - sealed interface NavTarget : Parcelable { - @Parcelize - data object AdminSettings : NavTarget - - @Parcelize - data object AdminList : NavTarget - - @Parcelize - data object ModeratorList : NavTarget - - @Parcelize - data class ChangeRoomPermissions(val section: ChangeRoomPermissionsSection) : NavTarget - } - - override fun onBuilt() { - super.onBuilt() - whenChildAttached { lifecycle, node: ChangeRoomMemberRolesEntryPoint.NodeProxy -> - lifecycle.coroutineScope.launch { - node.waitForRoleChanged() - backstack.pop() - } - } - } - - override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { - return when (navTarget) { - is NavTarget.AdminSettings -> { - val callback = object : RolesAndPermissionsNode.Callback { - override fun openAdminList() { - backstack.push(NavTarget.AdminList) - } - - 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, - plugins = listOf(callback), - ) - } - is NavTarget.AdminList -> { - changeRoomMemberRolesEntryPoint.createNode( - parentNode = this, - buildContext = buildContext, - room = joinedRoom, - listType = ChangeRoomMemberRolesListType.Admins, - ) - } - is NavTarget.ModeratorList -> { - changeRoomMemberRolesEntryPoint.createNode( - parentNode = this, - buildContext = buildContext, - room = joinedRoom, - listType = ChangeRoomMemberRolesListType.Moderators, - ) - } - is NavTarget.ChangeRoomPermissions -> { - val inputs = ChangeRoomPermissionsNode.Inputs(navTarget.section) - createNode( - buildContext = buildContext, - plugins = listOf(inputs), - ) - } - } - } - - @Composable - override fun View(modifier: Modifier) { - BackstackView() - } -} 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 deleted file mode 100644 index 5e7b77560a..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues -import kotlinx.collections.immutable.ImmutableList - -data class ChangeRoomPermissionsState( - val section: ChangeRoomPermissionsSection, - val currentPermissions: RoomPowerLevelsValues?, - 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/ChangeRoomPermissionsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt deleted file mode 100644 index 6acf4935dc..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -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.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.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.RoomPowerLevelsValues -import io.element.android.libraries.ui.strings.CommonStrings - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ChangeRoomPermissionsView( - state: ChangeRoomPermissionsState, - onBackClick: () -> 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( - titleStr = title, - navigationIcon = { - BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) - }, - actions = { - TextButton( - text = stringResource(CommonStrings.action_save), - onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, - enabled = state.hasChanges, - ) - } - ) - } - ) { padding -> - LazyColumn( - modifier = Modifier - .padding(padding) - .fillMaxSize() - ) { - for ((index, permissionItem) in state.items.withIndex()) { - item { - 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 = { onBackClick() }, - onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } - ) - - AsyncActionView( - async = state.confirmExitAction, - onSuccess = { onBackClick() }, - 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), - onSubmitClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, - onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) } - ) - }, - onErrorDismiss = {}, - ) -} - -@Composable -private fun SelectRoleItem( - permissionsItem: RoomPermissionType, - role: RoomMember.Role, - currentPermissions: RoomPowerLevelsValues?, - 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) - else -> error("Unsupported role selected: $role") - } - 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 RoomPowerLevelsValues.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(ChangeRoomPermissionsStateProvider::class) state: ChangeRoomPermissionsState) { - ElementPreview { - ChangeRoomPermissionsView( - state = state, - onBackClick = {}, - ) - } -} diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 814f352abd..38f3be744f 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -14,10 +14,10 @@ "Messages and content" "Admins and moderators" "Remove people and decline requests to join" - "Change room avatar" + "Change avatar" "Room details" - "Change room name" - "Change room topic" + "Change name" + "Change 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." diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt index f9bc40b711..52de9aa8f1 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt @@ -13,6 +13,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat import io.element.android.features.call.test.FakeElementCallEntryPoint import io.element.android.features.changeroommemberroles.test.FakeChangeRoomMemberRolesEntryPoint +import io.element.android.features.changeroommemberroles.test.FakeRolesAndPermissionsEntryPoint import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint import io.element.android.features.messages.test.FakeMessagesEntryPoint import io.element.android.features.poll.test.history.FakePollHistoryEntryPoint @@ -58,6 +59,7 @@ class DefaultRoomDetailsEntryPointTest { outgoingVerificationEntryPoint = FakeOutgoingVerificationEntryPoint(), reportRoomEntryPoint = FakeReportRoomEntryPoint(), changeRoomMemberRolesEntryPoint = FakeChangeRoomMemberRolesEntryPoint(), + rolesAndPermissionsEntryPoint = FakeRolesAndPermissionsEntryPoint(), ) } val callback = object : RoomDetailsEntryPoint.Callback { diff --git a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt index 6b5bd7f892..e05a7d1e8b 100644 --- a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt +++ b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt @@ -28,7 +28,6 @@ interface SpaceEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun navigateToRoom(roomId: RoomId, viaParameters: List) - fun navigateToRoomDetails() fun navigateToRoomMemberList() } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 1ef496d319..686729bae5 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -18,6 +18,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject @@ -26,14 +27,15 @@ import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowGraph import io.element.android.features.space.impl.leave.LeaveSpaceNode import io.element.android.features.space.impl.root.SpaceNode +import io.element.android.features.space.impl.settings.SpaceSettingsNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.DependencyInjectionGraphOwner import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.spaces.SpaceService import kotlinx.parcelize.Parcelize @@ -42,6 +44,7 @@ import kotlinx.parcelize.Parcelize class SpaceFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, + room: JoinedRoom, spaceService: SpaceService, graphFactory: SpaceFlowGraph.Factory, ) : BaseFlowNode( @@ -52,15 +55,17 @@ class SpaceFlowNode( buildContext = buildContext, plugins = plugins, ), DependencyInjectionGraphOwner { - private val inputs: SpaceEntryPoint.Inputs = inputs() private val callback: SpaceEntryPoint.Callback = callback() - private val spaceRoomList = spaceService.spaceRoomList(inputs.roomId) + private val spaceRoomList = spaceService.spaceRoomList(room.roomId) override val graph = graphFactory.create(spaceRoomList) sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget + @Parcelize + data object Settings : NavTarget + @Parcelize data object Leave : NavTarget } @@ -77,7 +82,16 @@ class SpaceFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Leave -> { - createNode(buildContext, listOf(inputs)) + val callback = object : LeaveSpaceNode.Callback { + override fun closeLeaveSpaceFlow() { + backstack.pop() + } + + override fun navigateToRolesAndPermissions() { + // TODO + } + } + createNode(buildContext, listOf(callback)) } NavTarget.Root -> { val callback = object : SpaceNode.Callback { @@ -85,8 +99,8 @@ class SpaceFlowNode( callback.navigateToRoom(roomId, viaParameters) } - override fun navigateToRoomDetails() { - callback.navigateToRoomDetails() + override fun navigateToSpaceSettings() { + backstack.push(NavTarget.Settings) } override fun navigateToRoomMemberList() { @@ -97,7 +111,35 @@ class SpaceFlowNode( backstack.push(NavTarget.Leave) } } - createNode(buildContext, listOf(inputs, callback)) + createNode(buildContext, listOf(callback)) + } + NavTarget.Settings -> { + val callback = object : SpaceSettingsNode.Callback { + override fun closeSettings() { + backstack.pop() + } + + override fun navigateToSpaceInfo() { + // TODO + } + + override fun navigateToSpaceMembers() { + callback.navigateToRoomMemberList() + } + + override fun navigateToRolesAndPermissions() { + // TODO + } + + override fun navigateToSecurityAndPrivacy() { + // TODO + } + + override fun startLeaveSpaceFlow() { + backstack.push(NavTarget.Leave) + } + } + createNode(buildContext, listOf(callback)) } } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt index c60bddea1d..6eaa5d4e4a 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt @@ -16,10 +16,10 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowScope -import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.JoinedRoom @ContributesNode(SpaceFlowScope::class) @AssistedInject @@ -27,12 +27,19 @@ class LeaveSpaceNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, matrixClient: MatrixClient, + room: JoinedRoom, presenterFactory: LeaveSpacePresenter.Factory, ) : Node(buildContext, plugins = plugins) { - private val inputs: SpaceEntryPoint.Inputs = inputs() - private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(inputs.roomId) + interface Callback : Plugin { + fun closeLeaveSpaceFlow() + fun navigateToRolesAndPermissions() + } + + private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(room.roomId) private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle) + private val callback: Callback = callback() + override fun onBuilt() { super.onBuilt() lifecycle.subscribe( @@ -47,7 +54,8 @@ class LeaveSpaceNode( val state = presenter.present() LeaveSpaceView( state = state, - onCancel = ::navigateUp, + onCancel = callback::closeLeaveSpaceFlow, + onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions, modifier = modifier ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt index 7432301f91..6f7a9903df 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt @@ -69,6 +69,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun LeaveSpaceView( state: LeaveSpaceState, onCancel: () -> Unit, + onRolesAndPermissionsClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -130,6 +131,9 @@ fun LeaveSpaceView( state.eventSink(LeaveSpaceEvents.LeaveSpace) }, onCancel = onCancel, + // TODO enable when navigation is ready + showRolesAndPermissionsButton = false, // state.isLastAdmin, + onRolesAndPermissionsClick = onRolesAndPermissionsClick, ) } } @@ -210,6 +214,8 @@ private fun LeaveSpaceButtons( showLeaveButton: Boolean, selectedRoomsCount: Int, onLeaveSpace: () -> Unit, + showRolesAndPermissionsButton: Boolean, + onRolesAndPermissionsClick: () -> Unit, onCancel: () -> Unit, ) { ButtonColumnMolecule( @@ -229,8 +235,14 @@ private fun LeaveSpaceButtons( destructive = true, ) } - // TODO For least admin space, add a button to open the settings. - // See https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4622-59600 + if (showRolesAndPermissionsButton) { + Button( + text = stringResource(CommonStrings.action_go_to_roles_and_permissions), + onClick = onRolesAndPermissionsClick, + modifier = Modifier.fillMaxWidth(), + leadingIcon = IconSource.Vector(CompoundIcons.Settings()), + ) + } TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_cancel), @@ -345,5 +357,6 @@ internal fun LeaveSpaceViewPreview( LeaveSpaceView( state = state, onCancel = {}, + onRolesAndPermissionsClick = {}, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt index 174fa71ee8..c0271782b4 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt @@ -42,7 +42,7 @@ class SpaceNode( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun navigateToRoom(roomId: RoomId, viaParameters: List) - fun navigateToRoomDetails() + fun navigateToSpaceSettings() fun navigateToRoomMemberList() fun startLeaveSpaceFlow() } @@ -80,7 +80,7 @@ class SpaceNode( callback.navigateToRoom(spaceRoom.roomId, spaceRoom.via) }, onDetailsClick = { - callback.navigateToRoomDetails() + callback.navigateToSpaceSettings() }, onShareSpace = { onShareRoom(context) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt index f4c415b718..2779ab2687 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt @@ -328,7 +328,7 @@ private fun SpaceViewTopBar( }, text = { Text( - text = stringResource(id = CommonStrings.action_leave), + text = stringResource(id = CommonStrings.action_leave_space), color = ElementTheme.colors.textCriticalPrimary, ) }, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt new file mode 100644 index 0000000000..a6fe90ade6 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +sealed interface SpaceSettingsEvents diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt new file mode 100644 index 0000000000..ea2e07db77 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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 dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.space.impl.di.SpaceFlowScope +import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback + +@ContributesNode(SpaceFlowScope::class) +@AssistedInject +class SpaceSettingsNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: SpaceSettingsPresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun closeSettings() + + fun navigateToSpaceInfo() + fun navigateToSpaceMembers() + fun navigateToRolesAndPermissions() + fun navigateToSecurityAndPrivacy() + fun startLeaveSpaceFlow() + } + + private val callback: Callback = callback() + private val stateFlow = launchMolecule { presenter.present() } + + @Composable + override fun View(modifier: Modifier) { + val state by stateFlow.collectAsState() + SpaceSettingsView( + state = state, + modifier = modifier, + onSpaceInfoClick = callback::navigateToSpaceInfo, + onBackClick = callback::closeSettings, + onMembersClick = callback::navigateToSpaceMembers, + onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions, + onSecurityAndPrivacyClick = callback::navigateToSecurityAndPrivacy, + onLeaveSpaceClick = callback::startLeaveSpaceFlow, + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt new file mode 100644 index 0000000000..6e89a0ba8d --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import dev.zacsweers.metro.Inject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin + +@Inject +class SpaceSettingsPresenter( + private val room: JoinedRoom, +) : Presenter { + @Composable + override fun present(): SpaceSettingsState { + val roomInfo by room.roomInfoFlow.collectAsState() + val isUserAdmin = room.isOwnUserAdmin() + return SpaceSettingsState( + roomId = room.roomId, + name = roomInfo.name.orEmpty(), + canonicalAlias = roomInfo.canonicalAlias, + avatarUrl = roomInfo.avatarUrl, + memberCount = roomInfo.activeMembersCount, + showRolesAndPermissions = isUserAdmin, + showSecurityAndPrivacy = isUserAdmin, + eventSink = {}, + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt new file mode 100644 index 0000000000..95b3615f63 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId + +data class SpaceSettingsState( + val roomId: RoomId, + val name: String, + val canonicalAlias: RoomAlias?, + val avatarUrl: String?, + val memberCount: Long, + val showRolesAndPermissions: Boolean, + val showSecurityAndPrivacy: Boolean, + val eventSink: (SpaceSettingsEvents) -> Unit +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt new file mode 100644 index 0000000000..db1b336653 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId + +open class SpaceSettingsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aSpaceSettingsState(), + aSpaceSettingsState(alias = null), + aSpaceSettingsState(showSecurityAndPrivacy = true), + aSpaceSettingsState(showRolesAndPermissions = true), + ) +} + +fun aSpaceSettingsState( + roomId: RoomId = RoomId("!aRoomId:element.io"), + name: String = "Space name", + alias: RoomAlias? = RoomAlias("#spacename:element.io"), + avatarUrl: String? = null, + memberCount: Long = 100, + showRolesAndPermissions: Boolean = false, + showSecurityAndPrivacy: Boolean = false, + eventSink: (SpaceSettingsEvents) -> Unit = {}, +) = SpaceSettingsState( + roomId = roomId, + name = name, + canonicalAlias = alias, + avatarUrl = avatarUrl, + memberCount = memberCount, + showRolesAndPermissions = showRolesAndPermissions, + showSecurityAndPrivacy = showSecurityAndPrivacy, + eventSink = eventSink, +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt new file mode 100644 index 0000000000..fae5bf2f03 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -0,0 +1,231 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.space.impl.R +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +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.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun SpaceSettingsView( + state: SpaceSettingsState, + onBackClick: () -> Unit, + onSpaceInfoClick: () -> Unit, + onMembersClick: () -> Unit, + onRolesAndPermissionsClick: () -> Unit, + onSecurityAndPrivacyClick: () -> Unit, + onLeaveSpaceClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + SpaceSettingsTopBar(onBackClick = onBackClick) + }, + ) { padding -> + Column( + modifier = Modifier + .padding(padding) + .verticalScroll(rememberScrollState()) + ) { + SpaceInfoSection( + roomId = state.roomId, + name = state.name, + avatarUrl = state.avatarUrl, + canonicalAlias = state.canonicalAlias?.value, + onSpaceInfoClick = onSpaceInfoClick, + ) + Section(isVisible = state.showSecurityAndPrivacy, content = { + SecurityAndPrivacyItem( + onClick = onSecurityAndPrivacyClick + ) + }) + Section(content = { + MembersItem(state.memberCount, onClick = onMembersClick) + if (state.showRolesAndPermissions) { + RolesAndPermissionsItem(onClick = onRolesAndPermissionsClick) + } + }) + Section(content = { + LeaveSpaceItem( + onClick = onLeaveSpaceClick + ) + }) + } + } +} + +@Composable +private fun SpaceInfoSection( + roomId: RoomId, + name: String, + avatarUrl: String?, + canonicalAlias: String?, + onSpaceInfoClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onSpaceInfoClick) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Avatar( + avatarData = AvatarData(roomId.value, name, avatarUrl, AvatarSize.SpaceListItem), + avatarType = AvatarType.Space(), + contentDescription = avatarUrl?.let { stringResource(CommonStrings.a11y_avatar) }, + ) + Spacer(Modifier.width(16.dp)) + Column { + Text( + text = name, + style = ElementTheme.typography.fontHeadingMdRegular, + color = ElementTheme.colors.textPrimary, + ) + if (canonicalAlias != null) { + Text( + text = canonicalAlias, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + } +} + +@Composable +private fun Section( + modifier: Modifier = Modifier, + isVisible: Boolean = true, + content: @Composable ColumnScope.() -> Unit, +) { + if (isVisible) { + PreferenceCategory(content = content, modifier = modifier) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SpaceSettingsTopBar( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + titleStr = stringResource(CommonStrings.common_settings), + navigationIcon = { BackButton(onClick = onBackClick) }, + modifier = modifier, + ) +} + +@Composable +private fun SecurityAndPrivacyItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_space_settings_security_and_privacy)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun MembersItem( + memberCount: Long, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(CommonStrings.common_people)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())), + trailingContent = ListItemContent.Text(memberCount.toString()), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun RolesAndPermissionsItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_space_settings_roles_and_permissions)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun LeaveSpaceItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { + Text(stringResource(CommonStrings.action_leave_space)) + }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave())), + style = ListItemStyle.Destructive, + onClick = onClick, + modifier = modifier, + ) +} + +@PreviewsDayNight +@Composable +internal fun SpaceSettingsViewPreview( + @PreviewParameter(SpaceSettingsStateProvider::class) state: SpaceSettingsState +) = ElementPreview { + SpaceSettingsView( + state = state, + onBackClick = {}, + onSpaceInfoClick = {}, + onMembersClick = {}, + onRolesAndPermissionsClick = {}, + onSecurityAndPrivacyClick = {}, + onLeaveSpaceClick = {}, + modifier = Modifier, + ) +} diff --git a/features/space/impl/src/main/res/values/localazy.xml b/features/space/impl/src/main/res/values/localazy.xml index c6ced29d41..a4df5e767d 100644 --- a/features/space/impl/src/main/res/values/localazy.xml +++ b/features/space/impl/src/main/res/values/localazy.xml @@ -10,4 +10,7 @@ "You will not be removed from the following room(s) because you\'re the only administrator:" "Leave %1$s?" "You are the only admin for %1$s" + "Leave space" + "Roles & permissions" + "Security & privacy" diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt index 3fd260dd4f..319d1eeeff 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt @@ -15,6 +15,7 @@ import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.FakeSpaceFlowGraph import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.tests.testutils.lambda.lambdaError @@ -40,12 +41,12 @@ class DefaultSpaceEntryPointTest { spaceService = FakeSpaceService( spaceRoomListResult = { _: RoomId -> FakeSpaceRoomList(A_ROOM_ID) } ), + room = FakeJoinedRoom(), graphFactory = FakeSpaceFlowGraph.Factory ) } val callback = object : SpaceEntryPoint.Callback { override fun navigateToRoom(roomId: RoomId, viaParameters: List) = lambdaError() - override fun navigateToRoomDetails() = lambdaError() override fun navigateToRoomMemberList() = lambdaError() } val result = entryPoint.createNode( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5cfc639d67..63101fe24c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,7 +52,7 @@ haze = "1.6.10" dependencyAnalysis = "3.4.1" # DI -metro = "0.7.4" +metro = "0.7.5" # Auto service autoservice = "1.1.1" @@ -103,7 +103,7 @@ androidx_recyclerview = "androidx.recyclerview:recyclerview:1.4.0" androidx_browser = "androidx.browser:browser:1.9.0" androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } -androidx_splash = "androidx.core:core-splashscreen:1.0.1" +androidx_splash = "androidx.core:core-splashscreen:1.2.0" androidx_media3_exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" } androidx_media3_ui = { module = "androidx.media3:media3-ui", version.ref = "media3" } androidx_media3_transformer = { module = "androidx.media3:media3-transformer", version.ref = "media3" } @@ -216,7 +216,7 @@ haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = color_picker = "io.mhssn:colorpicker:1.0.0" # Analytics -posthog = "com.posthog:posthog-android:3.25.0" +posthog = "com.posthog:posthog-android:3.26.0" sentry = "io.sentry:sentry-android:8.25.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt new file mode 100644 index 0000000000..f970904cc0 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun SaveChangesDialog( + onSubmitClick: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + title: String = stringResource(CommonStrings.dialog_unsaved_changes_title), + content: String = stringResource(CommonStrings.dialog_unsaved_changes_description_android), +) = ConfirmationDialog( + modifier = modifier, + title = title, + content = content, + onSubmitClick = onSubmitClick, + onDismiss = onDismiss, +) + +@PreviewsDayNight +@Composable +internal fun SaveChangesDialogPreview() = ElementPreview { + SaveChangesDialog( + onSubmitClick = {}, + onDismiss = {} + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt index 7be47338ca..fd76ef4c28 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt @@ -10,13 +10,12 @@ package io.element.android.libraries.designsystem.components.preferences import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.MenuAnchorType import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -25,14 +24,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.DropdownMenu import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem +import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.toEnabledColor @@ -87,6 +91,7 @@ fun PreferenceDropdown( onSelectOption = onSelectOption, expanded = isDropdownExpanded, onExpandedChange = { isDropdownExpanded = it }, + modifier = Modifier.fillMaxSize(0.3f) ) } ), @@ -114,27 +119,29 @@ private fun DropdownTrailingContent( onSelectOption: (T) -> Unit, modifier: Modifier = Modifier, ) { - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = onExpandedChange, + Row( modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, ) { - Row( - modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = selectedOption?.getText().orEmpty(), - maxLines = 1, - style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textSecondary, - ) - ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) - } - ExposedDropdownMenu( + Text( + text = selectedOption?.getText().orEmpty(), + maxLines = 1, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.End, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = CompoundIcons.ChevronDown(), + contentDescription = null, + tint = ElementTheme.colors.iconSecondary, + ) + DropdownMenu( expanded = expanded, + minWidth = 0.dp, onDismissRequest = { onExpandedChange(false) }, - matchTextFieldWidth = false, ) { options.forEach { option -> DropdownMenuItem( @@ -144,6 +151,15 @@ private fun DropdownTrailingContent( style = ElementTheme.typography.fontBodyMdRegular ) }, + trailingIcon = { + if (option == selectedOption) { + Icon( + imageVector = CompoundIcons.Check(), + contentDescription = null, + tint = ElementTheme.colors.iconAccentPrimary, + ) + } + }, onClick = { onSelectOption(option) onExpandedChange(false) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt index 6dc681b0e1..77a8c514a5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt @@ -10,8 +10,10 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties @@ -24,22 +26,24 @@ fun DropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, - // By default add a 16.dp offset to the menu - offset: DpOffset = DpOffset(x = 16.dp, y = 0.dp), + offset: DpOffset = DpOffset(x = 0.dp, y = 0.dp), properties: PopupProperties = PopupProperties(focusable = true), + minWidth: Dp = DropdownMenuDefaults.minWidth, content: @Composable ColumnScope.() -> Unit ) { - // Note: the internal shape corner radius should be 8dp, but there is a 4p value hardcoded in the internal Surface component androidx.compose.material3.DropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest, modifier = modifier - .background(color = ElementTheme.colors.bgCanvasDefault) - .widthIn(min = minMenuWidth), + .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) + .widthIn(min = minWidth), + shape = RoundedCornerShape(8.dp), offset = offset, properties = properties, content = content ) } -private val minMenuWidth = 200.dp +object DropdownMenuDefaults { + val minWidth = 200.dp +} diff --git a/libraries/di/src/main/kotlin/io/element/android/libraries/di/annotations/RoomCoroutineScope.kt b/libraries/di/src/main/kotlin/io/element/android/libraries/di/annotations/RoomCoroutineScope.kt new file mode 100644 index 0000000000..eee5115df9 --- /dev/null +++ b/libraries/di/src/main/kotlin/io/element/android/libraries/di/annotations/RoomCoroutineScope.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.di.annotations + +import dev.zacsweers.metro.Qualifier + +/** + * Qualifies a [CoroutineScope] object which represents the base coroutine scope to use for an active room. + */ +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +@Qualifier +annotation class RoomCoroutineScope diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt index 8cfaca077b..19d1572be5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt @@ -13,17 +13,20 @@ import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker import io.element.android.libraries.matrix.impl.ClientBuilderProvider +import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider import timber.log.Timber @ContributesBinding(AppScope::class) @Inject class RustHomeServerLoginCompatibilityChecker( private val clientBuilderProvider: ClientBuilderProvider, + private val userCertificatesProvider: UserCertificatesProvider, ) : HomeServerLoginCompatibilityChecker { override suspend fun check(url: String): Result = runCatchingExceptions { clientBuilderProvider.provide() .inMemoryStore() .serverNameOrHomeserverUrl(url) + .addRootCertificates(userCertificatesProvider.provides()) .build() .use { it.homeserverLoginDetails() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/RoomModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/RoomModule.kt new file mode 100644 index 0000000000..9fd9f7eade --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/RoomModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.di + +import dev.zacsweers.metro.BindingContainer +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.annotations.RoomCoroutineScope +import io.element.android.libraries.matrix.api.room.BaseRoom +import kotlinx.coroutines.CoroutineScope + +@BindingContainer +@ContributesTo(RoomScope::class) +object RoomModule { + @RoomCoroutineScope + @Provides + fun providesSessionCoroutineScope(room: BaseRoom): CoroutineScope { + return room.roomCoroutineScope + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt index 4557d47f5b..f24a4bc958 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt @@ -49,6 +49,7 @@ class RustHomeserverLoginCompatibilityCheckerTest { FakeFfiClientBuilder { FakeFfiClient(homeserverLoginDetailsResult = result) } - } + }, + userCertificatesProvider = FakeUserCertificatesProvider(), ) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 8db5fc6807..8c22c90cd7 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.test +import androidx.annotation.ColorInt import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId @@ -99,4 +100,5 @@ const val A_FORMATTED_DATE = "April 6, 1980 at 6:35 PM" const val A_LOGIN_HINT = "mxid:@alice:example.org" -const val A_COLOR_INT = 0xFF0000 +@ColorInt +const val A_COLOR_INT: Int = 0xFFFF0000.toInt() diff --git a/libraries/matrixui-test/build.gradle.kts b/libraries/matrixui-test/build.gradle.kts new file mode 100644 index 0000000000..c385d05b54 --- /dev/null +++ b/libraries/matrixui-test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.matrix.ui.test" +} + +dependencies { + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(libs.coil.compose) +} diff --git a/libraries/matrixui-test/src/main/kotlin/io/element/android/libraries/matrix/ui/test/media/FakeImageLoader.kt b/libraries/matrixui-test/src/main/kotlin/io/element/android/libraries/matrix/ui/test/media/FakeImageLoader.kt new file mode 100644 index 0000000000..05e380fb9e --- /dev/null +++ b/libraries/matrixui-test/src/main/kotlin/io/element/android/libraries/matrix/ui/test/media/FakeImageLoader.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.test.media + +import coil3.ComponentRegistry +import coil3.ImageLoader +import coil3.disk.DiskCache +import coil3.memory.MemoryCache +import coil3.request.Disposable +import coil3.request.ImageRequest +import coil3.request.ImageResult + +class FakeImageLoader : ImageLoader { + private val executedRequests = mutableListOf() + + override val defaults: ImageRequest.Defaults + get() = error("Not implemented") + override val components: ComponentRegistry + get() = error("Not implemented") + override val memoryCache: MemoryCache? + get() = error("Not implemented") + override val diskCache: DiskCache? + get() = error("Not implemented") + + override fun enqueue(request: ImageRequest): Disposable { + error("Not implemented") + } + + override suspend fun execute(request: ImageRequest): ImageResult { + executedRequests.add(request) + error("Not implemented") + } + + override fun shutdown() { + error("Not implemented") + } + + override fun newBuilder(): ImageLoader.Builder { + error("Not implemented") + } + + fun getExecutedRequestsData(): List { + return executedRequests.map { it.data } + } +} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoaderHolder.kt b/libraries/matrixui-test/src/main/kotlin/io/element/android/libraries/matrix/ui/test/media/FakeImageLoaderHolder.kt similarity index 67% rename from libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoaderHolder.kt rename to libraries/matrixui-test/src/main/kotlin/io/element/android/libraries/matrix/ui/test/media/FakeImageLoaderHolder.kt index 4c92dc8c18..4deef7274b 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoaderHolder.kt +++ b/libraries/matrixui-test/src/main/kotlin/io/element/android/libraries/matrix/ui/test/media/FakeImageLoaderHolder.kt @@ -1,21 +1,22 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright 2025 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.push.test.notifications +package io.element.android.libraries.matrix.ui.test.media import coil3.ImageLoader import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder -class FakeImageLoaderHolder : ImageLoaderHolder { - private val fakeImageLoader = FakeImageLoader() +class FakeImageLoaderHolder( + val fakeImageLoader: ImageLoader = FakeImageLoader(), +) : ImageLoaderHolder { override fun get(client: MatrixClient): ImageLoader { - return fakeImageLoader.getImageLoader() + return fakeImageLoader } override fun remove(sessionId: SessionId) { diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 091c74992f..2de79ab456 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -76,6 +76,7 @@ dependencies { testCommonDependencies(libs) testImplementation(libs.coil.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.matrixuiTest) testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.libraries.push.test) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index 01a1b1f9a9..6d52cbfe13 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -7,15 +7,10 @@ package io.element.android.libraries.push.impl.notifications -import androidx.annotation.VisibleForTesting -import androidx.core.app.NotificationManagerCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn -import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.annotations.AppCoroutineScope -import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -32,11 +27,7 @@ import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState import io.element.android.services.appnavstate.api.currentSessionId import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import timber.log.Timber - -private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag.NotificationLoggerTag) /** * This class receives notification events as they arrive from the PushHandler calling [onNotifiableEventReceived] and @@ -46,7 +37,7 @@ private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag. @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class DefaultNotificationDrawerManager( - private val notificationManager: NotificationManagerCompat, + private val notificationDisplayer: NotificationDisplayer, private val notificationRenderer: NotificationRenderer, private val appNavigationStateService: AppNavigationStateService, @AppCoroutineScope @@ -55,25 +46,17 @@ class DefaultNotificationDrawerManager( private val imageLoaderHolder: ImageLoaderHolder, private val activeNotificationsProvider: ActiveNotificationsProvider, ) : NotificationCleaner { - private var appNavigationStateObserver: Job? = null - // TODO EAx add a setting per user for this private var useCompleteNotificationFormat = true init { // Observe application state - appNavigationStateObserver = coroutineScope.launch { + coroutineScope.launch { appNavigationStateService.appNavigationState .collect { onAppNavigationStateChange(it.navigationState) } } } - // For test only - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun destroy() { - appNavigationStateObserver?.cancel() - } - private var currentAppNavigationState: NavigationState? = null private fun onAppNavigationStateChange(navigationState: NavigationState) { @@ -124,7 +107,7 @@ class DefaultNotificationDrawerManager( * Clear all known message events for a [sessionId]. */ override fun clearAllMessagesEvents(sessionId: SessionId) { - notificationManager.cancel(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) + notificationDisplayer.cancelNotification(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) clearSummaryNotificationIfNeeded(sessionId) } @@ -133,7 +116,7 @@ class DefaultNotificationDrawerManager( */ fun clearAllEvents(sessionId: SessionId) { activeNotificationsProvider.getNotificationsForSession(sessionId) - .forEach { notificationManager.cancel(it.tag, it.id) } + .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } } /** @@ -142,7 +125,7 @@ class DefaultNotificationDrawerManager( * Can also be called when a notification for this room is dismissed by the user. */ override fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) { - notificationManager.cancel(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) + notificationDisplayer.cancelNotification(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) clearSummaryNotificationIfNeeded(sessionId) } @@ -152,13 +135,13 @@ class DefaultNotificationDrawerManager( */ override fun clearMessagesForThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) { val tag = NotificationCreator.messageTag(roomId, threadId) - notificationManager.cancel(tag, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) + notificationDisplayer.cancelNotification(tag, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) clearSummaryNotificationIfNeeded(sessionId) } override fun clearMembershipNotificationForSession(sessionId: SessionId) { activeNotificationsProvider.getMembershipNotificationForSession(sessionId) - .forEach { notificationManager.cancel(it.tag, it.id) } + .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } clearSummaryNotificationIfNeeded(sessionId) } @@ -167,7 +150,7 @@ class DefaultNotificationDrawerManager( */ override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId) { activeNotificationsProvider.getMembershipNotificationForRoom(sessionId, roomId) - .forEach { notificationManager.cancel(it.tag, it.id) } + .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } clearSummaryNotificationIfNeeded(sessionId) } @@ -176,14 +159,14 @@ class DefaultNotificationDrawerManager( */ override fun clearEvent(sessionId: SessionId, eventId: EventId) { val id = NotificationIdProvider.getRoomEventNotificationId(sessionId) - notificationManager.cancel(eventId.value, id) + notificationDisplayer.cancelNotification(eventId.value, id) clearSummaryNotificationIfNeeded(sessionId) } private fun clearSummaryNotificationIfNeeded(sessionId: SessionId) { val summaryNotification = activeNotificationsProvider.getSummaryNotification(sessionId) if (summaryNotification != null && activeNotificationsProvider.count(sessionId) == 1) { - notificationManager.cancel(null, summaryNotification.id) + notificationDisplayer.cancelNotification(null, summaryNotification.id) } } @@ -201,29 +184,9 @@ class DefaultNotificationDrawerManager( // We have an avatar and a display name, use it userFromCache } else { - client.getSafeUserProfile() + client.getUserProfile().getOrNull() ?: MatrixUser(sessionId) } - notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader) } } - - private suspend fun MatrixClient.getSafeUserProfile(): MatrixUser { - return tryOrNull( - onException = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") }, - operation = { - val profile = getUserProfile().getOrNull() - // displayName cannot be empty else NotificationCompat.MessagingStyle() will crash - if (profile?.displayName.isNullOrEmpty()) { - profile?.copy(displayName = sessionId.value) - } else { - profile - } - } - ) ?: MatrixUser( - userId = sessionId, - displayName = sessionId.value, - avatarUrl = null - ) - } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt index a3becd94c1..33b2df410c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import android.graphics.Typeface import android.text.style.StyleSpan -import androidx.annotation.ColorInt import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import coil3.ImageLoader @@ -19,8 +18,8 @@ import dev.zacsweers.metro.ContributesBinding import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -31,39 +30,37 @@ import io.element.android.services.toolbox.api.strings.StringProvider interface NotificationDataFactory { suspend fun toNotifications( messages: List, - currentUser: MatrixUser, imageLoader: ImageLoader, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") fun toNotifications( invites: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") fun toNotifications( simpleEvents: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") fun toNotifications( fallback: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List fun createSummaryNotification( - currentUser: MatrixUser, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): SummaryNotification } @@ -77,9 +74,8 @@ class DefaultNotificationDataFactory( ) : NotificationDataFactory { override suspend fun toNotifications( messages: List, - currentUser: MatrixUser, imageLoader: ImageLoader, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { val messagesToDisplay = messages.filterNot { it.canNotBeDisplayed() } .groupBy { it.roomId } @@ -90,13 +86,12 @@ class DefaultNotificationDataFactory( eventsByThreadId.map { (threadId, events) -> val notification = roomGroupMessageCreator.createRoomMessage( - currentUser = currentUser, events = events, roomId = roomId, threadId = threadId, imageLoader = imageLoader, - existingNotification = getExistingNotificationForMessages(currentUser.userId, roomId, threadId), - color = color, + existingNotification = getExistingNotificationForMessages(notificationAccountParams.user.userId, roomId, threadId), + notificationAccountParams = notificationAccountParams, ) RoomNotification( notification = notification, @@ -121,12 +116,12 @@ class DefaultNotificationDataFactory( @Suppress("INAPPLICABLE_JVM_NAME") override fun toNotifications( invites: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { return invites.map { event -> OneShotNotification( - key = event.roomId.value, - notification = notificationCreator.createRoomInvitationNotification(event, color), + tag = event.roomId.value, + notification = notificationCreator.createRoomInvitationNotification(notificationAccountParams, event), summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -138,12 +133,12 @@ class DefaultNotificationDataFactory( @Suppress("INAPPLICABLE_JVM_NAME") override fun toNotifications( simpleEvents: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { return simpleEvents.map { event -> OneShotNotification( - key = event.eventId.value, - notification = notificationCreator.createSimpleEventNotification(event, color), + tag = event.eventId.value, + notification = notificationCreator.createSimpleEventNotification(notificationAccountParams, event), summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -155,12 +150,12 @@ class DefaultNotificationDataFactory( @Suppress("INAPPLICABLE_JVM_NAME") override fun toNotifications( fallback: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { return fallback.map { event -> OneShotNotification( - key = event.eventId.value, - notification = notificationCreator.createFallbackNotification(event, color), + tag = event.eventId.value, + notification = notificationCreator.createFallbackNotification(notificationAccountParams, event), summaryLine = event.description.orEmpty(), isNoisy = false, timestamp = event.timestamp @@ -169,23 +164,21 @@ class DefaultNotificationDataFactory( } override fun createSummaryNotification( - currentUser: MatrixUser, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): SummaryNotification { return when { roomNotifications.isEmpty() && invitationNotifications.isEmpty() && simpleNotifications.isEmpty() -> SummaryNotification.Removed else -> SummaryNotification.Update( summaryGroupMessageCreator.createSummaryNotification( - currentUser = currentUser, roomNotifications = roomNotifications, invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications, - color = color, + notificationAccountParams = notificationAccountParams, ) ) } @@ -254,7 +247,7 @@ data class RoomNotification( data class OneShotNotification( val notification: Notification, - val key: String, + val tag: String, val summaryLine: CharSequence, val isNoisy: Boolean, val timestamp: Long, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt index 4348c9bfb5..c1e7ba1d29 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt @@ -19,8 +19,8 @@ import io.element.android.libraries.di.annotations.ApplicationContext import timber.log.Timber interface NotificationDisplayer { - fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean - fun cancelNotificationMessage(tag: String?, id: Int) + fun showNotification(tag: String?, id: Int, notification: Notification): Boolean + fun cancelNotification(tag: String?, id: Int) fun displayDiagnosticNotification(notification: Notification): Boolean fun dismissDiagnosticNotification() } @@ -30,7 +30,7 @@ class DefaultNotificationDisplayer( @ApplicationContext private val context: Context, private val notificationManager: NotificationManagerCompat ) : NotificationDisplayer { - override fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean { + override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Timber.w("Not allowed to notify.") return false @@ -40,26 +40,28 @@ class DefaultNotificationDisplayer( return true } - override fun cancelNotificationMessage(tag: String?, id: Int) { + override fun cancelNotification(tag: String?, id: Int) { notificationManager.cancel(tag, id) } override fun displayDiagnosticNotification(notification: Notification): Boolean { - return showNotificationMessage( - tag = "DIAGNOSTIC", + return showNotification( + tag = TAG_DIAGNOSTIC, id = NOTIFICATION_ID_DIAGNOSTIC, notification = notification ) } override fun dismissDiagnosticNotification() { - cancelNotificationMessage( - tag = "DIAGNOSTIC", + cancelNotification( + tag = TAG_DIAGNOSTIC, id = NOTIFICATION_ID_DIAGNOSTIC ) } companion object { + private const val TAG_DIAGNOSTIC = "DIAGNOSTIC" + /* ========================================================================================== * IDs for notifications * ========================================================================================== */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt index 5248bce175..5da7389125 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt @@ -15,6 +15,7 @@ import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.api.notifications.NotificationIdProvider +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -22,6 +23,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent +import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.flow.first import timber.log.Timber @@ -32,6 +34,7 @@ class NotificationRenderer( private val notificationDisplayer: NotificationDisplayer, private val notificationDataFactory: NotificationDataFactory, private val enterpriseService: EnterpriseService, + private val sessionStore: SessionStore, ) { suspend fun render( currentUser: MatrixUser, @@ -41,24 +44,29 @@ class NotificationRenderer( ) { val color = enterpriseService.brandColorsFlow(currentUser.userId).first()?.toArgb() ?: NotificationConfig.NOTIFICATION_ACCENT_COLOR + val numberOfAccounts = sessionStore.numberOfSessions() + val notificationAccountParams = NotificationAccountParams( + user = currentUser, + color = color, + showSessionId = numberOfAccounts > 1, + ) val groupedEvents = eventsToProcess.groupByType() - val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, currentUser, imageLoader, color) - val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, color) - val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, color) - val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, color) + val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, imageLoader, notificationAccountParams) + val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, notificationAccountParams) + val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, notificationAccountParams) + val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, notificationAccountParams) val summaryNotification = notificationDataFactory.createSummaryNotification( - currentUser = currentUser, roomNotifications = roomNotifications, invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications, - color = color, + notificationAccountParams = notificationAccountParams, ) // Remove summary first to avoid briefly displaying it after dismissing the last notification if (summaryNotification == SummaryNotification.Removed) { Timber.tag(loggerTag.value).d("Removing summary notification") - notificationDisplayer.cancelNotificationMessage( + notificationDisplayer.cancelNotification( tag = null, id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId) ) @@ -69,7 +77,7 @@ class NotificationRenderer( roomId = notificationData.roomId, threadId = notificationData.threadId ) - notificationDisplayer.showNotificationMessage( + notificationDisplayer.showNotification( tag = tag, id = NotificationIdProvider.getRoomMessagesNotificationId(currentUser.userId), notification = notificationData.notification @@ -78,9 +86,9 @@ class NotificationRenderer( invitationNotifications.forEach { notificationData -> if (useCompleteNotificationFormat) { - Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.key}") - notificationDisplayer.showNotificationMessage( - tag = notificationData.key, + Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.tag}") + notificationDisplayer.showNotification( + tag = notificationData.tag, id = NotificationIdProvider.getRoomInvitationNotificationId(currentUser.userId), notification = notificationData.notification ) @@ -89,9 +97,9 @@ class NotificationRenderer( simpleNotifications.forEach { notificationData -> if (useCompleteNotificationFormat) { - Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.key}") - notificationDisplayer.showNotificationMessage( - tag = notificationData.key, + Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.tag}") + notificationDisplayer.showNotification( + tag = notificationData.tag, id = NotificationIdProvider.getRoomEventNotificationId(currentUser.userId), notification = notificationData.notification ) @@ -101,7 +109,7 @@ class NotificationRenderer( // Show only the first fallback notification if (fallbackNotifications.isNotEmpty()) { Timber.tag(loggerTag.value).d("Showing fallback notification") - notificationDisplayer.showNotificationMessage( + notificationDisplayer.showNotification( tag = "FALLBACK", id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId), notification = fallbackNotifications.first().notification @@ -111,7 +119,7 @@ class NotificationRenderer( // Update summary last to avoid briefly displaying it before other notifications if (summaryNotification is SummaryNotification.Update) { Timber.tag(loggerTag.value).d("Updating summary notification") - notificationDisplayer.showNotificationMessage( + notificationDisplayer.showNotification( tag = null, id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId), notification = summaryNotification.notification diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 853e7ffdfc..79121db5d7 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -9,15 +9,14 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import android.graphics.Bitmap -import androidx.annotation.ColorInt import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.factories.isSmartReplyError import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent @@ -25,13 +24,12 @@ import io.element.android.services.toolbox.api.strings.StringProvider interface RoomGroupMessageCreator { suspend fun createRoomMessage( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, events: List, roomId: RoomId, threadId: ThreadId?, imageLoader: ImageLoader, existingNotification: Notification?, - @ColorInt color: Int, ): Notification } @@ -42,13 +40,12 @@ class DefaultRoomGroupMessageCreator( private val notificationCreator: NotificationCreator, ) : RoomGroupMessageCreator { override suspend fun createRoomMessage( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, events: List, roomId: RoomId, threadId: ThreadId?, imageLoader: ImageLoader, existingNotification: Notification?, - @ColorInt color: Int, ): Notification { val lastKnownRoomEvent = events.last() val roomName = lastKnownRoomEvent.roomName ?: lastKnownRoomEvent.senderDisambiguatedDisplayName ?: "Room name (${roomId.value.take(8)}…)" @@ -66,8 +63,9 @@ class DefaultRoomGroupMessageCreator( val smartReplyErrors = events.filter { it.isSmartReplyError() } val roomIsDm = !roomIsGroup return notificationCreator.createMessagesListNotification( + notificationAccountParams = notificationAccountParams, RoomEventGroupInfo( - sessionId = currentUser.userId, + sessionId = notificationAccountParams.user.userId, roomId = roomId, roomDisplayName = roomName, isDm = roomIsDm, @@ -80,11 +78,9 @@ class DefaultRoomGroupMessageCreator( largeIcon = largeBitmap, lastMessageTimestamp = lastMessageTimestamp, tickerText = tickerText, - currentUser = currentUser, existingNotification = existingNotification, imageLoader = imageLoader, events = events, - color = color, ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt index 85947226f1..80bc92dc23 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt @@ -8,22 +8,20 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification -import androidx.annotation.ColorInt import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.services.toolbox.api.strings.StringProvider interface SummaryGroupMessageCreator { fun createSummaryNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, - @ColorInt color: Int, ): Notification } @@ -42,30 +40,25 @@ class DefaultSummaryGroupMessageCreator( private val notificationCreator: NotificationCreator, ) : SummaryGroupMessageCreator { override fun createSummaryNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, - @ColorInt color: Int, ): Notification { val summaryIsNoisy = roomNotifications.any { it.shouldBing } || invitationNotifications.any { it.isNoisy } || simpleNotifications.any { it.isNoisy } - val lastMessageTimestamp = roomNotifications.lastOrNull()?.latestTimestamp ?: invitationNotifications.lastOrNull()?.timestamp ?: simpleNotifications.last().timestamp - - // FIXME roomIdToEventMap.size is not correct, this is the number of rooms - val nbEvents = roomNotifications.size + simpleNotifications.size + val nbEvents = roomNotifications.size + invitationNotifications.size + simpleNotifications.size val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents) return notificationCreator.createSummaryListNotification( - currentUser, + notificationAccountParams = notificationAccountParams, sumTitle, noisy = summaryIsNoisy, lastMessageTimestamp = lastMessageTimestamp, - color = color, ) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt new file mode 100644 index 0000000000..da122b6a2a --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.notifications.factories + +import androidx.annotation.ColorInt +import io.element.android.libraries.matrix.api.user.MatrixUser + +data class NotificationAccountParams( + val user: MatrixUser, + @ColorInt val color: Int, + val showSessionId: Boolean, +) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 0dccdb46c2..d8ed2bb908 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo @@ -47,42 +48,40 @@ interface NotificationCreator { * Create a notification for a Room. */ suspend fun createMessagesListNotification( + notificationAccountParams: NotificationAccountParams, roomInfo: RoomEventGroupInfo, threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, tickerText: String, - currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, events: List, - @ColorInt color: Int, ): Notification fun createRoomInvitationNotification( + notificationAccountParams: NotificationAccountParams, inviteNotifiableEvent: InviteNotifiableEvent, - @ColorInt color: Int, ): Notification fun createSimpleEventNotification( + notificationAccountParams: NotificationAccountParams, simpleNotifiableEvent: SimpleNotifiableEvent, - @ColorInt color: Int, ): Notification fun createFallbackNotification( + notificationAccountParams: NotificationAccountParams, fallbackNotifiableEvent: FallbackNotifiableEvent, - @ColorInt color: Int, ): Notification /** * Create the summary notification. */ fun createSummaryListNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, compatSummary: String, noisy: Boolean, lastMessageTimestamp: Long, - @ColorInt color: Int, ): Notification fun createDiagnosticNotification( @@ -118,16 +117,15 @@ class DefaultNotificationCreator( * Create a notification for a Room. */ override suspend fun createMessagesListNotification( + notificationAccountParams: NotificationAccountParams, roomInfo: RoomEventGroupInfo, threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, tickerText: String, - currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, events: List, - @ColorInt color: Int, ): Notification { // Build the pending intent for when the notification is clicked val eventId = events.firstOrNull()?.eventId @@ -135,7 +133,6 @@ class DefaultNotificationCreator( threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId, threadId) else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId) } - val smallIcon = CommonDrawables.ic_notification val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION } val channelId = if (containsMissedCall) { notificationChannels.getChannelForIncomingCall(false) @@ -159,9 +156,6 @@ class DefaultNotificationCreator( setShortcutId(createShortcutId(roomInfo.sessionId, roomInfo.roomId)) } } - // Auto-bundling is enabled for 4 or more notifications on API 24+ (N+) - // devices and all Wear devices. But we want a custom grouping, so we specify the groupID - .setGroup(roomInfo.sessionId.value) .setGroupSummary(false) // In order to avoid notification making sound twice (due to the summary notification) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) @@ -171,8 +165,8 @@ class DefaultNotificationCreator( val messagingStyle = existingNotification?.let { MessagingStyle.extractMessagingStyleFromNotification(it) - } ?: messagingStyleFromCurrentUser( - user = currentUser, + } ?: createMessagingStyleFromCurrentUser( + user = notificationAccountParams.user, imageLoader = imageLoader, roomName = roomInfo.roomDisplayName, isThread = threadId != null, @@ -187,9 +181,7 @@ class DefaultNotificationCreator( .setWhen(lastMessageTimestamp) // MESSAGING_STYLE sets title and content for API 16 and above devices. .setStyle(messagingStyle) - .setSmallIcon(smallIcon) - // Set primary color (important for Wear 2.0 Notifications). - .setColor(color) + .configureWith(notificationAccountParams) // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for // 'importance' which is set in the NotificationChannel. The integers representing // 'priority' are different from 'importance', so make sure you don't mix them. @@ -202,7 +194,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(color, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -234,19 +226,16 @@ class DefaultNotificationCreator( } override fun createRoomInvitationNotification( + notificationAccountParams: NotificationAccountParams, inviteNotifiableEvent: InviteNotifiableEvent, - @ColorInt color: Int, ): Notification { - val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) .setContentTitle((inviteNotifiableEvent.roomName ?: buildMeta.applicationName).annotateForDebug(5)) .setContentText(inviteNotifiableEvent.description.annotateForDebug(6)) - .setGroup(inviteNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(color) + .configureWith(notificationAccountParams) .apply { addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) @@ -261,7 +250,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(color, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -277,19 +266,16 @@ class DefaultNotificationCreator( } override fun createSimpleEventNotification( + notificationAccountParams: NotificationAccountParams, simpleNotifiableEvent: SimpleNotifiableEvent, - @ColorInt color: Int, ): Notification { - val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) .setContentTitle(buildMeta.applicationName.annotateForDebug(7)) .setContentText(simpleNotifiableEvent.description.annotateForDebug(8)) - .setGroup(simpleNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(color) + .configureWith(notificationAccountParams) .setAutoCancel(true) .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId, null)) .apply { @@ -301,7 +287,7 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(color, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -310,19 +296,16 @@ class DefaultNotificationCreator( } override fun createFallbackNotification( + notificationAccountParams: NotificationAccountParams, fallbackNotifiableEvent: FallbackNotifiableEvent, - @ColorInt color: Int, ): Notification { - val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(false) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) .setContentTitle(buildMeta.applicationName.annotateForDebug(7)) .setContentText(fallbackNotifiableEvent.description.orEmpty().annotateForDebug(8)) - .setGroup(fallbackNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(color) + .configureWith(notificationAccountParams) .setAutoCancel(true) .setWhen(fallbackNotifiableEvent.timestamp) // Ideally we'd use `createOpenRoomPendingIntent` here, but the broken notification might apply to an invite @@ -343,24 +326,21 @@ class DefaultNotificationCreator( * Create the summary notification. */ override fun createSummaryListNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, compatSummary: String, noisy: Boolean, lastMessageTimestamp: Long, - @ColorInt color: Int, ): Notification { - val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(noisy) + val userId = notificationAccountParams.user.userId return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) // used in compat < N, after summary is built based on child notifications .setWhen(lastMessageTimestamp) .setCategory(NotificationCompat.CATEGORY_MESSAGE) - .setSmallIcon(smallIcon) - .setGroup(currentUser.userId.value) // set this notification as the summary for the group .setGroupSummary(true) - .setColor(color) + .configureWith(notificationAccountParams) .apply { if (noisy) { // Compat @@ -370,14 +350,14 @@ class DefaultNotificationCreator( setSound(it) } */ - setLights(color, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { // compat priority = NotificationCompat.PRIORITY_LOW } } - .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(currentUser.userId)) - .setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(currentUser.userId)) + .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(userId)) + .setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(userId)) .build() } @@ -468,7 +448,7 @@ class DefaultNotificationCreator( } } - private suspend fun messagingStyleFromCurrentUser( + private suspend fun createMessagingStyleFromCurrentUser( user: MatrixUser, imageLoader: ImageLoader, roomName: String, @@ -477,7 +457,8 @@ class DefaultNotificationCreator( ): MessagingStyle { return MessagingStyle( Person.Builder() - .setName(user.displayName?.annotateForDebug(50)) + // Note: name cannot be empty else NotificationCompat.MessagingStyle() will crash + .setName(user.getBestName().annotateForDebug(50)) .setIcon(bitmapLoader.getUserIcon(user.avatarUrl, imageLoader)) .setKey(user.userId.value) .build() @@ -497,4 +478,13 @@ class DefaultNotificationCreator( } } +private fun NotificationCompat.Builder.configureWith(notificationAccountParams: NotificationAccountParams) = apply { + setSmallIcon(CommonDrawables.ic_notification) + setColor(notificationAccountParams.color) + setGroup(notificationAccountParams.user.userId.value) + if (notificationAccountParams.showSessionId) { + setSubText(notificationAccountParams.user.userId.value) + } +} + fun NotifiableMessageEvent.isSmartReplyError() = outGoingMessage && outGoingMessageFailed diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt index 37d3b32c80..5be67606a4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt @@ -73,7 +73,7 @@ class DefaultOnRedactedEventReceived( oldMessage.person ) messagingStyle.messages[messageToRedactIndex] = newMessage - notificationDisplayer.showNotificationMessage( + notificationDisplayer.showNotification( statusBarNotification.tag, statusBarNotification.id, NotificationCompat.Builder(context, notification) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt index 694319c9e7..f5acd8ddb4 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt @@ -13,17 +13,17 @@ import androidx.core.app.NotificationCompat import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.test.A_COLOR_INT import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_TIMESTAMP import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL import io.element.android.libraries.matrix.ui.media.MediaRequestData +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader import io.element.android.libraries.push.impl.notifications.factories.MARK_AS_READ_ACTION_TITLE import io.element.android.libraries.push.impl.notifications.factories.QUICK_REPLY_ACTION_TITLE +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider @@ -44,23 +44,22 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( imageUriString = "aUri", ) ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, threadId = null, - color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(1) @Suppress("DEPRECATION") assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_LOW) assertThat(result.`when`).isEqualTo(A_TIMESTAMP) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -68,21 +67,20 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( noisy = true, ) ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, threadId = null, - color = A_COLOR_INT, ) @Suppress("DEPRECATION") assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_DEFAULT) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -130,9 +128,11 @@ class DefaultBaseRoomGroupMessageCreatorTest { sdkIntProvider = FakeBuildVersionSdkIntProvider(api) ) val result = sut.createRoomMessage( - currentUser = aMatrixUser( - // Some user avatar - avatarUrl = A_USER_AVATAR_1, + notificationAccountParams = aNotificationAccountParams( + user = aMatrixUser( + // Some user avatar + avatarUrl = A_USER_AVATAR_1, + ) ), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( @@ -141,13 +141,12 @@ class DefaultBaseRoomGroupMessageCreatorTest { ) ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, threadId = null, - color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(1) - assertThat(fakeImageLoader.getCoilRequests()).containsExactlyElementsIn(expectedCoilRequests) + assertThat(fakeImageLoader.getExecutedRequestsData()).containsExactlyElementsIn(expectedCoilRequests) } @Test @@ -155,16 +154,15 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP), aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10), ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, threadId = null, - color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(2) assertThat(result.`when`).isEqualTo(A_TIMESTAMP + 10) @@ -175,7 +173,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { QUICK_REPLY_ACTION_TITLE.takeIf { NotificationConfig.SHOW_QUICK_REPLY_ACTION }, ) ) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -183,7 +181,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( outGoingMessage = true, @@ -191,10 +189,9 @@ class DefaultBaseRoomGroupMessageCreatorTest { ), ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, threadId = null, - color = A_COLOR_INT, ) val actionTitles = result.actions?.map { it.title } assertThat(actionTitles).isEqualTo( @@ -202,7 +199,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { MARK_AS_READ_ACTION_TITLE.takeIf { NotificationConfig.SHOW_MARK_AS_READ_ACTION } ) ) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -210,21 +207,20 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( roomIsDm = true, ), ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, threadId = null, - color = A_COLOR_INT, ) assertThat(result.number).isEqualTo(1) assertThat(result.`when`).isEqualTo(A_TIMESTAMP) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index b2a4cc1c9b..0489b72dd3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -8,10 +8,10 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification -import androidx.core.app.NotificationManagerCompat +import androidx.compose.ui.graphics.Color import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService -import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -20,13 +20,17 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder import io.element.android.libraries.push.api.notifications.NotificationIdProvider +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState @@ -38,25 +42,19 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.mockk.every import io.mockk.mockk -import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(RobolectricTestRunner::class) class DefaultNotificationDrawerManagerTest { @Test fun `clearAllEvents should have no effect when queue is empty`() = runTest { val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager() defaultNotificationDrawerManager.clearAllEvents(A_SESSION_ID) - defaultNotificationDrawerManager.destroy() } @Test @@ -64,8 +62,8 @@ class DefaultNotificationDrawerManagerTest { // For now just call all the API. Later, add more valuable tests. val matrixUser = aMatrixUser(id = A_SESSION_ID.value, displayName = "alice", avatarUrl = "mxc://data") val mockRoomGroupMessageCreator = FakeRoomGroupMessageCreator( - createRoomMessageResult = lambdaRecorder { user, _, roomId, _, _, existingNotification -> - assertThat(user).isEqualTo(matrixUser) + createRoomMessageResult = lambdaRecorder { notificationAccountParams, _, roomId, _, _, existingNotification -> + assertThat(notificationAccountParams.user).isEqualTo(matrixUser) assertThat(roomId).isEqualTo(A_ROOM_ID) assertThat(existingNotification).isNull() Notification() @@ -88,7 +86,6 @@ class DefaultNotificationDrawerManagerTest { defaultNotificationDrawerManager.onNotifiableEventReceived(aNotifiableMessageEvent()) // Add the same Event again (will be ignored) defaultNotificationDrawerManager.onNotifiableEventReceived(aNotifiableMessageEvent()) - defaultNotificationDrawerManager.destroy() } @Test @@ -101,7 +98,7 @@ class DefaultNotificationDrawerManagerTest { ) ) val appNavigationStateService = FakeAppNavigationStateService(appNavigationState = appNavigationStateFlow) - val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( + createDefaultNotificationDrawerManager( appNavigationStateService = appNavigationStateService ) appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true)) @@ -117,17 +114,22 @@ class DefaultNotificationDrawerManagerTest { // Like a user sign out appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true)) runCurrent() - defaultNotificationDrawerManager.destroy() } @Test - fun `when MatrixClient has no cached user name a fallback one is used to render the notification`() = runTest { - val matrixClient = FakeMatrixClient(userDisplayName = null) + fun `when MatrixClient has no cached user name and avatar, the profile is loaded to render the notification`() = runTest { + val matrixClient = FakeMatrixClient( + userDisplayName = null, + userAvatarUrl = null, + ) val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }) val messageCreator = FakeRoomGroupMessageCreator() val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( matrixClientProvider = matrixClientProvider, roomGroupMessageCreator = messageCreator, + enterpriseService = FakeEnterpriseService( + initialBrandColor = Color.Red, + ) ) // Gets a display name from MatrixClient.getUserProfile matrixClient.givenGetProfileResult(A_SESSION_ID, Result.success(aMatrixUser(id = A_SESSION_ID.value, displayName = "alice"))) @@ -144,27 +146,41 @@ class DefaultNotificationDrawerManagerTest { messageCreator.createRoomMessageResult.assertions() .isCalledExactly(3) .withSequence( - listOf(value(aMatrixUser(id = A_SESSION_ID.value, displayName = "alice")), any(), any(), any(), any(), any()), - listOf(value(aMatrixUser(id = A_SESSION_ID.value, displayName = A_SESSION_ID.value)), any(), any(), any(), any(), any()), listOf( - value(aMatrixUser(id = A_SESSION_ID.value, displayName = A_SESSION_ID.value, avatarUrl = AN_AVATAR_URL)), + value(aNotificationAccountParams(user = aMatrixUser(id = A_SESSION_ID.value, displayName = "alice"))), + any(), + any(), + any(), + any(), + any(), + ), + listOf( + value(aNotificationAccountParams(user = aMatrixUser(id = A_SESSION_ID.value, displayName = ""))), + any(), + any(), + any(), + any(), + any(), + ), + listOf( + value(aNotificationAccountParams(user = aMatrixUser(id = A_SESSION_ID.value, displayName = null, avatarUrl = null))), + any(), any(), any(), any(), any(), - any() ), ) - - defaultNotificationDrawerManager.destroy() } @Test fun `clearSummaryNotificationIfNeeded will run after clearing all other notifications`() = runTest { - val notificationManager = mockk { - every { cancel(any(), any()) } returns Unit - } + val cancelNotificationResult = lambdaRecorder { _, _ -> } + val notificationDisplayer = FakeNotificationDisplayer( + cancelNotificationResult = cancelNotificationResult, + ) val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID) + val roomMessageId = NotificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID) val activeNotificationsProvider = FakeActiveNotificationsProvider( getSummaryNotificationResult = { mockk { @@ -174,7 +190,7 @@ class DefaultNotificationDrawerManagerTest { countResult = { 1 }, ) val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( - notificationManager = notificationManager, + notificationDisplayer = notificationDisplayer, activeNotificationsProvider = activeNotificationsProvider, ) @@ -182,24 +198,26 @@ class DefaultNotificationDrawerManagerTest { defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID) // Verify we asked to cancel the notification with summaryId - verify { notificationManager.cancel(null, summaryId) } - - defaultNotificationDrawerManager.destroy() + cancelNotificationResult.assertions().isCalledExactly(2).withSequence( + listOf(value(null), value(roomMessageId)), + listOf(value(null), value(summaryId)), + ) } private fun TestScope.createDefaultNotificationDrawerManager( - notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(RuntimeEnvironment.getApplication()), + notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), activeNotificationsProvider: FakeActiveNotificationsProvider = FakeActiveNotificationsProvider(), matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), + sessionStore: SessionStore = InMemorySessionStore(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), ): DefaultNotificationDrawerManager { - val context = RuntimeEnvironment.getApplication() return DefaultNotificationDrawerManager( - notificationManager = notificationManager, + notificationDisplayer = notificationDisplayer, notificationRenderer = NotificationRenderer( - notificationDisplayer = DefaultNotificationDisplayer(context, NotificationManagerCompat.from(context)), + notificationDisplayer = FakeNotificationDisplayer(), notificationDataFactory = DefaultNotificationDataFactory( notificationCreator = FakeNotificationCreator(), roomGroupMessageCreator = roomGroupMessageCreator, @@ -207,10 +225,11 @@ class DefaultNotificationDrawerManagerTest { activeNotificationsProvider = activeNotificationsProvider, stringProvider = FakeStringProvider(), ), - enterpriseService = FakeEnterpriseService(), + enterpriseService = enterpriseService, + sessionStore = sessionStore, ), appNavigationStateService = appNavigationStateService, - coroutineScope = this, + coroutineScope = backgroundScope, matrixClientProvider = matrixClientProvider, imageLoaderHolder = FakeImageLoaderHolder(), activeNotificationsProvider = activeNotificationsProvider, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt index 8b668a7f77..f2b26b3437 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt @@ -7,7 +7,6 @@ package io.element.android.libraries.push.impl.notifications -import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -16,23 +15,19 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notification.aNotificationData +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.tests.testutils.lambda.lambdaRecorder -import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -@RunWith(RobolectricTestRunner::class) class DefaultOnMissedCallNotificationHandlerTest { @OptIn(ExperimentalCoroutinesApi::class) @Test @@ -52,11 +47,9 @@ class DefaultOnMissedCallNotificationHandlerTest { val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler( matrixClientProvider = matrixClientProvider, defaultNotificationDrawerManager = DefaultNotificationDrawerManager( - notificationManager = mockk(relaxed = true), - notificationRenderer = NotificationRenderer( - notificationDisplayer = FakeNotificationDisplayer(), + notificationDisplayer = FakeNotificationDisplayer(), + notificationRenderer = createNotificationRenderer( notificationDataFactory = dataFactory, - enterpriseService = FakeEnterpriseService(), ), appNavigationStateService = FakeAppNavigationStateService(), coroutineScope = backgroundScope, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt index e34ea0848c..f701dae6c7 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt @@ -10,9 +10,8 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import androidx.core.app.NotificationCompat import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.matrix.test.A_COLOR_INT import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP @@ -34,7 +33,7 @@ class DefaultSummaryGroupMessageCreatorTest { ) val result = summaryCreator.createSummaryNotification( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), roomNotifications = listOf( RoomNotification( notification = Notification(), @@ -49,12 +48,11 @@ class DefaultSummaryGroupMessageCreatorTest { invitationNotifications = emptyList(), simpleNotifications = emptyList(), fallbackNotifications = emptyList(), - color = A_COLOR_INT, ) notificationCreator.createSummaryListNotificationResult.assertions() .isCalledOnce() - .with(any(), nonNull(), any(), any()) + .with(any(), any(), nonNull(), any(), any()) // Set from the events included @Suppress("DEPRECATION") diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt index f563106c14..6971fcbc41 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt @@ -11,9 +11,10 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EVENT_ID -import io.element.android.libraries.matrix.test.A_COLOR_INT import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator @@ -21,7 +22,6 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGrou import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.test.runTest import org.junit.Test @@ -51,16 +51,18 @@ class NotificationDataFactoryTest { @Test fun `given a room invitation when mapping to notification then it's added`() = testWith(notificationDataFactory) { - val expectedNotification = notificationCreator.createRoomInvitationNotificationResult(AN_INVITATION_EVENT) + val expectedNotification = notificationCreator.createRoomInvitationNotificationResult( + aNotificationAccountParams(), + AN_INVITATION_EVENT, + ) val roomInvitation = listOf(AN_INVITATION_EVENT) - - val result = toNotifications(roomInvitation, A_COLOR_INT) + val result = toNotifications(roomInvitation, aNotificationAccountParams()) assertThat(result).isEqualTo( listOf( OneShotNotification( notification = expectedNotification, - key = A_ROOM_ID.value, + tag = A_ROOM_ID.value, summaryLine = AN_INVITATION_EVENT.description, isNoisy = AN_INVITATION_EVENT.noisy, timestamp = AN_INVITATION_EVENT.timestamp @@ -71,20 +73,18 @@ class NotificationDataFactoryTest { @Test fun `given a simple event when mapping to notification then it's added`() = testWith(notificationDataFactory) { - val expectedNotification = notificationCreator.createRoomInvitationNotificationResult(AN_INVITATION_EVENT) - val roomInvitation = listOf(A_SIMPLE_EVENT) - - val result = toNotifications(roomInvitation, A_COLOR_INT) - - assertThat(result).isEqualTo( - listOf( - OneShotNotification( - notification = expectedNotification, - key = AN_EVENT_ID.value, - summaryLine = A_SIMPLE_EVENT.description, - isNoisy = A_SIMPLE_EVENT.noisy, - timestamp = AN_INVITATION_EVENT.timestamp - ) + val expectedNotification = notificationCreator.createRoomInvitationNotificationResult( + aNotificationAccountParams(), + AN_INVITATION_EVENT, + ) + val result = toNotifications(listOf(A_SIMPLE_EVENT), aNotificationAccountParams()) + assertThat(result).containsExactly( + OneShotNotification( + notification = expectedNotification, + tag = AN_EVENT_ID.value, + summaryLine = A_SIMPLE_EVENT.description, + isNoisy = A_SIMPLE_EVENT.noisy, + timestamp = AN_INVITATION_EVENT.timestamp ) ) } @@ -94,13 +94,14 @@ class NotificationDataFactoryTest { val events = listOf(A_MESSAGE_EVENT) val expectedNotification = RoomNotification( notification = fakeRoomGroupMessageCreator.createRoomMessage( - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), events = events, roomId = A_ROOM_ID, threadId = null, - imageLoader = FakeImageLoader().getImageLoader(), + imageLoader = FakeImageLoader(), existingNotification = null, - color = A_COLOR_INT, ), roomId = A_ROOM_ID, summaryLine = "A room name: Bob Hello world!", @@ -109,35 +110,33 @@ class NotificationDataFactoryTest { shouldBing = events.any { it.noisy }, threadId = null, ) - val roomWithMessage = listOf(A_MESSAGE_EVENT) - val fakeImageLoader = FakeImageLoader() val result = toNotifications( - messages = roomWithMessage, - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - imageLoader = fakeImageLoader.getImageLoader(), - color = A_COLOR_INT, + messages = listOf(A_MESSAGE_EVENT), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + imageLoader = fakeImageLoader, ) assertThat(result.size).isEqualTo(1) assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue() - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test fun `given a room with only redacted events when mapping to notification then is Empty`() = testWith(notificationDataFactory) { - val redactedRoom = listOf(A_MESSAGE_EVENT.copy(isRedacted = true)) - + val redactedRoom = A_MESSAGE_EVENT.copy(isRedacted = true) val fakeImageLoader = FakeImageLoader() val result = toNotifications( - messages = redactedRoom, - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - imageLoader = fakeImageLoader.getImageLoader(), - color = A_COLOR_INT, + messages = listOf(redactedRoom), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + imageLoader = fakeImageLoader, ) - assertThat(result).isEmpty() - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -151,13 +150,14 @@ class NotificationDataFactoryTest { val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted"))) val expectedNotification = RoomNotification( notification = fakeRoomGroupMessageCreator.createRoomMessage( - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), events = withRedactedRemoved, roomId = A_ROOM_ID, threadId = null, - imageLoader = FakeImageLoader().getImageLoader(), + imageLoader = FakeImageLoader(), existingNotification = null, - color = A_COLOR_INT, ), roomId = A_ROOM_ID, summaryLine = "A room name: Bob Hello world!", @@ -170,14 +170,15 @@ class NotificationDataFactoryTest { val fakeImageLoader = FakeImageLoader() val result = toNotifications( messages = roomWithRedactedMessage, - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - imageLoader = fakeImageLoader.getImageLoader(), - color = A_COLOR_INT, + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + imageLoader = fakeImageLoader, ) assertThat(result.size).isEqualTo(1) assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue() - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt index 589d7876f5..00ce37b09b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -7,14 +7,17 @@ package io.element.android.libraries.push.impl.notifications +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator @@ -23,7 +26,8 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -38,7 +42,7 @@ private const val USE_COMPLETE_NOTIFICATION_FORMAT = true private val A_SUMMARY_NOTIFICATION = SummaryNotification.Update(A_NOTIFICATION) private val ONE_SHOT_NOTIFICATION = - OneShotNotification(notification = A_NOTIFICATION, key = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1) + OneShotNotification(notification = A_NOTIFICATION, tag = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1) @RunWith(RobolectricTestRunner::class) class NotificationRendererTest { @@ -56,10 +60,9 @@ class NotificationRendererTest { ) private val notificationIdProvider = NotificationIdProvider - private val notificationRenderer = NotificationRenderer( + private val notificationRenderer = createNotificationRenderer( notificationDisplayer = notificationDisplayer, notificationDataFactory = notificationDataFactory, - enterpriseService = FakeEnterpriseService(), ) @Test @@ -75,7 +78,7 @@ class NotificationRendererTest { renderEventsAsNotifications(listOf(aNotifiableMessageEvent())) - notificationDisplayer.showNotificationMessageResult.assertions().isCalledExactly(2).withSequence( + notificationDisplayer.showNotificationResult.assertions().isCalledExactly(2).withSequence( listOf(value(A_ROOM_ID.value), value(notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)), value(A_NOTIFICATION)), listOf(value(null), value(notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)), value(A_SUMMARY_NOTIFICATION.notification)) ) @@ -83,11 +86,11 @@ class NotificationRendererTest { @Test fun `given a simple notification is added when rendering then show the simple notification and update summary`() = runTest { - notificationCreator.createSimpleNotificationResult = lambdaRecorder { _ -> ONE_SHOT_NOTIFICATION.copy(key = AN_EVENT_ID.value).notification } + notificationCreator.createSimpleNotificationResult = lambdaRecorder { _, _ -> ONE_SHOT_NOTIFICATION.copy(tag = AN_EVENT_ID.value).notification } renderEventsAsNotifications(listOf(aSimpleNotifiableEvent(eventId = AN_EVENT_ID))) - notificationDisplayer.showNotificationMessageResult.assertions().isCalledExactly(2).withSequence( + notificationDisplayer.showNotificationResult.assertions().isCalledExactly(2).withSequence( listOf(value(AN_EVENT_ID.value), value(notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)), value(A_NOTIFICATION)), listOf(value(null), value(notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)), value(A_SUMMARY_NOTIFICATION.notification)) ) @@ -95,11 +98,11 @@ class NotificationRendererTest { @Test fun `given an invitation notification is added when rendering then show the invitation notification and update summary`() = runTest { - notificationCreator.createRoomInvitationNotificationResult = lambdaRecorder { _ -> ONE_SHOT_NOTIFICATION.copy(key = AN_EVENT_ID.value).notification } + notificationCreator.createRoomInvitationNotificationResult = lambdaRecorder { _, _ -> ONE_SHOT_NOTIFICATION.copy(tag = AN_EVENT_ID.value).notification } renderEventsAsNotifications(listOf(anInviteNotifiableEvent())) - notificationDisplayer.showNotificationMessageResult.assertions().isCalledExactly(2).withSequence( + notificationDisplayer.showNotificationResult.assertions().isCalledExactly(2).withSequence( listOf(value(A_ROOM_ID.value), value(notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)), value(A_NOTIFICATION)), listOf(value(null), value(notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)), value(A_SUMMARY_NOTIFICATION.notification)) ) @@ -110,7 +113,19 @@ class NotificationRendererTest { MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL), useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT, eventsToProcess = events, - imageLoader = FakeImageLoader().getImageLoader(), + imageLoader = FakeImageLoader(), ) } } + +fun createNotificationRenderer( + notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), + notificationDataFactory: NotificationDataFactory = FakeNotificationDataFactory(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), + sessionStore: SessionStore = InMemorySessionStore(), +) = NotificationRenderer( + notificationDisplayer = notificationDisplayer, + notificationDataFactory = notificationDataFactory, + enterpriseService = enterpriseService, + sessionStore = sessionStore, +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt index d05966f9a6..82441883a7 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt @@ -20,9 +20,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClientProvider +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoaderHolder import io.element.android.libraries.push.impl.notifications.factories.FakeIntentProvider import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt index 045cc0492d..419a348f08 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.matrix.ui.test.media.FakeImageLoader import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.impl.notifications.DefaultNotificationBitmapLoader import io.element.android.libraries.push.impl.notifications.NotificationActionIds @@ -36,7 +37,6 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP @@ -65,6 +65,7 @@ class DefaultNotificationCreatorTest { fun `test createFallbackNotification`() { val sut = createNotificationCreator() val result = sut.createFallbackNotification( + notificationAccountParams = aNotificationAccountParams(), FallbackNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -77,7 +78,6 @@ class DefaultNotificationCreatorTest { timestamp = A_FAKE_TIMESTAMP, cause = null, ), - color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -88,6 +88,7 @@ class DefaultNotificationCreatorTest { fun `test createSimpleEventNotification`() { val sut = createNotificationCreator() val result = sut.createSimpleEventNotification( + notificationAccountParams = aNotificationAccountParams(), SimpleNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -103,7 +104,6 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, ), - color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -114,6 +114,7 @@ class DefaultNotificationCreatorTest { fun `test createSimpleEventNotification noisy`() { val sut = createNotificationCreator() val result = sut.createSimpleEventNotification( + notificationAccountParams = aNotificationAccountParams(), SimpleNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -129,7 +130,6 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, ), - color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -140,6 +140,7 @@ class DefaultNotificationCreatorTest { fun `test createRoomInvitationNotification`() { val sut = createNotificationCreator() val result = sut.createRoomInvitationNotification( + notificationAccountParams = aNotificationAccountParams(), InviteNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -156,7 +157,6 @@ class DefaultNotificationCreatorTest { isUpdated = false, roomName = "roomName", ), - color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -174,6 +174,7 @@ class DefaultNotificationCreatorTest { fun `test createRoomInvitationNotification noisy`() { val sut = createNotificationCreator() val result = sut.createRoomInvitationNotification( + notificationAccountParams = aNotificationAccountParams(), InviteNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -190,7 +191,6 @@ class DefaultNotificationCreatorTest { isUpdated = false, roomName = "roomName", ), - color = A_COLOR_INT, ) result.commonAssertions( expectedCategory = null, @@ -202,11 +202,10 @@ class DefaultNotificationCreatorTest { val sut = createNotificationCreator() val matrixUser = aMatrixUser() val result = sut.createSummaryListNotification( - currentUser = matrixUser, + notificationAccountParams = aNotificationAccountParams(user = matrixUser), compatSummary = "compatSummary", noisy = false, lastMessageTimestamp = 123_456L, - color = A_COLOR_INT, ) result.commonAssertions( expectedGroup = matrixUser.userId.value, @@ -218,11 +217,10 @@ class DefaultNotificationCreatorTest { val sut = createNotificationCreator() val matrixUser = aMatrixUser() val result = sut.createSummaryListNotification( - currentUser = matrixUser, + notificationAccountParams = aNotificationAccountParams(user = matrixUser), compatSummary = "compatSummary", noisy = true, lastMessageTimestamp = 123_456L, - color = A_COLOR_INT, ) result.commonAssertions( expectedGroup = matrixUser.userId.value, @@ -232,8 +230,8 @@ class DefaultNotificationCreatorTest { @Test fun `test createMessagesListNotification`() = runTest { val sut = createNotificationCreator() - aMatrixUser() val result = sut.createMessagesListNotification( + notificationAccountParams = aNotificationAccountParams(), roomInfo = RoomEventGroupInfo( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -247,11 +245,9 @@ class DefaultNotificationCreatorTest { largeIcon = null, lastMessageTimestamp = 123_456L, tickerText = "tickerText", - currentUser = aMatrixUser(), existingNotification = null, - imageLoader = FakeImageLoader().getImageLoader(), + imageLoader = FakeImageLoader(), events = listOf(aNotifiableMessageEvent()), - color = A_COLOR_INT, ) result.commonAssertions() } @@ -259,8 +255,8 @@ class DefaultNotificationCreatorTest { @Test fun `test createMessagesListNotification should bing and thread`() = runTest { val sut = createNotificationCreator() - aMatrixUser() val result = sut.createMessagesListNotification( + notificationAccountParams = aNotificationAccountParams(), roomInfo = RoomEventGroupInfo( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -274,17 +270,15 @@ class DefaultNotificationCreatorTest { largeIcon = null, lastMessageTimestamp = 123_456L, tickerText = "tickerText", - currentUser = aMatrixUser(), existingNotification = null, - imageLoader = FakeImageLoader().getImageLoader(), + imageLoader = FakeImageLoader(), events = listOf(aNotifiableMessageEvent()), - color = A_COLOR_INT, ) result.commonAssertions() } private fun Notification.commonAssertions( - expectedGroup: String? = A_SESSION_ID.value, + expectedGroup: String? = aMatrixUser().userId.value, expectedCategory: String? = NotificationCompat.CATEGORY_MESSAGE, ) { assertThat(contentIntent).isNotNull() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt new file mode 100644 index 0000000000..eca910b07e --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.notifications.factories + +import androidx.annotation.ColorInt +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.A_COLOR_INT +import io.element.android.libraries.matrix.ui.components.aMatrixUser + +fun aNotificationAccountParams( + user: MatrixUser = aMatrixUser(), + @ColorInt color: Int = A_COLOR_INT, + showSessionId: Boolean = false, +) = NotificationAccountParams( + user = user, + color = color, + showSessionId = showSessionId, +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt index 1cc4468b15..3a4acb757f 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt @@ -12,81 +12,84 @@ import android.graphics.Bitmap import androidx.annotation.ColorInt import coil3.ImageLoader import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent -import io.element.android.tests.testutils.lambda.LambdaFourParamsRecorder +import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.LambdaListAnyParamsRecorder -import io.element.android.tests.testutils.lambda.LambdaNoParamRecorder import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder +import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder import io.element.android.tests.testutils.lambda.lambdaAnyRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeNotificationCreator( var createMessagesListNotificationResult: LambdaListAnyParamsRecorder = lambdaAnyRecorder { A_NOTIFICATION }, - var createRoomInvitationNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> A_NOTIFICATION }, - var createSimpleNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> A_NOTIFICATION }, - var createFallbackNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> A_NOTIFICATION }, - var createSummaryListNotificationResult: LambdaFourParamsRecorder = - lambdaRecorder { _, _, _, _ -> A_NOTIFICATION }, - var createDiagnosticNotificationResult: LambdaNoParamRecorder = lambdaRecorder { A_NOTIFICATION }, + var createRoomInvitationNotificationResult: LambdaTwoParamsRecorder = + lambdaRecorder { _, _ -> A_NOTIFICATION }, + var createSimpleNotificationResult: LambdaTwoParamsRecorder = + lambdaRecorder { _, _ -> A_NOTIFICATION }, + var createFallbackNotificationResult: LambdaTwoParamsRecorder = + lambdaRecorder { _, _ -> A_NOTIFICATION }, + var createSummaryListNotificationResult: LambdaFiveParamsRecorder< + NotificationAccountParams, String, Boolean, Long, NotificationAccountParams, Notification + > = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION }, + var createDiagnosticNotificationResult: LambdaOneParamRecorder = + lambdaRecorder { _ -> A_NOTIFICATION }, ) : NotificationCreator { override suspend fun createMessagesListNotification( + notificationAccountParams: NotificationAccountParams, roomInfo: RoomEventGroupInfo, threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, tickerText: String, - currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, events: List, - @ColorInt color: Int, ): Notification { return createMessagesListNotificationResult( - listOf(roomInfo, threadId, largeIcon, lastMessageTimestamp, tickerText, currentUser, existingNotification, imageLoader, events) + listOf(notificationAccountParams, roomInfo, threadId, largeIcon, lastMessageTimestamp, tickerText, existingNotification, imageLoader, events) ) } override fun createRoomInvitationNotification( + notificationAccountParams: NotificationAccountParams, inviteNotifiableEvent: InviteNotifiableEvent, - @ColorInt color: Int, ): Notification { - return createRoomInvitationNotificationResult(inviteNotifiableEvent) + return createRoomInvitationNotificationResult(notificationAccountParams, inviteNotifiableEvent) } override fun createSimpleEventNotification( + notificationAccountParams: NotificationAccountParams, simpleNotifiableEvent: SimpleNotifiableEvent, - @ColorInt color: Int, ): Notification { - return createSimpleNotificationResult(simpleNotifiableEvent) + return createSimpleNotificationResult(notificationAccountParams, simpleNotifiableEvent) } override fun createFallbackNotification( + notificationAccountParams: NotificationAccountParams, fallbackNotifiableEvent: FallbackNotifiableEvent, - @ColorInt color: Int, ): Notification { - return createFallbackNotificationResult(fallbackNotifiableEvent) + return createFallbackNotificationResult(notificationAccountParams, fallbackNotifiableEvent) } override fun createSummaryListNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, compatSummary: String, noisy: Boolean, lastMessageTimestamp: Long, - @ColorInt color: Int, ): Notification { - return createSummaryListNotificationResult(currentUser, compatSummary, noisy, lastMessageTimestamp) + return createSummaryListNotificationResult(notificationAccountParams, compatSummary, noisy, lastMessageTimestamp, notificationAccountParams) } override fun createDiagnosticNotification( @ColorInt color: Int, ): Notification { - return createDiagnosticNotificationResult() + return createDiagnosticNotificationResult(color) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt index 9a0a5fe7ef..a897dbfc09 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt @@ -7,13 +7,12 @@ package io.element.android.libraries.push.impl.notifications.fake -import androidx.annotation.ColorInt import coil3.ImageLoader -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.NotificationDataFactory import io.element.android.libraries.push.impl.notifications.OneShotNotification import io.element.android.libraries.push.impl.notifications.RoomNotification import io.element.android.libraries.push.impl.notifications.SummaryNotification +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -25,14 +24,15 @@ import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeNotificationDataFactory( - var messageEventToNotificationsResult: LambdaThreeParamsRecorder, MatrixUser, ImageLoader, List> = - lambdaRecorder { _, _, _ -> emptyList() }, + var messageEventToNotificationsResult: LambdaThreeParamsRecorder< + List, ImageLoader, NotificationAccountParams, List + > = lambdaRecorder { _, _, _ -> emptyList() }, var summaryToNotificationsResult: LambdaFiveParamsRecorder< - MatrixUser, List, List, List, List, + NotificationAccountParams, SummaryNotification > = lambdaRecorder { _, _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) }, var inviteToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, @@ -42,18 +42,17 @@ class FakeNotificationDataFactory( ) : NotificationDataFactory { override suspend fun toNotifications( messages: List, - currentUser: MatrixUser, imageLoader: ImageLoader, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { - return messageEventToNotificationsResult(messages, currentUser, imageLoader) + return messageEventToNotificationsResult(messages, imageLoader, notificationAccountParams) } @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") override fun toNotifications( invites: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { return inviteToNotificationsResult(invites) } @@ -62,7 +61,7 @@ class FakeNotificationDataFactory( @Suppress("INAPPLICABLE_JVM_NAME") override fun toNotifications( simpleEvents: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { return simpleEventToNotificationsResult(simpleEvents) } @@ -71,25 +70,24 @@ class FakeNotificationDataFactory( @Suppress("INAPPLICABLE_JVM_NAME") override fun toNotifications( fallback: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): List { return fallbackEventToNotificationsResult(fallback) } override fun createSummaryNotification( - currentUser: MatrixUser, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, - @ColorInt color: Int, + notificationAccountParams: NotificationAccountParams, ): SummaryNotification { return summaryToNotificationsResult( - currentUser, roomNotifications, invitationNotifications, simpleNotifications, fallbackNotifications, + notificationAccountParams, ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt index cd3d047e2e..d1c5de9ffb 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt @@ -19,17 +19,17 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value class FakeNotificationDisplayer( - var showNotificationMessageResult: LambdaThreeParamsRecorder = lambdaRecorder { _, _, _ -> true }, - var cancelNotificationMessageResult: LambdaTwoParamsRecorder = lambdaRecorder { _, _ -> }, + var showNotificationResult: LambdaThreeParamsRecorder = lambdaRecorder { _, _, _ -> true }, + var cancelNotificationResult: LambdaTwoParamsRecorder = lambdaRecorder { _, _ -> }, var displayDiagnosticNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> true }, var dismissDiagnosticNotificationResult: LambdaNoParamRecorder = lambdaRecorder { -> }, ) : NotificationDisplayer { - override fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean { - return showNotificationMessageResult(tag, id, notification) + override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean { + return showNotificationResult(tag, id, notification) } - override fun cancelNotificationMessage(tag: String?, id: Int) { - return cancelNotificationMessageResult(tag, id) + override fun cancelNotification(tag: String?, id: Int) { + return cancelNotificationResult(tag, id) } override fun displayDiagnosticNotification(notification: Notification): Boolean { @@ -41,7 +41,7 @@ class FakeNotificationDisplayer( } fun verifySummaryCancelled(times: Int = 1) { - cancelNotificationMessageResult.assertions().isCalledExactly(times).withSequence( + cancelNotificationResult.assertions().isCalledExactly(times).withSequence( listOf(value(null), value(NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID))) ) } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt index 351300937b..6bb5b63977 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt @@ -8,12 +8,11 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification -import androidx.annotation.ColorInt import coil3.ImageLoader import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.RoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.tests.testutils.lambda.LambdaSixParamsRecorder @@ -22,18 +21,18 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder // We just can't make the param types fit @Suppress("MaxLineLength", "ktlint:standard:max-line-length", "ktlint:standard:parameter-wrapping") class FakeRoomGroupMessageCreator( - var createRoomMessageResult: LambdaSixParamsRecorder, RoomId, ThreadId?, ImageLoader, Notification?, Notification> = - lambdaRecorder { _, _, _, _, _, _ -> A_NOTIFICATION } + var createRoomMessageResult: LambdaSixParamsRecorder< + NotificationAccountParams, List, RoomId, ThreadId?, ImageLoader, Notification?, Notification + > = lambdaRecorder { _, _, _, _, _, _ -> A_NOTIFICATION } ) : RoomGroupMessageCreator { override suspend fun createRoomMessage( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, events: List, roomId: RoomId, threadId: ThreadId?, imageLoader: ImageLoader, existingNotification: Notification?, - @ColorInt color: Int, ): Notification { - return createRoomMessageResult(currentUser, events, roomId, threadId, imageLoader, existingNotification) + return createRoomMessageResult(notificationAccountParams, events, roomId, threadId, imageLoader, existingNotification) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt index bc8a5515c9..8a1e8bb4ed 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt @@ -8,30 +8,28 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification -import androidx.annotation.ColorInt -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.OneShotNotification import io.element.android.libraries.push.impl.notifications.RoomNotification import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeSummaryGroupMessageCreator( var createSummaryNotificationResult: LambdaFiveParamsRecorder< - MatrixUser, List, List, List, List, Notification> = + NotificationAccountParams, List, List, List, List, Notification> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION } ) : SummaryGroupMessageCreator { override fun createSummaryNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, - @ColorInt color: Int, ): Notification { return createSummaryNotificationResult( - currentUser, + notificationAccountParams, roomNotifications, invitationNotifications, simpleNotifications, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt index b27c96d8de..71ec7c3545 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt @@ -122,7 +122,7 @@ class DefaultOnRedactedEventReceivedTest { } ) }, - displayer = FakeNotificationDisplayer(showNotificationMessageResult = showNotificationLambda), + displayer = FakeNotificationDisplayer(showNotificationResult = showNotificationLambda), ) sut.onRedactedEventsReceived(listOf(ResolvedPushEvent.Redaction(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, null))) diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoader.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoader.kt deleted file mode 100644 index 067376cfbc..0000000000 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoader.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.push.test.notifications - -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import coil3.ImageLoader -import coil3.test.FakeImageLoaderEngine -import coil3.test.intercept -import org.robolectric.RuntimeEnvironment - -class FakeImageLoader { - private val coilRequests = mutableListOf() - - private var cache: ImageLoader? = null - - fun getImageLoader(): ImageLoader { - return cache ?: ImageLoader.Builder(RuntimeEnvironment.getApplication()) - .components { - val engine = FakeImageLoaderEngine.Builder() - .intercept( - predicate = { - coilRequests.add(it) - true - }, - drawable = ColorDrawable(Color.BLUE) - ) - .build() - add(engine) - } - .build() - .also { - cache = it - } - } - - fun getCoilRequests(): List { - return coilRequests.toList() - } -} diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 819554ae55..d82e203e9b 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -44,7 +44,7 @@ private const val versionMonth = 11 * Release number in the month. Value must be in [0,99]. * Do not update this value. it is updated by the release script. */ -private const val versionReleaseNumber = 1 +private const val versionReleaseNumber = 2 object Versions { /** diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_9_en.png deleted file mode 100644 index 9be427d4b6..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_9_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b4885040583de68748dc10cf85e0f395f7ae8a0ef08d8aeed14826fafec22990 -size 65024 diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_9_en.png deleted file mode 100644 index ccb2d31d48..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_9_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac14dd774fca2924f599bd831d4fcad9931011174108bae5da1bcf8a5db6c4cf -size 64688 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png index 4a2a7a2a97..12e42e376c 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:550ea0bbefea8fc1af9aed6bf9b8238547aba6d9bd670035b22c42e5e1788812 -size 39281 +oid sha256:657d1e0eff5254eb3d36a64212cca38ec129c240cbd61b8cfdeb8399a00a5251 +size 39103 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png index f1cbbadd81..5d4488f53a 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b511e657048cf668b48a135119ffce4449c99ea4c90065744d61a34502e75508 -size 36703 +oid sha256:3407623ce83d0c3f710ea45d693823d6125b1bc625c34e2922916110d8a2a442 +size 36717 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png index 7bfb23035b..ae30993ae5 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d20f0962fb92b64b0e8a507f2c30428d0349c176d1453ed423a6c447d350459e -size 47267 +oid sha256:d986fd93989b178dc08957270a75fee126c9910a45d067ca7c3353d188b69850 +size 47377 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png index d76c93c920..e1c896297c 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d39dbdbed4a1260170fc1a148860533320413efb367a5d31bc4111086378529c -size 47157 +oid sha256:cb5c468b4f8235595bcfb3355fa29cfd43567d51e27785357bdecb13ab1dadd9 +size 47268 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png index 9915cd5435..dc78febfda 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed87edce4fc8ea22a2ba6d666bbaf21b14a359e2a3eec828e2edede23d5d1b1e -size 47146 +oid sha256:e16ace640aa43a2d6b923a46fec94ca41e1b9ce1632cbd0319c646c08dc1b167 +size 47257 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png index 49d0d774af..bc69773fda 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d446944a0408921659ef954036211fe341e8b0529b6e0c7ecc315998e14b8f43 -size 47136 +oid sha256:f90e8efb79b3525fafdee503a0aa5280e1d5bf0275ea3be0e79ee323c7b03751 +size 47248 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png index 1f32e3cb20..3a3942319f 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61706045ca364252d30d9a36d276b83aeee5e4be2552fbd7a97be9378ebc3a37 -size 46979 +oid sha256:8afb2771627534be13f709b1e125d9d4ae26d4ca1a7db102b31ef14620bb45a9 +size 47089 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png index 6a6f712306..4d87b9f694 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23a53698059b50b46bcbb6cedd9434aaffa77db460d7495b9306c809d90d2126 -size 47270 +oid sha256:4e7b71b415175d4cae28404ca4aa361929df63fb670b9b5c86c327dd6df73ddf +size 47379 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png index 0f40386837..6ae9086f2a 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:779879fc4b976584305eca916e42bd3cf1b6c08bf9e857bf842ec4cd537d7c3a -size 46924 +oid sha256:693180156b1bad6c7e0f1ef698ec8b6787912c2ca2f7d94326e2cda043ba6819 +size 47034 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en.png index a1b6e813b0..fc1664cc49 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ec601dec7eb57e457ddb351fbe8f966b8f51e3dde12f3e7130e8229f4ed3079 -size 46559 +oid sha256:ba1df0d1d16e51d28ecbfcc34be2d7ffb6722efd8bc6089b4befb2d79f82ce5f +size 46668 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en.png index 8934c11f27..76ed86b02b 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c647eb273c63b6e28f91a87d9e85f00c83d7f0cb6748965cd0c1391cac7fe918 -size 52991 +oid sha256:3268b8e9c04a5a966f7d5b991299e783d6efe04cb05800ef45b2fd226b1d10bf +size 53105 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png index f7d1a84a24..b080444cda 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ec2983e926cff79f13fa0851c4c7dd6c4fd0433f19c774136cef5cdd0ea8b52 -size 48932 +oid sha256:e44bb25d897d6f4de241267c56c6e3bed1d68d3d21522c4d0200a82041ddc083 +size 48999 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png index d742e90a11..4879d67434 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c382988ddf0643e1c6e78aee175fed9232302a4bd6d10e078c3fdd74d471904b -size 48813 +oid sha256:1cb9fe598d7bb977561b403e43acf2dc2ab943f17cd82aa57981c2adafdc07e7 +size 48879 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png index eb2d20f165..f46bf72380 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b68b299ef3917fb201712817b056a3c328d9f0c4cee5e0216de49d1ee962ebe5 -size 48830 +oid sha256:981005ce4565843a041995c6c69cc9503a983de5df82433019fa33dc5ce3f597 +size 48896 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png index dc166a004a..0645a6d0b9 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26ef88a68a260fa9096651badadd93c4ce69d150add269cbdfd303b3289de629 -size 48814 +oid sha256:8f80185f3563d3e74abd3a1cccc79fbdfe47e4cf0e3bea98130698e74d09eab4 +size 48878 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png index 319eb1d0b0..a0d683dc0c 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c56cbb0fbc5aadecac6cdd23fee7ee93e18843c7923b0d73dbc90468c29a217 -size 48735 +oid sha256:1cc613718c250b031c8bf551996bb528956894fea2e2e98619b5f45361d6947d +size 48804 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png index 19460ca25f..c611d7e93a 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76c25bf891f2d649c3822448d288cc77a1f50626d58fe381268df89b346dee6a -size 48932 +oid sha256:badb0c35e0c1f6b3121a2469862b5559e061023513771a65ebdeb94ec7b98078 +size 49001 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png index e992029a8b..74bc5f88d8 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3314c594d699f376b5f06fd7da21ce318b5fee331c2e104ce04bbee59b5ba4fc -size 48753 +oid sha256:aa36e842c20867e60424763ec0e2d9ed7f01030cf0525df3b0965273ad3e7e06 +size 48823 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en.png index 5e995f6e3f..03e8601f97 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97111fd3e96fc9007b271a9ecbb3571034ea79fc7998b12bc854fcc43b6d31b4 -size 48438 +oid sha256:6f9a0affa7a07696ce1a0be56e1264b8b7213dd1fa5627b6af3307ba9ebaca36 +size 48508 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en.png index 6a161b9137..8f10026eed 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:704dba07bf3c49e8ba0f2cf62368c2ead4f27e6708572b4c38b5fffc89b89c26 -size 55181 +oid sha256:d0241bcb4fec4fe87d0a161960726bc9a33987b78c4748a6670771ca3e9b7584 +size 55254 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_en.png new file mode 100644 index 0000000000..2021890e00 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c180424588c0856aee7ba2de7d93845c96201ecbdc4f23a00b4d671a903a980 +size 60193 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png new file mode 100644 index 0000000000..dc8055d41c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab650b1fa9b1e7b464fb8ac9006d8d7705d9d4409093797efac24b9f4ad90afd +size 60109 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png new file mode 100644 index 0000000000..6c2cf322be --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d692109b09fbdd5f0c147ebbb7f352d62491f785a5266308d3a60447f91a6e00 +size 52103 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png new file mode 100644 index 0000000000..296a9fc1fe --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c6726e8db1fc6999fb43cac1614ad8179adc1191b4c1e62b8e66fc5fd70bf76 +size 49542 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png new file mode 100644 index 0000000000..7acc0ebd0f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8519f86746bb89c6236332f83fbfc00fad488593f487f3d4148285abd5a3cf7 +size 56356 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_0_en.png new file mode 100644 index 0000000000..180d3ca9c7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27d7653e6052117a360dfe0e03e16875958e9f19bd56355881e9399fedcabf6c +size 58578 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png new file mode 100644 index 0000000000..f0157f1ced --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2fad72bab0a58f75ca68aa67442dcae44946e0c2b156139e03536dda051f4115 +size 58471 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png new file mode 100644 index 0000000000..f3f664d4f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd4f60534b4df7f3631d79b39e7aeb22609d967f7edef0ee5dee4ea05f1aa183 +size 50635 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png new file mode 100644 index 0000000000..cb69a420a5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d860c6be97aa2929fb37e25f312f14ee17312b2e201268e232fc6a5dd2621ce2 +size 47394 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png new file mode 100644 index 0000000000..ade6d6b994 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c4e198a9cb65a66ea0854d9fcde915ea4a70826aef3ccbb4a8e7f2d38f285fd +size 54200 diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_10_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_11_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_12_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_1_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_2_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_3_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_4_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_5_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_5_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_6_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_7_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Day_8_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_en.png new file mode 100644 index 0000000000..d1b21ebee2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:393bf3509a833f7af78a57e546ce6d35ca3bef4d4523ff12af657d1c573b87c5 +size 61190 diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_10_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_10_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_10_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_11_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_11_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_11_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_12_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_12_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_12_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_1_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_2_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_2_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_2_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_3_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_3_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_4_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_4_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_4_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_5_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_5_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_5_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_6_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_7_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_7_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_7_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_8_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_ChangeRolesView_Night_8_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_8_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_9_en.png new file mode 100644 index 0000000000..f55e0ac7a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37a79b2f5d6aedaef2dec74defd0f2a17af49239058a606f0785e82e56d833ec +size 61887 diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Night_0_en.png rename to tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_en.png new file mode 100644 index 0000000000..7ba2742dab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b64f4c2c68338cd9acc6e2644b41eb3d30463e6ee5da2eba0d93b1ff2340a563 +size 28791 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_en.png new file mode 100644 index 0000000000..56f241f10c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6be26ff8ed8f9907fbab1778a0b112c649e8bd8059f0ae407b52634f9fe4b136 +size 30630 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_en.png new file mode 100644 index 0000000000..83a633fc78 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d57be3aa2c919a957f99a6b8da269fae5791dd5ffea4dd2113e91ee82796c35 +size 59091 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_en.png new file mode 100644 index 0000000000..fe7577ec55 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f55f5714eb073ad4649ff9b5d2924239db6063701cdca61941a970a5f7f78197 +size 28038 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_en.png new file mode 100644 index 0000000000..29e8c35dfe --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6dcb28cfda314fa41d86ae47c6e9e7275798515b735cf4ebfb51c26d4412b4ad +size 27047 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_en.png new file mode 100644 index 0000000000..d581dc2637 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7e2b3d4d2aa0349db9641afd230cf3c532f11bd05c2fd821657a5665190324f +size 37175 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_en.png new file mode 100644 index 0000000000..fe7577ec55 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f55f5714eb073ad4649ff9b5d2924239db6063701cdca61941a970a5f7f78197 +size 28038 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_en.png new file mode 100644 index 0000000000..cba28447f4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0b069df8c405ef8556a0cbe9a68fae9788ba55eaf940a11c1b20b1732d79114 +size 27344 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_en.png new file mode 100644 index 0000000000..16aae512d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfc46b428fcd6950911089d1f09a7cb6f6aa5009d2ce39f4b00ce706a3a41fb3 +size 27175 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_0_en.png new file mode 100644 index 0000000000..58c89081f0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:320d0da30157930114f60d10a1da565780f9869340dc9f0b71b70552e9b8bd0c +size 27990 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_1_en.png new file mode 100644 index 0000000000..d69a5bfb78 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0c78fbc29259d21b5f66d86bb943153d4a1f35409785629306a672234493dfd +size 29741 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_2_en.png new file mode 100644 index 0000000000..ceff3d7f25 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e8141af3945e72cbdedd38062d9606c4a1646fd75ce169cf5aec9369968404d +size 56721 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_3_en.png new file mode 100644 index 0000000000..367f6cbce4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af38118f74d11e03a7cc6776aa64d9955b9d494a38f21396393eea0b7e133f83 +size 26464 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_4_en.png new file mode 100644 index 0000000000..2fc5472b0d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c42bb6f9615803253f395759dc6d86c940131fec23f6c6f1d83cef9375452cd9 +size 25071 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_5_en.png new file mode 100644 index 0000000000..e0a8bfadda --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75747b889fd518a514c5058b86676c89c68ff9557505b39ee9ea04b62a83b8ab +size 34616 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_6_en.png new file mode 100644 index 0000000000..367f6cbce4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af38118f74d11e03a7cc6776aa64d9955b9d494a38f21396393eea0b7e133f83 +size 26464 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_7_en.png new file mode 100644 index 0000000000..dec47a2fcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c82ffeae6eb17e27996357e1538ade2b962ecb6b6ccda8a6e5535a8b98f991c +size 25386 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_8_en.png new file mode 100644 index 0000000000..9e823c8497 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a653512dc37cbe52f5623278358b643e9c8e6c706a1a3f6999a579b6d8ceecc +size 26423 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en.png deleted file mode 100644 index de3f3a93b9..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80f4f24de2db8c0b1462a6dd77cb138132dc37aa1cc000508d0cf66129fa486f -size 39962 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en.png deleted file mode 100644 index 9762549b31..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80b6d0cef1f4709ab0f519c3168d9e0f9b7a7ffff8f7d4a8fbc5b10653b33042 -size 36279 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en.png deleted file mode 100644 index 863e6bdf1c..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13095117980b3b3dde8df1b2404bad43187d17cdcaf51805cf45baa684bb5086 -size 46677 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en.png deleted file mode 100644 index d07b5f214e..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e32a7a1ec92aa005734e877ff995bc40420710e80eea350ffbc1530095ad48c -size 39857 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en.png deleted file mode 100644 index 42da0d9e8c..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3e89506e845b0a9d80831b1f65fa3793aee784d5a02ab69241a26384d9d43251 -size 36292 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en.png deleted file mode 100644 index df10b8c6e2..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79b02cbdef9a555911ba7e775642b08473f271a92e045d4f7c141ccfe63170ca -size 37204 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en.png deleted file mode 100644 index 6107455bd9..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:785820fd67e137c5347060cf4d07dda5dc24e2349c8bb2a418895fc6ce9069b1 -size 44079 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en.png deleted file mode 100644 index f2691ad84f..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:61e3c25edb23657021113a1201408200f85f0b2dcba9e96208a7125027cf642f -size 38699 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en.png deleted file mode 100644 index 5e779061e0..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11738c6de6751b37430db66d377478d38b7ac241c14d7bb2ab8b48206908fbda -size 35175 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en.png deleted file mode 100644 index 58226e7852..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a15405c5998265da00876bac43ab2d70fca74fab916d2014207b46997b0fed5 -size 45030 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en.png deleted file mode 100644 index 2d15a73061..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f524740b14ffe3b5a70983b42c308cdcd1d7bfe7dbd0738aed3e79dba4673bbe -size 38559 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en.png deleted file mode 100644 index 2fc07b3421..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c25628ffa6457a573b8678ab95ee7bb80315ba8a43d20a16e3d7c03baab7ea9f -size 35201 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en.png deleted file mode 100644 index 56520adc2a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab0970def368ef59ecfbc1b7cec7e0dcdd8156a6f642701acf9dd54527080568 -size 35440 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en.png deleted file mode 100644 index 7faaed8c07..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:742d98c87eaff31e460614f15ec4a5f03797669378099b5be3e0785320d0a0d1 -size 42171 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en.png deleted file mode 100644 index 1acc4da150..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f1129fe5ebc6cebbba907ca15e92b33d5ee645c431d0022758bfc5d6e28bf69e -size 41003 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png deleted file mode 100644 index e906d843c7..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fe1c01cc7c9ea9f50a0413505b516a86831416416c82488a9339785c89b3e0d3 -size 42769 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png deleted file mode 100644 index cf46f94231..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d08941b66dd884cb3eb73fea0ccbf535ab2b42a46fb1ac83d220832e42b486a6 -size 59083 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png deleted file mode 100644 index dccc6a1f32..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09ca684dc0f03aa68e658c4f1c780dde6d8c1739255899936f320595098c2c80 -size 39542 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png deleted file mode 100644 index 97b5326743..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e71878e1dd445a58b3d79db5863ec4449729bf0bb1549c09848923fb9bf052d9 -size 38390 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png deleted file mode 100644 index d25877daf2..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:558ba83131a9d79e32e0c5566a170e1c62b5bccbb4ad23f9bc58e271ea3f89e9 -size 48462 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png deleted file mode 100644 index dccc6a1f32..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09ca684dc0f03aa68e658c4f1c780dde6d8c1739255899936f320595098c2c80 -size 39542 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png deleted file mode 100644 index b1912920cb..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77f4b8e6ec426104ebe81308b2cf0ad111e3fbfa07c2086a2199ab8e9e952e8a -size 38719 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png deleted file mode 100644 index 4793c85c21..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9000726c2e4e6115bb18f09193d84d4ab1948e70e35f27d0bfa514be96584510 -size 39123 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en.png deleted file mode 100644 index aee278ece4..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d009d8ede606e8cd4bc150a292aac5c58c38195b188412a55df686ef74738e08 -size 39747 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png deleted file mode 100644 index 62e2a03cf7..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa618f575a11ed7c8521d754f54f41745ede3f2f9f95b242d9f04f641446c581 -size 41500 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png deleted file mode 100644 index e6fe537947..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b0063c4f776093e3c53beb164bd21d5ca0c32a865b10e848826a6f7859267f00 -size 56716 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png deleted file mode 100644 index 147635d0f9..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e47c40c3173a80230c88f7e242f0236e6de3ed255552eed2d54a71fb136aa842 -size 37809 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png deleted file mode 100644 index 98e04f83fe..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ae08d2f3f6bc8b5364d3b9ea3584c21efe3180001d0babf209c6ef3fbf41e9a -size 35871 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png deleted file mode 100644 index 5a7c8850fd..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01fe728028fd86ccaf8ff7b08eb0b1b1e21ad336da97f39d818304e769cd65aa -size 45374 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png deleted file mode 100644 index 147635d0f9..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e47c40c3173a80230c88f7e242f0236e6de3ed255552eed2d54a71fb136aa842 -size 37809 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png deleted file mode 100644 index c2c1b2c9f2..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a7ae5371560ae6a92f0fb6537d8359c1bc40a6c43e3f12fd365019cbb2debe72 -size 36210 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png deleted file mode 100644 index 1d42c39f20..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5c945af810f3bde63d99284b97b127a10b7ec222a560a406284b4dac2b0e3f9 -size 38022 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png new file mode 100644 index 0000000000..24c00cb1a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a573dcac3bab78db152da81c0a1b7803fcf70c2392cc05a20ae533edfde76730 +size 21620 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png new file mode 100644 index 0000000000..13d9ea54d2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a17f1005bf296f04a2d631c6d19313bc864ebcd6832a788f8fa6909b7562dcb5 +size 17874 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png new file mode 100644 index 0000000000..f408da9f38 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc34deb76678be310df0beb512d91fa3b6edf2d2c59a7daf46267f1c8012e12c +size 25422 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png new file mode 100644 index 0000000000..189eb42ad0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14d4cabaf98490e35bb75a12c3d37e3c499158b6d6b639084059ee1bc999e831 +size 26018 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png new file mode 100644 index 0000000000..d163b60818 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b13402b213b595b8a0a632eeb96263db7e8f7cbd3f56d8d235c38abf6df66128 +size 21235 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png new file mode 100644 index 0000000000..77db74b048 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7031872033a6c12acd7328a5ae0820ac2dfa00340f8a8b6cac746c98004b285c +size 17444 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png new file mode 100644 index 0000000000..9d45abb1c6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053e0d74fb1c01e5e434aa48606b5f0d24fb9a950b56555783b618679c4e7ad5 +size 24925 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png new file mode 100644 index 0000000000..9c36d80cef --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d201e92db03d2d0d1b7d786d472d5d0f3e946ba7b610b9769ceb597b4d827eb9 +size 25467 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png new file mode 100644 index 0000000000..090d6dce7d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d89a6c9a49b2b0e4fa37e219cec8e0dbe90ed8ca6e71f0ec5e8788076f91526 +size 23679 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png new file mode 100644 index 0000000000..3963c95375 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e496c8d67a678a7d4c729a3368c145afa080252dd68708b1ba95d98706cd614c +size 22265 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png index be32cb24a3..99527960b1 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.preferences_PreferenceDropdown_Preferences_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f02ee83a49656553dd59648442bf4d558f6257fff3b30b768673d531cbe10150 -size 30928 +oid sha256:73749a1655e26374776fa7e8aa83582b969cd40259a5ff580710a2b19acdddb6 +size 32072 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 82c273db2e..39d63484a9 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -210,7 +210,8 @@ { "name" : ":features:space:impl", "includeRegex" : [ - "screen\\.leave_space\\..*" + "screen\\.leave_space\\..*", + "screen\\.space_settings\\..*" ] }, { @@ -366,10 +367,11 @@ ] }, { - "name" : ":features:changeroommemberroles:impl", + "name" : ":features:rolesandpermissions:impl", "includeRegex" : [ "screen_room_change_.*", "screen_room_roles_.*", + "screen\\.room_roles_and_permissions\\..*", "screen_room_member_list.*" ] }