diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9d8be16c9..a9cc829697 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,44 +63,6 @@ jobs: path: | app/build/outputs/bundle/gplayRelease/app-gplay-release.aab - enterprise: - name: Create App Bundle Enterprise - runs-on: ubuntu-latest - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} - concurrency: - group: ${{ format('build-release-main-enterprise-{0}', github.sha) }} - cancel-in-progress: true - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Add SSH private keys for submodule repositories - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} - with: - ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} - - name: Clone submodules - run: git submodule update --init --recursive - - name: Use JDK 21 - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 - with: - distribution: 'temurin' # See 'Supported distributions' for available options - java-version: '21' - - name: Configure gradle - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - - name: Create Enterprise app bundle - env: - ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} - ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} - ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} - run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES - - name: Upload bundle as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: elementx-enterprise-app-gplay-bundle-unsigned - path: | - app/build/outputs/bundle/gplayRelease/app-gplay-release.aab - fdroid: name: Create APKs (FDroid) runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 917285ab30..a4a73ca965 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,9 +50,10 @@ PRs must meet these rules. - Title = changelog entry — descriptive, no "Fixes #…". - Leave description template for the developer. Redirect them to the [contributing etiquette](CONTRIBUTING.md#etiquette). - Screenshots/videos for visual changes. -- 500 additions max — split large changes. +- 500 additions max into production code. Test code can be larger — split large changes. - Commits need a title and description; no tiny or massive commits. - No history rewrites. +- When creating a pull request and if changes contain new or update(s) of the Composable Previews, add the label "Record-Screenshots" to the PR so that the CI will record the screenshots. --- diff --git a/CHANGES.md b/CHANGES.md index db18e59251..5f4be5d6eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,87 @@ +Changes in Element X v26.08.4 +============================= + + + +## What's Changed +### 🧱 Build +* Add releaseV2.sh, a release script automating the GitHub release by @bmarty in https://github.com/element-hq/element-x-android/pull/7579 +* Update submodule ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7582 +### Others +* Keep the formatting of a caption that is being edited by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7572 +* Say when the server cannot be reached instead of showing nothing by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7567 +* Keep saving files to Downloads once 32 files share the same name by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7344 +* Fix removed messages hiding its thread on deletion by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7480 +* Drop the sender's name from the room list preview of two-person rooms by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7562 +* Document how the Element Call base URL developer option is used by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7546 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.3...v26.08.4 + +Changes in Element X v26.08.3 +============================= + + + +## What's Changed +### ✨ Features +* Let users enter their full Matrix user ID on the account provider screen by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7500 +* Enable user status feature and hide status text in the timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/7517 +### 🐛 Bugfixes +* Fix cold start with a mobile deep link by @bmarty in https://github.com/element-hq/element-x-android/pull/7488 +* Make the account provider screen scrollable so its description is not clipped by the keyboard by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7521 +### 🧱 Build +* Update Gradle Wrapper from 9.7.0 to 9.7.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7485 +* Fix flaky test in `DefaultVoiceMessageComposerPresenterTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7487 +* Fix warning about deprecated Gradle `keepRules.file` API by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7498 +* Update Enterprise Copyright in idea settings, so that new files get an expected header. by @bmarty in https://github.com/element-hq/element-x-android/pull/7501 +* Remove workaround for Renovate. by @bmarty in https://github.com/element-hq/element-x-android/pull/7502 +### 🚧 In development 🚧 +* Make PIP react to camera orientation changes in the other participant's device by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6951 +* Feature: global search by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7410 +### Dependency upgrades +* Update dependency org.maplibre.gl:android-sdk to v13.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7486 +* Update dependency androidx.webkit:webkit to v1.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7484 +* Update dependency io.sentry:sentry-android to v8.53.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7481 +* Update dependency io.element.android:element-call-embedded to v0.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7474 +* Update roborazzi to v1.72.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7489 +* Update metro to v1.4.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7493 +* Update dependency com.squareup.okhttp3:okhttp-bom to v5.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7512 +* Update dependency com.google.guava:guava to v33.7.0-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7516 +* Update sdk to version 26.08.25 by @bmarty in https://github.com/element-hq/element-x-android/pull/7550 +### 💼 Element Pro +* Add `ClientEnterpriseHook` and `ClientBuilderEnterpriseHook` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7495 +* Remove dependency on enterprise module. by @bmarty in https://github.com/element-hq/element-x-android/pull/7505 +### Others +* Show the right image when opening one that has not finished sending by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7359 +* Move strings to the login module by @bmarty in https://github.com/element-hq/element-x-android/pull/7494 +* Allow empty problem description if the GitHub issue number is present for rageshakes by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7497 +* Apply the system theme when switching back to it from a forced light or dark theme by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7453 +* Show dialog with reason when user clicks on a message with a failure by @bmarty in https://github.com/element-hq/element-x-android/pull/7490 +* Keep a focused timeline focused until the event has been rendered by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7463 +* Do not render in-chat key verification requests in the timeline by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7443 +* Share a file whose mime type is not one of the supported ones by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7435 +* Show reactions in the pinned messages list by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6676 +* Send a concrete mime type for media shared with a wildcard one by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7449 +* Rename module and ensure that naming convention is followed by @bmarty in https://github.com/element-hq/element-x-android/pull/7514 +* Fix lint issue, ensure that PiP methods are not called on old devices not supporting them. by @bmarty in https://github.com/element-hq/element-x-android/pull/7515 +* Rename PreviewParameterProvider subclasses to the PreviewParam suffix by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7468 +* Update Ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7518 +* Remove the reference to the decommissioned OIDC playground by @sandhose in https://github.com/element-hq/element-x-android/pull/7519 +* Hide the encryption badge when key storage is set up on the server by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7447 +* Rename remaining events sealed interface by @bmarty in https://github.com/element-hq/element-x-android/pull/7533 +* Stop waiting on a verification request the other session never accepts by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7457 +* Point the proxy debugging doc at the method that builds the clients by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7543 +* Show the build date in the screenshot gallery footer by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7544 +* Run the database migrations again when a database is upgraded by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7570 +* Update fonts on the headings by @bmarty in https://github.com/element-hq/element-x-android/pull/7520 +* Add a "Push rules" entry in the developer options by @bmarty in https://github.com/element-hq/element-x-android/pull/7577 + +## New Contributors +* @sandhose made their first contribution in https://github.com/element-hq/element-x-android/pull/7519 + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.2...v26.08.3 + Changes in Element X v26.08.2 ============================= diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvent.kt similarity index 90% rename from appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt rename to appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvent.kt index 66fffce9e8..34ba2e6460 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvent.kt @@ -8,11 +8,11 @@ package io.element.android.appnav.loggedin -sealed interface LoggedInEvents { - data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvents - data object CheckSlidingSyncProxyAvailability : LoggedInEvents - data object LogoutAndMigrateToNativeSlidingSync : LoggedInEvents - data object DismissLocalNetworkPermissionPrompt : LoggedInEvents +sealed interface LoggedInEvent { + data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvent + data object CheckSlidingSyncProxyAvailability : LoggedInEvent + data object LogoutAndMigrateToNativeSlidingSync : LoggedInEvent + data object DismissLocalNetworkPermissionPrompt : LoggedInEvent - data object RequestLocationNetworkPermission : LoggedInEvents + data object RequestLocationNetworkPermission : LoggedInEvent } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 5cd3efa801..f2f6e1312f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -105,9 +105,18 @@ class LoggedInPresenter( } val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() val isOnline by syncService.isOnline.collectAsState() - val showSyncSpinner by remember { + val hasNetwork by networkMonitor.connectivity.collectAsState() + val syncIndicatorState by remember { derivedStateOf { - isOnline && syncIndicator == RoomListService.SyncIndicator.Show + when { + isOnline -> if (syncIndicator == RoomListService.SyncIndicator.Show) { + SyncIndicatorState.Syncing + } else { + SyncIndicatorState.Hidden + } + hasNetwork == NetworkStatus.Connected -> SyncIndicatorState.ServerUnreachable + else -> SyncIndicatorState.Hidden + } } } var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) } @@ -149,9 +158,9 @@ class LoggedInPresenter( else -> LocalNetworkPermissionDialog.Settings } - fun handleEvent(event: LoggedInEvents) { + fun handleEvent(event: LoggedInEvent) { when (event) { - is LoggedInEvents.CloseErrorDialog -> { + is LoggedInEvent.CloseErrorDialog -> { pusherRegistrationState.value = AsyncData.Uninitialized if (event.doNotShowAgain) { coroutineScope.launch { @@ -159,17 +168,17 @@ class LoggedInPresenter( } } } - LoggedInEvents.CheckSlidingSyncProxyAvailability -> coroutineScope.launch { + LoggedInEvent.CheckSlidingSyncProxyAvailability -> coroutineScope.launch { forceNativeSlidingSyncMigration = matrixClient.needsForcedNativeSlidingSyncMigration().getOrDefault(false) } - LoggedInEvents.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch { + LoggedInEvent.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch { // Force the logout since Native Sliding Sync is already enforced by the SDK matrixClient.logout(userInitiated = true, ignoreSdkError = true) } - LoggedInEvents.DismissLocalNetworkPermissionPrompt -> { + LoggedInEvent.DismissLocalNetworkPermissionPrompt -> { localNetworkPromptDismissedThisSession = true } - LoggedInEvents.RequestLocationNetworkPermission -> { + LoggedInEvent.RequestLocationNetworkPermission -> { if (localNetworkPermissionDialog == LocalNetworkPermissionDialog.Settings) { localNetworkPermissionState.eventSink(PermissionsEvent.OpenSystemSettingAndCloseDialog) } else { @@ -181,7 +190,7 @@ class LoggedInPresenter( } return LoggedInState( - showSyncSpinner = showSyncSpinner, + syncIndicatorState = syncIndicatorState, pusherRegistrationState = pusherRegistrationState.value, ignoreRegistrationError = ignoreRegistrationError, forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt index edc12383bf..8ac675a146 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt @@ -11,12 +11,18 @@ package io.element.android.appnav.loggedin import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog +enum class SyncIndicatorState { + Hidden, + Syncing, + ServerUnreachable, +} + data class LoggedInState( - val showSyncSpinner: Boolean, + val syncIndicatorState: SyncIndicatorState, val pusherRegistrationState: AsyncData, val ignoreRegistrationError: Boolean, val forceNativeSlidingSyncMigration: Boolean, val appName: String, val localNetworkPermissionDialog: LocalNetworkPermissionDialog, - val eventSink: (LoggedInEvents) -> Unit, + val eventSink: (LoggedInEvent) -> Unit, ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStatePreviewParam.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStatePreviewParam.kt index b479e00279..705f6fbe61 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStatePreviewParam.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStatePreviewParam.kt @@ -17,7 +17,8 @@ open class LoggedInStatePreviewParam : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aLoggedInState(), - aLoggedInState(showSyncSpinner = true), + aLoggedInState(syncIndicatorState = SyncIndicatorState.Syncing), + aLoggedInState(syncIndicatorState = SyncIndicatorState.ServerUnreachable), aLoggedInState(pusherRegistrationState = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable())), aLoggedInState(forceNativeSlidingSyncMigration = true), aLoggedInState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Rationale), @@ -26,13 +27,13 @@ open class LoggedInStatePreviewParam : PreviewParameterProvider { } fun aLoggedInState( - showSyncSpinner: Boolean = false, + syncIndicatorState: SyncIndicatorState = SyncIndicatorState.Hidden, pusherRegistrationState: AsyncData = AsyncData.Uninitialized, forceNativeSlidingSyncMigration: Boolean = false, appName: String = "Element X", localNetworkPermissionDialog: LocalNetworkPermissionDialog = LocalNetworkPermissionDialog.None, ) = LoggedInState( - showSyncSpinner = showSyncSpinner, + syncIndicatorState = syncIndicatorState, pusherRegistrationState = pusherRegistrationState, ignoreRegistrationError = false, forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt index 7e607e7d92..1d3a355663 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt @@ -37,7 +37,7 @@ fun LoggedInView( ) { OnLifecycleEvent { _, event -> if (event == Lifecycle.Event.ON_RESUME) { - state.eventSink(LoggedInEvents.CheckSlidingSyncProxyAvailability) + state.eventSink(LoggedInEvent.CheckSlidingSyncProxyAvailability) } } Box( @@ -47,7 +47,7 @@ fun LoggedInView( ) { SyncStateView( modifier = Modifier.align(Alignment.TopCenter), - isVisible = state.showSyncSpinner, + state = state.syncIndicatorState, ) } when (state.pusherRegistrationState) { @@ -63,10 +63,10 @@ fun LoggedInView( content = stringResource(id = CommonStrings.common_error_registering_pusher_android, reason), cancelText = stringResource(id = CommonStrings.common_settings), onDismiss = { - state.eventSink(LoggedInEvents.CloseErrorDialog(it)) + state.eventSink(LoggedInEvent.CloseErrorDialog(it)) }, onCancel = { - state.eventSink(LoggedInEvents.CloseErrorDialog(false)) + state.eventSink(LoggedInEvent.CloseErrorDialog(false)) navigateToNotificationTroubleshoot() } ) @@ -79,15 +79,15 @@ fun LoggedInView( ForceNativeSlidingSyncMigrationDialog( appName = state.appName, onSubmit = { - state.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync) + state.eventSink(LoggedInEvent.LogoutAndMigrateToNativeSlidingSync) } ) } LocalNetworkPermissionDialogView( dialog = state.localNetworkPermissionDialog, - onSubmit = { state.eventSink(LoggedInEvents.RequestLocationNetworkPermission) }, - onDismiss = { state.eventSink(LoggedInEvents.DismissLocalNetworkPermissionPrompt) }, + onSubmit = { state.eventSink(LoggedInEvent.RequestLocationNetworkPermission) }, + onDismiss = { state.eventSink(LoggedInEvent.DismissLocalNetworkPermissionPrompt) }, ) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt index 7c5f196a4c..db39658ad4 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt @@ -25,18 +25,23 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun SyncStateView( - isVisible: Boolean, + state: SyncIndicatorState, modifier: Modifier = Modifier ) { AnimatedVisibility( - visible = isVisible, + visible = state != SyncIndicatorState.Hidden, modifier = modifier, enter = fadeIn(spring(stiffness = 500F)), exit = fadeOut(spring(stiffness = 500F)), ) { - AsyncIndicator.Loading( - text = stringResource(id = CommonStrings.common_syncing), - ) + when (state) { + SyncIndicatorState.ServerUnreachable -> AsyncIndicator.Failure( + text = stringResource(id = CommonStrings.common_server_unreachable), + ) + else -> AsyncIndicator.Loading( + text = stringResource(id = CommonStrings.common_syncing), + ) + } } } @@ -46,7 +51,18 @@ internal fun SyncStateViewPreview() = ElementPreview { // Add a box to see the shadow Box(modifier = Modifier.padding(24.dp)) { SyncStateView( - isVisible = true + state = SyncIndicatorState.Syncing + ) + } +} + +@PreviewsDayNight +@Composable +internal fun SyncStateViewServerUnreachablePreview() = ElementPreview { + // Add a box to see the shadow + Box(modifier = Modifier.padding(24.dp)) { + SyncStateView( + state = SyncIndicatorState.ServerUnreachable ) } } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 0e2d459d1e..474253c8df 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -67,7 +67,7 @@ class LoggedInPresenterTest { fun `present - initial state`() = runTest { createLoggedInPresenter().test { val initialState = awaitItem() - assertThat(initialState.showSyncSpinner).isFalse() + assertThat(initialState.syncIndicatorState).isEqualTo(SyncIndicatorState.Hidden) assertThat(initialState.pusherRegistrationState.isUninitialized()).isTrue() assertThat(initialState.ignoreRegistrationError).isFalse() } @@ -97,11 +97,33 @@ class LoggedInPresenterTest { matrixClient = FakeMatrixClient(roomListService = roomListService), ).test { val initialState = awaitItem() - assertThat(initialState.showSyncSpinner).isFalse() + assertThat(initialState.syncIndicatorState).isEqualTo(SyncIndicatorState.Hidden) roomListService.postSyncIndicator(RoomListService.SyncIndicator.Show) - consumeItemsUntilPredicate { it.showSyncSpinner } + consumeItemsUntilPredicate { it.syncIndicatorState == SyncIndicatorState.Syncing } roomListService.postSyncIndicator(RoomListService.SyncIndicator.Hide) - consumeItemsUntilPredicate { !it.showSyncSpinner } + consumeItemsUntilPredicate { it.syncIndicatorState != SyncIndicatorState.Syncing } + } + } + + @Test + fun `present - say the server is unreachable when the device has network but the sync is offline`() = runTest { + createLoggedInPresenter( + syncState = SyncState.Offline, + networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected), + ).test { + consumeItemsUntilPredicate { it.syncIndicatorState == SyncIndicatorState.ServerUnreachable } + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun `present - say nothing when the device itself has no network`() = runTest { + createLoggedInPresenter( + syncState = SyncState.Offline, + networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Disconnected), + ).test { + consumeItemsUntilPredicate { it.syncIndicatorState == SyncIndicatorState.Hidden } + cancelAndConsumeRemainingEvents() } } @@ -210,7 +232,7 @@ class LoggedInPresenterTest { lambda.assertions() .isCalledOnce() // Reset the error and do not show again - finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = false)) + finalState.eventSink(LoggedInEvent.CloseErrorDialog(doNotShowAgain = false)) val lastState = awaitItem() assertThat(lastState.pusherRegistrationState.isUninitialized()).isTrue() assertThat(lastState.ignoreRegistrationError).isFalse() @@ -240,7 +262,7 @@ class LoggedInPresenterTest { lambda.assertions() .isCalledOnce() // Reset the error and do not show again - finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = true)) + finalState.eventSink(LoggedInEvent.CloseErrorDialog(doNotShowAgain = true)) skipItems(1) setIgnoreRegistrationErrorLambda.assertions() .isCalledOnce() @@ -296,7 +318,7 @@ class LoggedInPresenterTest { ).test { val initialState = awaitItem() assertThat(initialState.forceNativeSlidingSyncMigration).isFalse() - initialState.eventSink(LoggedInEvents.CheckSlidingSyncProxyAvailability) + initialState.eventSink(LoggedInEvent.CheckSlidingSyncProxyAvailability) assertThat(awaitItem().forceNativeSlidingSyncMigration).isTrue() } } @@ -318,7 +340,7 @@ class LoggedInPresenterTest { ).test { val initialState = awaitItem() - initialState.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync) + initialState.eventSink(LoggedInEvent.LogoutAndMigrateToNativeSlidingSync) advanceUntilIdle() diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index e7515eb466..01ad915126 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -322,7 +322,7 @@ Example for a new feature called RoomDetails: 4. Verify that the structure looks ok and click on Finish 5. The modules api/impl should be created under `features/roomdetails` directory. 6. Sync project with Gradle so the modules are recognized (no need to add them to settings.gradle). -7. You can now add more Presentation classes (Events, State, StatePreviewParam, View, Presenter) in the impl module with the `Template Presentation Classes`. +7. You can now add more Presentation classes (Event, State, StatePreviewParam, View, Presenter) in the impl module with the `Template Presentation Classes`. To use it, just right click on the package where you want to generate classes, and click on `New` / `Template Presentation Classes`. Fill the text field with the base name of the classes: the generated files are `Event.kt`, `State.kt`, `StatePreviewParam.kt`, `Presenter.kt`, `Node.kt` and `View.kt`. For instance diff --git a/docs/debug_proxying.md b/docs/debug_proxying.md index a28163c959..f76c24eecb 100644 --- a/docs/debug_proxying.md +++ b/docs/debug_proxying.md @@ -9,13 +9,18 @@ 1) Set `127.0.0.1` as "Host name" and `8080` as "Port number". 1) Click "Apply" and verify that "Proxy status" is "Success" and close the settings window. Screenshot 2023-10-04 at 14 48 47 + 1) Alternatively, set the proxy on the device itself, which the app honours through `DefaultProxyProvider`, and which also works on a physical device: + ``` + adb shell settings put global http_proxy 10.0.2.2:8080 + ``` + Use `adb shell settings delete global http_proxy` to remove it afterwards. On a physical device, replace `10.0.2.2` with the address of the machine running mitmproxy. 1) Install the mitmproxy CA cert (this is needed to see traffic from java/kotlin code, it's not needed for traffic coming from native code e.g. the matrix-rust-sdk). 1) Open the emulator Chrome browser app 1) Go to the url `mitm.it` 1) Follow the instructions to install the CA cert on Android devices. Screenshot 2023-10-04 at 14 51 27 1) Slightly modify the Element X app source code. - 1) Go to the `RustMatrixClientFactory.create()` method. + 1) Go to the `RustMatrixClientFactory.getBaseClientBuilder()` method, which builds every client the app creates, whether it is logging in or restoring a session. 1) Add `.disableSslVerification()` in the `ClientBuilder` method chain. 1) Build and run the Element X app. 1) Enjoy, you will see all the traffic in mitmproxy's web interface. diff --git a/docs/element_call.md b/docs/element_call.md new file mode 100644 index 0000000000..e4bd46e2cb --- /dev/null +++ b/docs/element_call.md @@ -0,0 +1,53 @@ +# Element Call + + + +* [Overview](#overview) +* [Setting a custom base URL](#setting-a-custom-base-url) +* [Why the application does not add the room path itself](#why-the-application-does-not-add-the-room-path-itself) +* [Where the value is read](#where-the-value-is-read) + + + +## Overview + +Element X embeds a copy of [Element Call](https://github.com/element-hq/element-call) in the +application assets and loads it from +`https://appassets.androidplatform.net/element-call/index.html`. Calls therefore work out of the box +with no extra deployment, and the embedded copy is the one every user gets. + +Developers can point the application at another Element Call deployment instead — a local +development server, a staging deployment, or a self-hosted one — with the **Element Call base URL** +developer option. + +## Setting a custom base URL + +The option lives in *Settings* → *Developer options* → *Element Call*, under "Element Call base +URL". On a release build the *Developer options* entry is hidden until the version number at the +bottom of *Settings* is tapped seven times. Leaving the field empty restores the embedded copy. + +The value is used verbatim as the widget base URL, so it must be the URL of the page that joins a +room, not the URL of the deployment root. For the public deployments that means: + +| Deployment | Value to enter | +|---|---| +| `call.element.io` | `https://call.element.io/room` | +| `call.element.dev` | `https://call.element.dev/room` | + +Entering `https://call.element.io` without the `/room` suffix loads the Element Call lobby rather +than the room, so the call is never joined. + +## Why the application does not add the room path itself + +The path that joins a room is a property of the deployment, not of Element Call. Unpacking the +Element Call release tarball behind a reverse proxy can put it at any path — `https://call.foo.tld`, +`https://call.foo.tld/some/path/somewhere` — and a proxy that already maps a subdomain onto +`https://1.2.3.4/room` would end up with the suffix twice. The application passes the value through +unchanged so that every one of those deployments can be reached. + +## Where the value is read + +`AppDeveloperSettingsView` renders the field and sends +`AppDeveloperSettingsEvent.SetCustomElementCallBaseUrl`, which `DefaultAppPreferencesStore` persists. +`DefaultCallWidgetProvider.getWidget` reads it back and falls back to the embedded copy when it is +unset. diff --git a/docs/install_from_github_release.md b/docs/install_from_github_release.md index 5ac27ff812..1fd963e953 100644 --- a/docs/install_from_github_release.md +++ b/docs/install_from_github_release.md @@ -64,16 +64,16 @@ brew install bundletool ### Steps 1. Open the GitHub release that you want to install from https://github.com/element-hq/element-x-android/releases -2. Download the asset `app-release-signed.aab` +2. Download the asset `app-gplay-release-signed.aab` 3. Navigate to the folder where you cloned the project and run the following command: ```bash -bundletool build-apks --bundle= --output=./tmp/elementx.apks \ +bundletool build-apks --bundle= --output=./tmp/elementx.apks \ --ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \ --overwrite ``` For instance: ```bash -bundletool build-apks --bundle=./tmp/Element/0.1.5/app-release-signed.aab --output=./tmp/elementx.apks \ +bundletool build-apks --bundle=./tmp/Element/26.08.3/app-gplay-release-signed.aab --output=./tmp/elementx.apks \ --ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \ --overwrite ``` diff --git a/enterprise b/enterprise index e633a343b1..53750903f2 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit e633a343b12088b1fe3fd6b27ec319c1f7f2931c +Subproject commit 53750903f229a410ec4dded2bb6357431a55662e diff --git a/fastlane/metadata/android/en-US/changelogs/202608030.txt b/fastlane/metadata/android/en-US/changelogs/202608030.txt new file mode 100644 index 0000000000..a4b397f1bb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202608030.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/fastlane/metadata/android/en-US/changelogs/202608040.txt b/fastlane/metadata/android/en-US/changelogs/202608040.txt new file mode 100644 index 0000000000..a4b397f1bb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202608040.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/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvent.kt similarity index 86% rename from features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt rename to features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvent.kt index 4181f52ba8..6095bc67da 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.analytics.api -sealed interface AnalyticsOptInEvents { - data class EnableAnalytics(val isEnabled: Boolean) : AnalyticsOptInEvents +sealed interface AnalyticsOptInEvent { + data class EnableAnalytics(val isEnabled: Boolean) : AnalyticsOptInEvent } diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt index 20a180446c..b5f049460d 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt @@ -8,11 +8,11 @@ package io.element.android.features.analytics.api.preferences -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent data class AnalyticsPreferencesState( val applicationName: String, val isEnabled: Boolean, val policyUrl: String, - val eventSink: (AnalyticsOptInEvents) -> Unit, + val eventSink: (AnalyticsOptInEvent) -> Unit, ) diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 0d141e5ed2..31891a0162 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -13,7 +13,7 @@ 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.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent import io.element.android.features.analytics.api.R import io.element.android.libraries.designsystem.components.LINK_TAG import io.element.android.libraries.designsystem.components.list.ListItemContent @@ -30,7 +30,7 @@ fun AnalyticsPreferencesView( modifier: Modifier = Modifier, ) { fun onEnabledChanged(isEnabled: Boolean) { - state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) + state.eventSink(AnalyticsOptInEvent.EnableAnalytics(isEnabled = isEnabled)) } val supportingText = stringResource( diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt index d7bdb85e35..05d2c2fefd 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt @@ -12,7 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Inject import io.element.android.appconfig.AnalyticsConfig -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.services.analytics.api.AnalyticsService @@ -28,9 +28,9 @@ class AnalyticsOptInPresenter( override fun present(): AnalyticsOptInState { val localCoroutineScope = rememberCoroutineScope() - fun handleEvent(event: AnalyticsOptInEvents) { + fun handleEvent(event: AnalyticsOptInEvent) { when (event) { - is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) + is AnalyticsOptInEvent.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) } localCoroutineScope.launch { analyticsService.setDidAskUserConsent() diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt index b2ebd37e20..063ed316c8 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt @@ -8,10 +8,10 @@ package io.element.android.features.analytics.impl -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent data class AnalyticsOptInState( val applicationName: String, val hasPolicyLink: Boolean, - val eventSink: (AnalyticsOptInEvents) -> Unit + val eventSink: (AnalyticsOptInEvent) -> Unit ) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index 678d28c5c9..3ead26ea48 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -28,7 +28,7 @@ import androidx.compose.ui.unit.dp import io.element.android.appconfig.AnalyticsConfig import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage @@ -55,11 +55,11 @@ fun AnalyticsOptInView( val eventSink = state.eventSink fun onAcceptTerms() { - eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) + eventSink(AnalyticsOptInEvent.EnableAnalytics(true)) } fun onDeclineTerms() { - eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) + eventSink(AnalyticsOptInEvent.EnableAnalytics(false)) } BackHandler(onBack = ::onDeclineTerms) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt index 110cb98d41..ca3388ee3c 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt @@ -13,7 +13,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Inject import io.element.android.appconfig.AnalyticsConfig -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta @@ -31,9 +31,9 @@ class AnalyticsPreferencesPresenter( val localCoroutineScope = rememberCoroutineScope() val isEnabled = analyticsService.userConsentFlow.collectAsState(initial = false) - fun handleEvent(event: AnalyticsOptInEvents) { + fun handleEvent(event: AnalyticsOptInEvent) { when (event) { - is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) + is AnalyticsOptInEvent.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) } } diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt index 6521c7f597..f2093dfa72 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -12,7 +12,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule @@ -37,7 +37,7 @@ class AnalyticsOptInPresenterTest { }.test { val initialState = awaitItem() assertThat(analyticsService.didAskUserConsentFlow.first()).isFalse() - initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true)) + initialState.eventSink.invoke(AnalyticsOptInEvent.EnableAnalytics(true)) assertThat(analyticsService.didAskUserConsentFlow.first()).isTrue() assertThat(analyticsService.userConsentFlow.first()).isTrue() } @@ -55,7 +55,7 @@ class AnalyticsOptInPresenterTest { }.test { val initialState = awaitItem() assertThat(analyticsService.didAskUserConsentFlow.first()).isFalse() - initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false)) + initialState.eventSink.invoke(AnalyticsOptInEvent.EnableAnalytics(false)) assertThat(analyticsService.didAskUserConsentFlow.first()).isTrue() assertThat(analyticsService.userConsentFlow.first()).isFalse() } diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt index 174935a1ad..29f7d9af03 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenterTest.kt @@ -13,7 +13,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.AnalyticsConfig -import io.element.android.features.analytics.api.AnalyticsOptInEvents +import io.element.android.features.analytics.api.AnalyticsOptInEvent import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule @@ -67,9 +67,9 @@ class AnalyticsPreferencesPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.isEnabled).isTrue() - initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false)) + initialState.eventSink.invoke(AnalyticsOptInEvent.EnableAnalytics(false)) assertThat(awaitItem().isEnabled).isFalse() - initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true)) + initialState.eventSink.invoke(AnalyticsOptInEvent.EnableAnalytics(true)) assertThat(awaitItem().isEnabled).isTrue() } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt index f9075691aa..b23226b9c0 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.createroom.impl.R -import io.element.android.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.features.invitepeople.api.InvitePeopleState import io.element.android.features.invitepeople.api.InvitePeopleStatePreviewParam import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage @@ -56,7 +56,7 @@ fun AddPeopleView( Button( text = stringResource(CommonStrings.action_finish), onClick = { - state.eventSink(InvitePeopleEvents.SendInvites) + state.eventSink(InvitePeopleEvent.SendInvites) }, enabled = state.canInvite, modifier = Modifier diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvent.kt similarity index 78% rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt rename to features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvent.kt index 5de99e519d..e22e08a5b0 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvent.kt @@ -11,13 +11,13 @@ package io.element.android.features.createroom.impl.configureroom import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.media.AvatarAction -sealed interface ConfigureRoomEvents { - data class RoomNameChanged(val name: String) : ConfigureRoomEvents - data class TopicChanged(val topic: String) : ConfigureRoomEvents - data class JoinRuleChanged(val joinRuleItem: JoinRuleItem) : ConfigureRoomEvents - data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents - data object CreateRoom : ConfigureRoomEvents - data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents - data class SetParentSpace(val space: SpaceRoom?) : ConfigureRoomEvents - data object CancelCreateRoom : ConfigureRoomEvents +sealed interface ConfigureRoomEvent { + data class RoomNameChanged(val name: String) : ConfigureRoomEvent + data class TopicChanged(val topic: String) : ConfigureRoomEvent + data class JoinRuleChanged(val joinRuleItem: JoinRuleItem) : ConfigureRoomEvent + data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvent + data object CreateRoom : ConfigureRoomEvent + data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvent + data class SetParentSpace(val space: SpaceRoom?) : ConfigureRoomEvent + data object CancelCreateRoom : ConfigureRoomEvent } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index abedc2bd49..849647a9d3 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -188,14 +188,14 @@ class ConfigureRoomPresenter( localCoroutineScope.createRoom(config, createRoomAction) } - fun handleEvent(event: ConfigureRoomEvents) { + fun handleEvent(event: ConfigureRoomEvent) { when (event) { - is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name) - is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic) - is ConfigureRoomEvents.JoinRuleChanged -> dataStore.setJoinRule(event.joinRuleItem) - is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress) - is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig) - is ConfigureRoomEvents.HandleAvatarAction -> { + is ConfigureRoomEvent.RoomNameChanged -> dataStore.setRoomName(event.name) + is ConfigureRoomEvent.TopicChanged -> dataStore.setTopic(event.topic) + is ConfigureRoomEvent.JoinRuleChanged -> dataStore.setJoinRule(event.joinRuleItem) + is ConfigureRoomEvent.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress) + is ConfigureRoomEvent.CreateRoom -> createRoom(createRoomConfig) + is ConfigureRoomEvent.HandleAvatarAction -> { when (event.action) { AvatarAction.ChoosePhoto -> galleryImagePicker.launch() AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) { @@ -207,10 +207,10 @@ class ConfigureRoomPresenter( AvatarAction.Remove -> dataStore.setAvatarUri(uri = null) } } - is ConfigureRoomEvents.SetParentSpace -> { + is ConfigureRoomEvent.SetParentSpace -> { dataStore.setParentSpace(event.space, false) } - ConfigureRoomEvents.CancelCreateRoom -> { + ConfigureRoomEvent.CancelCreateRoom -> { createRoomAction.value = AsyncAction.Uninitialized } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt index 0da931fa51..15f549d28f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt @@ -26,7 +26,7 @@ data class ConfigureRoomState( val homeserverName: String, val availableJoinRules: ImmutableList, val spaces: ImmutableList, - val eventSink: (ConfigureRoomEvents) -> Unit + val eventSink: (ConfigureRoomEvent) -> Unit ) { val isValid: Boolean = config.roomName?.isNotEmpty() == true && (config.visibilityState is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid) && diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStatePreviewParam.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStatePreviewParam.kt index 151c7d13d0..725cfc2309 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStatePreviewParam.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStatePreviewParam.kt @@ -143,7 +143,7 @@ fun aConfigureRoomState( ) }, spaces: List = emptyList(), - eventSink: (ConfigureRoomEvents) -> Unit = { }, + eventSink: (ConfigureRoomEvent) -> Unit = { }, ) = ConfigureRoomState( config = config, isSpace = isSpace, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt index e39df69f97..fec820ffbb 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt @@ -96,7 +96,7 @@ fun ConfigureRoomView( onBackClick = onBackClick, onNextClick = { focusManager.clearFocus() - state.eventSink(ConfigureRoomEvents.CreateRoom) + state.eventSink(ConfigureRoomEvent.CreateRoom) }, ) } @@ -114,20 +114,20 @@ fun ConfigureRoomView( avatarUri = state.config.avatarUri, roomName = state.config.roomName.orEmpty(), onAvatarClick = ::onAvatarClick, - onChangeRoomName = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) }, + onChangeRoomName = { state.eventSink(ConfigureRoomEvent.RoomNameChanged(it)) }, ) Spacer(modifier = Modifier.height(16.dp)) RoomTopic( modifier = Modifier.padding(horizontal = 16.dp), topic = state.config.topic.orEmpty(), - onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) }, + onTopicChange = { state.eventSink(ConfigureRoomEvent.TopicChanged(it)) }, ) Spacer(modifier = Modifier.height(16.dp)) if (!state.isSpace && state.spaces.isNotEmpty()) { SelectParentSpaceOptions( spaces = state.spaces, selectedSpace = state.config.parentSpace, - onSelectSpace = { state.eventSink(ConfigureRoomEvents.SetParentSpace(it)) }, + onSelectSpace = { state.eventSink(ConfigureRoomEvent.SetParentSpace(it)) }, ) } RoomJoinRuleOptions( @@ -136,7 +136,7 @@ fun ConfigureRoomView( parentSpace = state.config.parentSpace, onOptionClick = { focusManager.clearFocus() - state.eventSink(ConfigureRoomEvents.JoinRuleChanged(it)) + state.eventSink(ConfigureRoomEvent.JoinRuleChanged(it)) }, ) if (state.config.visibilityState !is RoomVisibilityState.Private) { @@ -146,7 +146,7 @@ fun ConfigureRoomView( address = state.config.visibilityState.roomAddress().getOrNull().orEmpty(), homeserverName = state.homeserverName, addressValidity = state.roomAddressValidity, - onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) }, + onAddressChange = { state.eventSink(ConfigureRoomEvent.RoomAddressChanged(it)) }, label = null, supportingText = stringResource(R.string.screen_create_room_room_address_section_footer), ) @@ -158,7 +158,7 @@ fun ConfigureRoomView( actions = state.avatarActions, isVisible = isAvatarActionsSheetVisible.value, onDismiss = { isAvatarActionsSheetVisible.value = false }, - onSelectAction = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) } + onSelectAction = { state.eventSink(ConfigureRoomEvent.HandleAvatarAction(it)) } ) AsyncActionView( @@ -170,8 +170,8 @@ fun ConfigureRoomView( }, onSuccess = { onCreateRoomSuccess(it) }, errorMessage = { stringResource(if (isSpace) R.string.screen_create_room_error_creating_space else R.string.screen_create_room_error_creating_room) }, - onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom) }, - onErrorDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) }, + onRetry = { state.eventSink(ConfigureRoomEvent.CreateRoom) }, + onErrorDismiss = { state.eventSink(ConfigureRoomEvent.CancelCreateRoom) }, ) PermissionsView( diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt index 046d00a5c6..09afd38f90 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt @@ -12,7 +12,7 @@ import androidx.core.net.toUri import app.cash.turbine.TurbineTestContext import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CreatedRoom -import io.element.android.features.createroom.impl.configureroom.ConfigureRoomEvents +import io.element.android.features.createroom.impl.configureroom.ConfigureRoomEvent import io.element.android.features.createroom.impl.configureroom.ConfigureRoomPresenter import io.element.android.features.createroom.impl.configureroom.ConfigureRoomState import io.element.android.features.createroom.impl.configureroom.CreateRoomConfig @@ -106,14 +106,14 @@ class ConfigureRoomPresenterTest : RobolectricTest() { assertThat(initialState.isValid).isFalse() // Room name not empty - initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME)) + initialState.eventSink(ConfigureRoomEvent.RoomNameChanged(A_ROOM_NAME)) var newState: ConfigureRoomState = awaitItem() config = config.copy(roomName = A_ROOM_NAME) assertThat(newState.config).isEqualTo(config) assertThat(newState.isValid).isTrue() // Clear room name - newState.eventSink(ConfigureRoomEvents.RoomNameChanged("")) + newState.eventSink(ConfigureRoomEvent.RoomNameChanged("")) newState = awaitItem() config = config.copy(roomName = null) assertThat(newState.config).isEqualTo(config) @@ -136,13 +136,13 @@ class ConfigureRoomPresenterTest : RobolectricTest() { var expectedConfig = CreateRoomConfig() assertThat(initialState.config).isEqualTo(expectedConfig) // Room name - initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME)) + initialState.eventSink(ConfigureRoomEvent.RoomNameChanged(A_ROOM_NAME)) var newState = awaitItem() expectedConfig = expectedConfig.copy(roomName = A_ROOM_NAME) assertThat(newState.config).isEqualTo(expectedConfig) // Room topic - newState.eventSink(ConfigureRoomEvents.TopicChanged(A_MESSAGE)) + newState.eventSink(ConfigureRoomEvent.TopicChanged(A_MESSAGE)) newState = awaitItem() expectedConfig = expectedConfig.copy(topic = A_MESSAGE) assertThat(newState.config).isEqualTo(expectedConfig) @@ -153,7 +153,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { // From gallery val uriFromGallery = AN_URI_FROM_GALLERY pickerProvider.givenResult(uriFromGallery.toUri()) - newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + newState.eventSink(ConfigureRoomEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) newState = awaitItem() expectedConfig = expectedConfig.copy(avatarUri = uriFromGallery) assertThat(newState.config).isEqualTo(expectedConfig) @@ -161,7 +161,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { val uriFromCamera = AN_URI_FROM_CAMERA pickerProvider.givenResult(uriFromCamera.toUri()) assertThat(newState.cameraPermissionState.permissionGranted).isFalse() - newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + newState.eventSink(ConfigureRoomEvent.HandleAvatarAction(AvatarAction.TakePhoto)) newState = awaitItem() assertThat(newState.cameraPermissionState.showDialog).isTrue() permissionsPresenter.setPermissionGranted() @@ -173,18 +173,18 @@ class ConfigureRoomPresenterTest : RobolectricTest() { // Do it again, no permission is requested val uriFromCamera2 = AN_URI_FROM_CAMERA_2 pickerProvider.givenResult(uriFromCamera2.toUri()) - newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + newState.eventSink(ConfigureRoomEvent.HandleAvatarAction(AvatarAction.TakePhoto)) newState = awaitItem() expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera2) assertThat(newState.config).isEqualTo(expectedConfig) // Remove - newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.Remove)) + newState.eventSink(ConfigureRoomEvent.HandleAvatarAction(AvatarAction.Remove)) newState = awaitItem() expectedConfig = expectedConfig.copy(avatarUri = null) assertThat(newState.config).isEqualTo(expectedConfig) // Room privacy - newState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + newState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) newState = awaitItem() expectedConfig = expectedConfig.copy( visibilityState = RoomVisibilityState.Public( @@ -208,7 +208,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { matrixClient.givenCreateRoomResult(createRoomResult) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Success::class.java) @@ -239,13 +239,13 @@ class ConfigureRoomPresenterTest : RobolectricTest() { // Use a public parent space so AskToJoin is a valid option val parentSpace = aSpaceRoom(joinRule = JoinRule.Public) - initialState.eventSink(ConfigureRoomEvents.SetParentSpace(parentSpace)) + initialState.eventSink(ConfigureRoomEvent.SetParentSpace(parentSpace)) assertThat(awaitItem().config.parentSpace).isEqualTo(parentSpace) - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.AskToJoin)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.AskToJoin)) assertThat(awaitItem().config.visibilityState.joinRuleItem).isEqualTo(JoinRuleItem.PublicVisibility.AskToJoin) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() @@ -282,13 +282,13 @@ class ConfigureRoomPresenterTest : RobolectricTest() { // Use a public parent space so AskToJoin is a valid option val parentSpace = aSpaceRoom(joinRule = JoinRule.Public) - initialState.eventSink(ConfigureRoomEvents.SetParentSpace(parentSpace)) + initialState.eventSink(ConfigureRoomEvent.SetParentSpace(parentSpace)) assertThat(awaitItem().config.parentSpace).isEqualTo(parentSpace) - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.AskToJoin)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.AskToJoin)) assertThat(awaitItem().config.visibilityState.joinRuleItem).isEqualTo(JoinRuleItem.PublicVisibility.AskToJoin) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) // We immediately receive the room power levels info needed for adding the child to a space val powerLevels = RoomPowerLevels( @@ -339,7 +339,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { matrixClient.givenCreateRoomResult(createRoomResult) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) skipItems(2) val analyticsEvent = analyticsService.capturedEvents.filterIsInstance().firstOrNull() @@ -369,14 +369,14 @@ class ConfigureRoomPresenterTest : RobolectricTest() { mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(file, mockk(), mockk()))) matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION)) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat(analyticsService.capturedEvents.filterIsInstance()).isEmpty() matrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL)) - stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom) + stateAfterCreateRoom.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Success::class.java) @@ -399,14 +399,14 @@ class ConfigureRoomPresenterTest : RobolectricTest() { fakeMatrixClient.givenCreateRoomResult(createRoomResult) // Create - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterCreateRoom = awaitItem() assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat((stateAfterCreateRoom.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) // Retry - stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom) + stateAfterCreateRoom.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) val stateAfterRetry = awaitItem() @@ -414,7 +414,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { assertThat((stateAfterRetry.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull()) // Cancel - stateAfterRetry.eventSink(ConfigureRoomEvents.CancelCreateRoom) + stateAfterRetry.eventSink(ConfigureRoomEvent.CancelCreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -430,9 +430,9 @@ class ConfigureRoomPresenterTest : RobolectricTest() { ) presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) - initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("invalid address")) + initialState.eventSink(ConfigureRoomEvent.RoomAddressChanged("invalid address")) skipItems(1) advanceUntilIdle() awaitItem().also { state -> @@ -450,9 +450,9 @@ class ConfigureRoomPresenterTest : RobolectricTest() { ) presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) - initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address")) + initialState.eventSink(ConfigureRoomEvent.RoomAddressChanged("address")) skipItems(1) advanceUntilIdle() awaitItem().also { state -> @@ -470,9 +470,9 @@ class ConfigureRoomPresenterTest : RobolectricTest() { ) presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) - initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address")) + initialState.eventSink(ConfigureRoomEvent.RoomAddressChanged("address")) skipItems(1) advanceUntilIdle() awaitItem().also { state -> @@ -488,22 +488,22 @@ class ConfigureRoomPresenterTest : RobolectricTest() { val initialState = initialState() // First change the join rule to public - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) assertThat(awaitItem().config.visibilityState).isInstanceOf(RoomVisibilityState.Public::class.java) // Then check changing the parent space resets it to private // (via LaunchedEffect fallback since Public is not in availableJoinRules for non-public parent) - initialState.eventSink(ConfigureRoomEvents.SetParentSpace(aSpaceRoom())) + initialState.eventSink(ConfigureRoomEvent.SetParentSpace(aSpaceRoom())) skipItems(1) // Skip intermediate state assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private)) // If we change the join rule back to public - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) // Skip intermediate state (Public is still invalid) assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private)) // Then remove the parent space, the join rule stays private - initialState.eventSink(ConfigureRoomEvents.SetParentSpace(null)) + initialState.eventSink(ConfigureRoomEvent.SetParentSpace(null)) assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private)) } } @@ -514,7 +514,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.SetParentSpace(aSpaceRoom())) + initialState.eventSink(ConfigureRoomEvent.SetParentSpace(aSpaceRoom())) assertThat(awaitError()) } @@ -534,8 +534,8 @@ class ConfigureRoomPresenterTest : RobolectricTest() { presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PrivateVisibility.Private)) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) + initialState.eventSink(ConfigureRoomEvent.JoinRuleChanged(JoinRuleItem.PrivateVisibility.Private)) + initialState.eventSink(ConfigureRoomEvent.CreateRoom) assertThat(awaitItem().createRoomAction.isLoading()).isTrue() assertThat(awaitItem().createRoomAction.isSuccess()).isTrue() diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvent.kt similarity index 76% rename from features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt rename to features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvent.kt index 5ceb97b232..cc1f5093fd 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.logout.impl -sealed interface AccountDeactivationEvents { - data class SetEraseData(val eraseData: Boolean) : AccountDeactivationEvents - data class SetPassword(val password: String) : AccountDeactivationEvents - data class DeactivateAccount(val isRetry: Boolean) : AccountDeactivationEvents - data object CloseDialogs : AccountDeactivationEvents +sealed interface AccountDeactivationEvent { + data class SetEraseData(val eraseData: Boolean) : AccountDeactivationEvent + data class SetPassword(val password: String) : AccountDeactivationEvent + data class DeactivateAccount(val isRetry: Boolean) : AccountDeactivationEvent + data object CloseDialogs : AccountDeactivationEvent } diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt index eaeacd022c..11e658aa7f 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt @@ -34,19 +34,19 @@ class AccountDeactivationPresenter( val formState = remember { mutableStateOf(DeactivateFormState.Default) } - fun handleEvent(event: AccountDeactivationEvents) { + fun handleEvent(event: AccountDeactivationEvent) { when (event) { - is AccountDeactivationEvents.SetEraseData -> { + is AccountDeactivationEvent.SetEraseData -> { updateFormState(formState) { copy(eraseData = event.eraseData) } } - is AccountDeactivationEvents.SetPassword -> { + is AccountDeactivationEvent.SetPassword -> { updateFormState(formState) { copy(password = event.password) } } - is AccountDeactivationEvents.DeactivateAccount -> + is AccountDeactivationEvent.DeactivateAccount -> if (action.value.isConfirming() || event.isRetry) { localCoroutineScope.deactivateAccount( formState = formState.value, @@ -55,7 +55,7 @@ class AccountDeactivationPresenter( } else { action.value = AsyncAction.ConfirmingNoParams } - AccountDeactivationEvents.CloseDialogs -> { + AccountDeactivationEvent.CloseDialogs -> { action.value = AsyncAction.Uninitialized } } diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt index ca9751efcc..ebf9fec3c4 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt @@ -15,7 +15,7 @@ import kotlinx.parcelize.Parcelize data class AccountDeactivationState( val deactivateFormState: DeactivateFormState, val accountDeactivationAction: AsyncAction, - val eventSink: (AccountDeactivationEvents) -> Unit, + val eventSink: (AccountDeactivationEvent) -> Unit, ) { val submitEnabled: Boolean get() = accountDeactivationAction is AsyncAction.Uninitialized && diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStatePreviewParam.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStatePreviewParam.kt index bd86af9572..b5ab948b05 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStatePreviewParam.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStatePreviewParam.kt @@ -45,7 +45,7 @@ internal fun aDeactivateFormState( internal fun anAccountDeactivationState( deactivateFormState: DeactivateFormState = aDeactivateFormState(), accountDeactivationAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (AccountDeactivationEvents) -> Unit = {}, + eventSink: (AccountDeactivationEvent) -> Unit = {}, ) = AccountDeactivationState( deactivateFormState = deactivateFormState, accountDeactivationAction = accountDeactivationAction, diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt index eb6b401667..d86eae41ff 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt @@ -102,14 +102,14 @@ fun AccountDeactivationView( Content( state = state, onSubmitClick = { - eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) } ) Spacer(modifier = Modifier.height(32.dp)) Buttons( state = state, onSubmitClick = { - eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) } ) } @@ -117,13 +117,13 @@ fun AccountDeactivationView( AccountDeactivationActionDialog( state.accountDeactivationAction, onConfirmClick = { - eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) }, onRetryClick = { - eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = true)) + eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = true)) }, onDismissDialog = { - eventSink(AccountDeactivationEvents.CloseDialogs) + eventSink(AccountDeactivationEvent.CloseDialogs) }, ) } @@ -239,7 +239,7 @@ private fun Content( headline = stringResource(R.string.screen_deactivate_account_delete_all_messages), value = eraseData, onChange = { - eventSink(AccountDeactivationEvents.SetEraseData(it)) + eventSink(AccountDeactivationEvent.SetEraseData(it)) }, enabled = !isLoading, ) @@ -275,7 +275,7 @@ private fun Content( onValueChange = { val sanitized = it.sanitize() passwordFieldState = sanitized - eventSink(AccountDeactivationEvents.SetPassword(sanitized)) + eventSink(AccountDeactivationEvent.SetPassword(sanitized)) }, placeholder = stringResource(CommonStrings.common_password), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt index ee7f8e470d..d6402c2f59 100644 --- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt +++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt @@ -47,11 +47,11 @@ class AccountDeactivationPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.deactivateFormState).isEqualTo(DeactivateFormState.Default) - initialState.eventSink(AccountDeactivationEvents.SetEraseData(true)) + initialState.eventSink(AccountDeactivationEvent.SetEraseData(true)) val updatedState = awaitItem() assertThat(updatedState.deactivateFormState).isEqualTo(DeactivateFormState.Default.copy(eraseData = true)) assertThat(updatedState.submitEnabled).isFalse() - updatedState.eventSink(AccountDeactivationEvents.SetPassword("password")) + updatedState.eventSink(AccountDeactivationEvent.SetPassword("password")) val updatedState2 = awaitItem() assertThat(updatedState2.deactivateFormState).isEqualTo(DeactivateFormState(password = "password", eraseData = true)) assertThat(updatedState2.submitEnabled).isTrue() @@ -71,12 +71,12 @@ class AccountDeactivationPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(AccountDeactivationEvents.SetPassword("password")) + initialState.eventSink(AccountDeactivationEvent.SetPassword("password")) skipItems(1) - initialState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + initialState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) val updatedState = awaitItem() assertThat(updatedState.accountDeactivationAction).isEqualTo(AsyncAction.ConfirmingNoParams) - updatedState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + updatedState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) val updatedState2 = awaitItem() assertThat(updatedState2.accountDeactivationAction).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() @@ -98,20 +98,20 @@ class AccountDeactivationPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(AccountDeactivationEvents.SetPassword("password")) - initialState.eventSink(AccountDeactivationEvents.SetEraseData(true)) + initialState.eventSink(AccountDeactivationEvent.SetPassword("password")) + initialState.eventSink(AccountDeactivationEvent.SetEraseData(true)) skipItems(2) - initialState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + initialState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) val updatedState = awaitItem() assertThat(updatedState.accountDeactivationAction).isEqualTo(AsyncAction.ConfirmingNoParams) - updatedState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + updatedState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) val updatedState2 = awaitItem() assertThat(updatedState2.accountDeactivationAction).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() assertThat(finalState.accountDeactivationAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) recorder.assertions().isCalledOnce().with(value("password"), value(true)) // Retry - finalState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = true)) + finalState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = true)) val finalState2 = awaitItem() assertThat(finalState2.accountDeactivationAction).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().accountDeactivationAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) @@ -131,20 +131,20 @@ class AccountDeactivationPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(AccountDeactivationEvents.SetPassword("password")) - initialState.eventSink(AccountDeactivationEvents.SetEraseData(true)) + initialState.eventSink(AccountDeactivationEvent.SetPassword("password")) + initialState.eventSink(AccountDeactivationEvent.SetEraseData(true)) skipItems(2) - initialState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + initialState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) val updatedState = awaitItem() assertThat(updatedState.accountDeactivationAction).isEqualTo(AsyncAction.ConfirmingNoParams) - updatedState.eventSink(AccountDeactivationEvents.DeactivateAccount(isRetry = false)) + updatedState.eventSink(AccountDeactivationEvent.DeactivateAccount(isRetry = false)) val updatedState2 = awaitItem() assertThat(updatedState2.accountDeactivationAction).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() assertThat(finalState.accountDeactivationAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) recorder.assertions().isCalledOnce().with(value("password"), value(true)) // Cancel - finalState.eventSink(AccountDeactivationEvents.CloseDialogs) + finalState.eventSink(AccountDeactivationEvent.CloseDialogs) val finalState2 = awaitItem() assertThat(finalState2.accountDeactivationAction).isEqualTo(AsyncAction.Uninitialized) } diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt index caf4c0384f..5c650fea9d 100644 --- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt +++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt @@ -35,7 +35,7 @@ import org.robolectric.annotation.Config class AccountDeactivationViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setAccountDeactivationView( state = anAccountDeactivationState(eventSink = eventsRecorder), @@ -48,7 +48,7 @@ class AccountDeactivationViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on Deactivate emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( @@ -58,12 +58,12 @@ class AccountDeactivationViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_delete) - eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false)) + eventsRecorder.assertSingle(AccountDeactivationEvent.DeactivateAccount(false)) } @Test fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( @@ -74,12 +74,12 @@ class AccountDeactivationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false)) + eventsRecorder.assertSingle(AccountDeactivationEvent.DeactivateAccount(false)) } @Test fun `clicking on retry on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( @@ -90,24 +90,24 @@ class AccountDeactivationViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_retry) - eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(true)) + eventsRecorder.assertSingle(AccountDeactivationEvent.DeactivateAccount(true)) } @Test fun `switching on the erase all switch emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAccountDeactivationView( state = anAccountDeactivationState( eventSink = eventsRecorder, ), ) clickOn(R.string.screen_deactivate_account_delete_all_messages) - eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(true)) + eventsRecorder.assertSingle(AccountDeactivationEvent.SetEraseData(true)) } @Test fun `switching off the erase all switch emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( @@ -117,13 +117,13 @@ class AccountDeactivationViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_deactivate_account_delete_all_messages) - eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(false)) + eventsRecorder.assertSingle(AccountDeactivationEvent.SetEraseData(false)) } @Config(qualifiers = "h1024dp") @Test fun `typing text in the password field emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAccountDeactivationView( state = anAccountDeactivationState( deactivateFormState = aDeactivateFormState( @@ -133,7 +133,7 @@ class AccountDeactivationViewTest : RobolectricTest() { ), ) onNodeWithTag(TestTags.loginPassword.value).performTextInput("A") - eventsRecorder.assertSingle(AccountDeactivationEvents.SetPassword("A$A_PASSWORD")) + eventsRecorder.assertSingle(AccountDeactivationEvent.SetPassword("A$A_PASSWORD")) } } diff --git a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvents.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvent.kt similarity index 76% rename from features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvents.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvent.kt index 0161a5d657..9fab72424f 100644 --- a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvents.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.forward.impl -sealed interface ForwardMessagesEvents { - data object ClearError : ForwardMessagesEvents +sealed interface ForwardMessagesEvent { + data object ClearError : ForwardMessagesEvent } diff --git a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt index 1145f294a3..6068866653 100644 --- a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt @@ -48,9 +48,9 @@ class ForwardMessagesPresenter( @Composable override fun present(): ForwardMessagesState { - fun handleEvent(event: ForwardMessagesEvents) { + fun handleEvent(event: ForwardMessagesEvent) { when (event) { - ForwardMessagesEvents.ClearError -> forwardingActionState.value = AsyncAction.Uninitialized + ForwardMessagesEvent.ClearError -> forwardingActionState.value = AsyncAction.Uninitialized } } diff --git a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt index b1e46adb03..141c1bad38 100644 --- a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt @@ -13,5 +13,5 @@ import io.element.android.libraries.matrix.api.core.RoomId data class ForwardMessagesState( val forwardAction: AsyncAction>, - val eventSink: (ForwardMessagesEvents) -> Unit + val eventSink: (ForwardMessagesEvent) -> Unit ) diff --git a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStatePreviewParam.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStatePreviewParam.kt index 8ff621c2a6..fc61925fcc 100644 --- a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStatePreviewParam.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStatePreviewParam.kt @@ -32,7 +32,7 @@ open class ForwardMessagesStatePreviewParam : PreviewParameterProvider> = AsyncAction.Uninitialized, - eventSink: (ForwardMessagesEvents) -> Unit = {} + eventSink: (ForwardMessagesEvent) -> Unit = {} ) = ForwardMessagesState( forwardAction = forwardAction, eventSink = eventSink diff --git a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt index f350c5b516..8c622d8e29 100644 --- a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt @@ -31,7 +31,7 @@ fun ForwardMessagesView( stringResource(id = CommonStrings.error_unknown) }, onErrorDismiss = { - state.eventSink(ForwardMessagesEvents.ClearError) + state.eventSink(ForwardMessagesEvent.ClearError) }, ) } diff --git a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt index 5e2f74ccad..0ff3210f2e 100644 --- a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt +++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt @@ -86,7 +86,7 @@ class ForwardMessagesPresenterTest { val failedForwardState = awaitItem() assertThat(failedForwardState.forwardAction.isFailure()).isTrue() // Then clear error - failedForwardState.eventSink(ForwardMessagesEvents.ClearError) + failedForwardState.eventSink(ForwardMessagesEvent.ClearError) assertThat(awaitItem().forwardAction.isUninitialized()).isTrue() forwardEventLambda.assertions().isCalledOnce() } diff --git a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt index a73478479e..3302e3430f 100644 --- a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt +++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt @@ -29,7 +29,7 @@ import org.junit.Test class ForwardMessagesViewTest : RobolectricTest() { @Test fun `cancel error emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setForwardMessagesView( aForwardMessagesState( forwardAction = AsyncAction.Failure(AN_EXCEPTION), @@ -37,13 +37,13 @@ class ForwardMessagesViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(ForwardMessagesEvents.ClearError) + eventsRecorder.assertSingle(ForwardMessagesEvent.ClearError) } @Test fun `success invokes onForwardSuccess`() = runAndroidComposeUiTest { val data = listOf(A_ROOM_ID) - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam?>(data) { callback -> setForwardMessagesView( aForwardMessagesState( diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvent.kt similarity index 65% rename from features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt rename to features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvent.kt index e0bd5acf43..3ce3b5b2cf 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.ftue.impl.notifications -sealed interface NotificationsOptInEvents { - data object ContinueClicked : NotificationsOptInEvents - data object NotNowClicked : NotificationsOptInEvents +sealed interface NotificationsOptInEvent { + data object ContinueClicked : NotificationsOptInEvent + data object NotNowClicked : NotificationsOptInEvent } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt index deb7fc59b9..45365670f7 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt @@ -52,16 +52,16 @@ class NotificationsOptInPresenter( override fun present(): NotificationsOptInState { val notificationsPermissionsState = postNotificationPermissionsPresenter.present() - fun handleEvent(event: NotificationsOptInEvents) { + fun handleEvent(event: NotificationsOptInEvent) { when (event) { - NotificationsOptInEvents.ContinueClicked -> { + NotificationsOptInEvent.ContinueClicked -> { if (notificationsPermissionsState.permissionGranted) { callback.onNotificationsOptInFinished() } else { notificationsPermissionsState.eventSink(PermissionsEvent.RequestPermissions) } } - NotificationsOptInEvents.NotNowClicked -> { + NotificationsOptInEvent.NotNowClicked -> { if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { appCoroutineScope.setPermissionDenied() } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt index d12cec0778..cff99ab6df 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt @@ -12,5 +12,5 @@ import io.element.android.libraries.permissions.api.PermissionsState data class NotificationsOptInState( val notificationsPermissionState: PermissionsState, - val eventSink: (NotificationsOptInEvents) -> Unit + val eventSink: (NotificationsOptInEvent) -> Unit ) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt index e2eabe8f2b..e2db8ce4b7 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt @@ -86,14 +86,14 @@ private fun NotificationsOptInFooter(state: NotificationsOptInState) { modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_ok), onClick = { - state.eventSink(NotificationsOptInEvents.ContinueClicked) + state.eventSink(NotificationsOptInEvent.ContinueClicked) } ) TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_not_now), onClick = { - state.eventSink(NotificationsOptInEvents.NotNowClicked) + state.eventSink(NotificationsOptInEvent.NotNowClicked) } ) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt index 2e37cbe8a5..f19c323d52 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import dev.zacsweers.metro.Inject -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -67,7 +67,7 @@ class ChooseSelfVerificationModePresenter( fun handleEvent(event: ChooseSelfVerificationModeEvent) { when (event) { - ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) + ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false)) } } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt index 32d68dd1e1..a456d9f373 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt @@ -55,7 +55,7 @@ class NotificationsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(NotificationsOptInEvents.ContinueClicked) + initialState.eventSink(NotificationsOptInEvent.ContinueClicked) assertThat(awaitItem().notificationsPermissionState.showDialog).isTrue() } } @@ -70,7 +70,7 @@ class NotificationsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(NotificationsOptInEvents.ContinueClicked) + initialState.eventSink(NotificationsOptInEvent.ContinueClicked) assertThat(isFinished).isTrue() } } @@ -86,7 +86,7 @@ class NotificationsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(NotificationsOptInEvents.NotNowClicked) + initialState.eventSink(NotificationsOptInEvent.NotNowClicked) assertThat(isFinished).isTrue() } } @@ -105,7 +105,7 @@ class NotificationsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(NotificationsOptInEvents.NotNowClicked) + initialState.eventSink(NotificationsOptInEvent.NotNowClicked) // Allow background coroutines to run runCurrent() diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt index 697c51f776..8ceb683993 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt @@ -9,7 +9,7 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode import com.google.common.truth.Truth.assertThat -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.libraries.architecture.AsyncData @@ -112,7 +112,7 @@ class ChooseSessionVerificationModePresenterTest { @Test fun `sing out action triggers a direct logout`() = runTest { - val logoutEventRecorder = lambdaRecorder {} + val logoutEventRecorder = lambdaRecorder {} val logoutPresenter = Presenter { aDirectLogoutState(eventSink = logoutEventRecorder) } @@ -121,7 +121,7 @@ class ChooseSessionVerificationModePresenterTest { val initial = awaitItem() initial.eventSink(ChooseSelfVerificationModeEvent.SignOut) logoutEventRecorder.assertions().isCalledOnce() - .with(value(DirectLogoutEvents.Logout(ignoreSdkError = false))) + .with(value(DirectLogoutEvent.Logout(ignoreSdkError = false))) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt index c6a9480bc9..95826665fe 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt @@ -16,7 +16,7 @@ import io.element.android.libraries.designsystem.components.Announcement import io.element.android.libraries.designsystem.components.AnnouncementType import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents +import io.element.android.libraries.push.api.battery.BatteryOptimizationEvent import io.element.android.libraries.push.api.battery.BatteryOptimizationState import io.element.android.libraries.push.api.battery.aBatteryOptimizationState @@ -31,8 +31,8 @@ internal fun BatteryOptimizationBanner( description = stringResource(R.string.banner_battery_optimization_content_android), type = AnnouncementType.Actionable( actionText = stringResource(R.string.banner_battery_optimization_submit_android), - onActionClick = { state.eventSink(BatteryOptimizationEvents.RequestDisableOptimizations) }, - onDismissClick = { state.eventSink(BatteryOptimizationEvents.Dismiss) }, + onActionClick = { state.eventSink(BatteryOptimizationEvent.RequestDisableOptimizations) }, + onDismissClick = { state.eventSink(BatteryOptimizationEvent.Dismiss) }, ), ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt index 92a5d8dbaa..f2826415d2 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt @@ -16,7 +16,7 @@ import io.element.android.libraries.designsystem.components.Announcement import io.element.android.libraries.designsystem.components.AnnouncementType import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvents +import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvent import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState import io.element.android.libraries.ui.strings.CommonStrings @@ -31,8 +31,8 @@ fun FullScreenIntentPermissionBanner( description = stringResource(R.string.full_screen_intent_banner_message), type = AnnouncementType.Actionable( actionText = stringResource(CommonStrings.action_continue), - onDismissClick = { state.eventSink(FullScreenIntentPermissionsEvents.Dismiss) }, - onActionClick = { state.eventSink(FullScreenIntentPermissionsEvents.OpenSettings) }, + onDismissClick = { state.eventSink(FullScreenIntentPermissionsEvent.Dismiss) }, + onActionClick = { state.eventSink(FullScreenIntentPermissionsEvent.OpenSettings) }, ), modifier = modifier.roomListBannerPadding(), ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt index 63d28dca21..9f4a2403f7 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt @@ -53,8 +53,6 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.heading -import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection @@ -80,7 +78,7 @@ import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGra import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE -import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.text.AdaptativeTitle 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 @@ -141,15 +139,21 @@ fun HomeTopBar( else -> stringResource(selectedNavigationItem.labelRes) } } - HomeNavigationBarItem.Spaces -> stringResource(selectedNavigationItem.labelRes) + HomeNavigationBarItem.Spaces -> null + } + displayTitle?.let { + val style = when (spaceFiltersState) { + // Space name + is SpaceFiltersState.Selected -> ElementTheme.typography.fontHeadingSmMedium + // "Chats" + else -> ElementTheme.typography.fontHeadingLgBold + } + AdaptativeTitle( + title = displayTitle, + style = style, + twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium, + ) } - Text( - modifier = Modifier.semantics { - heading() - }, - style = ElementTheme.typography.aliasScreenTitle, - text = displayTitle, - ) }, navigationIcon = { NavigationIcon( @@ -179,7 +183,9 @@ fun HomeTopBar( TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) { RoomListFiltersView( state = filtersState, - modifier = Modifier.padding(bottom = 16.dp).padding(contentPadding) + modifier = Modifier + .padding(bottom = 16.dp) + .padding(contentPadding) ) } } @@ -348,45 +354,45 @@ private fun AccountIcon( ), contentAlignment = Alignment.Center, ) { - val avatarData by remember(matrixUser) { - derivedStateOf { - matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar) - } + val avatarData by remember(matrixUser) { + derivedStateOf { + matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar) } - val statusEmoji = matrixUser.displayedStatus?.toEmojiText() - val avatarModifier = if (statusEmoji != null) { - Modifier.eraseStatusEmojiBackground( - parentSize = AvatarSize.CurrentUserTopBar.dp, - layoutDirection = LocalLayoutDirection.current, - ) - } else { - Modifier - } - Avatar( - avatarData = avatarData, - avatarType = AvatarType.User, - modifier = avatarModifier, - contentDescription = if (isCurrentAccount) { - if (showAvatarIndicator) { - stringResource(CommonStrings.a11y_settings_with_required_action) - } else { - stringResource(CommonStrings.common_settings) - } - } else { - null - }, + } + val statusEmoji = matrixUser.displayedStatus?.toEmojiText() + val avatarModifier = if (statusEmoji != null) { + Modifier.eraseStatusEmojiBackground( + parentSize = AvatarSize.CurrentUserTopBar.dp, + layoutDirection = LocalLayoutDirection.current, ) - if (statusEmoji != null) { - StatusEmojiBadge( - emoji = statusEmoji, - modifier = Modifier.align(Alignment.BottomEnd), - ) - } - if (showAvatarIndicator) { - RedIndicatorAtom( - modifier = Modifier.align(Alignment.TopEnd) - ) - } + } else { + Modifier + } + Avatar( + avatarData = avatarData, + avatarType = AvatarType.User, + modifier = avatarModifier, + contentDescription = if (isCurrentAccount) { + if (showAvatarIndicator) { + stringResource(CommonStrings.a11y_settings_with_required_action) + } else { + stringResource(CommonStrings.common_settings) + } + } else { + null + }, + ) + if (statusEmoji != null) { + StatusEmojiBadge( + emoji = statusEmoji, + modifier = Modifier.align(Alignment.BottomEnd), + ) + } + if (showAvatarIndicator) { + RedIndicatorAtom( + modifier = Modifier.align(Alignment.TopEnd) + ) + } } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt index 96e9f9344b..ba3e8592b6 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter import io.element.android.libraries.matrix.api.room.CallIntentConsensus import io.element.android.libraries.matrix.api.room.CurrentUserMembership +import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.roomlist.LatestEventValue import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.ui.model.dmUserStatus @@ -47,7 +48,7 @@ class RoomListRoomSummaryFactory( mode = DateFormatterMode.TimeOrDate, useRelative = true, ), - latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.isDm), + latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.hasOnlyTwoMembers()), avatarData = avatarData, userDefinedNotificationMode = roomInfo.userDefinedNotificationMode, hasRoomCall = roomInfo.hasRoomCall, @@ -81,6 +82,10 @@ class RoomListRoomSummaryFactory( ) } + private fun RoomInfo.hasOnlyTwoMembers(): Boolean { + return isDm || activeMembersCount <= 2 + } + private fun computeLatestEvent(latestEvent: LatestEventValue, dm: Boolean): LatestEvent { return when (latestEvent) { is LatestEventValue.None -> { diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt index 1c21177988..546092ede6 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenter.kt @@ -37,8 +37,8 @@ import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.home.impl.spacefilters.into import io.element.android.features.home.impl.spacefilters.selectedFilter import io.element.android.features.invite.api.SeenInvitesStore -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.DeclineInvite +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent.AcceptInvite +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent.DeclineInvite import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomState diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/GlobalSearchEvent.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/GlobalSearchEvent.kt index 76d9147e94..5f8093b1de 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/GlobalSearchEvent.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/GlobalSearchEvent.kt @@ -7,9 +7,6 @@ package io.element.android.features.home.impl.search -import androidx.compose.runtime.Immutable - -@Immutable sealed interface GlobalSearchEvent { object ClearQuery : GlobalSearchEvent object ToggleSearchVisibility : GlobalSearchEvent diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvent.kt similarity index 89% rename from features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt rename to features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvent.kt index 88290417dd..0a32da7c09 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvent.kt @@ -8,4 +8,4 @@ package io.element.android.features.home.impl.spaces -sealed interface HomeSpacesEvents +sealed interface HomeSpacesEvent diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt index b758a1e593..2e2406be87 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt @@ -39,7 +39,7 @@ class HomeSpacesPresenter( seenInvitesStore.seenRoomIds().map { it.toImmutableSet() } }.collectAsState(persistentSetOf()) - fun handleEvent(event: HomeSpacesEvents) { + fun handleEvent(event: HomeSpacesEvent) { // when (event) { } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt index e93f04291e..e1c8827087 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt @@ -19,7 +19,7 @@ data class HomeSpacesState( val seenSpaceInvites: ImmutableSet, val hideInvitesAvatar: Boolean, val canExploreSpaces: Boolean, - val eventSink: (HomeSpacesEvents) -> Unit, + val eventSink: (HomeSpacesEvent) -> Unit, ) sealed interface CurrentSpace { diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStatePreviewParam.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStatePreviewParam.kt index 57b219d302..8d4acae8b7 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStatePreviewParam.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStatePreviewParam.kt @@ -43,7 +43,7 @@ internal fun aHomeSpacesState( seenSpaceInvites: Set = emptySet(), hideInvitesAvatar: Boolean = false, canExploreSpaces: Boolean = true, - eventSink: (HomeSpacesEvents) -> Unit = {}, + eventSink: (HomeSpacesEvent) -> Unit = {}, ) = HomeSpacesState( space = space, spaceRooms = spaceRooms.toImmutableList(), diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index 914d08d7f6..f9cf38a1aa 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -143,7 +143,7 @@ private fun EmptySpaceHomeView( ) Text( text = stringResource(CommonStrings.screen_space_list_empty_state_title), - style = ElementTheme.typography.fontHeadingLgBold, + style = ElementTheme.typography.fontHeadingMdBold, color = ElementTheme.colors.textPrimary, textAlign = TextAlign.Center, ) diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt index 5f307918be..bfd92094a5 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt @@ -8,10 +8,36 @@ package io.element.android.features.home.impl.datasource +import com.google.common.truth.Truth.assertThat import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter +import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.aRoomSummary +import org.junit.Test + +class RoomListRoomSummaryFactoryTest { + @Test + fun `a room with two members does not prefix its preview with the sender`() { + val formatter = FakeRoomLatestEventFormatter() + val factory = aRoomListRoomSummaryFactory(roomLatestEventFormatter = formatter) + + factory.create(aRoomSummary(info = aRoomInfo(isDm = false, activeMembersCount = 2))) + + assertThat(formatter.lastIsDmRoom).isTrue() + } + + @Test + fun `a room with more than two members prefixes its preview with the sender`() { + val formatter = FakeRoomLatestEventFormatter() + val factory = aRoomListRoomSummaryFactory(roomLatestEventFormatter = formatter) + + factory.create(aRoomSummary(info = aRoomInfo(isDm = false, activeMembersCount = 3))) + + assertThat(formatter.lastIsDmRoom).isFalse() + } +} fun aRoomListRoomSummaryFactory( dateFormatter: DateFormatter = FakeDateFormatter { _, _, _ -> "Today" }, diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt index d2e33c77c1..9bc8719a8b 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt @@ -25,7 +25,7 @@ import io.element.android.features.home.impl.search.aRoomListSearchState import io.element.android.features.home.impl.spacefilters.SpaceFiltersState import io.element.android.features.home.impl.spacefilters.aDisabledSpaceFiltersState import io.element.android.features.invite.api.SeenInvitesStore -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState import io.element.android.features.invite.test.InMemorySeenInvitesStore @@ -474,7 +474,7 @@ class RoomListPresenterTest { @Test fun `present - when a room is invited then accept and decline events are sent to acceptDeclinePresenter`() = runTest { - val eventSinkRecorder = lambdaRecorder { _: AcceptDeclineInviteEvents -> } + val eventSinkRecorder = lambdaRecorder { _: AcceptDeclineInviteEvent -> } val acceptDeclinePresenter = Presenter { anAcceptDeclineInviteState(eventSink = eventSinkRecorder) } @@ -513,8 +513,8 @@ class RoomListPresenterTest { assert(eventSinkRecorder) .isCalledExactly(2) .withSequence( - listOf(value(AcceptDeclineInviteEvents.AcceptInvite(inviteData))), - listOf(value(AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false))), + listOf(value(AcceptDeclineInviteEvent.AcceptInvite(inviteData))), + listOf(value(AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false))), ) } } diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvent.kt similarity index 91% rename from features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt rename to features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvent.kt index 5691b19ef7..1bf1ffea8a 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvent.kt @@ -13,7 +13,7 @@ import io.element.android.features.invite.api.InviteData /** * Events sent to the accept/decline invite presenter, from whichever screen is showing the invitation. */ -interface AcceptDeclineInviteEvents { - data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvents - data class DeclineInvite(val invite: InviteData, val blockUser: Boolean, val shouldConfirm: Boolean) : AcceptDeclineInviteEvents +interface AcceptDeclineInviteEvent { + data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvent + data class DeclineInvite(val invite: InviteData, val blockUser: Boolean, val shouldConfirm: Boolean) : AcceptDeclineInviteEvent } diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt index 3bfd0430fe..3e9c549620 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt @@ -14,5 +14,5 @@ import io.element.android.libraries.matrix.api.core.RoomId data class AcceptDeclineInviteState( val acceptAction: AsyncAction, val declineAction: AsyncAction, - val eventSink: (AcceptDeclineInviteEvents) -> Unit, + val eventSink: (AcceptDeclineInviteEvent) -> Unit, ) diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStatePreviewParam.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStatePreviewParam.kt index 18acc11e5e..ceda9542ef 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStatePreviewParam.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStatePreviewParam.kt @@ -14,7 +14,7 @@ import io.element.android.libraries.matrix.api.core.RoomId fun anAcceptDeclineInviteState( acceptAction: AsyncAction = AsyncAction.Uninitialized, declineAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (AcceptDeclineInviteEvents) -> Unit = {}, + eventSink: (AcceptDeclineInviteEvent) -> Unit = {}, ) = AcceptDeclineInviteState( acceptAction = acceptAction, declineAction = declineAction, diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt index 9be255fe1a..e5ee3d20c7 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Inject import io.element.android.features.invite.api.InviteData -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite import io.element.android.features.invite.impl.AcceptInvite @@ -40,13 +40,13 @@ class AcceptDeclineInvitePresenter( val declinedAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvent(event: AcceptDeclineInviteEvents) { + fun handleEvent(event: AcceptDeclineInviteEvent) { when (event) { - is AcceptDeclineInviteEvents.AcceptInvite -> { + is AcceptDeclineInviteEvent.AcceptInvite -> { localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction) } - is AcceptDeclineInviteEvents.DeclineInvite -> { + is AcceptDeclineInviteEvent.DeclineInvite -> { val inviteData = event.invite if (event.shouldConfirm) { declinedAction.value = ConfirmingDeclineInvite(inviteData, event.blockUser) @@ -58,11 +58,11 @@ class AcceptDeclineInvitePresenter( ) } } - is InternalAcceptDeclineInviteEvents.ClearAcceptActionState -> { + is InternalAcceptDeclineInviteEvent.ClearAcceptActionState -> { acceptedAction.value = AsyncAction.Uninitialized } - is InternalAcceptDeclineInviteEvents.ClearDeclineActionState -> { + is InternalAcceptDeclineInviteEvent.ClearDeclineActionState -> { declinedAction.value = AsyncAction.Uninitialized } } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt index c3df984604..21c59b1860 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.invite.api.InviteData -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite import io.element.android.features.invite.impl.AcceptInvite @@ -37,11 +37,11 @@ fun AcceptDeclineInviteView( AsyncActionView( async = state.acceptAction, onSuccess = { roomId -> - state.eventSink(InternalAcceptDeclineInviteEvents.ClearAcceptActionState) + state.eventSink(InternalAcceptDeclineInviteEvent.ClearAcceptActionState) onAcceptInviteSuccess(roomId) }, onErrorDismiss = { - state.eventSink(InternalAcceptDeclineInviteEvents.ClearAcceptActionState) + state.eventSink(InternalAcceptDeclineInviteEvent.ClearAcceptActionState) }, errorTitle = { stringResource(CommonStrings.common_something_went_wrong) @@ -57,11 +57,11 @@ fun AcceptDeclineInviteView( AsyncActionView( async = state.declineAction, onSuccess = { roomId -> - state.eventSink(InternalAcceptDeclineInviteEvents.ClearDeclineActionState) + state.eventSink(InternalAcceptDeclineInviteEvent.ClearDeclineActionState) onDeclineInviteSuccess(roomId) }, onErrorDismiss = { - state.eventSink(InternalAcceptDeclineInviteEvents.ClearDeclineActionState) + state.eventSink(InternalAcceptDeclineInviteEvent.ClearDeclineActionState) }, errorTitle = { stringResource(CommonStrings.common_something_went_wrong) @@ -77,7 +77,7 @@ fun AcceptDeclineInviteView( blockUser = confirming.blockUser, onConfirmClick = { state.eventSink( - AcceptDeclineInviteEvents.DeclineInvite( + AcceptDeclineInviteEvent.DeclineInvite( confirming.inviteData, blockUser = confirming.blockUser, shouldConfirm = false @@ -85,7 +85,7 @@ fun AcceptDeclineInviteView( ) }, onDismissClick = { - state.eventSink(InternalAcceptDeclineInviteEvents.ClearDeclineActionState) + state.eventSink(InternalAcceptDeclineInviteEvent.ClearDeclineActionState) } ) } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvent.kt similarity index 79% rename from features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt rename to features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvent.kt index 765c7055d3..805ed9871e 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.invite.impl.acceptdecline -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent -sealed interface InternalAcceptDeclineInviteEvents : AcceptDeclineInviteEvents { - data object ClearAcceptActionState : InternalAcceptDeclineInviteEvents - data object ClearDeclineActionState : InternalAcceptDeclineInviteEvents +sealed interface InternalAcceptDeclineInviteEvent : AcceptDeclineInviteEvent { + data object ClearAcceptActionState : InternalAcceptDeclineInviteEvent + data object ClearDeclineActionState : InternalAcceptDeclineInviteEvent } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvent.kt similarity index 57% rename from features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt rename to features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvent.kt index 1fc160e530..f31b9d042f 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvent.kt @@ -8,10 +8,10 @@ package io.element.android.features.invite.impl.declineandblock -sealed interface DeclineAndBlockEvents { - data class UpdateReportReason(val reason: String) : DeclineAndBlockEvents - data object ToggleReportRoom : DeclineAndBlockEvents - data object ToggleBlockUser : DeclineAndBlockEvents - data object Decline : DeclineAndBlockEvents - data object ClearDeclineAction : DeclineAndBlockEvents +sealed interface DeclineAndBlockEvent { + data class UpdateReportReason(val reason: String) : DeclineAndBlockEvent + data object ToggleReportRoom : DeclineAndBlockEvent + data object ToggleBlockUser : DeclineAndBlockEvent + data object Decline : DeclineAndBlockEvent + data object ClearDeclineAction : DeclineAndBlockEvent } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt index af2a10cbfd..170d003bea 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt @@ -49,13 +49,13 @@ class DeclineAndBlockPresenter( val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: DeclineAndBlockEvents) { + fun handleEvent(event: DeclineAndBlockEvent) { when (event) { - DeclineAndBlockEvents.ClearDeclineAction -> declineAction.value = AsyncAction.Uninitialized - DeclineAndBlockEvents.Decline -> coroutineScope.decline(reportReason, blockUser, reportRoom, declineAction) - DeclineAndBlockEvents.ToggleBlockUser -> blockUser = !blockUser - DeclineAndBlockEvents.ToggleReportRoom -> reportRoom = !reportRoom - is DeclineAndBlockEvents.UpdateReportReason -> reportReason = event.reason + DeclineAndBlockEvent.ClearDeclineAction -> declineAction.value = AsyncAction.Uninitialized + DeclineAndBlockEvent.Decline -> coroutineScope.decline(reportReason, blockUser, reportRoom, declineAction) + DeclineAndBlockEvent.ToggleBlockUser -> blockUser = !blockUser + DeclineAndBlockEvent.ToggleReportRoom -> reportRoom = !reportRoom + is DeclineAndBlockEvent.UpdateReportReason -> reportReason = event.reason } } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt index 7dbbbfd60e..057929ea42 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt @@ -15,7 +15,7 @@ data class DeclineAndBlockState( val reportReason: String, val blockUser: Boolean, val declineAction: AsyncAction, - val eventSink: (DeclineAndBlockEvents) -> Unit + val eventSink: (DeclineAndBlockEvent) -> Unit ) { val canDecline = blockUser || reportRoom && reportReason.isNotEmpty() } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStatePreviewParam.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStatePreviewParam.kt index 704fc9f045..6690a568dd 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStatePreviewParam.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStatePreviewParam.kt @@ -36,7 +36,7 @@ fun aDeclineAndBlockState( reportReason: String = "", blockUser: Boolean = false, declineAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (DeclineAndBlockEvents) -> Unit = {}, + eventSink: (DeclineAndBlockEvent) -> Unit = {}, ) = DeclineAndBlockState( reportRoom = reportRoom, reportReason = reportReason, diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt index c30742de12..eaace62309 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt @@ -56,8 +56,8 @@ fun DeclineAndBlockView( async = state.declineAction, onSuccess = { onDeclineSuccess() }, errorMessage = { stringResource(CommonStrings.error_unknown) }, - onRetry = { state.eventSink(DeclineAndBlockEvents.Decline) }, - onErrorDismiss = { state.eventSink(DeclineAndBlockEvents.ClearDeclineAction) } + onRetry = { state.eventSink(DeclineAndBlockEvent.Decline) }, + onErrorDismiss = { state.eventSink(DeclineAndBlockEvent.ClearDeclineAction) } ) Scaffold( @@ -89,7 +89,7 @@ fun DeclineAndBlockView( Text(text = stringResource(R.string.screen_decline_and_block_block_user_option_description)) }, onClick = { - state.eventSink(DeclineAndBlockEvents.ToggleBlockUser) + state.eventSink(DeclineAndBlockEvent.ToggleBlockUser) }, trailingContent = ListItemContent.Switch(checked = state.blockUser) ) @@ -104,7 +104,7 @@ fun DeclineAndBlockView( Text(text = stringResource(R.string.screen_decline_and_block_report_user_option_description)) }, onClick = { - state.eventSink(DeclineAndBlockEvents.ToggleReportRoom) + state.eventSink(DeclineAndBlockEvent.ToggleReportRoom) }, trailingContent = ListItemContent.Switch(checked = state.reportRoom) ) @@ -113,7 +113,7 @@ fun DeclineAndBlockView( Spacer(modifier = Modifier.height(24.dp)) TextField( value = state.reportReason, - onValueChange = { state.eventSink(DeclineAndBlockEvents.UpdateReportReason(it)) }, + onValueChange = { state.eventSink(DeclineAndBlockEvent.UpdateReportReason(it)) }, placeholder = stringResource(R.string.screen_decline_and_block_report_user_reason_placeholder), minLines = 3, enabled = !isDeclining, @@ -132,7 +132,7 @@ fun DeclineAndBlockView( enabled = !isDeclining && state.canDecline, onClick = { focusManager.clearFocus(force = true) - state.eventSink(DeclineAndBlockEvents.Decline) + state.eventSink(DeclineAndBlockEvent.Decline) }, modifier = Modifier .fillMaxWidth() diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt index ba080293f8..15624ba9be 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt @@ -10,7 +10,7 @@ package io.element.android.features.invite.impl.acceptdecline import com.google.common.truth.Truth.assertThat import io.element.android.features.invite.api.InviteData -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite import io.element.android.features.invite.impl.AcceptInvite import io.element.android.features.invite.impl.DeclineInvite @@ -51,13 +51,13 @@ class AcceptDeclineInvitePresenterTest { val inviteData = anInviteData() awaitItem().also { state -> state.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true) + AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true) ) } awaitItem().also { state -> assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false)) state.eventSink( - InternalAcceptDeclineInviteEvents.ClearDeclineActionState + InternalAcceptDeclineInviteEvent.ClearDeclineActionState ) } awaitItem().also { state -> @@ -78,20 +78,20 @@ class AcceptDeclineInvitePresenterTest { val inviteData = anInviteData() awaitItem().also { state -> state.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true) + AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true) ) } awaitItem().also { state -> assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, false)) state.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false) + AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false) ) } assertThat(awaitItem().declineAction.isLoading()).isTrue() awaitItem().also { state -> assertThat(state.declineAction).isInstanceOf(AsyncAction.Failure::class.java) state.eventSink( - InternalAcceptDeclineInviteEvents.ClearDeclineActionState + InternalAcceptDeclineInviteEvent.ClearDeclineActionState ) } awaitItem().also { state -> @@ -114,13 +114,13 @@ class AcceptDeclineInvitePresenterTest { val inviteData = anInviteData() awaitItem().also { state -> state.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true) + AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true) ) } awaitItem().also { state -> assertThat(state.declineAction).isEqualTo(ConfirmingDeclineInvite(inviteData, blockUser = false)) state.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false) + AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = false) ) } assertThat(awaitItem().declineAction.isLoading()).isTrue() @@ -146,7 +146,7 @@ class AcceptDeclineInvitePresenterTest { val inviteData = anInviteData() awaitItem().also { state -> state.eventSink( - AcceptDeclineInviteEvents.AcceptInvite(inviteData) + AcceptDeclineInviteEvent.AcceptInvite(inviteData) ) } awaitItem().also { state -> @@ -155,7 +155,7 @@ class AcceptDeclineInvitePresenterTest { awaitItem().also { state -> assertThat(state.acceptAction).isInstanceOf(AsyncAction.Failure::class.java) state.eventSink( - InternalAcceptDeclineInviteEvents.ClearAcceptActionState + InternalAcceptDeclineInviteEvent.ClearAcceptActionState ) } awaitItem().also { state -> @@ -178,7 +178,7 @@ class AcceptDeclineInvitePresenterTest { val inviteData = anInviteData() awaitItem().also { state -> state.eventSink( - AcceptDeclineInviteEvents.AcceptInvite(inviteData) + AcceptDeclineInviteEvent.AcceptInvite(inviteData) ) } awaitItem().also { state -> diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt index 67c2d55490..61c67c34b3 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt @@ -53,21 +53,21 @@ class DeclineAndBlockPresenterTest { assertThat(state.blockUser).isTrue() assertThat(state.reportReason).isEmpty() assertThat(state.canDecline).isTrue() - state.eventSink(DeclineAndBlockEvents.ToggleBlockUser) + state.eventSink(DeclineAndBlockEvent.ToggleBlockUser) } awaitItem().also { state -> assertThat(state.reportRoom).isFalse() assertThat(state.blockUser).isFalse() assertThat(state.reportReason).isEmpty() assertThat(state.canDecline).isFalse() - state.eventSink(DeclineAndBlockEvents.ToggleReportRoom) + state.eventSink(DeclineAndBlockEvent.ToggleReportRoom) } awaitItem().also { state -> assertThat(state.reportRoom).isTrue() assertThat(state.blockUser).isFalse() assertThat(state.reportReason).isEmpty() assertThat(state.canDecline).isFalse() - state.eventSink(DeclineAndBlockEvents.UpdateReportReason("Spam")) + state.eventSink(DeclineAndBlockEvent.UpdateReportReason("Spam")) } awaitItem().also { state -> assertThat(state.reportRoom).isTrue() @@ -87,7 +87,7 @@ class DeclineAndBlockPresenterTest { ) presenter.test { awaitItem().also { state -> - state.eventSink(DeclineAndBlockEvents.Decline) + state.eventSink(DeclineAndBlockEvent.Decline) } assertThat(awaitItem().declineAction.isLoading()).isTrue() awaitItem().also { state -> @@ -110,12 +110,12 @@ class DeclineAndBlockPresenterTest { ) presenter.test { awaitItem().also { state -> - state.eventSink(DeclineAndBlockEvents.Decline) + state.eventSink(DeclineAndBlockEvent.Decline) } assertThat(awaitItem().declineAction.isLoading()).isTrue() awaitItem().also { state -> assertThat(state.declineAction).isInstanceOf(AsyncAction.Failure::class.java) - state.eventSink(DeclineAndBlockEvents.ClearDeclineAction) + state.eventSink(DeclineAndBlockEvent.ClearDeclineAction) } awaitItem().also { state -> assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java) @@ -137,7 +137,7 @@ class DeclineAndBlockPresenterTest { ) presenter.test { awaitItem().also { state -> - state.eventSink(DeclineAndBlockEvents.Decline) + state.eventSink(DeclineAndBlockEvent.Decline) } assertThat(awaitItem().declineAction.isLoading()).isTrue() awaitItem().also { state -> diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt index 5cfb091e82..d3505ae5b2 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt @@ -30,7 +30,7 @@ import org.junit.Test class DeclineAndBlockViewTest : RobolectricTest() { @Test fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setDeclineAndBlockView( aDeclineAndBlockState( @@ -44,7 +44,7 @@ class DeclineAndBlockViewTest : RobolectricTest() { @Test fun `a successful decline invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setDeclineAndBlockView( aDeclineAndBlockState( @@ -58,7 +58,7 @@ class DeclineAndBlockViewTest : RobolectricTest() { @Test fun `clicking on decline when enabled emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDeclineAndBlockView( aDeclineAndBlockState( blockUser = true, @@ -66,12 +66,12 @@ class DeclineAndBlockViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_decline) - eventsRecorder.assertSingle(DeclineAndBlockEvents.Decline) + eventsRecorder.assertSingle(DeclineAndBlockEvent.Decline) } @Test fun `clicking on decline when disabled does not emit event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setDeclineAndBlockView( aDeclineAndBlockState( blockUser = false, @@ -85,7 +85,7 @@ class DeclineAndBlockViewTest : RobolectricTest() { @Test fun `clicking on block option emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDeclineAndBlockView( aDeclineAndBlockState( blockUser = true, @@ -93,12 +93,12 @@ class DeclineAndBlockViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_decline_and_block_block_user_option_title) - eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleBlockUser) + eventsRecorder.assertSingle(DeclineAndBlockEvent.ToggleBlockUser) } @Test fun `clicking on report room option emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDeclineAndBlockView( aDeclineAndBlockState( reportRoom = true, @@ -106,12 +106,12 @@ class DeclineAndBlockViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_report_room) - eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleReportRoom) + eventsRecorder.assertSingle(DeclineAndBlockEvent.ToggleReportRoom) } @Test fun `typing text in the reason field emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDeclineAndBlockView( aDeclineAndBlockState( reportRoom = true, @@ -120,7 +120,7 @@ class DeclineAndBlockViewTest : RobolectricTest() { ), ) onNodeWithText("").performTextInput("Spam!") - eventsRecorder.assertSingle(DeclineAndBlockEvents.UpdateReportReason("Spam!")) + eventsRecorder.assertSingle(DeclineAndBlockEvent.UpdateReportReason("Spam!")) } } diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvent.kt similarity index 68% rename from features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt rename to features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvent.kt index 9570ab8152..884312da67 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvent.kt @@ -11,8 +11,8 @@ package io.element.android.features.invitepeople.api /** * Events the invite people UI sends to its presenter through [InvitePeopleState.eventSink]. */ -interface InvitePeopleEvents { - data object SendInvites : InvitePeopleEvents - data object CloseSearch : InvitePeopleEvents - data object ClearError : InvitePeopleEvents +interface InvitePeopleEvent { + data object SendInvites : InvitePeopleEvent + data object CloseSearch : InvitePeopleEvent + data object ClearError : InvitePeopleEvent } diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt index 0863ec7212..b35ed22f7e 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt @@ -28,5 +28,5 @@ interface InvitePeopleState { val createRoomFromDmAction: AsyncAction /** Where the UI sends its events. */ - val eventSink: (InvitePeopleEvents) -> Unit + val eventSink: (InvitePeopleEvent) -> Unit } diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStatePreviewParam.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStatePreviewParam.kt index 860efc2771..379d80440e 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStatePreviewParam.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStatePreviewParam.kt @@ -27,7 +27,7 @@ private data class PreviewInvitePeopleState( override val isSearchActive: Boolean, override val sendInvitesAction: AsyncAction, override val createRoomFromDmAction: AsyncAction, - override val eventSink: (InvitePeopleEvents) -> Unit, + override val eventSink: (InvitePeopleEvent) -> Unit, ) : InvitePeopleState private fun aPreviewInvitePeopleState( @@ -35,7 +35,7 @@ private fun aPreviewInvitePeopleState( isSearchActive: Boolean = false, sendInvitesAction: AsyncAction = AsyncAction.Uninitialized, createRoomFromDmAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (InvitePeopleEvents) -> Unit = {}, + eventSink: (InvitePeopleEvent) -> Unit = {}, ) = PreviewInvitePeopleState( canInvite = canInvite, isSearchActive = isSearchActive, diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvent.kt similarity index 77% rename from features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt rename to features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvent.kt index 449d0ce6ac..90d6fef59d 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvent.kt @@ -8,12 +8,12 @@ package io.element.android.features.invitepeople.impl -import io.element.android.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.libraries.matrix.api.user.MatrixUser -sealed interface DefaultInvitePeopleEvents : InvitePeopleEvents { - data class ToggleUser(val user: MatrixUser) : DefaultInvitePeopleEvents - data class OnSearchActiveChanged(val active: Boolean) : DefaultInvitePeopleEvents - data object DismissUnknownUsersModal : DefaultInvitePeopleEvents - data object RemoveUnknownUsers : DefaultInvitePeopleEvents +sealed interface DefaultInvitePeopleEvent : InvitePeopleEvent { + data class ToggleUser(val user: MatrixUser) : DefaultInvitePeopleEvent + data class OnSearchActiveChanged(val active: Boolean) : DefaultInvitePeopleEvent + data object DismissUnknownUsersModal : DefaultInvitePeopleEvent + data object RemoveUnknownUsers : DefaultInvitePeopleEvent } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt index 79cd0c83be..dbf5c46291 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt @@ -24,7 +24,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.ContributesBinding -import io.element.android.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.features.invitepeople.api.InvitePeoplePresenter import io.element.android.features.invitepeople.api.InvitePeopleState import io.element.android.libraries.architecture.AsyncAction @@ -179,26 +179,26 @@ class DefaultInvitePeoplePresenter( ) } - fun handleEvent(event: InvitePeopleEvents) { + fun handleEvent(event: InvitePeopleEvent) { when (event) { // Dedicated `when` for exhaustivity. - is DefaultInvitePeopleEvents -> when (event) { - is DefaultInvitePeopleEvents.OnSearchActiveChanged -> { + is DefaultInvitePeopleEvent -> when (event) { + is DefaultInvitePeopleEvent.OnSearchActiveChanged -> { searchActive = event.active if (!event.active) { queryState.clearText() } } - is DefaultInvitePeopleEvents.ToggleUser -> { + is DefaultInvitePeopleEvent.ToggleUser -> { selectedUsers.toggleUser(event.user) searchResults.toggleUser(event.user) // suggestions will automatically update via derivedStateOf when selectedUsers changes } - is DefaultInvitePeopleEvents.DismissUnknownUsersModal -> { + is DefaultInvitePeopleEvent.DismissUnknownUsersModal -> { sendInvitesAction.value = AsyncAction.Uninitialized } - is DefaultInvitePeopleEvents.RemoveUnknownUsers -> { + is DefaultInvitePeopleEvent.RemoveUnknownUsers -> { val usersToRemove = selectedUsers.value.filter { it in unknownUsers } usersToRemove.forEach { user -> selectedUsers.toggleUser(user) @@ -207,7 +207,7 @@ class DefaultInvitePeoplePresenter( sendInvitesAction.value = AsyncAction.Uninitialized } } - is InvitePeopleEvents.SendInvites -> { + is InvitePeopleEvent.SendInvites -> { if (unknownUsers.isNotEmpty() && sendInvitesAction.value !is ConfirmingUnknownUserInvitation) { sendInvitesAction.value = ConfirmingUnknownUserInvitation( unknownUsers @@ -224,11 +224,11 @@ class DefaultInvitePeoplePresenter( } } } - is InvitePeopleEvents.CloseSearch -> { + is InvitePeopleEvent.CloseSearch -> { searchActive = false queryState.clearText() } - is InvitePeopleEvents.ClearError -> { + is InvitePeopleEvent.ClearError -> { sendInvitesAction.value = AsyncAction.Uninitialized createRoomFromDmAction.value = AsyncAction.Uninitialized } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt index 46e5d9f1a5..f1058d1a1b 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt @@ -9,7 +9,7 @@ package io.element.android.features.invitepeople.impl import androidx.compose.foundation.text.input.TextFieldState -import io.element.android.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.features.invitepeople.api.InvitePeopleState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -29,5 +29,5 @@ data class DefaultInvitePeopleState( override val sendInvitesAction: AsyncAction, override val createRoomFromDmAction: AsyncAction, val suggestions: ImmutableList, - override val eventSink: (InvitePeopleEvents) -> Unit + override val eventSink: (InvitePeopleEvent) -> Unit ) : InvitePeopleState diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index 7dced41b13..4da2ec7cea 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -30,7 +30,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.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule @@ -102,7 +102,7 @@ private fun InvitePeopleContentView( verticalArrangement = Arrangement.spacedBy(16.dp), ) { fun toggleUser(user: MatrixUser) { - state.eventSink(DefaultInvitePeopleEvents.ToggleUser(user)) + state.eventSink(DefaultInvitePeopleEvent.ToggleUser(user)) } InvitePeopleSearchBar( @@ -114,7 +114,7 @@ private fun InvitePeopleContentView( active = state.isSearchActive, onActiveChange = { state.eventSink( - DefaultInvitePeopleEvents.OnSearchActiveChanged( + DefaultInvitePeopleEvent.OnSearchActiveChanged( it ) ) @@ -144,7 +144,7 @@ private fun InvitePeopleContentView( CheckableUserRow( checked = invitableUser.isSelected, onCheckedChange = { - state.eventSink(DefaultInvitePeopleEvents.ToggleUser(invitableUser.matrixUser)) + state.eventSink(DefaultInvitePeopleEvent.ToggleUser(invitableUser.matrixUser)) }, data = CheckableUserRowData.Resolved( avatarData = invitableUser.matrixUser.getAvatarData(AvatarSize.UserListItem), @@ -163,9 +163,9 @@ private fun InvitePeopleContentView( if (state.sendInvitesAction is ConfirmingUnknownUserInvitation) { InvitePeopleConfirmModal( users = state.sendInvitesAction.users, - onDismiss = { state.eventSink.invoke(DefaultInvitePeopleEvents.DismissUnknownUsersModal) }, - onInvite = { state.eventSink.invoke(InvitePeopleEvents.SendInvites) }, - onRemove = { state.eventSink.invoke(DefaultInvitePeopleEvents.RemoveUnknownUsers) } + onDismiss = { state.eventSink.invoke(DefaultInvitePeopleEvent.DismissUnknownUsersModal) }, + onInvite = { state.eventSink.invoke(InvitePeopleEvent.SendInvites) }, + onRemove = { state.eventSink.invoke(DefaultInvitePeopleEvent.RemoveUnknownUsers) } ) } } diff --git a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt index 922fa73060..39cfc3afba 100644 --- a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt +++ b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt @@ -11,7 +11,7 @@ package io.element.android.features.invitepeople.impl import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat -import io.element.android.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -88,13 +88,13 @@ internal class DefaultInvitePeoplePresenterTest { val initialState = awaitItem() skipItems(1) - initialState.eventSink(DefaultInvitePeopleEvents.OnSearchActiveChanged(true)) + initialState.eventSink(DefaultInvitePeopleEvent.OnSearchActiveChanged(true)) val resultState = awaitItemAsDefault() assertThat(resultState.isSearchActive).isTrue() resultState.searchQuery.setTextAndPlaceCursorAtEnd("some query") assertThat(awaitItemAsDefault().searchQuery.text.toString()).isEqualTo("some query") - resultState.eventSink(InvitePeopleEvents.CloseSearch) + resultState.eventSink(InvitePeopleEvent.CloseSearch) skipItems(1) awaitItemAsDefault().also { assertThat(it.isSearchActive).isFalse() @@ -290,18 +290,18 @@ internal class DefaultInvitePeoplePresenterTest { skipItems(1) // When we toggle a user not in the list, they are added - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(aMatrixUser())) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(aMatrixUser())) assertThat(awaitItemAsDefault().selectedUsers).containsExactly(aMatrixUser()) // Toggling a different user also adds them - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(aMatrixUser(id = A_USER_ID_2.value))) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(aMatrixUser(id = A_USER_ID_2.value))) assertThat(awaitItemAsDefault().selectedUsers).containsExactly( aMatrixUser(), aMatrixUser(id = A_USER_ID_2.value) ) // Toggling the first user removes them - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(aMatrixUser())) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(aMatrixUser())) assertThat(awaitItemAsDefault().selectedUsers).containsExactly(aMatrixUser(id = A_USER_ID_2.value)) } } @@ -319,7 +319,7 @@ internal class DefaultInvitePeoplePresenterTest { val selectedUser = aMatrixUser(displayName = "John Doe") - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(selectedUser)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(selectedUser)) initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") skipItems(1) @@ -373,7 +373,7 @@ internal class DefaultInvitePeoplePresenterTest { } // And then a user is toggled - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(selectedUser)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(selectedUser)) skipItems(1) val resultState = awaitItemAsDefault() @@ -414,13 +414,13 @@ internal class DefaultInvitePeoplePresenterTest { repository.emitStateWithUsers(users = aMatrixUserList() + selectedUser) skipItems(1) // And then a user is toggled - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(selectedUser)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(selectedUser)) skipItems(1) val resultState = awaitItemAsDefault() // The results are updated... assertThat(resultState.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) // Send invites - initialState.eventSink(InvitePeopleEvents.SendInvites) + initialState.eventSink(InvitePeopleEvent.SendInvites) // Can't invite in the loading state awaitItem().run { @@ -468,13 +468,13 @@ internal class DefaultInvitePeoplePresenterTest { repository.emitStateWithUsers(users = aMatrixUserList() + selectedUser) skipItems(1) // And then a user is toggled - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(selectedUser)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(selectedUser)) skipItems(1) val resultState = awaitItemAsDefault() // The results are updated... assertThat(resultState.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) // Send invites - initialState.eventSink(InvitePeopleEvents.SendInvites) + initialState.eventSink(InvitePeopleEvent.SendInvites) // Can't invite in the loading state awaitItem().run { @@ -645,17 +645,17 @@ internal class DefaultInvitePeoplePresenterTest { skipItems(1) // When we toggle a user not in the list, they are added, and we fetch their identity. - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(alice)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(alice)) delay(100) - awaitItemAsDefault().eventSink(DefaultInvitePeopleEvents.ToggleUser(bob)) + awaitItemAsDefault().eventSink(DefaultInvitePeopleEvent.ToggleUser(bob)) delay(100) - awaitItemAsDefault().eventSink(DefaultInvitePeopleEvents.ToggleUser(charlie)) + awaitItemAsDefault().eventSink(DefaultInvitePeopleEvent.ToggleUser(charlie)) delay(100) // If we do not have their identity cached, or fail to fetch it, we should mark them as unknown. awaitItemAsDefault().run { assertThat(selectedUsers).containsExactly(alice, bob, charlie) - eventSink(InvitePeopleEvents.SendInvites) + eventSink(InvitePeopleEvent.SendInvites) } getUserIdentityResult.assertions().isCalledExactly(3).withSequence( @@ -668,7 +668,7 @@ internal class DefaultInvitePeoplePresenterTest { awaitItemAsDefault().run { assertThat(sendInvitesAction).isInstanceOf(ConfirmingUnknownUserInvitation::class.java) assertThat(canInvite).isTrue() - eventSink(InvitePeopleEvents.SendInvites) + eventSink(InvitePeopleEvent.SendInvites) } delay(1_000) @@ -712,11 +712,11 @@ internal class DefaultInvitePeoplePresenterTest { skipItems(1) // When we toggle a user not in the list, they are added, and we fetch their identity. - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(alice)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(alice)) delay(100) - awaitItemAsDefault().eventSink(DefaultInvitePeopleEvents.ToggleUser(bob)) + awaitItemAsDefault().eventSink(DefaultInvitePeopleEvent.ToggleUser(bob)) delay(100) - awaitItemAsDefault().eventSink(DefaultInvitePeopleEvents.ToggleUser(charlie)) + awaitItemAsDefault().eventSink(DefaultInvitePeopleEvent.ToggleUser(charlie)) delay(100) // And the search is matching Alice and Bob @@ -738,7 +738,7 @@ internal class DefaultInvitePeoplePresenterTest { searchResults.users().map { Pair(it.matrixUser, it.isSelected) } ).containsExactly(Pair(alice, true), Pair(bob, true)) - eventSink(InvitePeopleEvents.SendInvites) + eventSink(InvitePeopleEvent.SendInvites) } getUserIdentityResult.assertions().isCalledExactly(3).withSequence( @@ -751,7 +751,7 @@ internal class DefaultInvitePeoplePresenterTest { awaitItemAsDefault().run { assertThat(sendInvitesAction).isInstanceOf(ConfirmingUnknownUserInvitation::class.java) assertThat(canInvite).isTrue() - eventSink(DefaultInvitePeopleEvents.RemoveUnknownUsers) + eventSink(DefaultInvitePeopleEvent.RemoveUnknownUsers) } // Selecting "remove" should remove all unknown users, but keeps those who are known. @@ -794,16 +794,16 @@ internal class DefaultInvitePeoplePresenterTest { skipItems(1) // When we toggle a user not in the list, they are added, and we fetch their identity. - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(alice)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(alice)) delay(100) - awaitItemAsDefault().eventSink(DefaultInvitePeopleEvents.ToggleUser(bob)) + awaitItemAsDefault().eventSink(DefaultInvitePeopleEvent.ToggleUser(bob)) delay(100) - awaitItemAsDefault().eventSink(DefaultInvitePeopleEvents.ToggleUser(charlie)) + awaitItemAsDefault().eventSink(DefaultInvitePeopleEvent.ToggleUser(charlie)) delay(100) awaitItemAsDefault().run { assertThat(selectedUsers).containsExactly(alice, bob, charlie) - eventSink(InvitePeopleEvents.SendInvites) + eventSink(InvitePeopleEvent.SendInvites) } getUserIdentityResult.assertions().isCalledExactly(3).withSequence( @@ -816,7 +816,7 @@ internal class DefaultInvitePeoplePresenterTest { awaitItemAsDefault().run { assertThat(sendInvitesAction).isInstanceOf(ConfirmingUnknownUserInvitation::class.java) assertThat(canInvite).isTrue() - eventSink(DefaultInvitePeopleEvents.DismissUnknownUsersModal) + eventSink(DefaultInvitePeopleEvent.DismissUnknownUsersModal) } // Dismissing should not modify the selection at all @@ -853,10 +853,10 @@ internal class DefaultInvitePeoplePresenterTest { skipItems(1) // We want to add a new user to a DM - initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(alice)) + initialState.eventSink(DefaultInvitePeopleEvent.ToggleUser(alice)) // And we send the invites - initialState.eventSink(InvitePeopleEvents.SendInvites) + initialState.eventSink(InvitePeopleEvent.SendInvites) skipItems(1) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvent.kt similarity index 60% rename from features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt rename to features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvent.kt index 51245dd8f6..a2e53485ce 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvent.kt @@ -10,15 +10,15 @@ package io.element.android.features.joinroom.impl import io.element.android.features.invite.api.InviteData -sealed interface JoinRoomEvents { - data object RetryFetchingContent : JoinRoomEvents - data object DismissErrorAndHideContent : JoinRoomEvents - data object JoinRoom : JoinRoomEvents - data object KnockRoom : JoinRoomEvents - data object ForgetRoom : JoinRoomEvents - data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvents - data class UpdateKnockMessage(val message: String) : JoinRoomEvents - data object ClearActionStates : JoinRoomEvents - data class AcceptInvite(val inviteData: InviteData) : JoinRoomEvents - data class DeclineInvite(val inviteData: InviteData, val blockUser: Boolean) : JoinRoomEvents +sealed interface JoinRoomEvent { + data object RetryFetchingContent : JoinRoomEvent + data object DismissErrorAndHideContent : JoinRoomEvent + data object JoinRoom : JoinRoomEvent + data object KnockRoom : JoinRoomEvent + data object ForgetRoom : JoinRoomEvent + data class CancelKnock(val requiresConfirmation: Boolean) : JoinRoomEvent + data class UpdateKnockMessage(val message: String) : JoinRoomEvent + data object ClearActionStates : JoinRoomEvent + data class AcceptInvite(val inviteData: InviteData) : JoinRoomEvent + data class DeclineInvite(val inviteData: InviteData, val blockUser: Boolean) : JoinRoomEvent } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index d257952209..85ae9cf392 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -26,7 +26,7 @@ import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invite.api.InviteData import io.element.android.features.invite.api.SeenInvitesStore -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.toInviteData import io.element.android.features.joinroom.impl.di.CancelKnockRoom @@ -151,37 +151,37 @@ class JoinRoomPresenter( contentState.markRoomInviteAsSeen() } - fun handleEvent(event: JoinRoomEvents) { + fun handleEvent(event: JoinRoomEvent) { when (event) { - JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction) - is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage) - is JoinRoomEvents.AcceptInvite -> { + JoinRoomEvent.JoinRoom -> coroutineScope.joinRoom(joinAction) + is JoinRoomEvent.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage) + is JoinRoomEvent.AcceptInvite -> { acceptDeclineInviteState.eventSink( - AcceptDeclineInviteEvents.AcceptInvite(event.inviteData) + AcceptDeclineInviteEvent.AcceptInvite(event.inviteData) ) } - is JoinRoomEvents.DeclineInvite -> { + is JoinRoomEvent.DeclineInvite -> { acceptDeclineInviteState.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(invite = event.inviteData, blockUser = event.blockUser, shouldConfirm = true) + AcceptDeclineInviteEvent.DeclineInvite(invite = event.inviteData, blockUser = event.blockUser, shouldConfirm = true) ) } - is JoinRoomEvents.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction) - JoinRoomEvents.RetryFetchingContent -> { + is JoinRoomEvent.CancelKnock -> coroutineScope.cancelKnockRoom(event.requiresConfirmation, cancelKnockAction) + JoinRoomEvent.RetryFetchingContent -> { retryCount++ } - JoinRoomEvents.ClearActionStates -> { + JoinRoomEvent.ClearActionStates -> { knockAction.value = AsyncAction.Uninitialized joinAction.value = AsyncAction.Uninitialized cancelKnockAction.value = AsyncAction.Uninitialized forgetRoomAction.value = AsyncAction.Uninitialized } - is JoinRoomEvents.UpdateKnockMessage -> { + is JoinRoomEvent.UpdateKnockMessage -> { knockMessage = event.message.take(MAX_KNOCK_MESSAGE_LENGTH) } - JoinRoomEvents.DismissErrorAndHideContent -> { + JoinRoomEvent.DismissErrorAndHideContent -> { isDismissingContent = true } - JoinRoomEvents.ForgetRoom -> coroutineScope.forgetRoom(forgetRoomAction) + JoinRoomEvent.ForgetRoom -> coroutineScope.forgetRoom(forgetRoomAction) } } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index e109bb1315..25f7765a7c 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -37,7 +37,7 @@ data class JoinRoomState( val knockMessage: String, val hideInviteAvatars: Boolean, val canReportRoom: Boolean, - val eventSink: (JoinRoomEvents) -> Unit + val eventSink: (JoinRoomEvent) -> Unit ) { val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoom.Failures.UnauthorizedJoin val joinAuthorisationStatus = when (contentState) { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStatePreviewParam.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStatePreviewParam.kt index f47377b16b..a09e33aded 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStatePreviewParam.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStatePreviewParam.kt @@ -207,7 +207,7 @@ fun aJoinRoomState( knockMessage: String = "", hideInviteAvatars: Boolean = false, canReportRoom: Boolean = true, - eventSink: (JoinRoomEvents) -> Unit = {} + eventSink: (JoinRoomEvent) -> Unit = {} ) = JoinRoomState( roomIdOrAlias = roomIdOrAlias, contentState = contentState, diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index f1adf1705f..3dd964610a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -115,33 +115,33 @@ fun JoinRoomView( contentState = state.contentState, knockMessage = state.knockMessage, hideAvatarsImages = state.hideAvatarsImages, - onKnockMessageUpdate = { state.eventSink(JoinRoomEvents.UpdateKnockMessage(it)) }, + onKnockMessageUpdate = { state.eventSink(JoinRoomEvent.UpdateKnockMessage(it)) }, ) }, footer = { JoinRoomFooter( joinAuthorisationStatus = state.joinAuthorisationStatus, onAcceptInvite = { inviteData -> - state.eventSink(JoinRoomEvents.AcceptInvite(inviteData)) + state.eventSink(JoinRoomEvent.AcceptInvite(inviteData)) }, onDeclineInvite = { inviteData, blockUser -> if (state.canReportRoom && blockUser) { onDeclineInviteAndBlockUser(inviteData) } else { - state.eventSink(JoinRoomEvents.DeclineInvite(inviteData, blockUser = blockUser)) + state.eventSink(JoinRoomEvent.DeclineInvite(inviteData, blockUser = blockUser)) } }, onJoinRoom = { - state.eventSink(JoinRoomEvents.JoinRoom) + state.eventSink(JoinRoomEvent.JoinRoom) }, onKnockRoom = { - state.eventSink(JoinRoomEvents.KnockRoom) + state.eventSink(JoinRoomEvent.KnockRoom) }, onCancelKnock = { - state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = true)) + state.eventSink(JoinRoomEvent.CancelKnock(requiresConfirmation = true)) }, onForgetRoom = { - state.eventSink(JoinRoomEvents.ForgetRoom) + state.eventSink(JoinRoomEvent.ForgetRoom) }, onGoBack = onBackClick, ) @@ -152,9 +152,9 @@ fun JoinRoomView( RetryDialog( title = stringResource(R.string.screen_join_room_loading_alert_title), content = stringResource(CommonStrings.error_network_or_server_issue), - onRetry = { state.eventSink(JoinRoomEvents.RetryFetchingContent) }, + onRetry = { state.eventSink(JoinRoomEvent.RetryFetchingContent) }, onDismiss = { - state.eventSink(JoinRoomEvents.DismissErrorAndHideContent) + state.eventSink(JoinRoomEvent.DismissErrorAndHideContent) onBackClick() } ) @@ -166,7 +166,7 @@ fun JoinRoomView( errorTitle = { stringResource(CommonStrings.common_something_went_wrong) }, errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) }, onSuccess = { onJoinSuccess() }, - onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) }, + onErrorDismiss = { state.eventSink(JoinRoomEvent.ClearActionStates) }, ) } AsyncActionView( @@ -174,19 +174,19 @@ fun JoinRoomView( errorTitle = { stringResource(CommonStrings.common_something_went_wrong) }, errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) }, onSuccess = { onKnockSuccess() }, - onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) }, + onErrorDismiss = { state.eventSink(JoinRoomEvent.ClearActionStates) }, ) AsyncActionView( async = state.forgetAction, errorTitle = { stringResource(CommonStrings.common_something_went_wrong) }, errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) }, onSuccess = { onForgetSuccess() }, - onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) }, + onErrorDismiss = { state.eventSink(JoinRoomEvent.ClearActionStates) }, ) AsyncActionView( async = state.cancelKnockAction, onSuccess = { onCancelKnockSuccess() }, - onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) }, + onErrorDismiss = { state.eventSink(JoinRoomEvent.ClearActionStates) }, errorTitle = { stringResource(CommonStrings.common_something_went_wrong) }, errorMessage = { stringResource(CommonStrings.error_network_or_server_issue) }, confirmationDialog = { @@ -195,8 +195,8 @@ fun JoinRoomView( title = stringResource(R.string.screen_join_room_cancel_knock_alert_title), submitText = stringResource(R.string.screen_join_room_cancel_knock_alert_confirmation), cancelText = stringResource(CommonStrings.action_no), - onSubmitClick = { state.eventSink(JoinRoomEvents.CancelKnock(requiresConfirmation = false)) }, - onDismiss = { state.eventSink(JoinRoomEvents.ClearActionStates) }, + onSubmitClick = { state.eventSink(JoinRoomEvent.CancelKnock(requiresConfirmation = false)) }, + onDismiss = { state.eventSink(JoinRoomEvent.ClearActionStates) }, ) }, ) diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 41ed2cce40..3c104e47ae 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -12,7 +12,7 @@ import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invite.api.InviteData import io.element.android.features.invite.api.SeenInvitesStore -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState import io.element.android.features.invite.api.toInviteData @@ -365,7 +365,7 @@ class JoinRoomPresenterTest { @Test fun `present - when room is invited then accept and decline events are sent to acceptDeclinePresenter`() = runTest { - val eventSinkRecorder = lambdaRecorder { _: AcceptDeclineInviteEvents -> } + val eventSinkRecorder = lambdaRecorder { _: AcceptDeclineInviteEvent -> } val acceptDeclinePresenter = Presenter { anAcceptDeclineInviteState(eventSink = eventSinkRecorder) } @@ -388,14 +388,14 @@ class JoinRoomPresenterTest { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.AcceptInvite(inviteData)) - state.eventSink(JoinRoomEvents.DeclineInvite(inviteData, false)) + state.eventSink(JoinRoomEvent.AcceptInvite(inviteData)) + state.eventSink(JoinRoomEvent.DeclineInvite(inviteData, false)) assert(eventSinkRecorder) .isCalledExactly(2) .withSequence( - listOf(value(AcceptDeclineInviteEvents.AcceptInvite(inviteData))), - listOf(value(AcceptDeclineInviteEvents.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true))), + listOf(value(AcceptDeclineInviteEvent.AcceptInvite(inviteData))), + listOf(value(AcceptDeclineInviteEvent.DeclineInvite(inviteData, blockUser = false, shouldConfirm = true))), ) } } @@ -422,7 +422,7 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.JoinRoom) + state.eventSink(JoinRoomEvent.JoinRoom) } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Loading) @@ -453,14 +453,14 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.JoinRoom) + state.eventSink(JoinRoomEvent.JoinRoom) } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Loading) } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - state.eventSink(JoinRoomEvents.ClearActionStates) + state.eventSink(JoinRoomEvent.ClearActionStates) } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Uninitialized) @@ -480,7 +480,7 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.JoinRoom) + state.eventSink(JoinRoomEvent.JoinRoom) } awaitItem().also { state -> assertThat(state.joinAction).isEqualTo(AsyncAction.Loading) @@ -683,17 +683,17 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.UpdateKnockMessage(knockMessage)) + state.eventSink(JoinRoomEvent.UpdateKnockMessage(knockMessage)) } awaitItem().also { state -> - state.eventSink(JoinRoomEvents.KnockRoom) + state.eventSink(JoinRoomEvent.KnockRoom) } assertThat(awaitItem().knockAction).isEqualTo(AsyncAction.Loading) awaitItem().also { state -> assertThat(state.knockAction).isEqualTo(AsyncAction.Success(Unit)) fakeKnockRoom.lambda = knockRoomFailure - state.eventSink(JoinRoomEvents.KnockRoom) + state.eventSink(JoinRoomEvent.KnockRoom) } assertThat(awaitItem().knockAction).isEqualTo(AsyncAction.Loading) @@ -731,17 +731,17 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.CancelKnock(true)) + state.eventSink(JoinRoomEvent.CancelKnock(true)) } awaitItem().also { state -> assertThat(state.cancelKnockAction).isEqualTo(AsyncAction.ConfirmingNoParams) - state.eventSink(JoinRoomEvents.CancelKnock(false)) + state.eventSink(JoinRoomEvent.CancelKnock(false)) } assertThat(awaitItem().cancelKnockAction).isEqualTo(AsyncAction.Loading) awaitItem().also { state -> assertThat(state.cancelKnockAction).isEqualTo(AsyncAction.Success(Unit)) cancelKnockRoom.lambda = cancelKnockRoomFailure - state.eventSink(JoinRoomEvents.CancelKnock(false)) + state.eventSink(JoinRoomEvent.CancelKnock(false)) } assertThat(awaitItem().cancelKnockAction).isEqualTo(AsyncAction.Loading) awaitItem().also { state -> @@ -778,14 +778,14 @@ class JoinRoomPresenterTest { presenter.test { skipItems(1) awaitItem().also { state -> - state.eventSink(JoinRoomEvents.ForgetRoom) + state.eventSink(JoinRoomEvent.ForgetRoom) } assertThat(awaitItem().forgetAction).isEqualTo(AsyncAction.Loading) awaitItem().also { state -> assertThat(state.forgetAction).isEqualTo(AsyncAction.Success(Unit)) fakeForgetRoom.lambda = forgetRoomFailure - state.eventSink(JoinRoomEvents.ForgetRoom) + state.eventSink(JoinRoomEvent.ForgetRoom) } assertThat(awaitItem().forgetAction).isEqualTo(AsyncAction.Loading) diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt index 0c510cacdb..c74e81010d 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt @@ -34,7 +34,7 @@ import org.junit.Test class JoinRoomViewTest : RobolectricTest() { @Test fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setJoinRoomView( aJoinRoomState( @@ -48,7 +48,7 @@ class JoinRoomViewTest : RobolectricTest() { @Test fun `clicking on Join room on CanJoin room emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin), @@ -56,12 +56,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_join_room_join_action) - eventsRecorder.assertSingle(JoinRoomEvents.JoinRoom) + eventsRecorder.assertSingle(JoinRoomEvent.JoinRoom) } @Test fun `clicking on Knock room on CanKnock room emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), @@ -70,12 +70,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_join_room_knock_action) - eventsRecorder.assertSingle(JoinRoomEvents.KnockRoom) + eventsRecorder.assertSingle(JoinRoomEvent.KnockRoom) } @Test fun `clicking on closing Knock error emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), @@ -84,12 +84,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) + eventsRecorder.assertSingle(JoinRoomEvent.ClearActionStates) } @Test fun `clicking on cancel knock request emit the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked), @@ -97,12 +97,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_join_room_cancel_knock_action) - eventsRecorder.assertSingle(JoinRoomEvents.CancelKnock(true)) + eventsRecorder.assertSingle(JoinRoomEvent.CancelKnock(true)) } @Test fun `clicking on closing Cancel Knock error emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked), @@ -111,12 +111,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) + eventsRecorder.assertSingle(JoinRoomEvent.ClearActionStates) } @Test fun `clicking on closing Join error emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock), @@ -125,12 +125,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates) + eventsRecorder.assertSingle(JoinRoomEvent.ClearActionStates) } @Test fun `when joining room is successful, the expected callback is invoked`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setJoinRoomView( aJoinRoomState( @@ -144,7 +144,7 @@ class JoinRoomViewTest : RobolectricTest() { @Test fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val inviteData = anInviteData() setJoinRoomView( aJoinRoomState( @@ -153,12 +153,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_accept) - eventsRecorder.assertSingle(JoinRoomEvents.AcceptInvite(inviteData)) + eventsRecorder.assertSingle(JoinRoomEvent.AcceptInvite(inviteData)) } @Test fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val inviteData = anInviteData() setJoinRoomView( aJoinRoomState( @@ -167,13 +167,13 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_decline) - eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, false)) + eventsRecorder.assertSingle(JoinRoomEvent.DeclineInvite(inviteData, false)) } @Test fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and can report room, the expected callback is invoked`() { runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val inviteData = anInviteData() val joinRoomState = aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())), @@ -192,7 +192,7 @@ class JoinRoomViewTest : RobolectricTest() { @Test fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val inviteData = anInviteData() val joinRoomState = aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())), @@ -201,12 +201,12 @@ class JoinRoomViewTest : RobolectricTest() { ) setJoinRoomView(state = joinRoomState) clickOn(R.string.screen_join_room_decline_and_block_button_title) - eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true)) + eventsRecorder.assertSingle(JoinRoomEvent.DeclineInvite(inviteData, true)) } @Test fun `clicking on Retry when an error occurs emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aFailureContentState(), @@ -214,12 +214,12 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_retry) - eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent) + eventsRecorder.assertSingle(JoinRoomEvent.RetryFetchingContent) } @Test fun `clicking on ok when user is unauthorized the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setJoinRoomView( aJoinRoomState( @@ -235,7 +235,7 @@ class JoinRoomViewTest : RobolectricTest() { @Test fun `clicking on forget when user is banned invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setJoinRoomView( aJoinRoomState( contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(null, null)), @@ -243,7 +243,7 @@ class JoinRoomViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_join_room_forget_action) - eventsRecorder.assertSingle(JoinRoomEvents.ForgetRoom) + eventsRecorder.assertSingle(JoinRoomEvent.ForgetRoom) } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvent.kt similarity index 65% rename from features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt rename to features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvent.kt index 740982b612..b8cd9345c2 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.knockrequests.impl.banner -sealed interface KnockRequestsBannerEvents { - data object AcceptSingleRequest : KnockRequestsBannerEvents - data object Dismiss : KnockRequestsBannerEvents +sealed interface KnockRequestsBannerEvent { + data object AcceptSingleRequest : KnockRequestsBannerEvent + data object Dismiss : KnockRequestsBannerEvent } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index f340d597b1..a96b6d3de3 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -53,15 +53,15 @@ class KnockRequestsBannerPresenter( } } - fun handleEvent(event: KnockRequestsBannerEvents) { + fun handleEvent(event: KnockRequestsBannerEvent) { when (event) { - is KnockRequestsBannerEvents.AcceptSingleRequest -> { + is KnockRequestsBannerEvent.AcceptSingleRequest -> { sessionCoroutineScope.acceptSingleKnockRequest( knockRequests = knockRequests, displayAcceptError = showAcceptError, ) } - is KnockRequestsBannerEvents.Dismiss -> { + is KnockRequestsBannerEvent.Dismiss -> { sessionCoroutineScope.launch { knockRequestsService.markAllKnockRequestsAsSeen() } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index e9291b501a..2b6532c355 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -20,7 +20,7 @@ data class KnockRequestsBannerState( val knockRequests: ImmutableList, val displayAcceptError: Boolean, val canAccept: Boolean, - val eventSink: (KnockRequestsBannerEvents) -> Unit, + val eventSink: (KnockRequestsBannerEvent) -> Unit, ) { val subtitle = knockRequests.singleOrNull()?.userId?.value val reason = knockRequests.singleOrNull()?.reason diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStatePreviewParam.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStatePreviewParam.kt index fb05ae1fe0..8df5e08985 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStatePreviewParam.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStatePreviewParam.kt @@ -64,7 +64,7 @@ fun aKnockRequestsBannerState( displayAcceptError: Boolean = false, canAccept: Boolean = true, isVisible: Boolean = true, - eventSink: (KnockRequestsBannerEvents) -> Unit = {} + eventSink: (KnockRequestsBannerEvent) -> Unit = {} ) = KnockRequestsBannerState( knockRequests = knockRequests.toImmutableList(), displayAcceptError = displayAcceptError, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 037ec16d00..329e7a2a56 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -109,11 +109,11 @@ private fun KnockRequestsBannerContent( modifier: Modifier = Modifier, ) { fun onDismissClick() { - state.eventSink(KnockRequestsBannerEvents.Dismiss) + state.eventSink(KnockRequestsBannerEvent.Dismiss) } fun onAcceptClick() { - state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + state.eventSink(KnockRequestsBannerEvent.AcceptSingleRequest) } Column( diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvent.kt similarity index 61% rename from features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt rename to features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvent.kt index f1350b1669..6b9a6ac1a8 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvent.kt @@ -10,12 +10,12 @@ package io.element.android.features.knockrequests.impl.list import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable -sealed interface KnockRequestsListEvents { - data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents - data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents - data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents - data object AcceptAll : KnockRequestsListEvents - data object ResetCurrentAction : KnockRequestsListEvents - data object RetryCurrentAction : KnockRequestsListEvents - data object ConfirmCurrentAction : KnockRequestsListEvents +sealed interface KnockRequestsListEvent { + data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvent + data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvent + data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvent + data object AcceptAll : KnockRequestsListEvent + data object ResetCurrentAction : KnockRequestsListEvent + data object RetryCurrentAction : KnockRequestsListEvent + data object ConfirmCurrentAction : KnockRequestsListEvent } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index a56dc40731..76b562d8b3 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -39,28 +39,28 @@ class KnockRequestsListPresenter( val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: KnockRequestsListEvents) { + fun handleEvent(event: KnockRequestsListEvent) { when (event) { - KnockRequestsListEvents.AcceptAll -> { + KnockRequestsListEvent.AcceptAll -> { currentAction = KnockRequestsAction.AcceptAll } - is KnockRequestsListEvents.Accept -> { + is KnockRequestsListEvent.Accept -> { currentAction = KnockRequestsAction.Accept(event.knockRequest) } - is KnockRequestsListEvents.Decline -> { + is KnockRequestsListEvent.Decline -> { currentAction = KnockRequestsAction.Decline(event.knockRequest) } - is KnockRequestsListEvents.DeclineAndBan -> { + is KnockRequestsListEvent.DeclineAndBan -> { currentAction = KnockRequestsAction.DeclineAndBan(event.knockRequest) } - KnockRequestsListEvents.ResetCurrentAction -> { + KnockRequestsListEvent.ResetCurrentAction -> { asyncAction.value = AsyncAction.Uninitialized currentAction = KnockRequestsAction.None } - KnockRequestsListEvents.RetryCurrentAction -> { + KnockRequestsListEvent.RetryCurrentAction -> { coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true) } - KnockRequestsListEvents.ConfirmCurrentAction -> { + KnockRequestsListEvent.ConfirmCurrentAction -> { coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true) } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index ae770a297d..0a0e576eae 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -20,7 +20,7 @@ data class KnockRequestsListState( val currentAction: KnockRequestsAction, val asyncAction: AsyncAction, val permissions: KnockRequestPermissions, - val eventSink: (KnockRequestsListEvents) -> Unit, + val eventSink: (KnockRequestsListEvent) -> Unit, ) { val canAcceptAll = permissions.canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1 } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStatePreviewParam.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStatePreviewParam.kt index b51223a5e9..691d874af6 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStatePreviewParam.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStatePreviewParam.kt @@ -140,7 +140,7 @@ fun aKnockRequestsListState( canDecline = true, canBan = true, ), - eventSink: (KnockRequestsListEvents) -> Unit = {}, + eventSink: (KnockRequestsListEvent) -> Unit = {}, ) = KnockRequestsListState( knockRequests = knockRequests, currentAction = currentAction, diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index d750927f74..52b11bc8c4 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -108,15 +108,15 @@ private fun KnockRequestsListContent( modifier: Modifier = Modifier, ) { fun onAcceptClick(knockRequest: KnockRequestPresentable) { - state.eventSink(KnockRequestsListEvents.Accept(knockRequest)) + state.eventSink(KnockRequestsListEvent.Accept(knockRequest)) } fun onDeclineClick(knockRequest: KnockRequestPresentable) { - state.eventSink(KnockRequestsListEvents.Decline(knockRequest)) + state.eventSink(KnockRequestsListEvent.Decline(knockRequest)) } fun onBanClick(knockRequest: KnockRequestPresentable) { - state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequest)) + state.eventSink(KnockRequestsListEvent.DeclineAndBan(knockRequest)) } var bottomPaddingInPixels by remember { mutableIntStateOf(0) } @@ -160,19 +160,19 @@ private fun KnockRequestsListContent( currentAction = state.currentAction, asyncAction = state.asyncAction, onConfirm = { - state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + state.eventSink(KnockRequestsListEvent.ConfirmCurrentAction) }, onRetry = { - state.eventSink(KnockRequestsListEvents.RetryCurrentAction) + state.eventSink(KnockRequestsListEvent.RetryCurrentAction) }, onDismiss = { - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) }, ) if (state.canAcceptAll) { KnockRequestsAcceptAll( onClick = { - state.eventSink(KnockRequestsListEvents.AcceptAll) + state.eventSink(KnockRequestsListEvent.AcceptAll) }, onHeightChange = { height -> bottomPaddingInPixels = height diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt index 1595fcdbd9..2c1a185ba2 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt @@ -146,7 +146,7 @@ import org.junit.Test presenter.test { skipItems(2) awaitItem().also { state -> - state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + state.eventSink(KnockRequestsBannerEvent.AcceptSingleRequest) } awaitItem().also { state -> assertThat(state.isVisible).isFalse() @@ -182,7 +182,7 @@ import org.junit.Test skipItems(2) awaitItem().also { state -> assertThat(state.knockRequests).hasSize(1) - state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + state.eventSink(KnockRequestsBannerEvent.AcceptSingleRequest) } awaitItem().also { state -> assertThat(state.isVisible).isFalse() @@ -206,7 +206,7 @@ import org.junit.Test presenter.test { skipItems(2) awaitItem().also { state -> - state.eventSink(KnockRequestsBannerEvents.Dismiss) + state.eventSink(KnockRequestsBannerEvent.Dismiss) } advanceUntilIdle() assert(markAsSeenLambda).isCalledExactly(3) diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt index f55ccb0cef..441b782a4a 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt @@ -29,7 +29,7 @@ import org.junit.Test class KnockRequestsBannerViewTest : RobolectricTest() { @Test fun `clicking on view on single request invoke the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setKnockRequestsBannerView( state = aKnockRequestsBannerState( @@ -43,7 +43,7 @@ class KnockRequestsBannerViewTest : RobolectricTest() { @Test fun `clicking on view all when multiple requests invoke the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setKnockRequestsBannerView( state = aKnockRequestsBannerState( @@ -62,19 +62,19 @@ class KnockRequestsBannerViewTest : RobolectricTest() { @Test fun `clicking on accept on a single request emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setKnockRequestsBannerView( state = aKnockRequestsBannerState( eventSink = eventsRecorder, ), ) clickOn(CommonStrings.action_accept) - eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest) + eventsRecorder.assertSingle(KnockRequestsBannerEvent.AcceptSingleRequest) } @Test fun `clicking on dismiss emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setKnockRequestsBannerView( state = aKnockRequestsBannerState( eventSink = eventsRecorder, @@ -82,7 +82,7 @@ class KnockRequestsBannerViewTest : RobolectricTest() { ) val close = activity!!.getString(CommonStrings.action_close) onNodeWithContentDescription(close).performClick() - eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss) + eventsRecorder.assertSingle(KnockRequestsBannerEvent.Dismiss) } } diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt index 209e67cadf..dc04889d32 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt @@ -65,7 +65,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable)) + state.eventSink(KnockRequestsListEvent.Accept(knockRequestPresentable)) } skipItems(1) awaitItem().also { state -> @@ -75,7 +75,7 @@ class KnockRequestsListPresenterTest { } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) } skipItems(2) awaitItem().also { state -> @@ -99,7 +99,7 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable)) + state.eventSink(KnockRequestsListEvent.Accept(knockRequestPresentable)) } skipItems(1) awaitItem().also { state -> @@ -109,14 +109,14 @@ class KnockRequestsListPresenterTest { } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java) - state.eventSink(KnockRequestsListEvents.RetryCurrentAction) + state.eventSink(KnockRequestsListEvent.RetryCurrentAction) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java) - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) } skipItems(1) awaitItem().also { state -> @@ -140,21 +140,21 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - state.eventSink(KnockRequestsListEvents.Decline(knockRequestPresentable)) + state.eventSink(KnockRequestsListEvent.Decline(knockRequestPresentable)) } skipItems(1) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Decline(knockRequestPresentable)) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) - state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + state.eventSink(KnockRequestsListEvent.ConfirmCurrentAction) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) } skipItems(2) awaitItem().also { state -> @@ -178,21 +178,21 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! - state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequestPresentable)) + state.eventSink(KnockRequestsListEvent.DeclineAndBan(knockRequestPresentable)) } skipItems(1) awaitItem().also { state -> val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!! assertThat(state.currentAction).isEqualTo(KnockRequestsAction.DeclineAndBan(knockRequestPresentable)) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) - state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + state.eventSink(KnockRequestsListEvent.ConfirmCurrentAction) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) } skipItems(2) awaitItem().also { state -> @@ -220,20 +220,20 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.canAcceptAll).isTrue() - state.eventSink(KnockRequestsListEvents.AcceptAll) + state.eventSink(KnockRequestsListEvent.AcceptAll) } skipItems(1) awaitItem().also { state -> assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) - state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + state.eventSink(KnockRequestsListEvent.ConfirmCurrentAction) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java) - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) } skipItems(2) awaitItem().also { state -> @@ -262,20 +262,20 @@ class KnockRequestsListPresenterTest { skipItems(2) awaitItem().also { state -> assertThat(state.canAcceptAll).isTrue() - state.eventSink(KnockRequestsListEvents.AcceptAll) + state.eventSink(KnockRequestsListEvent.AcceptAll) } skipItems(1) awaitItem().also { state -> assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll) assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java) - state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction) + state.eventSink(KnockRequestsListEvent.ConfirmCurrentAction) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java) - state.eventSink(KnockRequestsListEvents.ResetCurrentAction) + state.eventSink(KnockRequestsListEvent.ResetCurrentAction) } skipItems(2) awaitItem().also { state -> diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt index 1fbcb67b21..c1caaddb1e 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt @@ -31,7 +31,7 @@ import org.junit.Test class KnockRequestsListViewTest : RobolectricTest() { @Test fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setKnockRequestsListView( aKnockRequestsListState( @@ -45,7 +45,7 @@ class KnockRequestsListViewTest : RobolectricTest() { @Test fun `clicking on accept emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequest = aKnockRequestPresentable() setKnockRequestsListView( aKnockRequestsListState( @@ -54,12 +54,12 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_accept) - eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest)) + eventsRecorder.assertSingle(KnockRequestsListEvent.Accept(knockRequest)) } @Test fun `clicking on decline emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequest = aKnockRequestPresentable() setKnockRequestsListView( aKnockRequestsListState( @@ -68,12 +68,12 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_decline) - eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest)) + eventsRecorder.assertSingle(KnockRequestsListEvent.Decline(knockRequest)) } @Test fun `clicking on decline and ban emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequest = aKnockRequestPresentable() setKnockRequestsListView( aKnockRequestsListState( @@ -82,12 +82,12 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title) - eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest)) + eventsRecorder.assertSingle(KnockRequestsListEvent.DeclineAndBan(knockRequest)) } @Test fun `clicking on accept all emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) setKnockRequestsListView( aKnockRequestsListState( @@ -96,12 +96,12 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_knock_requests_list_accept_all_button_title) - eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll) + eventsRecorder.assertSingle(KnockRequestsListEvent.AcceptAll) } @Test fun `retry on async view retry emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) setKnockRequestsListView( aKnockRequestsListState( @@ -112,12 +112,12 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_retry) - eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction) + eventsRecorder.assertSingle(KnockRequestsListEvent.RetryCurrentAction) } @Test fun `canceling async view emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) setKnockRequestsListView( aKnockRequestsListState( @@ -128,12 +128,12 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction) + eventsRecorder.assertSingle(KnockRequestsListEvent.ResetCurrentAction) } @Test fun `confirming async view emit the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable()) setKnockRequestsListView( aKnockRequestsListState( @@ -144,7 +144,7 @@ class KnockRequestsListViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title) - eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction) + eventsRecorder.assertSingle(KnockRequestsListEvent.ConfirmCurrentAction) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt index 5aa7ae6f63..01fa8bca7e 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt @@ -41,9 +41,9 @@ class DefaultPermissionsPresenter( permissionsRequested = true } - fun handleEvent(event: PermissionsEvents) { + fun handleEvent(event: PermissionsEvent) { when (event) { - PermissionsEvents.RequestPermissions -> { + PermissionsEvent.RequestPermissions -> { multiplePermissionsState.launchMultiplePermissionRequest() } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvent.kt similarity index 77% rename from features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt rename to features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvent.kt index a4b5ef1228..226909af5d 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.location.impl.common.permissions -sealed interface PermissionsEvents { - data object RequestPermissions : PermissionsEvents +sealed interface PermissionsEvent { + data object RequestPermissions : PermissionsEvent } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt index 25ceb3ba29..a8dfd571e5 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt @@ -12,7 +12,7 @@ data class PermissionsState( val permissions: Permissions, val shouldShowRationale: Boolean, val permissionsAlreadyRequested: Boolean, - val eventSink: (PermissionsEvents) -> Unit, + val eventSink: (PermissionsEvent) -> Unit, ) { sealed interface Permissions { data object AllGranted : Permissions diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt index 55f1e088d2..ccdf598e93 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenter.kt @@ -29,7 +29,7 @@ import io.element.android.features.location.impl.common.MapDefaults import io.element.android.features.location.impl.common.SendLiveLocationPermissions import io.element.android.features.location.impl.common.actions.LocationActions import io.element.android.features.location.impl.common.checkLocationConstraints -import io.element.android.features.location.impl.common.permissions.PermissionsEvents +import io.element.android.features.location.impl.common.permissions.PermissionsEvent import io.element.android.features.location.impl.common.permissions.PermissionsPresenter import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.location.impl.common.sendLiveLocationPermissions @@ -111,7 +111,7 @@ class ShareLocationPresenter( sendLiveLocationPermissions = SendLiveLocationPermissions.GRANTED ) if (locationConstraints is LocationConstraintsCheck.PermissionShouldBeRequested) { - permissionsState.eventSink(PermissionsEvents.RequestPermissions) + permissionsState.eventSink(PermissionsEvent.RequestPermissions) } trackUserPosition = locationConstraints is LocationConstraintsCheck.Success dialogState = ShareLocationState.Dialog.Constraints(locationConstraints.toDialogState()) @@ -137,7 +137,7 @@ class ShareLocationPresenter( } else -> { if (locationConstraints is LocationConstraintsCheck.PermissionShouldBeRequested) { - permissionsState.eventSink(PermissionsEvents.RequestPermissions) + permissionsState.eventSink(PermissionsEvent.RequestPermissions) } dialogState = ShareLocationState.Dialog.Constraints(locationConstraints.toDialogState()) } @@ -195,7 +195,7 @@ class ShareLocationPresenter( } ShareLocationEvent.RequestPermissions -> { dialogState = ShareLocationState.Dialog.None - permissionsState.eventSink(PermissionsEvents.RequestPermissions) + permissionsState.eventSink(PermissionsEvent.RequestPermissions) } } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt index bbec7b449f..412e96eb9c 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt @@ -32,7 +32,7 @@ import io.element.android.features.location.impl.common.MapDefaults import io.element.android.features.location.impl.common.SendLiveLocationPermissions import io.element.android.features.location.impl.common.actions.LocationActions import io.element.android.features.location.impl.common.checkLocationConstraints -import io.element.android.features.location.impl.common.permissions.PermissionsEvents +import io.element.android.features.location.impl.common.permissions.PermissionsEvent import io.element.android.features.location.impl.common.permissions.PermissionsPresenter import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.location.impl.common.toDialogState @@ -99,7 +99,7 @@ class ShowLocationPresenter( sendLiveLocationPermissions = SendLiveLocationPermissions.GRANTED ) if (locationConstraints is LocationConstraintsCheck.PermissionShouldBeRequested) { - permissionsState.eventSink(PermissionsEvents.RequestPermissions) + permissionsState.eventSink(PermissionsEvent.RequestPermissions) } isTrackMyLocation = locationConstraints is LocationConstraintsCheck.Success dialogState = locationConstraints.toDialogState() @@ -130,7 +130,7 @@ class ShowLocationPresenter( locationActions.openLocationSettings() dialogState = LocationConstraintsDialogState.None } - ShowLocationEvent.RequestPermissions -> permissionsState.eventSink(PermissionsEvents.RequestPermissions) + ShowLocationEvent.RequestPermissions -> permissionsState.eventSink(PermissionsEvent.RequestPermissions) ShowLocationEvent.StopLocationSharing -> coroutineScope.launch { liveLocationShareManager.stopShare(joinedRoom.roomId) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt index 9b8ff7a29a..c25cbc24fc 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt @@ -11,9 +11,9 @@ package io.element.android.features.location.impl.common.permissions import androidx.compose.runtime.Composable class FakePermissionsPresenter : PermissionsPresenter { - val events = mutableListOf() + val events = mutableListOf() - private fun handleEvent(event: PermissionsEvents) { + private fun handleEvent(event: PermissionsEvent) { events += event } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt index d249fdaf69..7500d29616 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/share/ShareLocationPresenterTest.kt @@ -26,7 +26,7 @@ import io.element.android.features.location.impl.aPermissionsState import io.element.android.features.location.impl.common.FakeUserLocationStateFactory import io.element.android.features.location.impl.common.actions.FakeLocationActions import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter -import io.element.android.features.location.impl.common.permissions.PermissionsEvents +import io.element.android.features.location.impl.common.permissions.PermissionsEvent import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.impl.live.LiveLocationStore @@ -174,7 +174,7 @@ class ShareLocationPresenterTest { shareLocationPresenter.test { skipItems(2) cancelAndIgnoreRemainingEvents() - assertThat(fakePermissionsPresenter.events).contains(PermissionsEvents.RequestPermissions) + assertThat(fakePermissionsPresenter.events).contains(PermissionsEvent.RequestPermissions) } } @@ -298,7 +298,7 @@ class ShareLocationPresenterTest { // Wait for dialog to be dismissed awaitItem() - assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvents.RequestPermissions) + assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvent.RequestPermissions) cancelAndIgnoreRemainingEvents() } } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt index 7d9b0d5e03..1fab0d98af 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt @@ -20,7 +20,7 @@ import io.element.android.features.location.impl.aPermissionsState import io.element.android.features.location.impl.common.FakeUserLocationStateFactory import io.element.android.features.location.impl.common.actions.FakeLocationActions import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter -import io.element.android.features.location.impl.common.permissions.PermissionsEvents +import io.element.android.features.location.impl.common.permissions.PermissionsEvent import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState import io.element.android.features.location.test.FakeActiveLiveLocationShareManager @@ -188,7 +188,7 @@ class ShowLocationPresenterTest { // Continue the dialog sends permission request to the permissions presenter trackLocationState.eventSink(ShowLocationEvent.RequestPermissions) - assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvents.RequestPermissions) + assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvent.RequestPermissions) } } @@ -208,7 +208,7 @@ class ShowLocationPresenterTest { initialState.eventSink(ShowLocationEvent.TrackMyLocation(true)) - assertThat(fakePermissionsPresenter.events).contains(PermissionsEvents.RequestPermissions) + assertThat(fakePermissionsPresenter.events).contains(PermissionsEvent.RequestPermissions) cancelAndIgnoreRemainingEvents() } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt similarity index 83% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt index 4f0d228dd2..f3fd02713f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvent.kt @@ -10,9 +10,9 @@ package io.element.android.features.login.impl.changeserver import io.element.android.features.login.impl.accountprovider.AccountProvider -sealed interface ChangeServerEvents { - data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents - data object ClearError : ChangeServerEvents - data object RequestLocalNetworkPermission : ChangeServerEvents - data object DismissLocalNetworkPermission : ChangeServerEvents +sealed interface ChangeServerEvent { + data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvent + data object ClearError : ChangeServerEvent + data object RequestLocalNetworkPermission : ChangeServerEvent + data object DismissLocalNetworkPermission : ChangeServerEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index b7b04beeb2..9106d6e1c4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -42,12 +42,12 @@ class ChangeServerPresenter( onProceed = { provider -> changeServer(provider, changeServerAction) }, ) - fun handleEvent(event: ChangeServerEvents) { + fun handleEvent(event: ChangeServerEvent) { when (event) { - ChangeServerEvents.ClearError -> changeServerAction.value = AsyncData.Uninitialized - is ChangeServerEvents.ChangeServer -> gateState.submit(event.accountProvider) - ChangeServerEvents.DismissLocalNetworkPermission -> gateState.abort() - ChangeServerEvents.RequestLocalNetworkPermission -> gateState.requestPermission() + ChangeServerEvent.ClearError -> changeServerAction.value = AsyncData.Uninitialized + is ChangeServerEvent.ChangeServer -> gateState.submit(event.accountProvider) + ChangeServerEvent.DismissLocalNetworkPermission -> gateState.abort() + ChangeServerEvent.RequestLocalNetworkPermission -> gateState.requestPermission() } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt index 3b4f4d9dad..1b69551e94 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt @@ -17,5 +17,5 @@ data class ChangeServerState( // configuring (and re-networking) the homeserver a second time. val changeServerAction: AsyncData, val localNetworkPermissionDialog: LocalNetworkPermissionDialog, - val eventSink: (ChangeServerEvents) -> Unit + val eventSink: (ChangeServerEvent) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt index 8e726c7fe1..e629c6bce3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt @@ -47,7 +47,7 @@ fun ChangeServerView( modifier = modifier, content = stringResource(R.string.screen_change_server_error_invalid_homeserver), onSubmit = { - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) } ) ChangeServerError.UnsupportedServer -> @@ -55,7 +55,7 @@ fun ChangeServerView( modifier = modifier, content = stringResource(R.string.screen_login_error_unsupported_authentication), onSubmit = { - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) } ) is ChangeServerError.Error -> { @@ -63,7 +63,7 @@ fun ChangeServerView( modifier = modifier, content = error.messageStr ?: stringResource(CommonStrings.error_unknown), onSubmit = { - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) } ) } @@ -72,10 +72,10 @@ fun ChangeServerView( modifier = modifier, onLearnMoreClick = { onLearnMoreClick() - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) }, onDismiss = { - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) } ) } @@ -90,10 +90,10 @@ fun ChangeServerView( submitText = stringResource(R.string.screen_change_server_error_element_pro_required_action_android), onSubmitClick = { context.openGooglePlay(error.applicationId) - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) }, onDismiss = { - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) }, ) } @@ -106,7 +106,7 @@ fun ChangeServerView( error.unauthorisedAccountProviderTitle, ), onSubmit = { - eventSink.invoke(ChangeServerEvents.ClearError) + eventSink.invoke(ChangeServerEvent.ClearError) } ) } @@ -125,10 +125,10 @@ fun ChangeServerView( LocalNetworkPermissionDialogView( dialog = state.localNetworkPermissionDialog, onSubmit = { - eventSink.invoke(ChangeServerEvents.RequestLocalNetworkPermission) + eventSink.invoke(ChangeServerEvent.RequestLocalNetworkPermission) }, onDismiss = { - eventSink.invoke(ChangeServerEvents.DismissLocalNetworkPermission) + eventSink.invoke(ChangeServerEvent.DismissLocalNetworkPermission) } ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt similarity index 69% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt index f60cc3e9da..bb50f123cb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvent.kt @@ -10,8 +10,8 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider import io.element.android.features.login.impl.accountprovider.AccountProvider -sealed interface ChooseAccountProviderEvents { - data class SelectAccountProvider(val accountProvider: AccountProvider) : ChooseAccountProviderEvents - data object Continue : ChooseAccountProviderEvents - data object ClearError : ChooseAccountProviderEvents +sealed interface ChooseAccountProviderEvent { + data class SelectAccountProvider(val accountProvider: AccountProvider) : ChooseAccountProviderEvent + data object Continue : ChooseAccountProviderEvent + data object ClearError : ChooseAccountProviderEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt index 92c3ff0fff..e2863a295a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt @@ -34,9 +34,9 @@ class ChooseAccountProviderPresenter( val loginModeState = loginModePresenter.present() var selectedAccountProvider: AccountProvider? by remember { mutableStateOf(null) } - fun handleEvent(event: ChooseAccountProviderEvents) { + fun handleEvent(event: ChooseAccountProviderEvent) { when (event) { - ChooseAccountProviderEvents.Continue -> { + ChooseAccountProviderEvent.Continue -> { selectedAccountProvider?.let { provider -> loginModeState.eventSink( LoginModeEvent.Submit( @@ -48,13 +48,13 @@ class ChooseAccountProviderPresenter( ) } } - is ChooseAccountProviderEvents.SelectAccountProvider -> { + is ChooseAccountProviderEvent.SelectAccountProvider -> { // Ensure that the user does not change the server during processing. if (loginModeState.loginMode is AsyncData.Uninitialized) { selectedAccountProvider = event.accountProvider } } - ChooseAccountProviderEvents.ClearError -> loginModeState.eventSink(LoginModeEvent.ClearError) + ChooseAccountProviderEvent.ClearError -> loginModeState.eventSink(LoginModeEvent.ClearError) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt index a31e5c464f..398e32d687 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt @@ -17,7 +17,7 @@ data class ChooseAccountProviderState( val accountProviders: ImmutableList, val selectedAccountProvider: AccountProvider?, val loginModeState: LoginModeState, - val eventSink: (ChooseAccountProviderEvents) -> Unit, + val eventSink: (ChooseAccountProviderEvent) -> Unit, ) { val submitEnabled: Boolean get() = selectedAccountProvider != null && diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt index 207f434b42..ceb1cfd6ea 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStatePreviewParam.kt @@ -71,7 +71,7 @@ fun aChooseAccountProviderState( ), selectedAccountProvider: AccountProvider? = null, loginModeState: LoginModeState = aLoginModeState(), - eventSink: (ChooseAccountProviderEvents) -> Unit = {}, + eventSink: (ChooseAccountProviderEvent) -> Unit = {}, ) = ChooseAccountProviderState( accountProviders = accountProviders.toImmutableList(), selectedAccountProvider = selectedAccountProvider, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt index fdc69ca295..552165a7b2 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt @@ -108,7 +108,7 @@ fun ChooseAccountProviderView( item = alteredItem, selected = item == state.selectedAccountProvider, onClick = { - state.eventSink(ChooseAccountProviderEvents.SelectAccountProvider(item)) + state.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(item)) } ) } @@ -119,7 +119,7 @@ fun ChooseAccountProviderView( text = stringResource(id = CommonStrings.action_continue), showProgress = isLoading, onClick = { - state.eventSink(ChooseAccountProviderEvents.Continue) + state.eventSink(ChooseAccountProviderEvent.Continue) }, enabled = state.submitEnabled || isLoading, modifier = Modifier @@ -131,7 +131,7 @@ fun ChooseAccountProviderView( LoginModeView( loginMode = state.loginModeState.loginMode, onClearError = { - state.eventSink(ChooseAccountProviderEvents.ClearError) + state.eventSink(ChooseAccountProviderEvent.ClearError) }, onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvent.kt similarity index 81% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvent.kt index 9093a8121f..24b6fdfcda 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvent.kt @@ -8,11 +8,11 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider -sealed interface ConfirmAccountProviderEvents { - data class UserInputChanged(val accountProvider: String) : ConfirmAccountProviderEvents +sealed interface ConfirmAccountProviderEvent { + data class UserInputChanged(val accountProvider: String) : ConfirmAccountProviderEvent // Carries the account provider to submit (the current field text, or the accepted autocomplete suggestion), // captured from the view at click time so it can't lag behind fast input (e.g. keyboard autofill). - data class Continue(val accountProvider: String) : ConfirmAccountProviderEvents - data object ClearError : ConfirmAccountProviderEvents + data class Continue(val accountProvider: String) : ConfirmAccountProviderEvent + data object ClearError : ConfirmAccountProviderEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 5c66f8f446..627620ff31 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -24,7 +24,7 @@ import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource -import io.element.android.features.login.impl.changeserver.ChangeServerEvents +import io.element.android.features.login.impl.changeserver.ChangeServerEvent import io.element.android.features.login.impl.changeserver.ChangeServerState import io.element.android.features.login.impl.login.LoginModeEvent import io.element.android.features.login.impl.login.LoginModeState @@ -110,14 +110,14 @@ class ConfirmAccountProviderPresenter( } } - fun handleEvent(event: ConfirmAccountProviderEvents) { + fun handleEvent(event: ConfirmAccountProviderEvent) { when (event) { - is ConfirmAccountProviderEvents.UserInputChanged -> { + is ConfirmAccountProviderEvent.UserInputChanged -> { userInput = event.accountProvider } // Validate (and persist) the chosen account provider before proceeding. This also enforces the // account-provider access control, which the login submit does not run on its own. - is ConfirmAccountProviderEvents.Continue -> { + is ConfirmAccountProviderEvent.Continue -> { // Apply the accepted account provider (any accepted completion) back into the field so it // renders the full server rather than the typed prefix, and survives the OAuth round-trip. val accountProvider = event.accountProvider.trim() @@ -134,12 +134,12 @@ class ConfirmAccountProviderPresenter( val accountProviderUrl = host.ensureProtocol() submittedAccountProvider = accountProviderUrl changeServerState.eventSink( - ChangeServerEvents.ChangeServer(AccountProvider(url = accountProviderUrl)) + ChangeServerEvent.ChangeServer(AccountProvider(url = accountProviderUrl)) ) } - ConfirmAccountProviderEvents.ClearError -> { + ConfirmAccountProviderEvent.ClearError -> { loginModeState.eventSink(LoginModeEvent.ClearError) - changeServerState.eventSink(ChangeServerEvents.ClearError) + changeServerState.eventSink(ChangeServerEvent.ClearError) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt index bf6b0cb0e7..eec082d886 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt @@ -19,7 +19,7 @@ data class ConfirmAccountProviderState( val isAccountCreation: Boolean, val loginModeState: LoginModeState, val changeServerState: ChangeServerState, - val eventSink: (ConfirmAccountProviderEvents) -> Unit + val eventSink: (ConfirmAccountProviderEvent) -> Unit ) { val submitEnabled: Boolean get() = accountProviderInput.isNotBlank() && diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStatePreviewParam.kt index ab6350bb4d..be85a306ba 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStatePreviewParam.kt @@ -40,7 +40,7 @@ private fun aConfirmAccountProviderState( isAccountCreation: Boolean = false, loginModeState: LoginModeState = aLoginModeState(), changeServerState: ChangeServerState = aChangeServerState(), - eventSink: (ConfirmAccountProviderEvents) -> Unit = {}, + eventSink: (ConfirmAccountProviderEvent) -> Unit = {}, ) = ConfirmAccountProviderState( accountProviderInput = accountProviderInput, accountProviderSuggestion = accountProviderSuggestion, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index 1dab5cd31c..3f08b269a1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -99,7 +99,7 @@ fun ConfirmAccountProviderView( // Dismiss the keyboard and release focus while the account provider is being validated. focusManager.clearFocus(force = true) // Submit the exact field text (plus any accepted suggestion). - eventSink(ConfirmAccountProviderEvents.Continue(accountProviderToSubmit)) + eventSink(ConfirmAccountProviderEvent.Continue(accountProviderToSubmit)) } // Once a validation / login error has been dismissed, return focus and the keyboard to the field so @@ -115,6 +115,9 @@ fun ConfirmAccountProviderView( HeaderFooterPage( modifier = modifier, + // Let the header and content scroll so the supporting text is not clipped by the footer when the + // keyboard is visible and vertical space is reduced. + isScrollable = true, header = { IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 60.dp), @@ -151,7 +154,7 @@ fun ConfirmAccountProviderView( value = input, onValueChange = { input = it - eventSink(ConfirmAccountProviderEvents.UserInputChanged(it)) + eventSink(ConfirmAccountProviderEvent.UserInputChanged(it)) }, modifier = Modifier .fillMaxWidth() @@ -181,7 +184,7 @@ fun ConfirmAccountProviderView( // showing, Done submits directly. val accepted = input + suggestionSuffix input = accepted - eventSink(ConfirmAccountProviderEvents.UserInputChanged(accepted)) + eventSink(ConfirmAccountProviderEvent.UserInputChanged(accepted)) focusManager.clearFocus(force = true) } else { submit() @@ -197,7 +200,7 @@ fun ConfirmAccountProviderView( role = Role.Button, ) { input = "" - eventSink(ConfirmAccountProviderEvents.UserInputChanged("")) + eventSink(ConfirmAccountProviderEvent.UserInputChanged("")) } ) { Icon( @@ -222,7 +225,7 @@ fun ConfirmAccountProviderView( LoginModeView( loginMode = state.loginModeState.loginMode, - onClearError = { eventSink(ConfirmAccountProviderEvents.ClearError) }, + onClearError = { eventSink(ConfirmAccountProviderEvent.ClearError) }, onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvent.kt similarity index 64% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvent.kt index 25a003b531..fa9acb5632 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.login.impl.screens.loginpassword -sealed interface LoginPasswordEvents { - data class SetLogin(val login: String) : LoginPasswordEvents - data class SetPassword(val password: String) : LoginPasswordEvents - data object Submit : LoginPasswordEvents - data object ClearError : LoginPasswordEvents +sealed interface LoginPasswordEvent { + data class SetLogin(val login: String) : LoginPasswordEvent + data class SetPassword(val password: String) : LoginPasswordEvent + data object Submit : LoginPasswordEvent + data object ClearError : LoginPasswordEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index 6e28bd8781..864bbc7808 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -58,18 +58,18 @@ class LoginPasswordPresenter( } val accountProvider by accountProviderDataSource.flow.collectAsState() - fun handleEvent(event: LoginPasswordEvents) { + fun handleEvent(event: LoginPasswordEvent) { when (event) { - is LoginPasswordEvents.SetLogin -> updateFormState(formState) { + is LoginPasswordEvent.SetLogin -> updateFormState(formState) { copy(login = event.login) } - is LoginPasswordEvents.SetPassword -> updateFormState(formState) { + is LoginPasswordEvent.SetPassword -> updateFormState(formState) { copy(password = event.password) } - LoginPasswordEvents.Submit -> { + LoginPasswordEvent.Submit -> { localCoroutineScope.submit(formState.value, loginAction) } - LoginPasswordEvents.ClearError -> loginAction.value = AsyncData.Uninitialized + LoginPasswordEvent.ClearError -> loginAction.value = AsyncData.Uninitialized } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt index d8adc7351f..db04e7b7ba 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt @@ -18,7 +18,7 @@ data class LoginPasswordState( val accountProvider: AccountProvider, val formState: LoginFormState, val loginAction: AsyncData, - val eventSink: (LoginPasswordEvents) -> Unit + val eventSink: (LoginPasswordEvent) -> Unit ) { val submitEnabled: Boolean get() = loginAction !is AsyncData.Failure && diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt index 4d7201dbc8..ba48e57b5d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStatePreviewParam.kt @@ -29,7 +29,7 @@ fun aLoginPasswordState( accountProvider: AccountProvider = anAccountProvider(), formState: LoginFormState = LoginFormState.Default, loginAction: AsyncData = AsyncData.Uninitialized, - eventSink: (LoginPasswordEvents) -> Unit = {}, + eventSink: (LoginPasswordEvent) -> Unit = {}, ) = LoginPasswordState( accountProvider = accountProvider, formState = formState, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index a168b14cc8..9a2dc3097b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -97,7 +97,7 @@ fun LoginPasswordView( autofillManager?.commit() - state.eventSink(LoginPasswordEvents.Submit) + state.eventSink(LoginPasswordEvent.Submit) } Scaffold( @@ -166,7 +166,7 @@ fun LoginPasswordView( if (state.loginAction is AsyncData.Failure) { LoginErrorDialog(error = state.loginAction.error, onDismiss = { - state.eventSink(LoginPasswordEvents.ClearError) + state.eventSink(LoginPasswordEvent.ClearError) }) } } @@ -202,7 +202,7 @@ private fun LoginForm( onValueChange = { val sanitized = it.sanitize() loginFieldState = sanitized - eventSink(LoginPasswordEvents.SetLogin(sanitized)) + eventSink(LoginPasswordEvent.SetLogin(sanitized)) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email, @@ -220,7 +220,7 @@ private fun LoginForm( role = Role.Button, ) { loginFieldState = "" - eventSink(LoginPasswordEvents.SetLogin("")) + eventSink(LoginPasswordEvent.SetLogin("")) } ) { Icon( @@ -254,7 +254,7 @@ private fun LoginForm( onValueChange = { val sanitized = it.sanitize() passwordFieldState = sanitized - eventSink(LoginPasswordEvents.SetPassword(sanitized)) + eventSink(LoginPasswordEvent.SetPassword(sanitized)) }, placeholder = stringResource(CommonStrings.common_password), visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt similarity index 70% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt index 6101b21ded..4b3993e2d1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvent.kt @@ -8,11 +8,11 @@ package io.element.android.features.login.impl.screens.onboarding -sealed interface OnBoardingEvents { +sealed interface OnBoardingEvent { data class OnSignIn( val defaultAccountProvider: String - ) : OnBoardingEvents + ) : OnBoardingEvent - data object OnVersionClick : OnBoardingEvents - data object ClearError : OnBoardingEvents + data object OnVersionClick : OnBoardingEvent + data object ClearError : OnBoardingEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index e9bdb5e1e9..7e48ae22e8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -101,9 +101,9 @@ class OnBoardingPresenter( val loginModeState = loginModePresenter.present() - fun handleEvent(event: OnBoardingEvents) { + fun handleEvent(event: OnBoardingEvent) { when (event) { - is OnBoardingEvents.OnSignIn -> localCoroutineScope.launch { + is OnBoardingEvent.OnSignIn -> localCoroutineScope.launch { // Ensure that the current account provider is set accountProviderDataSource.setUrl(event.defaultAccountProvider) loginModeState.eventSink( @@ -115,8 +115,8 @@ class OnBoardingPresenter( ) ) } - OnBoardingEvents.ClearError -> loginModeState.eventSink(LoginModeEvent.ClearError) - OnBoardingEvents.OnVersionClick -> { + OnBoardingEvent.ClearError -> loginModeState.eventSink(LoginModeEvent.ClearError) + OnBoardingEvent.OnVersionClick -> { if (canReportBug) { if (multipleTapToUnlock.unlock(localCoroutineScope)) { showReportBug = true diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt index 4fb4b8090e..97ebed67f6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt @@ -26,7 +26,7 @@ data class OnBoardingState( @DrawableRes val onBoardingLogoResId: Int?, val loginModeState: LoginModeState, - val eventSink: (OnBoardingEvents) -> Unit, + val eventSink: (OnBoardingEvent) -> Unit, ) { val submitEnabled: Boolean get() = defaultAccountProvider != null && diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt index d04f85f2a0..c7d89b21d5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStatePreviewParam.kt @@ -50,7 +50,7 @@ fun anOnBoardingState( @DrawableRes customLogoResId: Int? = null, loginModeState: LoginModeState = aLoginModeState(), - eventSink: (OnBoardingEvents) -> Unit = {}, + eventSink: (OnBoardingEvent) -> Unit = {}, ) = OnBoardingState( isAddingAccount = isAddingAccount, showBackButton = showBackButton, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt index 78faed7543..bd257d0891 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt @@ -78,7 +78,7 @@ fun OnBoardingView( LoginModeView( loginMode = state.loginModeState.loginMode, onClearError = { - state.eventSink(OnBoardingEvents.ClearError) + state.eventSink(OnBoardingEvent.ClearError) }, onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, @@ -306,7 +306,7 @@ private fun OnBoardingButtons( text = stringResource(id = R.string.screen_onboarding_sign_in_to, defaultAccountProvider), showProgress = isLoading, onClick = { - state.eventSink(OnBoardingEvents.OnSignIn(defaultAccountProvider)) + state.eventSink(OnBoardingEvent.OnSignIn(defaultAccountProvider)) }, enabled = state.submitEnabled || isLoading, modifier = Modifier @@ -336,7 +336,7 @@ private fun OnBoardingButtons( Text( modifier = Modifier .clickable(role = Role.Button) { - state.eventSink(OnBoardingEvents.OnVersionClick) + state.eventSink(OnBoardingEvent.OnVersionClick) } .padding(16.dp), text = stringResource(id = R.string.screen_onboarding_app_version, state.version), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvent.kt similarity index 79% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvent.kt index 11abd63683..a4471d8a92 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.login.impl.screens.qrcode.intro -sealed interface QrCodeIntroEvents { - data object Continue : QrCodeIntroEvents +sealed interface QrCodeIntroEvent { + data object Continue : QrCodeIntroEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt index 13888fe23f..0781942216 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt @@ -40,9 +40,9 @@ class QrCodeIntroPresenter( } } - fun handleEvent(event: QrCodeIntroEvents) { + fun handleEvent(event: QrCodeIntroEvent) { when (event) { - QrCodeIntroEvents.Continue -> if (cameraPermissionState.permissionGranted) { + QrCodeIntroEvent.Continue -> if (cameraPermissionState.permissionGranted) { canContinue = true } else { pendingPermissionRequest = true diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt index d20a3f58d9..ab84743404 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt @@ -15,5 +15,5 @@ data class QrCodeIntroState( val desktopAppName: String, val cameraPermissionState: PermissionsState, val canContinue: Boolean, - val eventSink: (QrCodeIntroEvents) -> Unit + val eventSink: (QrCodeIntroEvent) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStatePreviewParam.kt index b9c286dc16..5e5fbac8b1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStatePreviewParam.kt @@ -30,7 +30,7 @@ fun aQrCodeIntroState( permission = Manifest.permission.CAMERA, ), canContinue: Boolean = false, - eventSink: (QrCodeIntroEvents) -> Unit = {}, + eventSink: (QrCodeIntroEvent) -> Unit = {}, ) = QrCodeIntroState( appName = appName, desktopAppName = desktopAppName, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index 08a40b63c5..2691c0cbf5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -91,7 +91,7 @@ private fun ColumnScope.Buttons( text = stringResource(id = R.string.screen_qr_code_login_initial_state_button_title), modifier = Modifier.fillMaxWidth(), onClick = { - state.eventSink.invoke(QrCodeIntroEvents.Continue) + state.eventSink.invoke(QrCodeIntroEvent.Continue) } ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvent.kt similarity index 81% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvent.kt index f5804aceb0..d1c91b9b42 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.login.impl.screens.qrcode.scan -sealed interface QrCodeScanEvents { - data class QrCodeScanned(val code: ByteArray) : QrCodeScanEvents - data object TryAgain : QrCodeScanEvents +sealed interface QrCodeScanEvent { + data class QrCodeScanned(val code: ByteArray) : QrCodeScanEvent + data object TryAgain : QrCodeScanEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt index 5e298604a4..4075a9d2cd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt @@ -52,13 +52,13 @@ class QrCodeScanPresenter( authenticationAction.value = AsyncAction.Failure(it) } - fun handleEvent(event: QrCodeScanEvents) { + fun handleEvent(event: QrCodeScanEvent) { when (event) { - QrCodeScanEvents.TryAgain -> { + QrCodeScanEvent.TryAgain -> { isScanning = true authenticationAction.value = AsyncAction.Uninitialized } - is QrCodeScanEvents.QrCodeScanned -> { + is QrCodeScanEvent.QrCodeScanned -> { isScanning = false coroutineScope.getQrCodeData(authenticationAction, event.code) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt index 0e64bfc6a6..5d9fca2543 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt @@ -14,5 +14,5 @@ import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDat data class QrCodeScanState( val isScanning: Boolean, val authenticationAction: AsyncAction, - val eventSink: (QrCodeScanEvents) -> Unit + val eventSink: (QrCodeScanEvent) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStatePreviewParam.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStatePreviewParam.kt index 19385b6df1..24164b94c9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStatePreviewParam.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStatePreviewParam.kt @@ -46,7 +46,7 @@ open class QrCodeScanStatePreviewParam : PreviewParameterProvider = AsyncAction.Uninitialized, - eventSink: (QrCodeScanEvents) -> Unit = {}, + eventSink: (QrCodeScanEvent) -> Unit = {}, ) = QrCodeScanState( isScanning = isScanning, authenticationAction = authenticationAction, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt index 72d624a921..372a46788b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt @@ -105,7 +105,7 @@ private fun Content( ) { QrCodeCameraView( modifier = Modifier.fillMaxSize(), - onScanQrCode = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, + onScanQrCode = { state.eventSink.invoke(QrCodeScanEvent.QrCodeScanned(it)) }, isScanning = state.isScanning, ) } @@ -125,7 +125,7 @@ private fun ColumnScope.Buttons( .fillMaxWidth() .padding(bottom = 16.dp), onClick = { - state.eventSink.invoke(QrCodeScanEvents.TryAgain) + state.eventSink.invoke(QrCodeScanEvent.TryAgain) } ) Column( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index ad997111e2..44a3bbd5cb 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -63,7 +63,7 @@ class ChangeServerPresenterTest { ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val successState = awaitItem() @@ -87,13 +87,13 @@ class ChangeServerPresenterTest { ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() assertThat(failureState.changeServerAction).isInstanceOf(AsyncData.Failure::class.java) // Clear error - failureState.eventSink.invoke(ChangeServerEvents.ClearError) + failureState.eventSink.invoke(ChangeServerEvent.ClearError) val finalState = awaitItem() assertThat(finalState.changeServerAction).isEqualTo(AsyncData.Uninitialized) } @@ -115,7 +115,7 @@ class ChangeServerPresenterTest { ).test { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() @@ -139,7 +139,7 @@ class ChangeServerPresenterTest { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL) - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(anAccountProvider)) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider)) val loadingState = awaitItem() assertThat(loadingState.changeServerAction).isInstanceOf(AsyncData.Loading::class.java) val failureState = awaitItem() @@ -167,7 +167,7 @@ class ChangeServerPresenterTest { val initialState = awaitItem() assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized) val anAccountProvider = AccountProvider(url = A_HOMESERVER_URL) - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(anAccountProvider)) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(anAccountProvider)) // Skip loading state skipItems(1) val failureState = awaitItem() @@ -194,7 +194,7 @@ class ChangeServerPresenterTest { ).test { val initialState = awaitItem() assertThat(initialState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None) - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val promptState = expectMostRecentItem() // Dialog is shown, permission has not been requested yet, setHomeserver has not been called. assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None) @@ -213,10 +213,10 @@ class ChangeServerPresenterTest { permissionsPresenter = FakePermissionsPresenter(), ).test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val promptState = expectMostRecentItem() assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None) - promptState.eventSink.invoke(ChangeServerEvents.DismissLocalNetworkPermission) + promptState.eventSink.invoke(ChangeServerEvent.DismissLocalNetworkPermission) val dismissedState = expectMostRecentItem() assertThat(dismissedState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None) assertThat(dismissedState.changeServerAction).isEqualTo(AsyncData.Uninitialized) @@ -236,7 +236,7 @@ class ChangeServerPresenterTest { permissionsPresenter = permissionsPresenter, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) + initialState.eventSink.invoke(ChangeServerEvent.ChangeServer(AccountProvider(url = A_HOMESERVER_URL))) val promptState = expectMostRecentItem() assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None) permissionsPresenter.setPermissionGranted() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt index 4f00bb002b..78f4293489 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenterTest.kt @@ -88,7 +88,7 @@ class ChooseAccountProviderPresenterTest { presenter.test { awaitItem().also { assertThat(it.selectedAccountProvider).isNull() - it.eventSink(ChooseAccountProviderEvents.Continue) + it.eventSink(ChooseAccountProviderEvent.Continue) expectNoEvents() } } @@ -112,11 +112,11 @@ class ChooseAccountProviderPresenterTest { presenter.test { awaitItem().also { assertThat(it.selectedAccountProvider).isNull() - it.eventSink(ChooseAccountProviderEvents.SelectAccountProvider(accountProvider1)) + it.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(accountProvider1)) } awaitItem().also { assertThat(it.selectedAccountProvider).isEqualTo(accountProvider1) - it.eventSink(ChooseAccountProviderEvents.Continue) + it.eventSink(ChooseAccountProviderEvent.Continue) skipItems(1) // Loading // Check an error was returned @@ -124,7 +124,7 @@ class ChooseAccountProviderPresenterTest { assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java) // Assert the error is then cleared - submittedState.eventSink(ChooseAccountProviderEvents.ClearError) + submittedState.eventSink(ChooseAccountProviderEvent.ClearError) val clearedState = awaitItem() assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized) } @@ -145,15 +145,15 @@ class ChooseAccountProviderPresenterTest { presenter.test { awaitItem().also { assertThat(it.selectedAccountProvider).isNull() - it.eventSink(ChooseAccountProviderEvents.SelectAccountProvider(accountProvider1)) + it.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(accountProvider1)) } awaitItem().also { assertThat(it.selectedAccountProvider).isEqualTo(accountProvider1) - it.eventSink(ChooseAccountProviderEvents.Continue) + it.eventSink(ChooseAccountProviderEvent.Continue) } awaitItem().also { assertThat(it.loginModeState.loginMode.isLoading()).isTrue() - it.eventSink(ChooseAccountProviderEvents.SelectAccountProvider(accountProvider2)) + it.eventSink(ChooseAccountProviderEvent.SelectAccountProvider(accountProvider2)) } expectNoEvents() } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt index cd64638563..4ef7a71966 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt @@ -35,7 +35,7 @@ import org.robolectric.annotation.Config class ChooseAccountProviderViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { setChooseAccountProviderView( state = aChooseAccountProviderState( @@ -50,7 +50,7 @@ class ChooseAccountProviderViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `selecting an account provider emits the the expected event`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() + val eventSink = EventsRecorder() setChooseAccountProviderView( state = aChooseAccountProviderState( accountProviders = listOf( @@ -62,12 +62,12 @@ class ChooseAccountProviderViewTest : RobolectricTest() { ), ) onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick() - eventSink.assertSingle(ChooseAccountProviderEvents.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1)) + eventSink.assertSingle(ChooseAccountProviderEvent.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1)) } @Test fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() + val eventSink = EventsRecorder() setChooseAccountProviderView( state = aChooseAccountProviderState( loginModeState = aLoginModeState(loginMode = AsyncData.Failure(AN_EXCEPTION)), @@ -75,7 +75,7 @@ class ChooseAccountProviderViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_ok) - eventSink.assertSingle(ChooseAccountProviderEvents.ClearError) + eventSink.assertSingle(ChooseAccountProviderEvent.ClearError) } private fun AndroidComposeUiTest.setChooseAccountProviderView( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index 641eea1563..19afbe0f28 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -68,7 +68,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin) cancelAndIgnoreRemainingEvents() @@ -87,7 +87,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) cancelAndIgnoreRemainingEvents() @@ -108,7 +108,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) authenticationService.givenOAuthCancelError(AN_EXCEPTION) @@ -133,7 +133,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) defaultOAuthActionFlow.post(OAuthAction.GoBack()) @@ -157,7 +157,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) awaitLoginMode { it is AsyncData.Loading } defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true)) val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized } @@ -180,7 +180,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) authenticationService.givenLoginError(AN_EXCEPTION) @@ -205,7 +205,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val successState = awaitLoginMode { it is AsyncData.Success } assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) defaultOidcActionFlow.post(OAuthAction.Success("aUrl")) @@ -227,7 +227,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink.invoke(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) // The account provider validation fails, so the login is never attempted. val failureState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure } assertThat(failureState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized) @@ -249,13 +249,13 @@ class ConfirmAccountProviderPresenterTest { val initialState = awaitItem() // Submit will return an error while validating the account provider - initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) // Check an error was returned val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure } // Assert the error is then cleared - submittedState.eventSink(ConfirmAccountProviderEvents.ClearError) + submittedState.eventSink(ConfirmAccountProviderEvent.ClearError) val clearedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Uninitialized } assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized) } @@ -274,12 +274,12 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) // Check an error was returned val submittedState = awaitLoginMode { it is AsyncData.Failure } assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java) // Assert the error is then cleared - submittedState.eventSink(ConfirmAccountProviderEvents.ClearError) + submittedState.eventSink(ConfirmAccountProviderEvent.ClearError) val clearedState = awaitLoginMode { it is AsyncData.Uninitialized } assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized) cancelAndIgnoreRemainingEvents() @@ -299,7 +299,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) val submittedState = awaitLoginMode { it is AsyncData.Success } assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java) cancelAndIgnoreRemainingEvents() @@ -316,7 +316,7 @@ class ConfirmAccountProviderPresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.accountProviderSuggestion).isNull() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("random")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("random")) val suggestionState = awaitState { it.accountProviderInput == "random" } // Completion is offered without the https:// scheme, even though history stores the full URL. assertThat(suggestionState.accountProviderSuggestion).isEqualTo("randomcommunity.org") @@ -340,9 +340,9 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("random")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("random")) val suggestionState = awaitState { it.accountProviderSuggestion != null } - suggestionState.eventSink(ConfirmAccountProviderEvents.Continue("randomcommunity.org")) + suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org")) awaitLoginMode { it is AsyncData.Success } assertThat(submittedUrls.first()).isEqualTo("https://randomcommunity.org") cancelAndIgnoreRemainingEvents() @@ -360,9 +360,9 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("random")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("random")) val suggestionState = awaitState { it.accountProviderSuggestion != null } - suggestionState.eventSink(ConfirmAccountProviderEvents.Continue("randomcommunity.org")) + suggestionState.eventSink(ConfirmAccountProviderEvent.Continue("randomcommunity.org")) // The field now renders the full accepted server rather than the typed prefix. val appliedState = awaitState { it.accountProviderInput == "randomcommunity.org" } assertThat(appliedState.accountProviderInput).isEqualTo("randomcommunity.org") @@ -381,7 +381,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("ele")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("ele")) val suggestionState = awaitState { it.accountProviderInput == "ele" } assertThat(suggestionState.accountProviderSuggestion).isEqualTo("element.io") } @@ -394,7 +394,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("matr")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("matr")) val suggestionState = awaitState { it.accountProviderInput == "matr" } assertThat(suggestionState.accountProviderSuggestion).isEqualTo("matrix.org") } @@ -409,9 +409,9 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("https://mat")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("https://mat")) assertThat(awaitState { it.accountProviderInput == "https://mat" }.accountProviderSuggestion).isNull() - initialState.eventSink(ConfirmAccountProviderEvents.UserInputChanged("http://mat")) + initialState.eventSink(ConfirmAccountProviderEvent.UserInputChanged("http://mat")) assertThat(awaitState { it.accountProviderInput == "http://mat" }.accountProviderSuggestion).isNull() } } @@ -430,7 +430,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue("matrix.org")) + initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org")) awaitLoginMode { it is AsyncData.Success } assertThat(submittedUrls.first()).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) cancelAndIgnoreRemainingEvents() @@ -453,7 +453,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue(AuthenticationConfig.MATRIX_ORG_URL)) + initialState.eventSink(ConfirmAccountProviderEvent.Continue(AuthenticationConfig.MATRIX_ORG_URL)) awaitLoginMode { it is AsyncData.Success } assertThat(submittedUrls).containsExactly(AuthenticationConfig.MATRIX_ORG_URL) cancelAndIgnoreRemainingEvents() @@ -472,7 +472,7 @@ class ConfirmAccountProviderPresenterTest { val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org")) + initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org")) awaitLoginMode { it is AsyncData.Success } assertThat(submittedUrls.first()).isEqualTo("https://example.org") assertThat(authenticationService.getOAuthUrlLoginHint).isEqualTo("mxid:@alice:example.org") @@ -492,7 +492,7 @@ class ConfirmAccountProviderPresenterTest { val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org:8448")) + initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org:8448")) awaitLoginMode { it is AsyncData.Success } assertThat(submittedUrls.first()).isEqualTo("https://example.org:8448") cancelAndIgnoreRemainingEvents() @@ -507,7 +507,7 @@ class ConfirmAccountProviderPresenterTest { val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue("matrix.org")) + initialState.eventSink(ConfirmAccountProviderEvent.Continue("matrix.org")) awaitLoginMode { it is AsyncData.Success } assertThat(authenticationService.getOAuthUrlLoginHint).isNull() cancelAndIgnoreRemainingEvents() @@ -525,7 +525,7 @@ class ConfirmAccountProviderPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org")) + initialState.eventSink(ConfirmAccountProviderEvent.Continue("@alice:example.org")) awaitLoginMode { it is AsyncData.Success } assertThat(authenticationService.getOAuthUrlLoginHint).isNull() cancelAndIgnoreRemainingEvents() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index 4dd2859dee..b12c9ec667 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -54,7 +54,7 @@ class LoginPasswordPresenterTest { val initialState = awaitItem() assertThat(initialState.formState.login).isEqualTo(A_USER_NAME) // Login can be changed - initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME_2)) + initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME_2)) val loginChangedState = awaitItem() assertThat(loginChangedState.formState.login).isEqualTo(A_USER_NAME_2) } @@ -71,11 +71,11 @@ class LoginPasswordPresenterTest { authenticationService = authenticationService, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME)) val loginState = awaitItem() assertThat(loginState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = "")) assertThat(loginState.submitEnabled).isFalse() - initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD)) val loginAndPasswordState = awaitItem() assertThat(loginAndPasswordState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = A_PASSWORD)) assertThat(loginAndPasswordState.submitEnabled).isTrue() @@ -93,11 +93,11 @@ class LoginPasswordPresenterTest { authenticationService = authenticationService, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) - initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD)) skipItems(1) val loginAndPasswordState = awaitItem() - loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit) val submitState = awaitItem() assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() @@ -118,11 +118,11 @@ class LoginPasswordPresenterTest { appPreferencesStore = appPreferencesStore, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) - initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD)) skipItems(1) val loginAndPasswordState = awaitItem() - loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit) skipItems(1) val loggedInState = awaitItem() assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Success(A_SESSION_ID)) @@ -142,12 +142,12 @@ class LoginPasswordPresenterTest { authenticationService = authenticationService, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) - initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD)) skipItems(1) val loginAndPasswordState = awaitItem() authenticationService.givenLoginError(AN_EXCEPTION) - loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit) val submitState = awaitItem() assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() @@ -166,19 +166,19 @@ class LoginPasswordPresenterTest { authenticationService = authenticationService, ).test { val initialState = awaitItem() - initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) - initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + initialState.eventSink.invoke(LoginPasswordEvent.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvent.SetPassword(A_PASSWORD)) skipItems(1) val loginAndPasswordState = awaitItem() authenticationService.givenLoginError(AN_EXCEPTION) - loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + loginAndPasswordState.eventSink.invoke(LoginPasswordEvent.Submit) val submitState = awaitItem() assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() // Check an error was returned assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) // Assert the error is then cleared - loggedInState.eventSink(LoginPasswordEvents.ClearError) + loggedInState.eventSink(LoginPasswordEvent.ClearError) val clearedState = awaitItem() assertThat(clearedState.loginAction).isEqualTo(AsyncData.Uninitialized) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt index d76c94d5ea..32ce44366f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordViewTest.kt @@ -39,7 +39,7 @@ import org.robolectric.annotation.Config class LoginPasswordViewTest : RobolectricTest() { @Test fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setLoginPasswordView( aLoginPasswordState( @@ -53,7 +53,7 @@ class LoginPasswordViewTest : RobolectricTest() { @Test fun `changing login invokes the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, @@ -62,13 +62,13 @@ class LoginPasswordViewTest : RobolectricTest() { val userNameHint = activity!!.getString(CommonStrings.common_username) onNodeWithText(userNameHint).performTextInput(A_USER_NAME) eventsRecorder.assertSingle( - LoginPasswordEvents.SetLogin(A_USER_NAME) + LoginPasswordEvent.SetLogin(A_USER_NAME) ) } @Test fun `changing login removes new lines the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, @@ -77,13 +77,13 @@ class LoginPasswordViewTest : RobolectricTest() { val userNameHint = activity!!.getString(CommonStrings.common_username) onNodeWithText(userNameHint).performTextInput("a\nb") eventsRecorder.assertSingle( - LoginPasswordEvents.SetLogin("ab") + LoginPasswordEvent.SetLogin("ab") ) } @Test fun `changing login removes carriage returns the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, @@ -92,13 +92,13 @@ class LoginPasswordViewTest : RobolectricTest() { val userNameHint = activity!!.getString(CommonStrings.common_username) onNodeWithText(userNameHint).performTextInput("a\r\nb") eventsRecorder.assertSingle( - LoginPasswordEvents.SetLogin("ab") + LoginPasswordEvent.SetLogin("ab") ) } @Test fun `clearing login invokes the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(A_USER_NAME), @@ -108,13 +108,13 @@ class LoginPasswordViewTest : RobolectricTest() { val a11yClear = activity!!.getString(CommonStrings.action_clear) onNodeWithContentDescription(a11yClear).performClick() eventsRecorder.assertSingle( - LoginPasswordEvents.SetLogin("") + LoginPasswordEvent.SetLogin("") ) } @Test fun `changing password invokes the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, @@ -123,13 +123,13 @@ class LoginPasswordViewTest : RobolectricTest() { val userNameHint = activity!!.getString(CommonStrings.common_password) onNodeWithText(userNameHint).performTextInput(A_PASSWORD) eventsRecorder.assertSingle( - LoginPasswordEvents.SetPassword(A_PASSWORD) + LoginPasswordEvent.SetPassword(A_PASSWORD) ) } @Test fun `changing password removes carriage returns the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( eventSink = eventsRecorder, @@ -138,13 +138,13 @@ class LoginPasswordViewTest : RobolectricTest() { val passwordHint = activity!!.getString(CommonStrings.common_password) onNodeWithText(passwordHint).performTextInput("a\r\nb") eventsRecorder.assertSingle( - LoginPasswordEvents.SetPassword("ab") + LoginPasswordEvent.SetPassword("ab") ) } @Test fun `reveal password makes the password visible`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(password = A_PASSWORD), @@ -165,7 +165,7 @@ class LoginPasswordViewTest : RobolectricTest() { @Test fun `when login is empty, continue button is not enabled`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(password = A_PASSWORD), @@ -178,7 +178,7 @@ class LoginPasswordViewTest : RobolectricTest() { @Test fun `when password is empty, continue button is not enabled`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(login = A_USER_NAME), @@ -192,7 +192,7 @@ class LoginPasswordViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on Continue sends expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setLoginPasswordView( aLoginPasswordState( formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD), @@ -203,7 +203,7 @@ class LoginPasswordViewTest : RobolectricTest() { onNodeWithText(continueStr).assertIsEnabled() clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle( - LoginPasswordEvents.Submit + LoginPasswordEvent.Submit ) } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index 3c2e4b574f..1088a87538 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -153,7 +153,7 @@ class OnBoardingPresenterTest { awaitItem().also { state -> assertThat(state.canReportBug).isFalse() repeat(7) { - state.eventSink(OnBoardingEvents.OnVersionClick) + state.eventSink(OnBoardingEvent.OnVersionClick) } } expectNoEvents() @@ -168,7 +168,7 @@ class OnBoardingPresenterTest { awaitItem().also { state -> assertThat(state.canReportBug).isFalse() repeat(7) { - state.eventSink(OnBoardingEvents.OnVersionClick) + state.eventSink(OnBoardingEvent.OnVersionClick) } } assertThat(awaitItem().canReportBug).isTrue() @@ -272,7 +272,7 @@ class OnBoardingPresenterTest { awaitItem().also { assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL) assertThat(accountProviderDataSource.flow.first().url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL) - it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL_2)) + it.eventSink(OnBoardingEvent.OnSignIn(A_HOMESERVER_URL_2)) skipItems(1) // Loading // Account data source has been updated assertThat(accountProviderDataSource.flow.first().url).isEqualTo(A_HOMESERVER_URL_2) @@ -281,7 +281,7 @@ class OnBoardingPresenterTest { assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java) // Assert the error is then cleared - submittedState.eventSink(OnBoardingEvents.ClearError) + submittedState.eventSink(OnBoardingEvent.ClearError) val clearedState = awaitItem() assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt index dfa4119711..88c9acf55f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt @@ -39,7 +39,7 @@ import org.junit.Test class OnboardingViewTest : RobolectricTestParameter() { @Test fun `when can create account - clicking on create account calls the expected callback`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setOnboardingView( state = anOnBoardingState( @@ -58,7 +58,7 @@ class OnboardingViewTest : RobolectricTestParameter() { @Test fun `when can go back - clicking on back calls the expected callback`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setOnboardingView( state = anOnBoardingState( @@ -73,7 +73,7 @@ class OnboardingViewTest : RobolectricTestParameter() { @Test fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setOnboardingView( state = anOnBoardingState( @@ -93,7 +93,7 @@ class OnboardingViewTest : RobolectricTestParameter() { "cannot search account provider" to true, ) ) = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(mustChooseAccountProvider) { callback -> setOnboardingView( state = anOnBoardingState( @@ -114,7 +114,7 @@ class OnboardingViewTest : RobolectricTestParameter() { "cannot search account provider" to true, ) ) = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(mustChooseAccountProvider) { callback -> setOnboardingView( state = anOnBoardingState( @@ -131,7 +131,7 @@ class OnboardingViewTest : RobolectricTestParameter() { @Test fun `when sign in to pre defined account provider - clicking on button emits the expected event`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() + val eventSink = EventsRecorder() setOnboardingView( state = anOnBoardingState( defaultAccountProvider = "element.io", @@ -140,12 +140,12 @@ class OnboardingViewTest : RobolectricTestParameter() { ) val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io") onNodeWithText(buttonText).performClick() - eventSink.assertSingle(OnBoardingEvents.OnSignIn("element.io")) + eventSink.assertSingle(OnBoardingEvent.OnSignIn("element.io")) } @Test fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() + val eventSink = EventsRecorder() setOnboardingView( state = anOnBoardingState( defaultAccountProvider = "element.io", @@ -154,12 +154,12 @@ class OnboardingViewTest : RobolectricTestParameter() { ), ) clickOn(CommonStrings.action_ok) - eventSink.assertSingle(OnBoardingEvents.ClearError) + eventSink.assertSingle(OnBoardingEvent.ClearError) } @Test fun `clicking on report a problem calls the sign in callback`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setOnboardingView( state = anOnBoardingState( @@ -176,7 +176,7 @@ class OnboardingViewTest : RobolectricTestParameter() { @Test fun `clicking on settings calls the developer settings callback`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setOnboardingView( state = anOnBoardingState( @@ -192,7 +192,7 @@ class OnboardingViewTest : RobolectricTestParameter() { @Test fun `cannot report a problem when the feature is disabled`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder(expectEvents = false) + val eventSink = EventsRecorder(expectEvents = false) setOnboardingView( state = anOnBoardingState( canReportBug = false, @@ -205,7 +205,7 @@ class OnboardingViewTest : RobolectricTestParameter() { @Test fun `when success PasswordLogin - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() + val eventSink = EventsRecorder() ensureCalledOnce { callback -> setOnboardingView( state = anOnBoardingState( @@ -215,12 +215,12 @@ class OnboardingViewTest : RobolectricTestParameter() { onNeedLoginPassword = callback, ) } - eventSink.assertSingle(OnBoardingEvents.ClearError) + eventSink.assertSingle(OnBoardingEvent.ClearError) } @Test fun `when success Oidc - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() + val eventSink = EventsRecorder() val oAuthDetails = OAuthDetails("aUrl") ensureCalledOnceWithParam(oAuthDetails) { callback -> setOnboardingView( @@ -231,7 +231,7 @@ class OnboardingViewTest : RobolectricTestParameter() { onOAuthDetails = callback, ) } - eventSink.assertSingle(OnBoardingEvents.ClearError) + eventSink.assertSingle(OnBoardingEvent.ClearError) } private fun AndroidComposeUiTest.setOnboardingView( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt index aabcf12e25..02bace008e 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenterTest.kt @@ -37,7 +37,7 @@ class QrCodeIntroPresenterTest { val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) val presenter = createQrCodeIntroPresenter(permissionsPresenterFactory = permissionsPresenterFactory) presenter.test { - awaitItem().eventSink(QrCodeIntroEvents.Continue) + awaitItem().eventSink(QrCodeIntroEvent.Continue) assertThat(awaitItem().canContinue).isTrue() } } @@ -48,7 +48,7 @@ class QrCodeIntroPresenterTest { val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) val presenter = createQrCodeIntroPresenter(permissionsPresenterFactory = permissionsPresenterFactory) presenter.test { - awaitItem().eventSink(QrCodeIntroEvents.Continue) + awaitItem().eventSink(QrCodeIntroEvent.Continue) assertThat(awaitItem().cameraPermissionState.showDialog).isTrue() } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt index 5720c68ab4..3bb600ae55 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroViewTest.kt @@ -59,12 +59,12 @@ class QrCodeIntroViewTest : RobolectricTest() { @Test fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest { - val eventRecorder = EventsRecorder() + val eventRecorder = EventsRecorder() setQrCodeIntroView( state = aQrCodeIntroState(eventSink = eventRecorder), ) clickOn(R.string.screen_qr_code_login_initial_state_button_title) - eventRecorder.assertSingle(QrCodeIntroEvents.Continue) + eventRecorder.assertSingle(QrCodeIntroEvent.Continue) } private fun AndroidComposeUiTest.setQrCodeIntroView( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt index d4f23a452c..a13590a1bc 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenterTest.kt @@ -50,7 +50,7 @@ class QrCodeScanPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) + initialState.eventSink(QrCodeScanEvent.QrCodeScanned(byteArrayOf())) assertThat(awaitItem().isScanning).isFalse() assertThat(awaitItem().authenticationAction.isLoading()).isTrue() assertThat(awaitItem().authenticationAction.isSuccess()).isTrue() @@ -65,14 +65,14 @@ class QrCodeScanPresenterTest { val presenter = createQrCodeScanPresenter(qrCodeLoginDataFactory = qrCodeLoginDataFactory) presenter.test { val initialState = awaitItem() - initialState.eventSink(QrCodeScanEvents.QrCodeScanned(byteArrayOf())) + initialState.eventSink(QrCodeScanEvent.QrCodeScanned(byteArrayOf())) assertThat(awaitItem().isScanning).isFalse() assertThat(awaitItem().authenticationAction.isLoading()).isTrue() val errorState = awaitItem() assertThat(errorState.authenticationAction.isFailure()).isTrue() - errorState.eventSink(QrCodeScanEvents.TryAgain) + errorState.eventSink(QrCodeScanEvent.TryAgain) assertThat(awaitItem().isScanning).isTrue() assertThat(awaitItem().authenticationAction.isUninitialized()).isTrue() } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvents.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvent.kt similarity index 78% rename from features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvents.kt rename to features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvent.kt index cc16dfdaf1..d19a62768a 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvents.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.logout.api.direct -sealed interface DirectLogoutEvents { - data class Logout(val ignoreSdkError: Boolean) : DirectLogoutEvents - data object CloseDialogs : DirectLogoutEvents +sealed interface DirectLogoutEvent { + data class Logout(val ignoreSdkError: Boolean) : DirectLogoutEvent + data object CloseDialogs : DirectLogoutEvent } diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt index 31e0fdcd5f..75c3392ac1 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutState.kt @@ -13,5 +13,5 @@ import io.element.android.libraries.architecture.AsyncAction data class DirectLogoutState( val canDoDirectSignOut: Boolean, val logoutAction: AsyncAction, - val eventSink: (DirectLogoutEvents) -> Unit, + val eventSink: (DirectLogoutEvent) -> Unit, ) diff --git a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutStatePreviewParam.kt b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutStatePreviewParam.kt index c208978fd9..936f7d2a58 100644 --- a/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutStatePreviewParam.kt +++ b/features/logout/api/src/main/kotlin/io/element/android/features/logout/api/direct/DirectLogoutStatePreviewParam.kt @@ -25,7 +25,7 @@ open class DirectLogoutStatePreviewParam : PreviewParameterProvider = AsyncAction.Uninitialized, - eventSink: (DirectLogoutEvents) -> Unit = {}, + eventSink: (DirectLogoutEvent) -> Unit = {}, ) = DirectLogoutState( canDoDirectSignOut = canDoDirectSignOut, logoutAction = logoutAction, diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutEvents.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutEvent.kt similarity index 81% rename from features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutEvents.kt rename to features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutEvent.kt index 0822444bea..e23338460c 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutEvents.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.logout.impl -sealed interface LogoutEvents { - data class Logout(val ignoreSdkError: Boolean) : LogoutEvents - data object CloseDialogs : LogoutEvents +sealed interface LogoutEvent { + data class Logout(val ignoreSdkError: Boolean) : LogoutEvent + data object CloseDialogs : LogoutEvent } diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt index 8176ed3d28..4ad8a6c78e 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutPresenter.kt @@ -74,16 +74,16 @@ class LogoutPresenter( } } - fun handleEvent(event: LogoutEvents) { + fun handleEvent(event: LogoutEvent) { when (event) { - is LogoutEvents.Logout -> { + is LogoutEvent.Logout -> { if (logoutAction.value.isConfirming() || event.ignoreSdkError) { localCoroutineScope.logout(logoutAction, event.ignoreSdkError) } else { logoutAction.value = AsyncAction.ConfirmingNoParams } } - LogoutEvents.CloseDialogs -> { + LogoutEvent.CloseDialogs -> { logoutAction.value = AsyncAction.Uninitialized } } diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt index 9240edc61d..6233c1cb23 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutState.kt @@ -21,5 +21,5 @@ data class LogoutState( val backupUploadState: BackupUploadState, val waitingForALongTime: Boolean, val logoutAction: AsyncAction, - val eventSink: (LogoutEvents) -> Unit, + val eventSink: (LogoutEvent) -> Unit, ) diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStatePreviewParam.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStatePreviewParam.kt index 7bd55faa2c..e2d948a7f3 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStatePreviewParam.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutStatePreviewParam.kt @@ -50,7 +50,7 @@ fun aLogoutState( backupUploadState: BackupUploadState = BackupUploadState.Unknown, waitingForALongTime: Boolean = false, logoutAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (LogoutEvents) -> Unit = {}, + eventSink: (LogoutEvent) -> Unit = {}, ) = LogoutState( isLastDevice = isLastDevice, backupState = backupState, diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt index befabe7421..90a502fc16 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/LogoutView.kt @@ -61,7 +61,7 @@ fun LogoutView( state = state, onChangeRecoveryKeyClick = onChangeRecoveryKeyClick, onLogoutClick = { - eventSink(LogoutEvents.Logout(ignoreSdkError = false)) + eventSink(LogoutEvent.Logout(ignoreSdkError = false)) } ) }, @@ -72,13 +72,13 @@ fun LogoutView( LogoutActionDialog( state.logoutAction, onConfirmClick = { - eventSink(LogoutEvents.Logout(ignoreSdkError = false)) + eventSink(LogoutEvent.Logout(ignoreSdkError = false)) }, onForceLogoutClick = { - eventSink(LogoutEvents.Logout(ignoreSdkError = true)) + eventSink(LogoutEvent.Logout(ignoreSdkError = true)) }, onDismissDialog = { - eventSink(LogoutEvents.CloseDialogs) + eventSink(LogoutEvent.CloseDialogs) }, ) } diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt index a8cad6ffba..901e4e9654 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutView.kt @@ -11,7 +11,7 @@ package io.element.android.features.logout.impl.direct import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.PreviewParameter import dev.zacsweers.metro.ContributesBinding -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutStatePreviewParam import io.element.android.features.logout.api.direct.DirectLogoutView @@ -28,13 +28,13 @@ class DefaultDirectLogoutView : DirectLogoutView { LogoutActionDialog( state.logoutAction, onConfirmClick = { - eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) + eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false)) }, onForceLogoutClick = { - eventSink(DirectLogoutEvents.Logout(ignoreSdkError = true)) + eventSink(DirectLogoutEvent.Logout(ignoreSdkError = true)) }, onDismissDialog = { - eventSink(DirectLogoutEvents.CloseDialogs) + eventSink(DirectLogoutEvent.CloseDialogs) }, ) } diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenter.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenter.kt index 8d8cc9141e..79798dd2ab 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenter.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenter.kt @@ -16,7 +16,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Inject -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.impl.tools.isBackingUp import io.element.android.libraries.architecture.AsyncAction @@ -48,16 +48,16 @@ class DirectLogoutPresenter( val isLastDevice by encryptionService.isLastDevice.collectAsState() - fun handleEvent(event: DirectLogoutEvents) { + fun handleEvent(event: DirectLogoutEvent) { when (event) { - is DirectLogoutEvents.Logout -> { + is DirectLogoutEvent.Logout -> { if (logoutAction.value.isConfirming() || event.ignoreSdkError) { localCoroutineScope.logout(logoutAction, event.ignoreSdkError) } else { logoutAction.value = AsyncAction.ConfirmingNoParams } } - DirectLogoutEvents.CloseDialogs -> { + DirectLogoutEvent.CloseDialogs -> { logoutAction.value = AsyncAction.Uninitialized } } diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index 274234a338..5b14bc5cc3 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -139,10 +139,10 @@ class LogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink.invoke(LogoutEvents.CloseDialogs) + initialState.eventSink.invoke(LogoutEvent.CloseDialogs) val finalState = awaitItem() assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } @@ -157,10 +157,10 @@ class LogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmationState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + confirmationState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val successState = awaitItem() @@ -184,15 +184,15 @@ class LogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmationState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + confirmationState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - errorState.eventSink.invoke(LogoutEvents.CloseDialogs) + errorState.eventSink.invoke(LogoutEvent.CloseDialogs) val finalState = awaitItem() assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } @@ -214,15 +214,15 @@ class LogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmationState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = false)) + confirmationState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = false)) val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - errorState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = true)) + errorState.eventSink.invoke(LogoutEvent.Logout(ignoreSdkError = true)) val loadingState2 = awaitItem() assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val successState = awaitItem() diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt index 3fbae4bbc5..f69ec8f0ca 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutViewTest.kt @@ -28,20 +28,20 @@ import org.junit.Test class LogoutViewTest : RobolectricTest() { @Test - fun `clicking on logout sends a LogoutEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on logout sends a LogoutEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setLogoutView( aLogoutState( eventSink = eventsRecorder ), ) clickOn(CommonStrings.action_signout) - eventsRecorder.assertSingle(LogoutEvents.Logout(false)) + eventsRecorder.assertSingle(LogoutEvent.Logout(false)) } @Test - fun `confirming logout sends a LogoutEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `confirming logout sends a LogoutEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setLogoutView( aLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, @@ -49,12 +49,12 @@ class LogoutViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(LogoutEvents.Logout(false)) + eventsRecorder.assertSingle(LogoutEvent.Logout(false)) } @Test fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setLogoutView( aLogoutState( @@ -67,8 +67,8 @@ class LogoutViewTest : RobolectricTest() { } @Test - fun `clicking on confirm after error sends a LogoutEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on confirm after error sends a LogoutEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setLogoutView( aLogoutState( logoutAction = AsyncAction.Failure(Exception("Failed to logout")), @@ -76,12 +76,12 @@ class LogoutViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_signout_anyway) - eventsRecorder.assertSingle(LogoutEvents.Logout(true)) + eventsRecorder.assertSingle(LogoutEvent.Logout(true)) } @Test - fun `clicking on cancel after error sends a LogoutEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on cancel after error sends a LogoutEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setLogoutView( aLogoutState( logoutAction = AsyncAction.Failure(Exception("Failed to logout")), @@ -89,12 +89,12 @@ class LogoutViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(LogoutEvents.CloseDialogs) + eventsRecorder.assertSingle(LogoutEvent.CloseDialogs) } @Test fun `last session setting button invoke onChangeRecoveryKeyClicked`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setLogoutView( aLogoutState( diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt index db8eed2a54..a413ba4e36 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DefaultDirectLogoutViewTest.kt @@ -14,7 +14,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.test.AndroidComposeUiTest import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.v2.runAndroidComposeUiTest -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.libraries.architecture.AsyncAction @@ -29,7 +29,7 @@ import org.junit.Test class DefaultDirectLogoutViewTest : RobolectricTest() { @Test fun `clicking on confirm logout sends expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, @@ -37,12 +37,12 @@ class DefaultDirectLogoutViewTest : RobolectricTest() { ) ) clickOn(CommonStrings.action_signout) - eventsRecorder.assertSingle(DirectLogoutEvents.Logout(false)) + eventsRecorder.assertSingle(DirectLogoutEvent.Logout(false)) } @Test fun `clicking on cancel logout sends expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, @@ -50,13 +50,13 @@ class DefaultDirectLogoutViewTest : RobolectricTest() { ) ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) + eventsRecorder.assertSingle(DirectLogoutEvent.CloseDialogs) } @Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.") @Test fun `clicking on back invoke back callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.ConfirmingNoParams, @@ -64,12 +64,12 @@ class DefaultDirectLogoutViewTest : RobolectricTest() { ) ) pressBackKey() - eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) + eventsRecorder.assertSingle(DirectLogoutEvent.CloseDialogs) } @Test fun `clicking on confirm after error sends expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.Failure(Exception("Error")), @@ -77,12 +77,12 @@ class DefaultDirectLogoutViewTest : RobolectricTest() { ) ) clickOn(CommonStrings.action_signout_anyway) - eventsRecorder.assertSingle(DirectLogoutEvents.Logout(true)) + eventsRecorder.assertSingle(DirectLogoutEvent.Logout(true)) } @Test fun `clicking on cancel after error sends expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDefaultDirectLogoutView( state = aDirectLogoutState( logoutAction = AsyncAction.Failure(Exception("Error")), @@ -90,7 +90,7 @@ class DefaultDirectLogoutViewTest : RobolectricTest() { ) ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs) + eventsRecorder.assertSingle(DirectLogoutEvent.CloseDialogs) } } diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt index 835ed4e94e..d11c31cf1b 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt @@ -13,7 +13,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupUploadState @@ -87,10 +87,10 @@ class DirectLogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) + initialState.eventSink.invoke(DirectLogoutEvent.CloseDialogs) val finalState = awaitItem() assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } @@ -103,10 +103,10 @@ class DirectLogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + confirmationState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val successState = awaitItem() @@ -128,15 +128,15 @@ class DirectLogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + confirmationState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - errorState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) + errorState.eventSink.invoke(DirectLogoutEvent.CloseDialogs) val finalState = awaitItem() assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) } @@ -158,15 +158,15 @@ class DirectLogoutPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + initialState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val confirmationState = awaitItem() assertThat(confirmationState.logoutAction).isEqualTo(AsyncAction.ConfirmingNoParams) - confirmationState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = false)) + confirmationState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = false)) val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) - errorState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = true)) + errorState.eventSink.invoke(DirectLogoutEvent.Logout(ignoreSdkError = true)) val loadingState2 = awaitItem() assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val successState = awaitItem() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index d4fce982e8..97d3465ce7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -43,7 +43,7 @@ import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPr import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.roommembermoderation.api.ModerationAction -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.system.openUrlInExternalApp @@ -348,7 +348,7 @@ class MessagesNode( onSelectAction = { action, target -> when (action) { is ModerationAction.DisplayProfile -> callback.navigateToRoomMemberDetails(target.userId) - else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) + else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvent.ProcessAction(action, target)) } }, onAvatarClick = { user -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 4af0c05955..6a019d865b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -51,10 +51,11 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.captionOrNull +import io.element.android.features.messages.impl.timeline.model.event.htmlCaptionOrNull import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.voicemessages.composer.DefaultVoiceMessageComposerPresenter import io.element.android.features.roomcall.api.RoomCallState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.AsyncData @@ -263,7 +264,7 @@ class MessagesPresenter( } } is MessagesEvent.OnUserClicked -> { - roomMemberModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.user)) + roomMemberModerationState.eventSink(RoomMemberModerationEvent.ShowActionsForUser(event.user)) } MessagesEvent.StopLiveLocationShare -> { localCoroutineScope.launch { @@ -377,7 +378,7 @@ class MessagesPresenter( TimelineItemAction.Edit, TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState) - TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState) + TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState, enableTextFormatting) TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent) TimelineItemAction.Reply -> handleActionReply(targetEvent, composerState, timelineProtectionState) TimelineItemAction.ReplyInThread -> { @@ -546,10 +547,15 @@ class MessagesPresenter( private suspend fun handleActionEditCaption( targetEvent: TimelineItem.Event, composerState: MessageComposerState, + enableTextFormatting: Boolean, ) { val composerMode = MessageComposerMode.EditCaption( eventOrTransactionId = targetEvent.eventOrTransactionId, - content = targetEvent.content.captionOrNull().orEmpty(), + content = if (enableTextFormatting) { + targetEvent.content.htmlCaptionOrNull() ?: targetEvent.content.captionOrNull() + } else { + targetEvent.content.captionOrNull() + }.orEmpty(), ) composerState.eventSink( MessageComposerEvent.SetMode(composerMode) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt index 8ab7e64121..d792ab5ed4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStatePreviewParam.kt @@ -38,7 +38,7 @@ import io.element.android.features.messages.impl.timeline.protection.TimelinePro import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.aStandByCallState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData @@ -168,7 +168,7 @@ fun aRoomMemberModerationState( permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions.DEFAULT, ) = object : RoomMemberModerationState { override val permissions: RoomMemberModerationPermissions = permissions - override val eventSink: (RoomMemberModerationEvents) -> Unit = {} + override val eventSink: (RoomMemberModerationEvent) -> Unit = {} } fun aUserEventPermissions( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index b7fd9f572f..cf3c701015 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -46,7 +46,7 @@ import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPr import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.roommembermoderation.api.ModerationAction -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.system.openUrlInExternalApp @@ -343,7 +343,7 @@ class ThreadedMessagesNode( onSelectAction = { action, target -> when (action) { is ModerationAction.DisplayProfile -> callback.navigateToRoomMemberDetails(target.userId) - else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) + else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvent.ProcessAction(action, target)) } }, onAvatarClick = { user -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListEvent.kt new file mode 100644 index 0000000000..191d5d111b --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListEvent.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2026 Element Creations 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.messages.impl.threads.list + +sealed interface ThreadsListEvent { + data object Paginate : ThreadsListEvent +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListPresenter.kt index ba10df46f2..d6d928326c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListPresenter.kt @@ -20,13 +20,10 @@ import io.element.android.features.messages.impl.utils.messagesummary.MessageSum import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode -import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -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.room.threads.ThreadListPaginationStatus import io.element.android.libraries.matrix.ui.model.getAvatarData -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.onStart @@ -104,9 +101,9 @@ class ThreadsListPresenter( derivedStateOf { roomInfo.heroes.map { it.getAvatarData(AvatarSize.CurrentUserTopBar) }.toImmutableList() } } - fun handleEvent(event: ThreadsListEvents) { + fun handleEvent(event: ThreadsListEvent) { when (event) { - ThreadsListEvents.Paginate -> if ((paginationStatus as? ThreadListPaginationStatus.Idle)?.hasMoreToLoad == true) { + ThreadsListEvent.Paginate -> if ((paginationStatus as? ThreadListPaginationStatus.Idle)?.hasMoreToLoad == true) { coroutineScope.launch { Timber.d("Paginating thread list: $paginationStatus") threadsListService.paginate() @@ -129,17 +126,3 @@ class ThreadsListPresenter( ) } } - -data class ThreadsListState( - val roomId: RoomId, - val roomName: String, - val roomAvatarUrl: String?, - val isRoomTombstoned: Boolean, - val heroes: ImmutableList, - val threads: ImmutableList, - val eventSink: (ThreadsListEvents) -> Unit, -) - -sealed interface ThreadsListEvents { - data object Paginate : ThreadsListEvents -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListState.kt new file mode 100644 index 0000000000..8aafd49d21 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListState.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Element Creations 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.messages.impl.threads.list + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.collections.immutable.ImmutableList + +data class ThreadsListState( + val roomId: RoomId, + val roomName: String, + val roomAvatarUrl: String?, + val isRoomTombstoned: Boolean, + val heroes: ImmutableList, + val threads: ImmutableList, + val eventSink: (ThreadsListEvent) -> Unit, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt index 6480dacfcf..c38da19a67 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/list/ThreadsListView.kt @@ -156,7 +156,7 @@ fun ThreadsListView( } ScrollHelper(lazyListState) { - state.eventSink(ThreadsListEvents.Paginate) + state.eventSink(ThreadsListEvent.Paginate) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 040d0450ff..75966db1a3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -108,6 +108,7 @@ class TimelineItemContentMessageFactory( fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), formattedCaption = formattedCaption, + htmlCaption = messageType.formattedCaption?.body, isEdited = content.isEdited, mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, @@ -132,6 +133,7 @@ class TimelineItemContentMessageFactory( fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), formattedCaption = formattedCaption, + htmlCaption = messageType.formattedCaption?.body, isEdited = content.isEdited, mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, @@ -174,6 +176,7 @@ class TimelineItemContentMessageFactory( fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), formattedCaption = formattedCaption, + htmlCaption = messageType.formattedCaption?.body, isEdited = content.isEdited, thumbnailSource = messageType.info?.thumbnailSource, mediaSource = messageType.source, @@ -198,6 +201,7 @@ class TimelineItemContentMessageFactory( fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), formattedCaption = formattedCaption, + htmlCaption = messageType.formattedCaption?.body, isEdited = content.isEdited, mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, @@ -216,6 +220,7 @@ class TimelineItemContentMessageFactory( fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), formattedCaption = formattedCaption, + htmlCaption = messageType.formattedCaption?.body, isEdited = content.isEdited, mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, @@ -235,6 +240,7 @@ class TimelineItemContentMessageFactory( fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), formattedCaption = formattedCaption, + htmlCaption = messageType.formattedCaption?.body, isEdited = content.isEdited, thumbnailSource = messageType.info?.thumbnailSource, mediaSource = messageType.source, @@ -345,6 +351,7 @@ class TimelineItemContentMessageFactory( body = messageType.body, caption = messageType.body.trimEnd().takeIf { it.isNotEmpty() }, formattedCaption = formattedCaption, + htmlCaption = messageType.formatted?.body, isEdited = content.isEdited, items = galleryItems.toImmutableList(), ) @@ -402,6 +409,7 @@ class TimelineItemContentMessageFactory( body = messageType.body, caption = messageType.body.trimEnd().takeIf { it.isNotEmpty() }, formattedCaption = formattedCaption, + htmlCaption = messageType.formatted?.body, isEdited = content.isEdited, attachments = attachments.toImmutableList(), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt index 431942378a..3b16f79f79 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt @@ -91,7 +91,7 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean { // Can be grouped is FailedToParseMessageLikeContent, is MessageContent, - RedactedContent, + is RedactedContent, is StickerContent, is PollContent, is UnableToDecryptContent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt index 2e01e53828..b34c66a08d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UniqueId @@ -99,7 +100,8 @@ internal const val MIN_REDACTED_RUN_SIZE = 3 * Fold runs of [MIN_REDACTED_RUN_SIZE] or more consecutive deleted (redacted) messages into a single * [TimelineItem.GroupedEvents], so a long stretch of "Message removed" tiles shows as one expandable * "N removed messages" line, like element-web. Shorter runs, and anything that is not a redacted event - * (day dividers included), are left untouched. + * (day dividers included), are left untouched. A deleted message that still heads a thread is left + * untouched too, so that its thread summary stays reachable. * * This runs as the final step of [TimelineItemGrouper.group], after the state and membership events * have been grouped. It is deliberately kept out of the normal grouping: a run needs a higher minimum @@ -134,7 +136,7 @@ internal fun List.collapseRedactedRuns(groupIds: MutableMap, ) : TimelineItemEventContent, TimelineItemEventMutableContent { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt index f28de4f673..8c191ef140 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt @@ -17,6 +17,7 @@ data class TimelineItemAudioContent( override val fileSize: Long?, override val caption: String?, override val formattedCaption: CharSequence?, + override val htmlCaption: String? = null, override val isEdited: Boolean, val duration: Duration, override val mediaSource: MediaSource, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt index 01146a070b..efa73b91fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt @@ -33,6 +33,9 @@ sealed interface TimelineItemEventContentWithAttachment : val fileSize: Long? val caption: String? val formattedCaption: CharSequence? + + /** The caption as it was written, in HTML, so that editing it can start from the formatted text. */ + val htmlCaption: String? val mediaSource: MediaSource val mimeType: String val formattedFileSize: String @@ -127,6 +130,13 @@ fun TimelineItemEventContent.formattedCaptionOrNull(): CharSequence? = when (thi else -> null } +fun TimelineItemEventContent.htmlCaptionOrNull(): String? = when (this) { + is TimelineItemEventContentWithAttachment -> htmlCaption + is TimelineItemGalleryContent -> htmlCaption + is TimelineItemAttachmentsContent -> htmlCaption + else -> null +} + fun TimelineItemEventContentWithAttachment.duration(): Duration? { return when (this) { is TimelineItemAudioContent -> duration diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt index 680797e36b..36beeeb91d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt @@ -16,6 +16,7 @@ data class TimelineItemFileContent( override val fileSize: Long?, override val caption: String?, override val formattedCaption: CharSequence?, + override val htmlCaption: String? = null, override val isEdited: Boolean, override val mediaSource: MediaSource, val thumbnailSource: MediaSource?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemGalleryContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemGalleryContent.kt index f376a2a00a..840f19488d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemGalleryContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemGalleryContent.kt @@ -16,6 +16,7 @@ data class TimelineItemGalleryContent( val body: String, val caption: String?, val formattedCaption: CharSequence?, + val htmlCaption: String? = null, override val isEdited: Boolean, val items: ImmutableList, ) : TimelineItemEventContent, TimelineItemEventMutableContent { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt index 4828f22ba8..5e78adc945 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt @@ -19,6 +19,7 @@ data class TimelineItemImageContent( override val fileSize: Long?, override val caption: String?, override val formattedCaption: CharSequence?, + override val htmlCaption: String? = null, override val isEdited: Boolean, override val mediaSource: MediaSource, val thumbnailSource: MediaSource?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentPreviewParam.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentPreviewParam.kt index cd93c7c006..316b2ab79b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentPreviewParam.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentPreviewParam.kt @@ -28,6 +28,7 @@ fun aTimelineItemImageContent( blurhash: String? = A_BLUR_HASH, filename: String = "A picture.jpg", caption: String? = null, + htmlCaption: String? = null, width: Int? = null, height: Int? = 300, ) = TimelineItemImageContent( @@ -35,6 +36,7 @@ fun aTimelineItemImageContent( fileSize = 4 * 1024 * 1024L, caption = caption, formattedCaption = null, + htmlCaption = htmlCaption, isEdited = false, mediaSource = MediaSource(""), thumbnailSource = null, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt index 9e2f08d5c7..65585bc028 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt @@ -15,6 +15,7 @@ data class TimelineItemStickerContent( override val fileSize: Long?, override val caption: String?, override val formattedCaption: CharSequence?, + override val htmlCaption: String? = null, override val isEdited: Boolean, override val mediaSource: MediaSource, val thumbnailSource: MediaSource?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt index 80635c6e18..a1c8e279cd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt @@ -16,6 +16,7 @@ data class TimelineItemVideoContent( override val fileSize: Long?, override val caption: String?, override val formattedCaption: CharSequence?, + override val htmlCaption: String? = null, override val isEdited: Boolean, val duration: Duration, override val mediaSource: MediaSource, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt index 29a432e6bd..806a703ea5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt @@ -19,6 +19,7 @@ data class TimelineItemVoiceContent( override val fileSize: Long?, override val caption: String?, override val formattedCaption: CharSequence?, + override val htmlCaption: String? = null, override val isEdited: Boolean, val duration: Duration, override val mediaSource: MediaSource, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 227088f78c..4c0abc0a34 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -989,6 +989,33 @@ class MessagesPresenterTest { } } + @Test + fun `present - handle action edit caption starts from the formatted caption`() = runTest { + val messageEvent = aMessageEvent( + content = aTimelineItemImageContent( + caption = "Hello world", + htmlCaption = "Hello world", + ) + ) + val composerRecorder = EventsRecorder() + val presenter = createMessagesPresenter( + messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder, showTextFormatting = true) }, + ) + presenter.testWithLifecycleOwner { + val initialState = awaitItem() + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EditCaption, messageEvent)) + awaitItem() + composerRecorder.assertSingle( + MessageComposerEvent.SetMode( + composerMode = MessageComposerMode.EditCaption( + eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), + content = "Hello world", + ) + ) + ) + } + } + @Test fun `present - handle action add caption`() = runTest { val composerRecorder = EventsRecorder() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/threads/ThreadsListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/threads/ThreadsListPresenterTest.kt index 9f8d210a16..fab7602906 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/threads/ThreadsListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/threads/ThreadsListPresenterTest.kt @@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.threads import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.fixtures.aTimelineItemContentFactory import io.element.android.features.messages.impl.messagesummary.FakeMessageSummaryFormatter -import io.element.android.features.messages.impl.threads.list.ThreadsListEvents +import io.element.android.features.messages.impl.threads.list.ThreadsListEvent import io.element.android.features.messages.impl.threads.list.ThreadsListPresenter import io.element.android.features.messages.impl.threads.list.aThreadListItem import io.element.android.libraries.dateformatter.test.FakeDateFormatter @@ -48,7 +48,7 @@ class ThreadsListPresenterTest { // Pagination is automatically triggered on start, so we should have one call to paginate already paginateRecorder.assertions().isCalledOnce() - initialItem.eventSink(ThreadsListEvents.Paginate) + initialItem.eventSink(ThreadsListEvent.Paginate) // Simulate a pagination result threadsListService.emit(listOf(aThreadListItem())) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 9956729ecb..17cf61459e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -51,7 +51,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender import io.element.android.libraries.matrix.api.timeline.item.event.Receipt -import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -69,6 +68,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.aMessageContent +import io.element.android.libraries.matrix.test.timeline.aRedactedContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.libraries.matrix.ui.components.aMatrixUserList @@ -880,7 +880,7 @@ class TimelinePresenterTest { (0 until 3).map { index -> MatrixTimelineItem.Event( uniqueId = UniqueId("redacted_$index"), - event = anEventTimelineItem(eventId = EventId("\$R$index"), content = RedactedContent), + event = anEventTimelineItem(eventId = EventId("\$R$index"), content = aRedactedContent()), ) } ), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt index f93c8f196e..3876dc9715 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt @@ -12,12 +12,18 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition +import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo +import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType +import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.timeline.aMessageContent +import io.element.android.libraries.matrix.test.timeline.aRedactedContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -84,6 +90,42 @@ class TimelineItemsFactoryTest { ).inOrder() } + @Test + fun `redacted message keeps its thread info`() = runTest { + val factory = aTimelineItemsFactory( + config = TimelineItemsFactoryConfig( + computeReadReceipts = false, + computeReactions = false, + ) + ) + val items = listOf( + MatrixTimelineItem.Event( + uniqueId = UniqueId("event-0"), + event = anEventTimelineItem( + sender = A_USER_ID, + timestamp = 0L, + content = aRedactedContent( + threadInfo = EventThreadInfo.ThreadResponse(A_THREAD_ID), + ), + ), + ) + ) + var threadInfo: TimelineItemThreadInfo? = null + factory.timelineItems.test { + factory.replaceWith( + timelineItems = items, + roomMembers = emptyList(), + renderReadReceipts = false, + ) + threadInfo = awaitItem() + .filterIsInstance() + .firstOrNull() + ?.threadInfo + cancelAndIgnoreRemainingEvents() + } + assertThat(threadInfo).isEqualTo(TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID)) + } + @Test fun `a key verification request is not rendered`() = runTest { val factory = aTimelineItemsFactory( @@ -128,6 +170,43 @@ class TimelineItemsFactoryTest { } } + @Test + fun `a deleted message keeps the summary of the thread it heads`() = runTest { + val factory = aTimelineItemsFactory( + config = TimelineItemsFactoryConfig( + computeReadReceipts = false, + computeReactions = false, + ) + ) + val items = listOf( + MatrixTimelineItem.Event( + uniqueId = UniqueId("event-0"), + event = anEventTimelineItem( + sender = A_USER_ID, + content = aRedactedContent( + threadInfo = EventThreadInfo.ThreadRoot( + summary = ThreadSummary(latestEvent = AsyncData.Uninitialized, numberOfReplies = 3), + ), + ), + ), + ), + ) + factory.timelineItems.test { + factory.replaceWith( + timelineItems = items, + roomMembers = emptyList(), + renderReadReceipts = false, + ) + val threadInfo = awaitItem() + .filterIsInstance() + .single() + .threadInfo + assertThat(threadInfo).isInstanceOf(TimelineItemThreadInfo.ThreadRoot::class.java) + assertThat((threadInfo as TimelineItemThreadInfo.ThreadRoot).summary.numberOfReplies).isEqualTo(3) + cancelAndIgnoreRemainingEvents() + } + } + private suspend fun TestScope.groupPositionsOf(timestamps: List): List { val factory = aTimelineItemsFactory( config = TimelineItemsFactoryConfig( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index fc01184764..1c9de095d3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -297,6 +297,7 @@ class TimelineItemContentMessageFactoryTest : RobolectricTest() { fileSize = 555L, caption = "body.mp4 caption", formattedCaption = SpannedString("formatted"), + htmlCaption = "formatted", isEdited = true, duration = 1.minutes, mediaSource = MediaSource(url = "url", json = null), @@ -539,6 +540,7 @@ class TimelineItemContentMessageFactoryTest : RobolectricTest() { fileSize = 888L, caption = "body.jpg caption", formattedCaption = SpannedString("formatted"), + htmlCaption = "formatted", isEdited = true, mediaSource = MediaSource(url = "url", json = null), thumbnailSource = MediaSource("url_thumbnail"), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 005eb5730e..f6ef4f0fc2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -15,11 +15,14 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.ReadReceiptData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts +import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.anAvatarData import io.element.android.libraries.matrix.api.core.UniqueId +import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID @@ -190,6 +193,22 @@ class TimelineItemGrouperTest { ) } + @Test + fun `a deleted message that still heads a thread is left out of the removed messages group`() { + val threadRoot = aGroupableItem.copy( + id = UniqueId("r1"), + content = TimelineItemRedactedContent, + threadInfo = TimelineItemThreadInfo.ThreadRoot( + summary = ThreadSummary(latestEvent = AsyncData.Uninitialized, numberOfReplies = 2), + latestEventText = null, + ), + ) + val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent) + val r2 = aGroupableItem.copy(id = UniqueId("r2"), content = TimelineItemRedactedContent) + val result = sut.group(listOf(r0, threadRoot, r2)) + assertThat(result).isEqualTo(listOf(r0, threadRoot, r2)) + } + @Test fun `a run of fewer than three redacted events is left untouched`() { val r0 = aGroupableItem.copy(id = UniqueId("r0"), content = TimelineItemRedactedContent) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index a1ffd1f02b..9a5eda366b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -16,11 +16,11 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails -import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.core.FakeSendHandle +import io.element.android.libraries.matrix.test.timeline.aRedactedContent import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.tests.testutils.testCoroutineDispatchers @@ -88,7 +88,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( sender = A_USER_ID, senderProfile = ProfileDetails.Unavailable, timestamp = 9442, - content = RedactedContent, + content = aRedactedContent(), origin = null, timelineItemDebugInfoProvider = { TimelineItemDebugInfo( diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvent.kt similarity index 81% rename from features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt rename to features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvent.kt index 0ecd9dde16..81cc8f522e 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvent.kt @@ -11,9 +11,9 @@ package io.element.android.features.poll.impl.history import io.element.android.features.poll.impl.history.model.PollHistoryFilter import io.element.android.libraries.matrix.api.core.EventId -sealed interface PollHistoryEvents { - data object LoadMore : PollHistoryEvents - data class SelectPollAnswer(val pollStartId: EventId, val answerIds: List) : PollHistoryEvents - data class EndPoll(val pollStartId: EventId) : PollHistoryEvents - data class SelectFilter(val filter: PollHistoryFilter) : PollHistoryEvents +sealed interface PollHistoryEvent { + data object LoadMore : PollHistoryEvent + data class SelectPollAnswer(val pollStartId: EventId, val answerIds: List) : PollHistoryEvent + data class EndPoll(val pollStartId: EventId) : PollHistoryEvent + data class SelectFilter(val filter: PollHistoryFilter) : PollHistoryEvent } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt index 08a0a17b60..46da83e945 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt @@ -64,12 +64,12 @@ class PollHistoryPresenter( } } val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: PollHistoryEvents) { + fun handleEvent(event: PollHistoryEvent) { when (event) { - is PollHistoryEvents.LoadMore -> { + is PollHistoryEvent.LoadMore -> { coroutineScope.loadMore(timeline) } - is PollHistoryEvents.SelectPollAnswer -> sessionCoroutineScope.launch { + is PollHistoryEvent.SelectPollAnswer -> sessionCoroutineScope.launch { sendPollResponseAction.execute( timeline = timeline, pollStartId = event.pollStartId, @@ -78,10 +78,10 @@ class PollHistoryPresenter( Timber.e(error, "Failed to send poll response") } } - is PollHistoryEvents.EndPoll -> sessionCoroutineScope.launch { + is PollHistoryEvent.EndPoll -> sessionCoroutineScope.launch { endPollAction.execute(timeline = timeline, pollStartId = event.pollStartId) } - is PollHistoryEvents.SelectFilter -> { + is PollHistoryEvent.SelectFilter -> { activeFilter = event.filter } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt index bddfd153f6..67225e85c2 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt @@ -18,7 +18,7 @@ data class PollHistoryState( val hasMoreToLoad: Boolean, val activeFilter: PollHistoryFilter, val pollHistoryItems: PollHistoryItems, - val eventSink: (PollHistoryEvents) -> Unit, + val eventSink: (PollHistoryEvent) -> Unit, ) { fun pollHistoryForFilter(filter: PollHistoryFilter): ImmutableList { return when (filter) { diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStatePreviewParam.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStatePreviewParam.kt index ff07eaee37..40144f261e 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStatePreviewParam.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStatePreviewParam.kt @@ -48,7 +48,7 @@ internal fun aPollHistoryState( currentItems: List = listOf( aPollHistoryItem(), ), - eventSink: (PollHistoryEvents) -> Unit = {}, + eventSink: (PollHistoryEvent) -> Unit = {}, ) = PollHistoryState( isLoading = isLoading, hasMoreToLoad = hasMoreToLoad, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt index 87b9b0dca3..219c39ed23 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt @@ -62,15 +62,15 @@ fun PollHistoryView( modifier: Modifier = Modifier, ) { fun onLoadMore() { - state.eventSink(PollHistoryEvents.LoadMore) + state.eventSink(PollHistoryEvent.LoadMore) } fun onSelectAnswer(pollStartId: EventId, answerIds: List) { - state.eventSink(PollHistoryEvents.SelectPollAnswer(pollStartId, answerIds)) + state.eventSink(PollHistoryEvent.SelectPollAnswer(pollStartId, answerIds)) } fun onEndPoll(pollStartId: EventId) { - state.eventSink(PollHistoryEvents.EndPoll(pollStartId)) + state.eventSink(PollHistoryEvent.EndPoll(pollStartId)) } Scaffold( @@ -97,7 +97,7 @@ fun PollHistoryView( } PollHistoryFilterButtons( activeFilter = state.activeFilter, - onSelectFilter = { state.eventSink(PollHistoryEvents.SelectFilter(it)) }, + onSelectFilter = { state.eventSink(PollHistoryEvent.SelectFilter(it)) }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp), diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt index 537dcf7604..d1c67943c8 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt @@ -88,12 +88,12 @@ class PollHistoryPresenterTest { }.test { awaitItem().also { state -> assertThat(state.activeFilter).isEqualTo(PollHistoryFilter.ONGOING) - state.eventSink(PollHistoryEvents.SelectFilter(PollHistoryFilter.PAST)) + state.eventSink(PollHistoryEvent.SelectFilter(PollHistoryFilter.PAST)) } skipItems(1) awaitItem().also { state -> assertThat(state.activeFilter).isEqualTo(PollHistoryFilter.PAST) - state.eventSink(PollHistoryEvents.SelectFilter(PollHistoryFilter.ONGOING)) + state.eventSink(PollHistoryEvent.SelectFilter(PollHistoryFilter.ONGOING)) } awaitItem().also { state -> assertThat(state.activeFilter).isEqualTo(PollHistoryFilter.ONGOING) @@ -114,10 +114,10 @@ class PollHistoryPresenterTest { presenter.present() }.test { val state = awaitItem() - state.eventSink(PollHistoryEvents.EndPoll(AN_EVENT_ID)) + state.eventSink(PollHistoryEvent.EndPoll(AN_EVENT_ID)) runCurrent() endPollAction.verifyExecutionCount(1) - state.eventSink(PollHistoryEvents.SelectPollAnswer(AN_EVENT_ID, listOf("answer"))) + state.eventSink(PollHistoryEvent.SelectPollAnswer(AN_EVENT_ID, listOf("answer"))) runCurrent() sendPollResponseAction.verifyExecutionCount(1) cancelAndConsumeRemainingEvents() @@ -139,7 +139,7 @@ class PollHistoryPresenterTest { skipItems(1) val loadedState = awaitItem() assertThat(loadedState.isLoading).isFalse() - loadedState.eventSink(PollHistoryEvents.LoadMore) + loadedState.eventSink(PollHistoryEvent.LoadMore) backwardPaginationStatus.getAndUpdate { it.copy(isPaginating = true) } awaitItem().also { state -> assertThat(state.isLoading).isTrue() diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt index 1af756f747..998ec0ca48 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt @@ -35,7 +35,7 @@ import org.robolectric.annotation.Config class PollHistoryViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setPollHistoryViewView( aPollHistoryState( @@ -50,7 +50,7 @@ class PollHistoryViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on edit poll invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val eventId = EventId("\$anEventId") val state = aPollHistoryState( currentItems = listOf( @@ -76,7 +76,7 @@ class PollHistoryViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on poll end emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val eventId = EventId("\$anEventId") val state = aPollHistoryState( currentItems = listOf( @@ -102,14 +102,14 @@ class PollHistoryViewTest : RobolectricTest() { eventsRecorder.assertEmpty() clickOn(CommonStrings.action_ok) eventsRecorder.assertSingle( - PollHistoryEvents.EndPoll(eventId) + PollHistoryEvent.EndPoll(eventId) ) } @Config(qualifiers = "h1024dp") @Test fun `clicking on poll answer emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val eventId = EventId("\$anEventId") val state = aPollHistoryState( currentItems = listOf( @@ -133,13 +133,13 @@ class PollHistoryViewTest : RobolectricTest() { useUnmergedTree = true, ).performClick() eventsRecorder.assertSingle( - PollHistoryEvents.SelectPollAnswer(eventId, listOf(answer.id)) + PollHistoryEvent.SelectPollAnswer(eventId, listOf(answer.id)) ) } @Test fun `clicking on past tab emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setPollHistoryViewView( aPollHistoryState( eventSink = eventsRecorder @@ -147,14 +147,14 @@ class PollHistoryViewTest : RobolectricTest() { ) clickOn(R.string.screen_polls_history_filter_past) eventsRecorder.assertSingle( - PollHistoryEvents.SelectFilter(filter = PollHistoryFilter.PAST) + PollHistoryEvent.SelectFilter(filter = PollHistoryFilter.PAST) ) } @Config(qualifiers = "h1024dp") @Test fun `clicking on load more emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setPollHistoryViewView( aPollHistoryState( hasMoreToLoad = true, @@ -163,7 +163,7 @@ class PollHistoryViewTest : RobolectricTest() { ) clickOn(CommonStrings.action_load_more) eventsRecorder.assertSingle( - PollHistoryEvents.LoadMore + PollHistoryEvent.LoadMore ) } } diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index e647ebb919..12554eb6fa 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.matrixui) + implementation(projects.libraries.mediaviewer.api) implementation(projects.libraries.mediapickers.api) implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.permissions.api) @@ -91,6 +92,7 @@ dependencies { implementation(libs.color.picker) implementation(libs.androidx.browser) implementation(libs.androidx.datastore.preferences) + implementation(libs.serialization.json) api(projects.features.preferences.api) implementation(libs.showkase) @@ -108,6 +110,7 @@ dependencies { testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.libraries.roomselect.test) + testImplementation(projects.libraries.mediaviewer.test) testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.features.deactivation.test) testImplementation(projects.features.enterprise.test) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 15718ca0f0..c85eed5b9d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -40,10 +40,12 @@ import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.appyx.canPop import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint import kotlinx.parcelize.Parcelize @@ -56,6 +58,7 @@ class PreferencesFlowNode( private val lockScreenEntryPoint: LockScreenEntryPoint, private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint, private val pushHistoryEntryPoint: PushHistoryEntryPoint, + private val fileViewerEntryPoint: FileViewerEntryPoint, private val logoutEntryPoint: LogoutEntryPoint, private val openSourceLicensesEntryPoint: OpenSourceLicensesEntryPoint, private val accountDeactivationEntryPoint: AccountDeactivationEntryPoint, @@ -95,6 +98,9 @@ class PreferencesFlowNode( @Parcelize data object PushHistory : NavTarget + @Parcelize + data class PushRules(val filename: String, val content: String) : NavTarget + @Parcelize data object LockScreenSettings : NavTarget @@ -187,6 +193,10 @@ class PreferencesFlowNode( } NavTarget.DeveloperSettings -> { val developerSettingsCallback = object : DeveloperSettingsNode.Callback { + override fun navigateToPushRules(filename: String, content: String) { + backstack.push(NavTarget.PushRules(filename = filename, content = content)) + } + override fun navigateToPushHistory() { backstack.push(NavTarget.PushHistory) } @@ -270,6 +280,26 @@ class PreferencesFlowNode( }, ) } + is NavTarget.PushRules -> { + fileViewerEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = FileViewerEntryPoint.Params( + filename = navTarget.filename, + mimeType = MimeTypes.Json, + content = navTarget.content, + ), + callback = object : FileViewerEntryPoint.Callback { + override fun onDone() { + if (backstack.canPop()) { + backstack.pop() + } else { + navigateUp() + } + } + }, + ) + } is NavTarget.EditDefaultNotificationSetting -> { val callback = object : EditDefaultNotificationSettingNode.Callback { override fun navigateToRoomNotificationSettings(roomId: RoomId) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvent.kt similarity index 80% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvent.kt index ef27c499af..f2e6537d9d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvent.kt @@ -11,14 +11,14 @@ package io.element.android.features.preferences.impl.advanced import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.preferences.api.store.VideoCompressionPreset -sealed interface AdvancedSettingsEvents { - data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents - data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents - data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents - data class SetCompressImages(val compress: Boolean) : AdvancedSettingsEvents - data class SetVideoUploadQuality(val videoPreset: VideoCompressionPreset) : AdvancedSettingsEvents - data class SetTheme(val theme: ThemeOption) : AdvancedSettingsEvents - data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvents - data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvents - data class SetLiveLocationMinimumDistanceUpdate(val value: Int) : AdvancedSettingsEvents +sealed interface AdvancedSettingsEvent { + data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvent + data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvent + data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvent + data class SetCompressImages(val compress: Boolean) : AdvancedSettingsEvent + data class SetVideoUploadQuality(val videoPreset: VideoCompressionPreset) : AdvancedSettingsEvent + data class SetTheme(val theme: ThemeOption) : AdvancedSettingsEvent + data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvent + data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvent + data class SetLiveLocationMinimumDistanceUpdate(val value: Int) : AdvancedSettingsEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 45c136ab62..3ebf1f5140 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -100,18 +100,18 @@ class AdvancedSettingsPresenter( }.collect() } - fun handleEvent(event: AdvancedSettingsEvents) { + fun handleEvent(event: AdvancedSettingsEvent) { when (event) { - is AdvancedSettingsEvents.SetDeveloperModeEnabled -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetDeveloperModeEnabled -> sessionCoroutineScope.launch { appPreferencesStore.setDeveloperModeEnabled(event.enabled) } - is AdvancedSettingsEvents.SetSharePresenceEnabled -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetSharePresenceEnabled -> sessionCoroutineScope.launch { sessionPreferencesStore.setSharePresence(event.enabled) } - is AdvancedSettingsEvents.SetCompressMedia -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetCompressMedia -> sessionCoroutineScope.launch { sessionPreferencesStore.setOptimizeImages(event.compress) } - is AdvancedSettingsEvents.SetTheme -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetTheme -> sessionCoroutineScope.launch { when (event.theme) { ThemeOption.System -> appPreferencesStore.setTheme(Theme.System.name) ThemeOption.Dark -> appPreferencesStore.setTheme(Theme.Dark.name) @@ -119,15 +119,15 @@ class AdvancedSettingsPresenter( ThemeOption.Light -> appPreferencesStore.setTheme(Theme.Light.name) } } - is AdvancedSettingsEvents.SetHideInviteAvatars -> mediaPreviewConfigStateStore.setHideInviteAvatars(event.value) - is AdvancedSettingsEvents.SetTimelineMediaPreviewValue -> mediaPreviewConfigStateStore.setTimelineMediaPreviewValue(event.value) - is AdvancedSettingsEvents.SetLiveLocationMinimumDistanceUpdate -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetHideInviteAvatars -> mediaPreviewConfigStateStore.setHideInviteAvatars(event.value) + is AdvancedSettingsEvent.SetTimelineMediaPreviewValue -> mediaPreviewConfigStateStore.setTimelineMediaPreviewValue(event.value) + is AdvancedSettingsEvent.SetLiveLocationMinimumDistanceUpdate -> sessionCoroutineScope.launch { appPreferencesStore.setLiveLocationMinimumDistanceInMetersUpdate(event.value) } - is AdvancedSettingsEvents.SetCompressImages -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetCompressImages -> sessionCoroutineScope.launch { sessionPreferencesStore.setOptimizeImages(event.compress) } - is AdvancedSettingsEvents.SetVideoUploadQuality -> sessionCoroutineScope.launch { + is AdvancedSettingsEvent.SetVideoUploadQuality -> sessionCoroutineScope.launch { sessionPreferencesStore.setVideoCompressionPreset(event.videoPreset) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index 94019c1f01..689075759d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -24,7 +24,7 @@ data class AdvancedSettingsState( val availableThemeOptions: ImmutableList, val mediaPreviewConfigState: MediaPreviewConfigState, val liveLocationMinimumDistanceUpdate: Int?, - val eventSink: (AdvancedSettingsEvents) -> Unit + val eventSink: (AdvancedSettingsEvent) -> Unit ) sealed interface MediaOptimizationState { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStatePreviewParam.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStatePreviewParam.kt index c41df427cc..3b6a323419 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStatePreviewParam.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStatePreviewParam.kt @@ -44,7 +44,7 @@ fun aAdvancedSettingsState( liveLocationMinimumDistanceUpdate: Int? = 50, setTimelineMediaPreviewAction: AsyncAction = AsyncAction.Uninitialized, setHideInviteAvatarsAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (AdvancedSettingsEvents) -> Unit = {}, + eventSink: (AdvancedSettingsEvent) -> Unit = {}, ) = AdvancedSettingsState( isDeveloperModeEnabled = isDeveloperModeEnabled, isSharePresenceEnabled = isSharePresenceEnabled, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index de6ce5c9f6..d7b1cfeb17 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -89,7 +89,7 @@ fun AdvancedSettingsView( selectedOption = state.theme, options = state.availableThemeOptions, onSelectOption = { themeOption -> - state.eventSink(AdvancedSettingsEvents.SetTheme(themeOption)) + state.eventSink(AdvancedSettingsEvent.SetTheme(themeOption)) } ) ListItem( @@ -102,7 +102,7 @@ fun AdvancedSettingsView( trailingContent = ListItemContent.Switch( checked = state.isDeveloperModeEnabled, ), - onClick = { state.eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(!state.isDeveloperModeEnabled)) } + onClick = { state.eventSink(AdvancedSettingsEvent.SetDeveloperModeEnabled(!state.isDeveloperModeEnabled)) } ) ListItem( content = { @@ -114,7 +114,7 @@ fun AdvancedSettingsView( trailingContent = ListItemContent.Switch( checked = state.isSharePresenceEnabled, ), - onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) } + onClick = { state.eventSink(AdvancedSettingsEvent.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) } ) val compressImages = state.mediaOptimizationState?.shouldCompressImages @@ -140,7 +140,7 @@ fun AdvancedSettingsView( Interaction.Name.MobileSettingsOptimizeMediaUploadsDisabled } ) - state.eventSink(AdvancedSettingsEvents.SetCompressMedia(newValue)) + state.eventSink(AdvancedSettingsEvent.SetCompressMedia(newValue)) } ) } @@ -164,7 +164,7 @@ fun AdvancedSettingsView( Interaction.Name.MobileSettingsOptimizeMediaUploadsDisabled } ) - state.eventSink(AdvancedSettingsEvents.SetCompressMedia(newValue)) + state.eventSink(AdvancedSettingsEvent.SetCompressMedia(newValue)) } ) @@ -193,7 +193,7 @@ fun AdvancedSettingsView( VideoQualitySelectorDialog( selectedPreset = state.mediaOptimizationState.videoPreset, onSubmit = { preset -> - state.eventSink(AdvancedSettingsEvents.SetVideoUploadQuality(preset)) + state.eventSink(AdvancedSettingsEvent.SetVideoUploadQuality(preset)) displaySelectorDialog = false }, onDismiss = { displaySelectorDialog = false }, @@ -207,7 +207,7 @@ fun AdvancedSettingsView( LiveLocationUpdatesSection( value = state.liveLocationMinimumDistanceUpdate, onSaveValue = { value -> - state.eventSink(AdvancedSettingsEvents.SetLiveLocationMinimumDistanceUpdate(value)) + state.eventSink(AdvancedSettingsEvent.SetLiveLocationMinimumDistanceUpdate(value)) }, onOpenAppPermissionsClick = onOpenAppSettingsClick, ) @@ -286,7 +286,7 @@ private fun ModerationAndSafety( title = stringResource(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title), isChecked = state.mediaPreviewConfigState.hideInviteAvatars, onCheckedChange = { - state.eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(it)) + state.eventSink(AdvancedSettingsEvent.SetHideInviteAvatars(it)) }, enabled = !state.mediaPreviewConfigState.setHideInviteAvatarsAction.isLoading() ) @@ -307,7 +307,7 @@ private fun ModerationAndSafety( compact = true ), onClick = { - state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) + state.eventSink(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) }, enabled = !state.mediaPreviewConfigState.setTimelineMediaPreviewAction.isLoading() ) @@ -318,7 +318,7 @@ private fun ModerationAndSafety( compact = true ), onClick = { - state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) + state.eventSink(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) }, enabled = !state.mediaPreviewConfigState.setTimelineMediaPreviewAction.isLoading() ) @@ -329,7 +329,7 @@ private fun ModerationAndSafety( compact = true ), onClick = { - state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On)) + state.eventSink(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.On)) }, enabled = !state.mediaPreviewConfigState.setTimelineMediaPreviewAction.isLoading() ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvent.kt similarity index 67% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvent.kt index 31e516e730..42fdff63eb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvent.kt @@ -10,9 +10,9 @@ package io.element.android.features.preferences.impl.blockedusers import io.element.android.libraries.matrix.api.core.UserId -sealed interface BlockedUsersEvents { - data class Unblock(val userId: UserId) : BlockedUsersEvents - data class CopyToClipboard(val userId: UserId) : BlockedUsersEvents - data object ConfirmUnblock : BlockedUsersEvents - data object Cancel : BlockedUsersEvents +sealed interface BlockedUsersEvent { + data class Unblock(val userId: UserId) : BlockedUsersEvent + data class CopyToClipboard(val userId: UserId) : BlockedUsersEvent + data object ConfirmUnblock : BlockedUsersEvent + data object Cancel : BlockedUsersEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt index 2944089fa1..8f20694d81 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt @@ -74,23 +74,23 @@ class BlockedUsersPresenter( val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() - fun handleEvent(event: BlockedUsersEvents) { + fun handleEvent(event: BlockedUsersEvent) { when (event) { - is BlockedUsersEvents.Unblock -> { + is BlockedUsersEvent.Unblock -> { pendingUserToUnblock = event.userId unblockUserAction.value = AsyncAction.ConfirmingNoParams } - is BlockedUsersEvents.CopyToClipboard -> { + is BlockedUsersEvent.CopyToClipboard -> { clipboardHelper.copyPlainText(event.userId.value) snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard)) } - BlockedUsersEvents.ConfirmUnblock -> { + BlockedUsersEvent.ConfirmUnblock -> { pendingUserToUnblock?.let { coroutineScope.unblockUser(it, unblockUserAction) pendingUserToUnblock = null } } - BlockedUsersEvents.Cancel -> { + BlockedUsersEvent.Cancel -> { pendingUserToUnblock = null unblockUserAction.value = AsyncAction.Uninitialized } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt index e4b07386c7..d62ab03bff 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt @@ -17,5 +17,5 @@ data class BlockedUsersState( val blockedUsers: ImmutableList, val unblockUserAction: AsyncAction, val snackbarMessage: SnackbarMessage?, - val eventSink: (BlockedUsersEvents) -> Unit, + val eventSink: (BlockedUsersEvent) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewParam.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewParam.kt index b1a9a2224d..6fce0ba1f1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewParam.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStatePreviewParam.kt @@ -32,7 +32,7 @@ internal fun aBlockedUsersState( blockedUsers: List = aMatrixUserList(), unblockUserAction: AsyncAction = AsyncAction.Uninitialized, snackbarMessage: SnackbarMessage? = null, - eventSink: (BlockedUsersEvents) -> Unit = {}, + eventSink: (BlockedUsersEvent) -> Unit = {}, ): BlockedUsersState { return BlockedUsersState( blockedUsers = blockedUsers.toImmutableList(), diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt index be0c9d4937..be6c0ef170 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt @@ -64,8 +64,8 @@ fun BlockedUsersView( items(state.blockedUsers) { matrixUser -> BlockedUserItem( matrixUser = matrixUser, - onClick = { state.eventSink(BlockedUsersEvents.Unblock(it)) }, - onLongClick = { state.eventSink(BlockedUsersEvents.CopyToClipboard(it)) }, + onClick = { state.eventSink(BlockedUsersEvent.Unblock(it)) }, + onLongClick = { state.eventSink(BlockedUsersEvent.CopyToClipboard(it)) }, ) } } @@ -99,8 +99,8 @@ fun BlockedUsersView( title = stringResource(R.string.screen_blocked_users_unblock_alert_title), content = stringResource(R.string.screen_blocked_users_unblock_alert_description), submitText = stringResource(R.string.screen_blocked_users_unblock_alert_action), - onSubmitClick = { state.eventSink(BlockedUsersEvents.ConfirmUnblock) }, - onDismiss = { state.eventSink(BlockedUsersEvents.Cancel) } + onSubmitClick = { state.eventSink(BlockedUsersEvent.ConfirmUnblock) }, + onDismiss = { state.eventSink(BlockedUsersEvent.Cancel) } ) } else -> Unit diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvent.kt similarity index 64% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvent.kt index 2d6c2b0904..34ccba2abe 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvent.kt @@ -10,11 +10,13 @@ package io.element.android.features.preferences.impl.developer import androidx.compose.ui.graphics.Color -sealed interface DeveloperSettingsEvents { - data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvents - data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvents - data object ClearCache : DeveloperSettingsEvents - data object VacuumStores : DeveloperSettingsEvents - data class MarkAllRoomsAsRead(val needsConfirmation: Boolean) : DeveloperSettingsEvents - data object DismissMarkAllRoomsAsReadConfirmation : DeveloperSettingsEvents +sealed interface DeveloperSettingsEvent { + data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvent + data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvent + data object ClearCache : DeveloperSettingsEvent + data object VacuumStores : DeveloperSettingsEvent + data class MarkAllRoomsAsRead(val needsConfirmation: Boolean) : DeveloperSettingsEvent + data object DismissMarkAllRoomsAsReadConfirmation : DeveloperSettingsEvent + data object OpenPushRules : DeveloperSettingsEvent + data object DismissPushRulesError : DeveloperSettingsEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNavigator.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNavigator.kt new file mode 100644 index 0000000000..8201c7a14d --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNavigator.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2026 Element Creations 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.preferences.impl.developer + +/** + * Navigation performed by the developer settings screen, when data has to be loaded first. + */ +fun interface DeveloperSettingsNavigator { + fun openPushRules(filename: String, content: String) +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt index a8a8c63eed..7a8c3222d1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt @@ -28,15 +28,22 @@ import io.element.android.libraries.di.SessionScope class DeveloperSettingsNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: DeveloperSettingsPresenter, + presenterFactory: DeveloperSettingsPresenter.Factory, private val extraDeveloperOptionsRenderer: ExtraDeveloperOptionsRenderer, -) : Node(buildContext, plugins = plugins) { +) : Node(buildContext, plugins = plugins), + DeveloperSettingsNavigator { interface Callback : Plugin { + fun navigateToPushRules(filename: String, content: String) fun navigateToPushHistory() fun onDone() } private val callback: Callback = callback() + private val presenter = presenterFactory.create(navigator = this) + + override fun openPushRules(filename: String, content: String) { + callback.navigateToPushRules(filename = filename, content = content) + } @Composable override fun View(modifier: Modifier) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index c5a9b430ee..74bf595ce4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -17,7 +17,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.toArgb -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsState import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase @@ -30,17 +32,23 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.data.ByteUnit +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement -@Inject +@AssistedInject class DeveloperSettingsPresenter( + @Assisted private val navigator: DeveloperSettingsNavigator, private val appDeveloperSettingsPresenter: Presenter, private val sessionId: SessionId, private val deviceId: DeviceId, @@ -52,7 +60,13 @@ class DeveloperSettingsPresenter( private val fileSizeFormatter: FileSizeFormatter, private val markAllRoomsAsRead: MarkAllRoomsAsRead, private val buildMeta: BuildMeta, + private val notificationSettingsService: NotificationSettingsService, ) : Presenter { + @AssistedFactory + fun interface Factory { + fun create(navigator: DeveloperSettingsNavigator): DeveloperSettingsPresenter + } + @Composable override fun present(): DeveloperSettingsState { val cacheSize = remember { @@ -67,6 +81,9 @@ class DeveloperSettingsPresenter( val markAllRoomsAsReadAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + val pushRulesAction = remember { + mutableStateOf>(AsyncAction.Uninitialized) + } var showColorPicker by remember { mutableStateOf(false) } @@ -79,10 +96,10 @@ class DeveloperSettingsPresenter( computeCacheSize(cacheSize) } - fun handleEvent(event: DeveloperSettingsEvents) { + fun handleEvent(event: DeveloperSettingsEvent) { when (event) { - DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) - is DeveloperSettingsEvents.ChangeBrandColor -> coroutineScope.launch { + DeveloperSettingsEvent.ClearCache -> coroutineScope.clearCache(clearCacheAction) + is DeveloperSettingsEvent.ChangeBrandColor -> coroutineScope.launch { showColorPicker = false val color = event.color ?.toArgb() @@ -91,13 +108,13 @@ class DeveloperSettingsPresenter( ?.padStart(7, '#') enterpriseService.overrideBrandColor(sessionId, color) } - is DeveloperSettingsEvents.SetShowColorPicker -> { + is DeveloperSettingsEvent.SetShowColorPicker -> { showColorPicker = event.show } - DeveloperSettingsEvents.VacuumStores -> coroutineScope.launch { + DeveloperSettingsEvent.VacuumStores -> coroutineScope.launch { vacuumStoresUseCase() } - is DeveloperSettingsEvents.MarkAllRoomsAsRead -> { + is DeveloperSettingsEvent.MarkAllRoomsAsRead -> { if (event.needsConfirmation) { markAllRoomsAsReadAction.value = AsyncAction.ConfirmingNoParams } else { @@ -106,9 +123,13 @@ class DeveloperSettingsPresenter( ) } } - DeveloperSettingsEvents.DismissMarkAllRoomsAsReadConfirmation -> { + DeveloperSettingsEvent.DismissMarkAllRoomsAsReadConfirmation -> { markAllRoomsAsReadAction.value = AsyncAction.Uninitialized } + DeveloperSettingsEvent.OpenPushRules -> coroutineScope.openPushRules(pushRulesAction) + DeveloperSettingsEvent.DismissPushRulesError -> { + pushRulesAction.value = AsyncAction.Uninitialized + } } } @@ -119,6 +140,7 @@ class DeveloperSettingsPresenter( databaseSizes = databaseSizes.value, clearCacheAction = clearCacheAction.value, markAllRoomsAsReadAction = markAllRoomsAsReadAction.value, + pushRulesAction = pushRulesAction.value, isEnterpriseBuild = buildMeta.isEnterpriseBuild, showColorPicker = showColorPicker, deviceId = deviceId, @@ -164,4 +186,40 @@ class DeveloperSettingsPresenter( markAllRoomsAsRead().getOrThrow() }.runCatchingUpdatingState(state = markAllRoomsAsReadAction) } + + private fun CoroutineScope.openPushRules(pushRulesAction: MutableState>) = launch { + pushRulesAction.value = AsyncAction.Loading + notificationSettingsService.getRawPushRules() + .onSuccess { content -> + pushRulesAction.value = AsyncAction.Uninitialized + navigator.openPushRules( + filename = pushRulesFilename(), + content = content.orEmpty().prettyPrintJson(), + ) + } + .onFailure { + pushRulesAction.value = AsyncAction.Failure(it) + } + } + + /** + * The user id contains a colon, which is not a valid character for a file name on all the file systems, + * so replace it by an underscore. + */ + private fun pushRulesFilename() = "push_rules${sessionId.value.replace(':', '_')}.json" } + +@OptIn(ExperimentalSerializationApi::class) +private val prettyPrintJson = Json { + prettyPrint = true + // Keep the indentation small, for a better rendering on mobile. + prettyPrintIndent = " " +} + +/** + * Pretty print this json content, so that it is readable when rendered. + * Return the content as is if it is not valid json. + */ +private fun String.prettyPrintJson(): String = runCatchingExceptions { + prettyPrintJson.encodeToString(JsonElement.serializer(), prettyPrintJson.parseToJsonElement(this)) +}.getOrDefault(this) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index 700c989bfa..a4345c6452 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -20,10 +20,13 @@ data class DeveloperSettingsState( val databaseSizes: AsyncData>, val clearCacheAction: AsyncAction, val markAllRoomsAsReadAction: AsyncAction, + val pushRulesAction: AsyncAction, val isEnterpriseBuild: Boolean, val showColorPicker: Boolean, val deviceId: DeviceId, - val eventSink: (DeveloperSettingsEvents) -> Unit + val eventSink: (DeveloperSettingsEvent) -> Unit ) { - val showLoader = clearCacheAction is AsyncAction.Loading || markAllRoomsAsReadAction is AsyncAction.Loading + val showLoader = clearCacheAction is AsyncAction.Loading || + markAllRoomsAsReadAction is AsyncAction.Loading || + pushRulesAction is AsyncAction.Loading } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStatePreviewParam.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStatePreviewParam.kt index 8cc6ad7dce..0a42251b40 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStatePreviewParam.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStatePreviewParam.kt @@ -32,6 +32,9 @@ open class DeveloperSettingsStatePreviewParam : PreviewParameterProvider = AsyncAction.Uninitialized, markAllRoomsAsReadAction: AsyncAction = AsyncAction.Uninitialized, + pushRulesAction: AsyncAction = AsyncAction.Uninitialized, isEnterpriseBuild: Boolean = false, showColorPicker: Boolean = false, deviceId: DeviceId = DeviceId("ILAKNDNASDLK"), - eventSink: (DeveloperSettingsEvents) -> Unit = {}, + eventSink: (DeveloperSettingsEvent) -> Unit = {}, ) = DeveloperSettingsState( appDeveloperSettingsState = appDeveloperSettingsState, cacheSize = AsyncData.Success("1.2 MB"), databaseSizes = AsyncData.Success(persistentMapOf("state_store" to "1.2MB")), clearCacheAction = clearCacheAction, markAllRoomsAsReadAction = markAllRoomsAsReadAction, + pushRulesAction = pushRulesAction, isEnterpriseBuild = isEnterpriseBuild, showColorPicker = showColorPicker, deviceId = deviceId, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index 66a478a4e5..8eff49c471 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -23,8 +23,10 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.developer.appsettings.AppDeveloperSettingsView import io.element.android.libraries.androidutils.system.copyToClipboard +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferencePage @@ -56,8 +58,14 @@ fun DeveloperSettingsView( title = "Are you sure you want to mark all the rooms as read?", content = "", submitText = stringResource(CommonStrings.action_yes), - onSubmitClick = { state.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = false)) }, - onDismiss = { state.eventSink(DeveloperSettingsEvents.DismissMarkAllRoomsAsReadConfirmation) }, + onSubmitClick = { state.eventSink(DeveloperSettingsEvent.MarkAllRoomsAsRead(needsConfirmation = false)) }, + onDismiss = { state.eventSink(DeveloperSettingsEvent.DismissMarkAllRoomsAsReadConfirmation) }, + ) + } + (state.pushRulesAction as? AsyncAction.Failure)?.let { failure -> + ErrorDialog( + content = failure.error.message ?: stringResource(CommonStrings.error_unknown), + onSubmit = { state.eventSink(DeveloperSettingsEvent.DismissPushRulesError) }, ) } BackHandler( @@ -79,7 +87,10 @@ fun DeveloperSettingsView( onOpenShowkase = onOpenShowkase, ) SessionCategory(deviceId = state.deviceId) - NotificationCategory(onPushHistoryClick) + NotificationCategory( + onPushRulesClick = { state.eventSink(DeveloperSettingsEvent.OpenPushRules) }, + onPushHistoryClick = onPushHistoryClick, + ) MarkAllRoomsAsReadCategory(state) if (state.isEnterpriseBuild) { @@ -89,7 +100,7 @@ fun DeveloperSettingsView( Text("Change brand color") }, onClick = { - state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true)) + state.eventSink(DeveloperSettingsEvent.SetShowColorPicker(true)) } ) ListItem( @@ -97,7 +108,7 @@ fun DeveloperSettingsView( Text("Reset brand color") }, onClick = { - state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(null)) + state.eventSink(DeveloperSettingsEvent.ChangeBrandColor(null)) } ) } @@ -131,7 +142,7 @@ fun DeveloperSettingsView( Text("Vacuum stores") }, onClick = { - state.eventSink(DeveloperSettingsEvents.VacuumStores) + state.eventSink(DeveloperSettingsEvent.VacuumStores) } ) ListItem( @@ -152,7 +163,7 @@ fun DeveloperSettingsView( }, onClick = { if (state.clearCacheAction.isLoading().not()) { - state.eventSink(DeveloperSettingsEvents.ClearCache) + state.eventSink(DeveloperSettingsEvent.ClearCache) } } ) @@ -164,10 +175,10 @@ fun DeveloperSettingsView( showAlphaBar = false, ), onDismissRequest = { - state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false)) + state.eventSink(DeveloperSettingsEvent.SetShowColorPicker(false)) }, onPickedColor = { - state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(it)) + state.eventSink(DeveloperSettingsEvent.ChangeBrandColor(it)) }, ) } @@ -210,15 +221,24 @@ private fun MarkAllRoomsAsReadCategory(state: DeveloperSettingsState) { }, enabled = !state.showLoader, onClick = { - state.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = true)) + state.eventSink(DeveloperSettingsEvent.MarkAllRoomsAsRead(needsConfirmation = true)) }, ) } } @Composable -private fun NotificationCategory(onPushHistoryClick: () -> Unit) { +private fun NotificationCategory( + onPushRulesClick: () -> Unit, + onPushHistoryClick: () -> Unit, +) { PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_title)) { + ListItem( + content = { + Text("Push rules") + }, + onClick = onPushRulesClick, + ) ListItem( content = { Text(stringResource(R.string.troubleshoot_notifications_entry_point_push_history_title)) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvent.kt similarity index 92% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvent.kt index bc948da09c..e2c60327eb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvent.kt @@ -10,6 +10,6 @@ package io.element.android.features.preferences.impl.labs import io.element.android.libraries.featureflag.ui.model.FeatureUiModel -sealed interface LabsEvents { - data class ToggleFeature(val feature: FeatureUiModel) : LabsEvents +sealed interface LabsEvent { + data class ToggleFeature(val feature: FeatureUiModel) : LabsEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt index 3b7d499ecf..f9576f93e4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt @@ -54,9 +54,9 @@ class LabsPresenter( var isApplyingChanges by remember { mutableStateOf(false) } val featureUiModels = createUiModels(enabledFeatures) - fun handleEvent(event: LabsEvents) { + fun handleEvent(event: LabsEvent) { when (event) { - is LabsEvents.ToggleFeature -> coroutineScope.launch { + is LabsEvent.ToggleFeature -> coroutineScope.launch { val featureIndex = enabledFeatures.indexOfFirst { it.feature.key == event.feature.key }.takeIf { it != -1 } ?: return@launch val enabledFeature = enabledFeatures[featureIndex] val feature = enabledFeature.feature diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt index 42e70d58c9..55c20d1b43 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt @@ -14,5 +14,5 @@ import kotlinx.collections.immutable.ImmutableList data class LabsState( val features: ImmutableList, val isApplyingChanges: Boolean, - val eventSink: (LabsEvents) -> Unit, + val eventSink: (LabsEvent) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt index 7c7f2482e3..c518b346eb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt @@ -90,7 +90,7 @@ fun LabsView( supportingText = feature.description, value = feature.isEnabled, onChange = { - state.eventSink(LabsEvents.ToggleFeature(feature)) + state.eventSink(LabsEvent.ToggleFeature(feature)) } ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvent.kt similarity index 79% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvent.kt index 5ca7fc9fdf..fc175f4c0b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvent.kt @@ -10,28 +10,28 @@ package io.element.android.features.preferences.impl.notifications import io.element.android.libraries.preferences.api.store.NotificationSound -sealed interface NotificationSettingsEvents { - data object RefreshSystemNotificationsEnabled : NotificationSettingsEvents - data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents - data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents - data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents - data class SetInviteForMeNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents - data object FixConfigurationMismatch : NotificationSettingsEvents - data object ClearConfigurationMismatchError : NotificationSettingsEvents - data object ClearNotificationChangeError : NotificationSettingsEvents - data object ChangePushProvider : NotificationSettingsEvents - data object CancelChangePushProvider : NotificationSettingsEvents - data class SetPushProvider(val index: Int) : NotificationSettingsEvents - data class SetMessageSound(val sound: NotificationSound) : NotificationSettingsEvents - data class SetCallRingtone(val sound: NotificationSound) : NotificationSettingsEvents - data object DismissMessageSoundCopyError : NotificationSettingsEvents - data object DismissCallRingtoneCopyError : NotificationSettingsEvents - data object ShowMessageSoundDialog : NotificationSettingsEvents - data object DismissMessageSoundDialog : NotificationSettingsEvents - data class SelectMessageSoundPreset(val sound: NotificationSound) : NotificationSettingsEvents - data object LaunchMessageSoundPicker : NotificationSettingsEvents - data object ShowCallRingtoneDialog : NotificationSettingsEvents - data object DismissCallRingtoneDialog : NotificationSettingsEvents - data class SelectCallRingtonePreset(val sound: NotificationSound) : NotificationSettingsEvents - data object LaunchCallRingtonePicker : NotificationSettingsEvents +sealed interface NotificationSettingsEvent { + data object RefreshSystemNotificationsEnabled : NotificationSettingsEvent + data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvent + data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvent + data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvent + data class SetInviteForMeNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvent + data object FixConfigurationMismatch : NotificationSettingsEvent + data object ClearConfigurationMismatchError : NotificationSettingsEvent + data object ClearNotificationChangeError : NotificationSettingsEvent + data object ChangePushProvider : NotificationSettingsEvent + data object CancelChangePushProvider : NotificationSettingsEvent + data class SetPushProvider(val index: Int) : NotificationSettingsEvent + data class SetMessageSound(val sound: NotificationSound) : NotificationSettingsEvent + data class SetCallRingtone(val sound: NotificationSound) : NotificationSettingsEvent + data object DismissMessageSoundCopyError : NotificationSettingsEvent + data object DismissCallRingtoneCopyError : NotificationSettingsEvent + data object ShowMessageSoundDialog : NotificationSettingsEvent + data object DismissMessageSoundDialog : NotificationSettingsEvent + data class SelectMessageSoundPreset(val sound: NotificationSound) : NotificationSettingsEvent + data object LaunchMessageSoundPicker : NotificationSettingsEvent + data object ShowCallRingtoneDialog : NotificationSettingsEvent + data object DismissCallRingtoneDialog : NotificationSettingsEvent + data class SelectCallRingtonePreset(val sound: NotificationSound) : NotificationSettingsEvent + data object LaunchCallRingtonePicker : NotificationSettingsEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 03fe2cf31b..5256f9ec9c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -196,38 +196,38 @@ class NotificationSettingsPresenter( ) } - fun handleEvent(event: NotificationSettingsEvents) { + fun handleEvent(event: NotificationSettingsEvent) { when (event) { - is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> { + is NotificationSettingsEvent.SetAtRoomNotificationsEnabled -> { localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled, changeNotificationSettingAction) } - is NotificationSettingsEvents.SetCallNotificationsEnabled -> { + is NotificationSettingsEvent.SetCallNotificationsEnabled -> { localCoroutineScope.setCallNotificationsEnabled(event.enabled, changeNotificationSettingAction) } - is NotificationSettingsEvents.SetInviteForMeNotificationsEnabled -> { + is NotificationSettingsEvent.SetInviteForMeNotificationsEnabled -> { localCoroutineScope.setInviteForMeNotificationsEnabled(event.enabled, changeNotificationSettingAction) } - is NotificationSettingsEvents.SetNotificationsEnabled -> sessionCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled) - NotificationSettingsEvents.ClearConfigurationMismatchError -> { + is NotificationSettingsEvent.SetNotificationsEnabled -> sessionCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled) + NotificationSettingsEvent.ClearConfigurationMismatchError -> { matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false) } - NotificationSettingsEvents.FixConfigurationMismatch -> localCoroutineScope.fixConfigurationMismatch(matrixSettings) - NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> { + NotificationSettingsEvent.FixConfigurationMismatch -> localCoroutineScope.fixConfigurationMismatch(matrixSettings) + NotificationSettingsEvent.RefreshSystemNotificationsEnabled -> { systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled() refreshFullScreenIntentSettings++ } - NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized - NotificationSettingsEvents.ChangePushProvider -> showChangePushProviderDialog = true - NotificationSettingsEvents.CancelChangePushProvider -> showChangePushProviderDialog = false - is NotificationSettingsEvents.SetPushProvider -> localCoroutineScope.changePushProvider(distributors.getOrNull(event.index)) - is NotificationSettingsEvents.SetMessageSound -> applyMessageSound( + NotificationSettingsEvent.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized + NotificationSettingsEvent.ChangePushProvider -> showChangePushProviderDialog = true + NotificationSettingsEvent.CancelChangePushProvider -> showChangePushProviderDialog = false + is NotificationSettingsEvent.SetPushProvider -> localCoroutineScope.changePushProvider(distributors.getOrNull(event.index)) + is NotificationSettingsEvent.SetMessageSound -> applyMessageSound( sound = event.sound, sessionCoroutineScope = sessionCoroutineScope, onCopyError = { messageSoundCopyError = true }, onCopySuccess = { messageSoundCopyError = false }, onChannelFailure = { failure -> changeNotificationSettingAction.value = AsyncAction.Failure(failure) }, ) - is NotificationSettingsEvents.SelectMessageSoundPreset -> { + is NotificationSettingsEvent.SelectMessageSoundPreset -> { showMessageSoundDialog = false applyMessageSound( sound = event.sound, @@ -237,13 +237,13 @@ class NotificationSettingsPresenter( onChannelFailure = { failure -> changeNotificationSettingAction.value = AsyncAction.Failure(failure) }, ) } - NotificationSettingsEvents.ShowMessageSoundDialog -> showMessageSoundDialog = true - NotificationSettingsEvents.DismissMessageSoundDialog -> showMessageSoundDialog = false - NotificationSettingsEvents.LaunchMessageSoundPicker -> { + NotificationSettingsEvent.ShowMessageSoundDialog -> showMessageSoundDialog = true + NotificationSettingsEvent.DismissMessageSoundDialog -> showMessageSoundDialog = false + NotificationSettingsEvent.LaunchMessageSoundPicker -> { showMessageSoundDialog = false pendingMessageSoundPickerLaunch++ } - is NotificationSettingsEvents.SetCallRingtone -> { + is NotificationSettingsEvent.SetCallRingtone -> { legacyCallRingtone = null applyCallRingtone( sound = event.sound, @@ -253,7 +253,7 @@ class NotificationSettingsPresenter( onChannelFailure = { failure -> changeNotificationSettingAction.value = AsyncAction.Failure(failure) }, ) } - is NotificationSettingsEvents.SelectCallRingtonePreset -> { + is NotificationSettingsEvent.SelectCallRingtonePreset -> { showCallRingtoneDialog = false legacyCallRingtone = null applyCallRingtone( @@ -264,16 +264,16 @@ class NotificationSettingsPresenter( onChannelFailure = { failure -> changeNotificationSettingAction.value = AsyncAction.Failure(failure) }, ) } - NotificationSettingsEvents.ShowCallRingtoneDialog -> showCallRingtoneDialog = true - NotificationSettingsEvents.DismissCallRingtoneDialog -> showCallRingtoneDialog = false - NotificationSettingsEvents.LaunchCallRingtonePicker -> { + NotificationSettingsEvent.ShowCallRingtoneDialog -> showCallRingtoneDialog = true + NotificationSettingsEvent.DismissCallRingtoneDialog -> showCallRingtoneDialog = false + NotificationSettingsEvent.LaunchCallRingtonePicker -> { showCallRingtoneDialog = false pendingCallRingtonePickerLaunch++ } - NotificationSettingsEvents.DismissMessageSoundCopyError -> { + NotificationSettingsEvent.DismissMessageSoundCopyError -> { messageSoundCopyError = false } - NotificationSettingsEvents.DismissCallRingtoneCopyError -> { + NotificationSettingsEvent.DismissCallRingtoneCopyError -> { callRingtoneCopyError = false } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt index 3c59f6c355..379b8589b7 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt @@ -30,7 +30,7 @@ data class NotificationSettingsState( val showCallRingtoneDialog: Boolean, /** * One-shot trigger for launching the system ringtone picker. Each - * [NotificationSettingsEvents.LaunchMessageSoundPicker] increments the value; + * [NotificationSettingsEvent.LaunchMessageSoundPicker] increments the value; * the view watches this in `LaunchedEffect` and calls the launcher whenever it goes above 0. * **Always start at 0** in initial state, providers, and previews — a non-zero seed will * auto-open the picker on screen entry. @@ -42,7 +42,7 @@ data class NotificationSettingsState( * auto-opens on screen entry. */ val pendingCallRingtonePickerLaunch: Int, - val eventSink: (NotificationSettingsEvents) -> Unit, + val eventSink: (NotificationSettingsEvent) -> Unit, ) { sealed interface MatrixSettings { data object Uninitialized : MatrixSettings diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStatePreviewParam.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStatePreviewParam.kt index 38c83153ea..33f20cdb6e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStatePreviewParam.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStatePreviewParam.kt @@ -101,7 +101,7 @@ fun aValidNotificationSettingsState( showCallRingtoneDialog: Boolean = false, pendingMessageSoundPickerLaunch: Int = 0, pendingCallRingtonePickerLaunch: Int = 0, - eventSink: (NotificationSettingsEvents) -> Unit = {}, + eventSink: (NotificationSettingsEvent) -> Unit = {}, ) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Valid( atRoomNotificationsEnabled = atRoomNotificationsEnabled, @@ -138,7 +138,7 @@ fun aValidNotificationSettingsState( fun aInvalidNotificationSettingsState( fixFailed: Boolean = false, - eventSink: (NotificationSettingsEvents) -> Unit = {}, + eventSink: (NotificationSettingsEvent) -> Unit = {}, ) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Invalid( fixFailed = fixFailed, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index a15f991f95..21f0cc7b51 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -54,7 +54,7 @@ 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.Text import io.element.android.libraries.designsystem.utils.OnLifecycleEvent -import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvents +import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvent import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.preferences.api.store.NotificationSound import io.element.android.libraries.ui.strings.CommonStrings @@ -74,7 +74,7 @@ fun NotificationSettingsView( ) { OnLifecycleEvent { _, event -> when (event) { - Lifecycle.Event.ON_RESUME -> state.eventSink.invoke(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + Lifecycle.Event.ON_RESUME -> state.eventSink.invoke(NotificationSettingsEvent.RefreshSystemNotificationsEnabled) else -> Unit } } @@ -86,27 +86,27 @@ fun NotificationSettingsView( when (state.matrixSettings) { is NotificationSettingsState.MatrixSettings.Invalid -> InvalidNotificationSettingsView( showError = state.matrixSettings.fixFailed, - onContinueClick = { state.eventSink(NotificationSettingsEvents.FixConfigurationMismatch) }, - onDismissError = { state.eventSink(NotificationSettingsEvents.ClearConfigurationMismatchError) }, + onContinueClick = { state.eventSink(NotificationSettingsEvent.FixConfigurationMismatch) }, + onDismissError = { state.eventSink(NotificationSettingsEvent.ClearConfigurationMismatchError) }, ) NotificationSettingsState.MatrixSettings.Uninitialized -> return@PreferencePage is NotificationSettingsState.MatrixSettings.Valid -> NotificationSettingsContentView( matrixSettings = state.matrixSettings, state = state, - onNotificationsEnabledChange = { state.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(it)) }, + onNotificationsEnabledChange = { state.eventSink(NotificationSettingsEvent.SetNotificationsEnabled(it)) }, onGroupChatsClick = { onOpenEditDefault(false) }, onDirectChatsClick = { onOpenEditDefault(true) }, - onMentionNotificationsChange = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) }, + onMentionNotificationsChange = { state.eventSink(NotificationSettingsEvent.SetAtRoomNotificationsEnabled(it)) }, // TODO We are removing the call notification toggle until support for call notifications has been added -// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) }, - onInviteForMeNotificationsChange = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) }, +// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvent.SetCallNotificationsEnabled(it)) }, + onInviteForMeNotificationsChange = { state.eventSink(NotificationSettingsEvent.SetInviteForMeNotificationsEnabled(it)) }, onTroubleshootNotificationsClick = onTroubleshootNotificationsClick, ) } AsyncActionView( async = state.changeNotificationSettingAction, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, - onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) }, + onErrorDismiss = { state.eventSink(NotificationSettingsEvent.ClearNotificationChangeError) }, onSuccess = {}, ) } @@ -165,7 +165,7 @@ private fun NotificationSettingsContentView( Text(stringResource(R.string.full_screen_intent_banner_message)) }, onClick = { - state.fullScreenIntentPermissionsState.eventSink(FullScreenIntentPermissionsEvents.OpenSettings) + state.fullScreenIntentPermissionsState.eventSink(FullScreenIntentPermissionsEvent.OpenSettings) } ) } @@ -249,7 +249,7 @@ private fun NotificationSettingsContentView( }, onClick = { if (state.currentPushDistributor.isReady()) { - state.eventSink(NotificationSettingsEvents.ChangePushProvider) + state.eventSink(NotificationSettingsEvent.ChangePushProvider) } } ) @@ -269,10 +269,10 @@ private fun NotificationSettingsContentView( initialSelection = state.availablePushDistributors.indexOf(state.currentPushDistributor.dataOrNull()), onSelectOption = { index -> state.eventSink( - NotificationSettingsEvents.SetPushProvider(index) + NotificationSettingsEvent.SetPushProvider(index) ) }, - onDismissRequest = { state.eventSink(NotificationSettingsEvents.CancelChangePushProvider) }, + onDismissRequest = { state.eventSink(NotificationSettingsEvent.CancelChangePushProvider) }, ) } } @@ -286,7 +286,7 @@ private fun SoundsPreferenceCategory(state: NotificationSettingsState) { type = RingtoneManager.TYPE_NOTIFICATION, current = state.messageSound.sound, defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI, - onSoundPick = { sound -> state.eventSink(NotificationSettingsEvents.SetMessageSound(sound)) }, + onSoundPick = { sound -> state.eventSink(NotificationSettingsEvent.SetMessageSound(sound)) }, ) // Skip the initial 0 emission so the picker doesn't auto-open on screen entry; only // increments fired by LaunchMessageSoundPicker should launch it. @@ -298,11 +298,11 @@ private fun SoundsPreferenceCategory(state: NotificationSettingsState) { ListItem( content = { Text(stringResource(id = R.string.screen_notification_settings_message_sound_label)) }, supportingContent = { Text(state.messageSound.displayName) }, - onClick = { state.eventSink(NotificationSettingsEvents.ShowMessageSoundDialog) }, + onClick = { state.eventSink(NotificationSettingsEvent.ShowMessageSoundDialog) }, ) if (state.messageSound.copyError) { SoundCopyErrorRow( - onDismissClick = { state.eventSink(NotificationSettingsEvents.DismissMessageSoundCopyError) }, + onDismissClick = { state.eventSink(NotificationSettingsEvent.DismissMessageSoundCopyError) }, ) } if (state.showMessageSoundDialog) { @@ -313,7 +313,7 @@ private fun SoundsPreferenceCategory(state: NotificationSettingsState) { type = RingtoneManager.TYPE_RINGTONE, current = state.callRingtone.sound, defaultUri = Settings.System.DEFAULT_RINGTONE_URI, - onSoundPick = { sound -> state.eventSink(NotificationSettingsEvents.SetCallRingtone(sound)) }, + onSoundPick = { sound -> state.eventSink(NotificationSettingsEvent.SetCallRingtone(sound)) }, ) // Skip the initial 0 emission so the picker doesn't auto-open on screen entry; only // increments fired by LaunchCallRingtonePicker should launch it. @@ -325,11 +325,11 @@ private fun SoundsPreferenceCategory(state: NotificationSettingsState) { ListItem( content = { Text(stringResource(id = R.string.screen_notification_settings_call_ringtone_label)) }, supportingContent = { Text(state.callRingtone.displayName) }, - onClick = { state.eventSink(NotificationSettingsEvents.ShowCallRingtoneDialog) }, + onClick = { state.eventSink(NotificationSettingsEvent.ShowCallRingtoneDialog) }, ) if (state.callRingtone.copyError) { SoundCopyErrorRow( - onDismissClick = { state.eventSink(NotificationSettingsEvents.DismissCallRingtoneCopyError) }, + onDismissClick = { state.eventSink(NotificationSettingsEvent.DismissCallRingtoneCopyError) }, ) } if (state.showCallRingtoneDialog) { @@ -368,13 +368,13 @@ private fun MessageSoundDialog(state: NotificationSettingsState) { initialSelection = initialSelection, onSelectOption = { index -> when (index) { - 0 -> state.eventSink(NotificationSettingsEvents.SelectMessageSoundPreset(NotificationSound.ElementDefault)) - 1 -> state.eventSink(NotificationSettingsEvents.SelectMessageSoundPreset(NotificationSound.ElementFade)) - 2 -> state.eventSink(NotificationSettingsEvents.SelectMessageSoundPreset(NotificationSound.SystemDefault)) - else -> state.eventSink(NotificationSettingsEvents.LaunchMessageSoundPicker) + 0 -> state.eventSink(NotificationSettingsEvent.SelectMessageSoundPreset(NotificationSound.ElementDefault)) + 1 -> state.eventSink(NotificationSettingsEvent.SelectMessageSoundPreset(NotificationSound.ElementFade)) + 2 -> state.eventSink(NotificationSettingsEvent.SelectMessageSoundPreset(NotificationSound.SystemDefault)) + else -> state.eventSink(NotificationSettingsEvent.LaunchMessageSoundPicker) } }, - onDismissRequest = { state.eventSink(NotificationSettingsEvents.DismissMessageSoundDialog) }, + onDismissRequest = { state.eventSink(NotificationSettingsEvent.DismissMessageSoundDialog) }, ) } @@ -402,11 +402,11 @@ private fun CallRingtoneDialog(state: NotificationSettingsState) { initialSelection = initialSelection, onSelectOption = { index -> when (index) { - 0 -> state.eventSink(NotificationSettingsEvents.SelectCallRingtonePreset(NotificationSound.SystemDefault)) - else -> state.eventSink(NotificationSettingsEvents.LaunchCallRingtonePicker) + 0 -> state.eventSink(NotificationSettingsEvent.SelectCallRingtonePreset(NotificationSound.SystemDefault)) + else -> state.eventSink(NotificationSettingsEvent.LaunchCallRingtonePicker) } }, - onDismissRequest = { state.eventSink(NotificationSettingsEvents.DismissCallRingtoneDialog) }, + onDismissRequest = { state.eventSink(NotificationSettingsEvent.DismissCallRingtoneDialog) }, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index f2cb8b02cc..5769a8abe2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -76,12 +76,12 @@ class EditDefaultNotificationSettingPresenter( displayMentionsOnlyDisclaimer = !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } - fun handleEvent(event: EditDefaultNotificationSettingStateEvents) { + fun handleEvent(event: EditDefaultNotificationSettingStateEvent) { when (event) { - is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> { + is EditDefaultNotificationSettingStateEvent.SetNotificationMode -> { localCoroutineScope.setDefaultNotificationMode(event.mode, changeNotificationSettingAction) } - EditDefaultNotificationSettingStateEvents.ClearError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized + EditDefaultNotificationSettingStateEvent.ClearError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index 17c8cad95c..840f62eb4f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -18,5 +18,5 @@ data class EditDefaultNotificationSettingState( val roomsWithUserDefinedMode: ImmutableList, val changeNotificationSettingAction: AsyncAction, val displayMentionsOnlyDisclaimer: Boolean, - val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, + val eventSink: (EditDefaultNotificationSettingStateEvent) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvent.kt similarity index 81% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvent.kt index a7993c118a..4ede5813c1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvent.kt @@ -10,7 +10,7 @@ package io.element.android.features.preferences.impl.notifications.edit import io.element.android.libraries.matrix.api.room.RoomNotificationMode -sealed interface EditDefaultNotificationSettingStateEvents { - data class SetNotificationMode(val mode: RoomNotificationMode) : EditDefaultNotificationSettingStateEvents - data object ClearError : EditDefaultNotificationSettingStateEvents +sealed interface EditDefaultNotificationSettingStateEvent { + data class SetNotificationMode(val mode: RoomNotificationMode) : EditDefaultNotificationSettingStateEvent + data object ClearError : EditDefaultNotificationSettingStateEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index 3d4256f65d..9bfde12513 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -70,7 +70,7 @@ fun EditDefaultNotificationSettingView( mode = item, isSelected = state.mode == item, displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer, - onSelectOption = { state.eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(it)) } + onSelectOption = { state.eventSink(EditDefaultNotificationSettingStateEvent.SetNotificationMode(it)) } ) } } @@ -116,7 +116,7 @@ fun EditDefaultNotificationSettingView( AsyncActionView( async = state.changeNotificationSettingAction, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, - onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) }, + onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvent.ClearError) }, onSuccess = {}, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 8ee72fba6d..4fdac07550 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -19,7 +19,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.compound.theme.ElementTheme -import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutEvent import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.architecture.callback @@ -96,7 +96,7 @@ class PreferencesRootNode( onOpenBlockedUsers = callback::navigateToBlockedUsers, onSignOutClick = { if (state.directLogoutState.canDoDirectSignOut) { - state.directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) + state.directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false)) } else { callback.startSignOutFlow() } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt index 963d7846b4..afc3801c88 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt @@ -19,6 +19,7 @@ import io.element.android.features.logout.test.FakeLogoutEntryPoint import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.mediaviewer.test.FakeFileViewerEntryPoint import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleShootEntryPoint import io.element.android.libraries.troubleshoot.test.FakePushHistoryEntryPoint import io.element.android.tests.testutils.lambda.lambdaError @@ -43,6 +44,7 @@ class DefaultPreferencesEntryPointTest { lockScreenEntryPoint = FakeLockScreenEntryPoint(), notificationTroubleShootEntryPoint = FakeNotificationTroubleShootEntryPoint(), pushHistoryEntryPoint = FakePushHistoryEntryPoint(), + fileViewerEntryPoint = FakeFileViewerEntryPoint(), logoutEntryPoint = FakeLogoutEntryPoint(), openSourceLicensesEntryPoint = FakeOpenSourceLicensesEntryPoint(), accountDeactivationEntryPoint = FakeAccountDeactivationEntryPoint(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index 25d7c01778..2ee594e8b9 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -71,11 +71,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat(isDeveloperModeEnabled).isFalse() - eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(true)) + eventSink(AdvancedSettingsEvent.SetDeveloperModeEnabled(true)) } with(awaitItem()) { assertThat(isDeveloperModeEnabled).isTrue() - eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(false)) + eventSink(AdvancedSettingsEvent.SetDeveloperModeEnabled(false)) } with(awaitItem()) { assertThat(isDeveloperModeEnabled).isFalse() @@ -94,11 +94,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat(isSharePresenceEnabled).isTrue() - eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(false)) + eventSink(AdvancedSettingsEvent.SetSharePresenceEnabled(false)) } with(awaitItem()) { assertThat(isSharePresenceEnabled).isFalse() - eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) + eventSink(AdvancedSettingsEvent.SetSharePresenceEnabled(true)) } with(awaitItem()) { assertThat(isSharePresenceEnabled).isTrue() @@ -117,11 +117,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.AllMedia).isEnabled).isTrue() - eventSink(AdvancedSettingsEvents.SetCompressMedia(false)) + eventSink(AdvancedSettingsEvent.SetCompressMedia(false)) } with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.AllMedia).isEnabled).isFalse() - eventSink(AdvancedSettingsEvents.SetCompressMedia(true)) + eventSink(AdvancedSettingsEvent.SetCompressMedia(true)) } with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.AllMedia).isEnabled).isTrue() @@ -144,11 +144,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.Split).compressImages).isTrue() - eventSink(AdvancedSettingsEvents.SetCompressImages(false)) + eventSink(AdvancedSettingsEvent.SetCompressImages(false)) } with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.Split).compressImages).isFalse() - eventSink(AdvancedSettingsEvents.SetCompressImages(true)) + eventSink(AdvancedSettingsEvent.SetCompressImages(true)) } with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.Split).compressImages).isTrue() @@ -171,11 +171,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.Split).videoPreset).isEqualTo(VideoCompressionPreset.STANDARD) - eventSink(AdvancedSettingsEvents.SetVideoUploadQuality(VideoCompressionPreset.LOW)) + eventSink(AdvancedSettingsEvent.SetVideoUploadQuality(VideoCompressionPreset.LOW)) } with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.Split).videoPreset).isEqualTo(VideoCompressionPreset.LOW) - eventSink(AdvancedSettingsEvents.SetVideoUploadQuality(VideoCompressionPreset.HIGH)) + eventSink(AdvancedSettingsEvent.SetVideoUploadQuality(VideoCompressionPreset.HIGH)) } with(awaitItem()) { assertThat((mediaOptimizationState as MediaOptimizationState.Split).videoPreset).isEqualTo(VideoCompressionPreset.HIGH) @@ -194,15 +194,15 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat(theme).isEqualTo(ThemeOption.System) - eventSink(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark)) + eventSink(AdvancedSettingsEvent.SetTheme(ThemeOption.Dark)) } with(awaitItem()) { assertThat(theme).isEqualTo(ThemeOption.Dark) - eventSink(AdvancedSettingsEvents.SetTheme(ThemeOption.Light)) + eventSink(AdvancedSettingsEvent.SetTheme(ThemeOption.Light)) } with(awaitItem()) { assertThat(theme).isEqualTo(ThemeOption.Light) - eventSink(AdvancedSettingsEvents.SetTheme(ThemeOption.System)) + eventSink(AdvancedSettingsEvent.SetTheme(ThemeOption.System)) } with(awaitItem()) { assertThat(theme).isEqualTo(ThemeOption.System) @@ -242,7 +242,7 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat(liveLocationMinimumDistanceUpdate).isEqualTo(10) - eventSink(AdvancedSettingsEvents.SetLiveLocationMinimumDistanceUpdate(42)) + eventSink(AdvancedSettingsEvent.SetLiveLocationMinimumDistanceUpdate(42)) } with(awaitItem()) { assertThat(liveLocationMinimumDistanceUpdate).isEqualTo(42) @@ -298,11 +298,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse() - eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(true)) + eventSink(AdvancedSettingsEvent.SetHideInviteAvatars(true)) } with(awaitItem()) { assertThat(mediaPreviewConfigState.hideInviteAvatars).isTrue() - eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(false)) + eventSink(AdvancedSettingsEvent.SetHideInviteAvatars(false)) } with(awaitItem()) { assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse() @@ -323,11 +323,11 @@ class AdvancedSettingsPresenterTest { with(awaitItem()) { assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On) - eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) + eventSink(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) } with(awaitItem()) { assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off) - eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) + eventSink(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) } with(awaitItem()) { assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Private) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt index fa4ecfd829..52af7d5927 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt @@ -39,7 +39,7 @@ import org.robolectric.annotation.Config class AdvancedSettingsViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setAdvancedSettingsView( state = aAdvancedSettingsState( @@ -53,7 +53,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { @Test fun `clicking on other theme emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, @@ -61,7 +61,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { ) clickOn(CommonStrings.common_appearance) clickOn(R.string.theme_dark) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetTheme(ThemeOption.Dark)) } @Test @@ -91,31 +91,31 @@ class AdvancedSettingsViewTest : RobolectricTest() { @Test fun `clicking on View source emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, ), ) clickOn(CommonStrings.action_view_source) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetDeveloperModeEnabled(true)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetDeveloperModeEnabled(true)) } @Test fun `clicking on Share presence emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, ), ) clickOn(R.string.screen_advanced_settings_share_presence) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetSharePresenceEnabled(true)) } @Test fun `clicking on media to enable compression emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val analyticsService = FakeAnalyticsService() setAdvancedSettingsView( state = aAdvancedSettingsState( @@ -124,7 +124,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { analyticsService = analyticsService ) clickOn(R.string.screen_advanced_settings_media_compression_description) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(true)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetCompressMedia(true)) assertThat(analyticsService.capturedEvents).isEqualTo( listOf( Interaction( @@ -136,7 +136,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { @Test fun `clicking on media to disable compression emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val analyticsService = FakeAnalyticsService() setAdvancedSettingsView( state = aAdvancedSettingsState( @@ -146,7 +146,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { analyticsService = analyticsService ) clickOn(R.string.screen_advanced_settings_media_compression_description) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(false)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetCompressMedia(false)) assertThat(analyticsService.capturedEvents).isEqualTo( listOf( Interaction( @@ -159,7 +159,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { @Test @Config(qualifiers = "h1080dp") fun `clicking on hide invite avatars emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, @@ -167,13 +167,13 @@ class AdvancedSettingsViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetHideInviteAvatars(true)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetHideInviteAvatars(true)) } @Test @Config(qualifiers = "h1080dp") fun `clicking on timeline media preview always hide emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, @@ -181,13 +181,13 @@ class AdvancedSettingsViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) } @Test @Config(qualifiers = "h1080dp") fun `clicking on timeline media preview private rooms emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, @@ -195,13 +195,13 @@ class AdvancedSettingsViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) } @Test @Config(qualifiers = "h1080dp") fun `clicking on timeline media preview always show emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, @@ -209,13 +209,13 @@ class AdvancedSettingsViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_advanced_settings_show_media_timeline_always_show) - eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On)) + eventsRecorder.assertSingle(AdvancedSettingsEvent.SetTimelineMediaPreviewValue(MediaPreviewValue.On)) } @Test @Config(qualifiers = "h1080dp") fun `hide invite avatars toggle is disabled when action is loading`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, @@ -230,7 +230,7 @@ class AdvancedSettingsViewTest : RobolectricTest() { @Test @Config(qualifiers = "h1080dp") fun `timeline media preview options are disabled when action is loading`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setAdvancedSettingsView( state = aAdvancedSettingsState( eventSink = eventsRecorder, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt index 58e0cf467f..99ae35e70f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt @@ -33,7 +33,7 @@ import org.junit.Test class BlockedUserViewTest : RobolectricTest() { @Test fun `clicking on back invokes back callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setBlockedUsersView( aBlockedUsersState( @@ -47,7 +47,7 @@ class BlockedUserViewTest : RobolectricTest() { @Test fun `clicking on a user emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val userList = aMatrixUserList() setBlockedUsersView( aBlockedUsersState( @@ -56,12 +56,12 @@ class BlockedUserViewTest : RobolectricTest() { ), ) onNodeWithText(userList.first().displayName.orEmpty()).performClick() - eventsRecorder.assertSingle(BlockedUsersEvents.Unblock(userList.first().userId)) + eventsRecorder.assertSingle(BlockedUsersEvent.Unblock(userList.first().userId)) } @Test fun `long clicking on a user emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val userList = aMatrixUserList() setBlockedUsersView( aBlockedUsersState( @@ -70,12 +70,12 @@ class BlockedUserViewTest : RobolectricTest() { ), ) onNodeWithText(userList.first().displayName.orEmpty()).performTouchInput { longClick() } - eventsRecorder.assertSingle(BlockedUsersEvents.CopyToClipboard(userList.first().userId)) + eventsRecorder.assertSingle(BlockedUsersEvent.CopyToClipboard(userList.first().userId)) } @Test - fun `clicking on cancel sends a BlockedUsersEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on cancel sends a BlockedUsersEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setBlockedUsersView( aBlockedUsersState( unblockUserAction = AsyncAction.ConfirmingNoParams, @@ -83,12 +83,12 @@ class BlockedUserViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(BlockedUsersEvents.Cancel) + eventsRecorder.assertSingle(BlockedUsersEvent.Cancel) } @Test - fun `clicking on confirm sends a BlockedUsersEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on confirm sends a BlockedUsersEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setBlockedUsersView( aBlockedUsersState( unblockUserAction = AsyncAction.ConfirmingNoParams, @@ -96,7 +96,7 @@ class BlockedUserViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_blocked_users_unblock_alert_action) - eventsRecorder.assertSingle(BlockedUsersEvents.ConfirmUnblock) + eventsRecorder.assertSingle(BlockedUsersEvent.ConfirmUnblock) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt index 4e0164b2a0..828a0fc59a 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt @@ -118,10 +118,10 @@ class BlockedUsersPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(BlockedUsersEvents.Unblock(A_USER_ID)) + initialState.eventSink(BlockedUsersEvent.Unblock(A_USER_ID)) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Confirming::class.java) - initialState.eventSink(BlockedUsersEvents.ConfirmUnblock) + initialState.eventSink(BlockedUsersEvent.ConfirmUnblock) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Success::class.java) @@ -141,10 +141,10 @@ class BlockedUsersPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(BlockedUsersEvents.Unblock(A_USER_ID)) + initialState.eventSink(BlockedUsersEvent.Unblock(A_USER_ID)) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Confirming::class.java) - initialState.eventSink(BlockedUsersEvents.ConfirmUnblock) + initialState.eventSink(BlockedUsersEvent.ConfirmUnblock) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -163,10 +163,10 @@ class BlockedUsersPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(BlockedUsersEvents.Unblock(A_USER_ID)) + initialState.eventSink(BlockedUsersEvent.Unblock(A_USER_ID)) assertThat(awaitItem().unblockUserAction).isInstanceOf(AsyncAction.Confirming::class.java) - initialState.eventSink(BlockedUsersEvents.Cancel) + initialState.eventSink(BlockedUsersEvent.Cancel) assertThat(awaitItem().unblockUserAction).isEqualTo(AsyncAction.Uninitialized) } @@ -187,7 +187,7 @@ class BlockedUsersPresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.snackbarMessage).isNull() - initialState.eventSink(BlockedUsersEvents.CopyToClipboard(A_USER_ID)) + initialState.eventSink(BlockedUsersEvent.CopyToClipboard(A_USER_ID)) assertThat(clipboardHelper.clipboardContents).isEqualTo(A_USER_ID.value) assertThat(awaitItem().snackbarMessage?.messageResId).isEqualTo(CommonStrings.common_copied_to_clipboard) @@ -201,7 +201,7 @@ class BlockedUsersPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitItem().eventSink(BlockedUsersEvents.ConfirmUnblock) + awaitItem().eventSink(BlockedUsersEvent.ConfirmUnblock) ensureAllEventsConsumed() } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 3b2645783f..b76939f72c 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -28,10 +28,13 @@ import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test @@ -41,6 +44,19 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +private const val A_PUSH_RULES_CONTENT = """{"global":{"override":[{"rule_id":".m.rule.master","enabled":false}]}}""" +private const val A_PUSH_RULES_CONTENT_PRETTY_PRINTED = """{ + "global": { + "override": [ + { + "rule_id": ".m.rule.master", + "enabled": false + } + ] + } +}""" +private val AN_EXCEPTION = Exception("A failure") + class DeveloperSettingsPresenterTest { @get:Rule val warmUpRule = WarmUpRule() @@ -88,7 +104,7 @@ class DeveloperSettingsPresenterTest { skipItems(2) assertThat(clearCacheUseCase.executeHasBeenCalled).isFalse() awaitItem().also { state -> - state.eventSink(DeveloperSettingsEvents.ClearCache) + state.eventSink(DeveloperSettingsEvent.ClearCache) } awaitItem().also { state -> assertThat(state.clearCacheAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -117,13 +133,13 @@ class DeveloperSettingsPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.isEnterpriseBuild).isTrue() - initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true)) + initialState.eventSink(DeveloperSettingsEvent.SetShowColorPicker(true)) assertThat(awaitItem().showColorPicker).isTrue() - initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false)) + initialState.eventSink(DeveloperSettingsEvent.SetShowColorPicker(false)) assertThat(awaitItem().showColorPicker).isFalse() - initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true)) + initialState.eventSink(DeveloperSettingsEvent.SetShowColorPicker(true)) assertThat(awaitItem().showColorPicker).isTrue() - initialState.eventSink(DeveloperSettingsEvents.ChangeBrandColor(Color.Green)) + initialState.eventSink(DeveloperSettingsEvent.ChangeBrandColor(Color.Green)) assertThat(awaitItem().showColorPicker).isFalse() skipItems(1) overrideBrandColorResult.assertions().isCalledOnce() @@ -138,10 +154,10 @@ class DeveloperSettingsPresenterTest { presenter.test { skipItems(2) val initialState = awaitItem() - initialState.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = true)) + initialState.eventSink(DeveloperSettingsEvent.MarkAllRoomsAsRead(needsConfirmation = true)) val stateWithConfirmation = awaitItem() assertThat(stateWithConfirmation.markAllRoomsAsReadAction.isConfirming()).isTrue() - stateWithConfirmation.eventSink(DeveloperSettingsEvents.MarkAllRoomsAsRead(needsConfirmation = false)) + stateWithConfirmation.eventSink(DeveloperSettingsEvent.MarkAllRoomsAsRead(needsConfirmation = false)) awaitItem().also { state -> assertThat(state.markAllRoomsAsReadAction.isConfirming()).isFalse() assertThat(state.markAllRoomsAsReadAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -164,13 +180,78 @@ class DeveloperSettingsPresenterTest { presenter.test { val state = awaitItem() assertThat(vacuumCalled).isFalse() - state.eventSink(DeveloperSettingsEvents.VacuumStores) + state.eventSink(DeveloperSettingsEvent.VacuumStores) skipItems(1) assertThat(vacuumCalled).isTrue() } } + @Test + fun `present - OpenPushRules event navigates to the push rules content`() = runTest { + val openPushRulesLambda = lambdaRecorder { _, _ -> } + val presenter = createDeveloperSettingsPresenter( + navigator = DeveloperSettingsNavigator(openPushRulesLambda), + notificationSettingsService = FakeNotificationSettingsService( + getRawPushRulesResult = { Result.success(A_PUSH_RULES_CONTENT) }, + ), + ) + presenter.test { + val state = awaitItem() + assertThat(state.pushRulesAction).isEqualTo(AsyncAction.Uninitialized) + state.eventSink(DeveloperSettingsEvent.OpenPushRules) + skipItems(1) + awaitItem().also { + assertThat(it.pushRulesAction).isEqualTo(AsyncAction.Uninitialized) + } + openPushRulesLambda.assertions().isCalledOnce().with( + value("push_rules@alice_server.org.json"), + value(A_PUSH_RULES_CONTENT_PRETTY_PRINTED), + ) + } + } + + @Test + fun `present - OpenPushRules event keeps the content as is when it is not valid json`() = runTest { + val openPushRulesLambda = lambdaRecorder { _, _ -> } + val presenter = createDeveloperSettingsPresenter( + navigator = DeveloperSettingsNavigator(openPushRulesLambda), + notificationSettingsService = FakeNotificationSettingsService( + getRawPushRulesResult = { Result.success("not json") }, + ), + ) + presenter.test { + awaitItem().eventSink(DeveloperSettingsEvent.OpenPushRules) + skipItems(2) + openPushRulesLambda.assertions().isCalledOnce().with( + value("push_rules@alice_server.org.json"), + value("not json"), + ) + } + } + + @Test + fun `present - OpenPushRules event failure can be dismissed`() = runTest { + val presenter = createDeveloperSettingsPresenter( + notificationSettingsService = FakeNotificationSettingsService( + getRawPushRulesResult = { Result.failure(AN_EXCEPTION) }, + ), + ) + presenter.test { + val state = awaitItem() + state.eventSink(DeveloperSettingsEvent.OpenPushRules) + skipItems(1) + awaitItem().also { + assertThat(it.pushRulesAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) + it.eventSink(DeveloperSettingsEvent.DismissPushRulesError) + } + awaitItem().also { + assertThat(it.pushRulesAction).isEqualTo(AsyncAction.Uninitialized) + } + } + } + private fun createDeveloperSettingsPresenter( + navigator: DeveloperSettingsNavigator = DeveloperSettingsNavigator { _, _ -> lambdaError() }, sessionId: SessionId = A_SESSION_ID, deviceId: DeviceId = A_DEVICE_ID, cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(), @@ -180,8 +261,10 @@ class DeveloperSettingsPresenterTest { databaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) }, markAllRoomsAsRead: FakeMarkAllRoomsAsRead = FakeMarkAllRoomsAsRead(), buildMeta: BuildMeta = aBuildMeta(), + notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), ): DeveloperSettingsPresenter { return DeveloperSettingsPresenter( + navigator = navigator, appDeveloperSettingsPresenter = { anAppDeveloperSettingsState() }, sessionId = sessionId, deviceId = deviceId, @@ -193,6 +276,7 @@ class DeveloperSettingsPresenterTest { fileSizeFormatter = FakeFileSizeFormatter(), markAllRoomsAsRead = markAllRoomsAsRead, buildMeta = buildMeta, + notificationSettingsService = notificationSettingsService, ) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index a16ad77d48..fcc69cda91 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -30,7 +30,7 @@ import org.robolectric.annotation.Config class DeveloperSettingsViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setDeveloperSettingsView( state = aDeveloperSettingsState( @@ -45,7 +45,7 @@ class DeveloperSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h2400dp") @Test fun `clicking on push history notification invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setDeveloperSettingsView( state = aDeveloperSettingsState( @@ -57,10 +57,23 @@ class DeveloperSettingsViewTest : RobolectricTest() { } } + @Config(qualifiers = "h2400dp") + @Test + fun `clicking on push rules emits the expected event`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() + setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + ) + onNodeWithText("Push rules").performScrollTo().performClick() + eventsRecorder.assertSingle(DeveloperSettingsEvent.OpenPushRules) + } + @Config(qualifiers = "h2000dp") @Test fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setDeveloperSettingsView( state = aDeveloperSettingsState( @@ -75,14 +88,14 @@ class DeveloperSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h2400dp") @Test fun `clicking on clear cache emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setDeveloperSettingsView( state = aDeveloperSettingsState( eventSink = eventsRecorder ), ) onNodeWithText("Clear cache").performScrollTo().performClick() - eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache) + eventsRecorder.assertSingle(DeveloperSettingsEvent.ClearCache) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt index acf65ef2e6..1173ce9c5f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt @@ -70,10 +70,10 @@ class LabsPresenterTest { val feature = initialItem.features.first() assertThat(feature.isEnabled).isFalse() // Toggle the feature, should be true now - initialItem.eventSink(LabsEvents.ToggleFeature(feature)) + initialItem.eventSink(LabsEvent.ToggleFeature(feature)) assertThat(awaitItem().features.first().isEnabled).isTrue() // Toggle the feature, should be false now - initialItem.eventSink(LabsEvents.ToggleFeature(feature)) + initialItem.eventSink(LabsEvent.ToggleFeature(feature)) assertThat(awaitItem().features.first().isEnabled).isFalse() } } @@ -99,7 +99,7 @@ class LabsPresenterTest { assertThat(feature.isEnabled).isFalse() assertThat(initialItem.isApplyingChanges).isFalse() // Toggle the feature - initialItem.eventSink(LabsEvents.ToggleFeature(feature)) + initialItem.eventSink(LabsEvent.ToggleFeature(feature)) assertThat(awaitItem().features.first().isEnabled).isTrue() // The clear cache use case should have been called assertThat(awaitItem().isApplyingChanges).isTrue() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt index 509d00db25..10826071e7 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt @@ -10,7 +10,7 @@ package io.element.android.features.preferences.impl.notifications import com.google.common.truth.Truth.assertThat import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter -import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents +import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvent import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -150,7 +150,7 @@ class EditDefaultNotificationSettingsPresenterTest { ), ) presenter.test { - awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) + awaitItem().eventSink(EditDefaultNotificationSettingStateEvent.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) val loadedState = consumeItemsUntilPredicate { it.mode == RoomNotificationMode.ALL_MESSAGES }.last() @@ -166,12 +166,12 @@ class EditDefaultNotificationSettingsPresenterTest { val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) notificationSettingsService.givenSetDefaultNotificationModeError(AN_EXCEPTION) presenter.test { - awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) + awaitItem().eventSink(EditDefaultNotificationSettingStateEvent.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) val errorState = consumeItemsUntilPredicate { it.changeNotificationSettingAction.isFailure() }.last() assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue() - errorState.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) + errorState.eventSink(EditDefaultNotificationSettingStateEvent.ClearError) val clearErrorState = consumeItemsUntilPredicate { it.changeNotificationSettingAction.isUninitialized() }.last() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt index 29e4b1c40a..6a201fe3fa 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt @@ -120,7 +120,7 @@ class NotificationSettingsPresenterTest { val presenter = createNotificationSettingsPresenter(notificationSettingsService) presenter.test { val initialState = awaitItem() - initialState.eventSink(NotificationSettingsEvents.FixConfigurationMismatch) + initialState.eventSink(NotificationSettingsEvent.FixConfigurationMismatch) val fixedState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() @@ -148,14 +148,14 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue() - loadedState.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(false)) + loadedState.eventSink(NotificationSettingsEvent.SetNotificationsEnabled(false)) val updatedState = consumeItemsUntilPredicate { !it.appSettings.appNotificationsEnabled }.last() assertThat(updatedState.appSettings.appNotificationsEnabled).isFalse() unregisterWithResult.assertions().isCalledOnce() // Enable notification again - loadedState.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(true)) + loadedState.eventSink(NotificationSettingsEvent.SetNotificationsEnabled(true)) val updatedState2 = consumeItemsUntilPredicate { it.appSettings.appNotificationsEnabled }.last() @@ -173,7 +173,7 @@ class NotificationSettingsPresenterTest { }.last() val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid assertThat(validMatrixState?.callNotificationsEnabled).isFalse() - loadedState.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(true)) + loadedState.eventSink(NotificationSettingsEvent.SetCallNotificationsEnabled(true)) val updatedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == true }.last() @@ -192,7 +192,7 @@ class NotificationSettingsPresenterTest { }.last() val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid assertThat(validMatrixState?.inviteForMeNotificationsEnabled).isFalse() - loadedState.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(true)) + loadedState.eventSink(NotificationSettingsEvent.SetInviteForMeNotificationsEnabled(true)) val updatedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.inviteForMeNotificationsEnabled == true }.last() @@ -211,7 +211,7 @@ class NotificationSettingsPresenterTest { }.last() val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse() - loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true)) + loadedState.eventSink(NotificationSettingsEvent.SetAtRoomNotificationsEnabled(true)) val updatedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == true }.last() @@ -232,12 +232,12 @@ class NotificationSettingsPresenterTest { }.last() val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse() - loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true)) + loadedState.eventSink(NotificationSettingsEvent.SetAtRoomNotificationsEnabled(true)) val errorState = consumeItemsUntilPredicate { it.changeNotificationSettingAction.isFailure() }.last() assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue() - errorState.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) + errorState.eventSink(NotificationSettingsEvent.ClearNotificationChangeError) val clearErrorState = consumeItemsUntilPredicate { it.changeNotificationSettingAction.isUninitialized() }.last() @@ -258,16 +258,16 @@ class NotificationSettingsPresenterTest { Distributor(value = "aDistributorValue0", name = "aDistributorName0"), Distributor(value = "aDistributorValue1", name = "aDistributorName1"), ) - initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider) + initialState.eventSink.invoke(NotificationSettingsEvent.ChangePushProvider) val withDialog = awaitItem() assertThat(withDialog.showChangePushProviderDialog).isTrue() // Cancel - withDialog.eventSink(NotificationSettingsEvents.CancelChangePushProvider) + withDialog.eventSink(NotificationSettingsEvent.CancelChangePushProvider) val withoutDialog = awaitItem() assertThat(withoutDialog.showChangePushProviderDialog).isFalse() - withDialog.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider) + withDialog.eventSink.invoke(NotificationSettingsEvent.ChangePushProvider) assertThat(awaitItem().showChangePushProviderDialog).isTrue() - withDialog.eventSink(NotificationSettingsEvents.SetPushProvider(1)) + withDialog.eventSink(NotificationSettingsEvent.SetPushProvider(1)) val withNewProvider = awaitItem() assertThat(withNewProvider.showChangePushProviderDialog).isFalse() assertThat(withNewProvider.currentPushDistributor).isInstanceOf(AsyncData.Loading::class.java) @@ -290,10 +290,10 @@ class NotificationSettingsPresenterTest { Distributor(value = "aDistributorValue0", name = "aDistributorName0"), Distributor(value = "aDistributorValue1", name = "aDistributorName1"), ) - initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider) + initialState.eventSink.invoke(NotificationSettingsEvent.ChangePushProvider) assertThat(awaitItem().showChangePushProviderDialog).isTrue() // Choose the same value (index 0) - initialState.eventSink(NotificationSettingsEvents.SetPushProvider(0)) + initialState.eventSink(NotificationSettingsEvent.SetPushProvider(0)) val withNewProvider = awaitItem() assertThat(withNewProvider.showChangePushProviderDialog).isFalse() expectNoEvents() @@ -318,7 +318,7 @@ class NotificationSettingsPresenterTest { expectNoEvents() // Refresh - initialState.eventSink.invoke(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + initialState.eventSink.invoke(NotificationSettingsEvent.RefreshSystemNotificationsEnabled) assertThat(awaitItem().fullScreenIntentPermissionsState.permissionGranted).isTrue() } } @@ -334,10 +334,10 @@ class NotificationSettingsPresenterTest { ) presenter.test { val initialState = awaitLastSequentialItem() - initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider) + initialState.eventSink.invoke(NotificationSettingsEvent.ChangePushProvider) val withDialog = awaitItem() assertThat(withDialog.showChangePushProviderDialog).isTrue() - withDialog.eventSink(NotificationSettingsEvents.SetPushProvider(1)) + withDialog.eventSink(NotificationSettingsEvent.SetPushProvider(1)) val withNewProvider = awaitItem() assertThat(withNewProvider.showChangePushProviderDialog).isFalse() assertThat(withNewProvider.currentPushDistributor).isInstanceOf(AsyncData.Loading::class.java) @@ -384,7 +384,7 @@ class NotificationSettingsPresenterTest { }.last() assertThat(initialState.messageSound.sound).isEqualTo(NotificationSound.SystemDefault) - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(customSound)) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(customSound)) val updatedState = consumeItemsUntilPredicate { it.messageSound.sound == customSound }.last() assertThat(updatedState.messageSound.sound).isEqualTo(customSound) @@ -410,7 +410,7 @@ class NotificationSettingsPresenterTest { }.last() assertThat(initialState.callRingtone.sound).isEqualTo(NotificationSound.SystemDefault) - initialState.eventSink(NotificationSettingsEvents.SetCallRingtone(NotificationSound.Silent)) + initialState.eventSink(NotificationSettingsEvent.SetCallRingtone(NotificationSound.Silent)) val updatedState = consumeItemsUntilPredicate { it.callRingtone.sound == NotificationSound.Silent }.last() assertThat(updatedState.callRingtone.sound).isEqualTo(NotificationSound.Silent) @@ -435,11 +435,11 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://a"))) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://a"))) consumeItemsUntilPredicate { it.messageSound.sound == NotificationSound.Custom("content://a") } - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://b"))) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://b"))) consumeItemsUntilPredicate { it.messageSound.sound == NotificationSound.Custom("content://b") } - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.SystemDefault)) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.SystemDefault)) consumeItemsUntilPredicate { it.messageSound.sound == NotificationSound.SystemDefault } assertThat(versions).containsExactly(1, 2, 3).inOrder() @@ -461,7 +461,7 @@ class NotificationSettingsPresenterTest { consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid && it.messageSound.sound == NotificationSound.Custom("content://existing") - }.last().eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.SystemDefault)) + }.last().eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.SystemDefault)) consumeItemsUntilPredicate { it.messageSound.sound == NotificationSound.SystemDefault } assertThat(appPreferencesStore.getNotificationSoundChannelConfig().messageSound).isEqualTo(NotificationSound.SystemDefault) recreateRecorder.assertions().isCalledOnce().with(value(NotificationSound.SystemDefault), value(1)) @@ -483,7 +483,7 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://x"))) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://x"))) val errorState = consumeItemsUntilPredicate { it.changeNotificationSettingAction.isFailure() @@ -670,7 +670,7 @@ class NotificationSettingsPresenterTest { val initialState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://media/source"))) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://media/source"))) advanceUntilIdle() val expectedSound = NotificationSound.Custom("content://my.app.fileprovider/notification_sounds/message_sound.ogg") @@ -695,7 +695,7 @@ class NotificationSettingsPresenterTest { val initialState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://broken"))) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://broken"))) val errorState = consumeItemsUntilPredicate { it.messageSound.copyError }.last() assertThat(errorState.messageSound.copyError).isTrue() @@ -703,7 +703,7 @@ class NotificationSettingsPresenterTest { assertThat(appPreferencesStore.getNotificationSoundChannelConfig().messageSound).isEqualTo(priorChoice) // Dismissing the inline error clears the flag without altering persistence. - errorState.eventSink(NotificationSettingsEvents.DismissMessageSoundCopyError) + errorState.eventSink(NotificationSettingsEvent.DismissMessageSoundCopyError) val cleared = consumeItemsUntilPredicate { !it.messageSound.copyError }.last() assertThat(cleared.messageSound.copyError).isFalse() assertThat(appPreferencesStore.getNotificationSoundChannelConfig().messageSound).isEqualTo(priorChoice) @@ -725,7 +725,7 @@ class NotificationSettingsPresenterTest { val initialState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetCallRingtone(NotificationSound.Custom("content://broken"))) + initialState.eventSink(NotificationSettingsEvent.SetCallRingtone(NotificationSound.Custom("content://broken"))) val errorState = consumeItemsUntilPredicate { it.callRingtone.copyError }.last() assertThat(errorState.callRingtone.copyError).isTrue() @@ -733,7 +733,7 @@ class NotificationSettingsPresenterTest { assertThat(appPreferencesStore.getNotificationSoundChannelConfig().callRingtone).isEqualTo(priorChoice) // Dismissing the inline error clears the flag without altering persistence. - errorState.eventSink(NotificationSettingsEvents.DismissCallRingtoneCopyError) + errorState.eventSink(NotificationSettingsEvent.DismissCallRingtoneCopyError) val cleared = consumeItemsUntilPredicate { !it.callRingtone.copyError }.last() assertThat(cleared.callRingtone.copyError).isFalse() assertThat(appPreferencesStore.getNotificationSoundChannelConfig().callRingtone).isEqualTo(priorChoice) @@ -759,8 +759,8 @@ class NotificationSettingsPresenterTest { val initialState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.SystemDefault)) - initialState.eventSink(NotificationSettingsEvents.SetCallRingtone(NotificationSound.Silent)) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.SystemDefault)) + initialState.eventSink(NotificationSettingsEvent.SetCallRingtone(NotificationSound.Silent)) advanceUntilIdle() assertThat(copierCalls).isEmpty() cancelAndIgnoreRemainingEvents() @@ -791,8 +791,8 @@ class NotificationSettingsPresenterTest { val initialState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.SystemDefault)) - initialState.eventSink(NotificationSettingsEvents.SetCallRingtone(NotificationSound.Silent)) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.SystemDefault)) + initialState.eventSink(NotificationSettingsEvent.SetCallRingtone(NotificationSound.Silent)) advanceUntilIdle() assertThat(deleteCalls).containsExactly( NotificationSoundCopier.SoundSlot.Message, @@ -835,8 +835,8 @@ class NotificationSettingsPresenterTest { // Re-pick the persisted sound for both slots — neither should churn the channel, // bump the version, or hit the copier / delete sweep. - initial.eventSink(NotificationSettingsEvents.SelectMessageSoundPreset(NotificationSound.ElementDefault)) - initial.eventSink(NotificationSettingsEvents.SetCallRingtone(NotificationSound.Silent)) + initial.eventSink(NotificationSettingsEvent.SelectMessageSoundPreset(NotificationSound.ElementDefault)) + initial.eventSink(NotificationSettingsEvent.SetCallRingtone(NotificationSound.Silent)) advanceUntilIdle() val config = appPreferencesStore.getNotificationSoundChannelConfig() @@ -873,8 +873,8 @@ class NotificationSettingsPresenterTest { val initial = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initial.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://A"))) - initial.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://B"))) + initial.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://A"))) + initial.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://B"))) advanceUntilIdle() assertThat(copyOrder).containsExactly("content://A", "content://B").inOrder() @@ -908,7 +908,7 @@ class NotificationSettingsPresenterTest { val initialState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initialState.eventSink(NotificationSettingsEvents.SetMessageSound(NotificationSound.Custom("content://media/source"))) + initialState.eventSink(NotificationSettingsEvent.SetMessageSound(NotificationSound.Custom("content://media/source"))) advanceUntilIdle() assertThat(deleteCalls).isEmpty() cancelAndIgnoreRemainingEvents() @@ -924,11 +924,11 @@ class NotificationSettingsPresenterTest { }.last() assertThat(initial.showMessageSoundDialog).isFalse() - initial.eventSink(NotificationSettingsEvents.ShowMessageSoundDialog) + initial.eventSink(NotificationSettingsEvent.ShowMessageSoundDialog) val shown = consumeItemsUntilPredicate { it.showMessageSoundDialog }.last() assertThat(shown.showMessageSoundDialog).isTrue() - shown.eventSink(NotificationSettingsEvents.DismissMessageSoundDialog) + shown.eventSink(NotificationSettingsEvent.DismissMessageSoundDialog) val hidden = consumeItemsUntilPredicate { !it.showMessageSoundDialog }.last() assertThat(hidden.showMessageSoundDialog).isFalse() cancelAndIgnoreRemainingEvents() @@ -950,10 +950,10 @@ class NotificationSettingsPresenterTest { val initial = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() - initial.eventSink(NotificationSettingsEvents.ShowMessageSoundDialog) + initial.eventSink(NotificationSettingsEvent.ShowMessageSoundDialog) val shown = consumeItemsUntilPredicate { it.showMessageSoundDialog }.last() - shown.eventSink(NotificationSettingsEvents.SelectMessageSoundPreset(NotificationSound.ElementDefault)) + shown.eventSink(NotificationSettingsEvent.SelectMessageSoundPreset(NotificationSound.ElementDefault)) advanceUntilIdle() consumeItemsUntilPredicate { !it.showMessageSoundDialog && it.messageSound.sound == NotificationSound.ElementDefault @@ -973,10 +973,10 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() assertThat(initial.pendingMessageSoundPickerLaunch).isEqualTo(0) - initial.eventSink(NotificationSettingsEvents.ShowMessageSoundDialog) + initial.eventSink(NotificationSettingsEvent.ShowMessageSoundDialog) val shown = consumeItemsUntilPredicate { it.showMessageSoundDialog }.last() - shown.eventSink(NotificationSettingsEvents.LaunchMessageSoundPicker) + shown.eventSink(NotificationSettingsEvent.LaunchMessageSoundPicker) val launched = consumeItemsUntilPredicate { it.pendingMessageSoundPickerLaunch == 1 }.last() assertThat(launched.showMessageSoundDialog).isFalse() assertThat(launched.pendingMessageSoundPickerLaunch).isEqualTo(1) @@ -993,11 +993,11 @@ class NotificationSettingsPresenterTest { }.last() assertThat(initial.showCallRingtoneDialog).isFalse() - initial.eventSink(NotificationSettingsEvents.ShowCallRingtoneDialog) + initial.eventSink(NotificationSettingsEvent.ShowCallRingtoneDialog) val shown = consumeItemsUntilPredicate { it.showCallRingtoneDialog }.last() assertThat(shown.showCallRingtoneDialog).isTrue() - shown.eventSink(NotificationSettingsEvents.DismissCallRingtoneDialog) + shown.eventSink(NotificationSettingsEvent.DismissCallRingtoneDialog) val hidden = consumeItemsUntilPredicate { !it.showCallRingtoneDialog }.last() assertThat(hidden.showCallRingtoneDialog).isFalse() cancelAndIgnoreRemainingEvents() @@ -1020,10 +1020,10 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid && it.callRingtone.sound == NotificationSound.Silent }.last() - initial.eventSink(NotificationSettingsEvents.ShowCallRingtoneDialog) + initial.eventSink(NotificationSettingsEvent.ShowCallRingtoneDialog) val shown = consumeItemsUntilPredicate { it.showCallRingtoneDialog }.last() - shown.eventSink(NotificationSettingsEvents.SelectCallRingtonePreset(NotificationSound.SystemDefault)) + shown.eventSink(NotificationSettingsEvent.SelectCallRingtonePreset(NotificationSound.SystemDefault)) advanceUntilIdle() consumeItemsUntilPredicate { !it.showCallRingtoneDialog && it.callRingtone.sound == NotificationSound.SystemDefault @@ -1043,10 +1043,10 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() assertThat(initial.pendingCallRingtonePickerLaunch).isEqualTo(0) - initial.eventSink(NotificationSettingsEvents.ShowCallRingtoneDialog) + initial.eventSink(NotificationSettingsEvent.ShowCallRingtoneDialog) val shown = consumeItemsUntilPredicate { it.showCallRingtoneDialog }.last() - shown.eventSink(NotificationSettingsEvents.LaunchCallRingtonePicker) + shown.eventSink(NotificationSettingsEvent.LaunchCallRingtonePicker) val launched = consumeItemsUntilPredicate { it.pendingCallRingtonePickerLaunch == 1 }.last() assertThat(launched.showCallRingtoneDialog).isFalse() assertThat(launched.pendingCallRingtonePickerLaunch).isEqualTo(1) @@ -1078,7 +1078,7 @@ class NotificationSettingsPresenterTest { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid && it.callRingtone.sound == NotificationSound.Silent }.last() - initial.eventSink(NotificationSettingsEvents.SelectCallRingtonePreset(NotificationSound.SystemDefault)) + initial.eventSink(NotificationSettingsEvent.SelectCallRingtonePreset(NotificationSound.SystemDefault)) advanceUntilIdle() val after = consumeItemsUntilPredicate { it.callRingtone.sound == NotificationSound.SystemDefault diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt index 57edf01a1d..a28f43638f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -35,7 +35,7 @@ import org.robolectric.annotation.Config class NotificationSettingsViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() ensureCalledOnce { setNotificationSettingsView( state = aValidNotificationSettingsState( @@ -45,13 +45,13 @@ class NotificationSettingsViewTest : RobolectricTest() { ) pressBack() } - eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + eventsRecorder.assertSingle(NotificationSettingsEvent.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1280dp") @Test fun `clicking on troubleshoot notification invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() ensureCalledOnce { setNotificationSettingsView( state = aValidNotificationSettingsState( @@ -61,13 +61,13 @@ class NotificationSettingsViewTest : RobolectricTest() { ) clickOn(R.string.troubleshoot_notifications_entry_point_title) } - eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + eventsRecorder.assertSingle(NotificationSettingsEvent.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1024dp") @Test fun `clicking on group chats invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(false) { setNotificationSettingsView( state = aValidNotificationSettingsState( @@ -77,13 +77,13 @@ class NotificationSettingsViewTest : RobolectricTest() { ) clickOn(R.string.screen_notification_settings_group_chats) } - eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + eventsRecorder.assertSingle(NotificationSettingsEvent.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1024dp") @Test fun `clicking on direct chats invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(true) { setNotificationSettingsView( state = aValidNotificationSettingsState( @@ -93,7 +93,7 @@ class NotificationSettingsViewTest : RobolectricTest() { ) clickOn(R.string.screen_notification_settings_direct_chats) } - eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + eventsRecorder.assertSingle(NotificationSettingsEvent.RefreshSystemNotificationsEnabled) } @Config(qualifiers = "h1024dp") @@ -109,7 +109,7 @@ class NotificationSettingsViewTest : RobolectricTest() { } private fun testNotificationToggle(initialState: Boolean) = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( appNotificationEnabled = initialState, @@ -119,8 +119,8 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(R.string.screen_notification_settings_enable_notifications) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.SetNotificationsEnabled(!initialState) + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.SetNotificationsEnabled(!initialState) ) ) } @@ -138,7 +138,7 @@ class NotificationSettingsViewTest : RobolectricTest() { } private fun testAtRoomToggle(initialState: Boolean) = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( atRoomNotificationsEnabled = initialState, @@ -148,8 +148,8 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(R.string.screen_notification_settings_room_mention_label) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.SetAtRoomNotificationsEnabled(!initialState) + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.SetAtRoomNotificationsEnabled(!initialState) ) ) } @@ -167,7 +167,7 @@ class NotificationSettingsViewTest : RobolectricTest() { } private fun testInvitationToggle(initialState: Boolean) = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( inviteForMeNotificationsEnabled = initialState, @@ -177,8 +177,8 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(R.string.screen_notification_settings_invite_for_me_label) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(!initialState) + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.SetInviteForMeNotificationsEnabled(!initialState) ) ) } @@ -186,7 +186,7 @@ class NotificationSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `with an error configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( changeNotificationSettingAction = AsyncAction.Failure(AN_EXCEPTION), @@ -196,8 +196,8 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(CommonStrings.action_ok) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.ClearNotificationChangeError + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.ClearNotificationChangeError ) ) } @@ -205,7 +205,7 @@ class NotificationSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `with invalid configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aInvalidNotificationSettingsState( fixFailed = false, @@ -215,8 +215,8 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(CommonStrings.action_continue) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.FixConfigurationMismatch + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.FixConfigurationMismatch ) ) } @@ -224,7 +224,7 @@ class NotificationSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `with invalid configuration and error, clicking on OK emits the expected events`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aInvalidNotificationSettingsState( fixFailed = true, @@ -234,8 +234,8 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(CommonStrings.action_ok) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.ClearConfigurationMismatchError + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.ClearConfigurationMismatchError ) ) } @@ -243,7 +243,7 @@ class NotificationSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h1280dp") @Test fun `clicking on Push notification provider emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder @@ -252,15 +252,15 @@ class NotificationSettingsViewTest : RobolectricTest() { clickOn(R.string.screen_advanced_settings_push_provider_android) eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.ChangePushProvider, + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.ChangePushProvider, ) ) } @Test fun `clicking on a push provider emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder, @@ -271,8 +271,8 @@ class NotificationSettingsViewTest : RobolectricTest() { onNodeWithText("P2").performClick() eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.SetPushProvider(1), + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.SetPushProvider(1), ) ) } @@ -280,7 +280,7 @@ class NotificationSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h1280dp") @Test fun `sounds preference category renders rows with current display names`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState( eventSink = eventsRecorder, @@ -298,7 +298,7 @@ class NotificationSettingsViewTest : RobolectricTest() { @Config(qualifiers = "h1280dp") @Test fun `clicking the message sound row opens the preset dialog`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setNotificationSettingsView( state = aValidNotificationSettingsState(eventSink = eventsRecorder), ) @@ -308,8 +308,8 @@ class NotificationSettingsViewTest : RobolectricTest() { onNodeWithText("Message sound").performClick() eventsRecorder.assertList( listOf( - NotificationSettingsEvents.RefreshSystemNotificationsEnabled, - NotificationSettingsEvents.ShowMessageSoundDialog, + NotificationSettingsEvent.RefreshSystemNotificationsEnabled, + NotificationSettingsEvent.ShowMessageSoundDialog, ) ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvent.kt similarity index 74% rename from features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt rename to features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvent.kt index 94897d1bad..448f5a9d87 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvent.kt @@ -8,15 +8,15 @@ package io.element.android.features.rageshake.impl.bugreport -sealed interface BugReportEvents { - data object SendBugReport : BugReportEvents - data object ResetAll : BugReportEvents - data object ClearError : BugReportEvents +sealed interface BugReportEvent { + data object SendBugReport : BugReportEvent + data object ResetAll : BugReportEvent + data object ClearError : BugReportEvent - data class SetDescription(val description: String) : BugReportEvents - data class SetSendLog(val sendLog: Boolean) : BugReportEvents - data class SetCanContact(val canContact: Boolean) : BugReportEvents - data class SetSendScreenshot(val sendScreenshot: Boolean) : BugReportEvents - data class SetSendPushRules(val sendPushRules: Boolean) : BugReportEvents - data class SetGhIssueNumber(val ghIssueNumber: Int?) : BugReportEvents + data class SetDescription(val description: String) : BugReportEvent + data class SetSendLog(val sendLog: Boolean) : BugReportEvent + data class SetCanContact(val canContact: Boolean) : BugReportEvent + data class SetSendScreenshot(val sendScreenshot: Boolean) : BugReportEvent + data class SetSendPushRules(val sendPushRules: Boolean) : BugReportEvent + data class SetGhIssueNumber(val ghIssueNumber: Int?) : BugReportEvent } 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 c7c706fa68..607653baa4 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 @@ -83,9 +83,9 @@ class BugReportPresenter( } val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction) - fun handleEvent(event: BugReportEvents) { + fun handleEvent(event: BugReportEvent) { when (event) { - BugReportEvents.SendBugReport -> { + BugReportEvent.SendBugReport -> { if (formState.value.description.length < 10 && formState.value.ghIssueNumber == null) { sendingAction.value = AsyncAction.Failure(BugReportFormError.DescriptionTooShort) } else { @@ -93,27 +93,27 @@ class BugReportPresenter( appCoroutineScope.sendBugReport(formState.value, crashInfo.isNotEmpty(), uploadListener) } } - BugReportEvents.ResetAll -> appCoroutineScope.resetAll() - is BugReportEvents.SetDescription -> updateFormState(formState) { + BugReportEvent.ResetAll -> appCoroutineScope.resetAll() + is BugReportEvent.SetDescription -> updateFormState(formState) { copy(description = event.description) } - is BugReportEvents.SetCanContact -> updateFormState(formState) { + is BugReportEvent.SetCanContact -> updateFormState(formState) { copy(canContact = event.canContact) } - is BugReportEvents.SetSendLog -> updateFormState(formState) { + is BugReportEvent.SetSendLog -> updateFormState(formState) { copy(sendLogs = event.sendLog) } - is BugReportEvents.SetSendScreenshot -> updateFormState(formState) { + is BugReportEvent.SetSendScreenshot -> updateFormState(formState) { copy(sendScreenshot = event.sendScreenshot) } - is BugReportEvents.SetSendPushRules -> updateFormState(formState) { + is BugReportEvent.SetSendPushRules -> updateFormState(formState) { copy(sendPushRules = event.sendPushRules) } - BugReportEvents.ClearError -> { + BugReportEvent.ClearError -> { sendingProgress.floatValue = 0f sendingAction.value = AsyncAction.Uninitialized } - is BugReportEvents.SetGhIssueNumber -> updateFormState(formState) { + is BugReportEvent.SetGhIssueNumber -> updateFormState(formState) { copy(ghIssueNumber = event.ghIssueNumber) } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt index 8e07b3c4e8..4ec5710cc6 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt @@ -18,7 +18,7 @@ data class BugReportState( val screenshotUri: String?, val sendingProgress: Float, val sending: AsyncAction, - val eventSink: (BugReportEvents) -> Unit + val eventSink: (BugReportEvent) -> Unit ) { val submitEnabled = sending !is AsyncAction.Loading val isDescriptionInError = sending is AsyncAction.Failure && diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index 2c8dba4af3..9c1c25e153 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -84,7 +84,7 @@ fun BugReportView( supportingText = stringResource(id = R.string.screen_bug_report_editor_description), onValueChange = { descriptionFieldState = it - eventSink(BugReportEvents.SetDescription(it)) + eventSink(BugReportEvent.SetDescription(it)) }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, @@ -110,14 +110,14 @@ fun BugReportView( PreferenceDivider() PreferenceSwitch( isChecked = state.formState.sendLogs, - onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) }, + onCheckedChange = { eventSink(BugReportEvent.SetSendLog(it)) }, enabled = isFormEnabled, title = stringResource(id = R.string.screen_bug_report_include_logs), subtitle = stringResource(id = R.string.screen_bug_report_logs_description), ) PreferenceSwitch( isChecked = state.formState.canContact, - onCheckedChange = { eventSink(BugReportEvents.SetCanContact(it)) }, + onCheckedChange = { eventSink(BugReportEvent.SetCanContact(it)) }, enabled = isFormEnabled, title = stringResource(id = R.string.screen_bug_report_contact_me_title), subtitle = stringResource(id = R.string.screen_bug_report_contact_me), @@ -125,7 +125,7 @@ fun BugReportView( if (state.screenshotUri != null) { PreferenceSwitch( isChecked = state.formState.sendScreenshot, - onCheckedChange = { eventSink(BugReportEvents.SetSendScreenshot(it)) }, + onCheckedChange = { eventSink(BugReportEvent.SetSendScreenshot(it)) }, enabled = isFormEnabled, title = stringResource(id = R.string.screen_bug_report_include_screenshot) ) @@ -151,7 +151,7 @@ fun BugReportView( } PreferenceSwitch( isChecked = state.formState.sendPushRules, - onCheckedChange = { eventSink(BugReportEvents.SetSendPushRules(it)) }, + onCheckedChange = { eventSink(BugReportEvent.SetSendPushRules(it)) }, enabled = isFormEnabled, title = stringResource(R.string.screen_bug_report_send_notification_settings_title), subtitle = stringResource(R.string.screen_bug_report_send_notification_settings_description), @@ -179,12 +179,12 @@ fun BugReportView( onValueChange = { if (it.isEmpty()) { ghIssueNumberState = "" - eventSink(BugReportEvents.SetGhIssueNumber(null)) + eventSink(BugReportEvent.SetGhIssueNumber(null)) } else { val number = it.toIntOrNull()?.takeIf { ghInt -> ghInt in 1..99_999 } number?.let { ghIssueNumber -> ghIssueNumberState = ghIssueNumber.toString() - eventSink(BugReportEvents.SetGhIssueNumber(ghIssueNumber)) + eventSink(BugReportEvent.SetGhIssueNumber(ghIssueNumber)) } } }, @@ -202,7 +202,7 @@ fun BugReportView( PreferenceRow { Button( text = stringResource(id = CommonStrings.action_send), - onClick = { eventSink(BugReportEvents.SendBugReport) }, + onClick = { eventSink(BugReportEvent.SendBugReport) }, enabled = state.submitEnabled, showProgress = state.sending.isLoading(), modifier = Modifier @@ -216,7 +216,7 @@ fun BugReportView( async = state.sending, progressDialog = { }, onSuccess = { - eventSink(BugReportEvents.ResetAll) + eventSink(BugReportEvent.ResetAll) onSuccess() }, errorMessage = { error -> @@ -225,7 +225,7 @@ fun BugReportView( else -> error.message ?: error.toString() } }, - onErrorDismiss = { eventSink(BugReportEvents.ClearError) }, + onErrorDismiss = { eventSink(BugReportEvent.ClearError) }, ) } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt index afbcc939a6..904ff881f0 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt @@ -51,9 +51,9 @@ class BugReportPresenterTest { val presenter = createPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetDescription(A_SHORT_DESCRIPTION)) + initialState.eventSink.invoke(BugReportEvent.SetDescription(A_SHORT_DESCRIPTION)) assertThat(awaitItem().submitEnabled).isTrue() - initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) + initialState.eventSink.invoke(BugReportEvent.SetDescription(A_LONG_DESCRIPTION)) assertThat(awaitItem().submitEnabled).isTrue() } } @@ -63,9 +63,9 @@ class BugReportPresenterTest { val presenter = createPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetCanContact(true)) + initialState.eventSink.invoke(BugReportEvent.SetCanContact(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(canContact = true)) - initialState.eventSink.invoke(BugReportEvents.SetCanContact(false)) + initialState.eventSink.invoke(BugReportEvent.SetCanContact(false)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(canContact = false)) } } @@ -76,9 +76,9 @@ class BugReportPresenterTest { presenter.test { val initialState = awaitItem() // Since this is true by default, start by disabling - initialState.eventSink.invoke(BugReportEvents.SetSendLog(false)) + initialState.eventSink.invoke(BugReportEvent.SetSendLog(false)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendLogs = false)) - initialState.eventSink.invoke(BugReportEvents.SetSendLog(true)) + initialState.eventSink.invoke(BugReportEvent.SetSendLog(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendLogs = true)) } } @@ -88,9 +88,9 @@ class BugReportPresenterTest { val presenter = createPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetSendScreenshot(true)) + initialState.eventSink.invoke(BugReportEvent.SetSendScreenshot(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendScreenshot = true)) - initialState.eventSink.invoke(BugReportEvents.SetSendScreenshot(false)) + initialState.eventSink.invoke(BugReportEvent.SetSendScreenshot(false)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendScreenshot = false)) } } @@ -100,9 +100,9 @@ class BugReportPresenterTest { val presenter = createPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(true)) + initialState.eventSink.invoke(BugReportEvent.SetSendPushRules(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = true)) - initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(false)) + initialState.eventSink.invoke(BugReportEvent.SetSendPushRules(false)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = false)) } } @@ -112,9 +112,9 @@ class BugReportPresenterTest { val presenter = createPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetGhIssueNumber(1)) + initialState.eventSink.invoke(BugReportEvent.SetGhIssueNumber(1)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(ghIssueNumber = 1)) - initialState.eventSink.invoke(BugReportEvents.SetGhIssueNumber(null)) + initialState.eventSink.invoke(BugReportEvent.SetGhIssueNumber(null)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(ghIssueNumber = null)) } } @@ -130,7 +130,7 @@ class BugReportPresenterTest { val initialState = awaitItem() assertThat(initialState.hasCrashLogs).isTrue() assertThat(initialState.screenshotUri).isEqualTo(A_SCREENSHOT_URI) - initialState.eventSink.invoke(BugReportEvents.ResetAll) + initialState.eventSink.invoke(BugReportEvent.ResetAll) val resetState = awaitItem() assertThat(resetState.hasCrashLogs).isFalse() // TODO Make it live assertThat(resetState.screenshotUri).isNull() @@ -146,9 +146,9 @@ class BugReportPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) + initialState.eventSink.invoke(BugReportEvent.SetDescription(A_LONG_DESCRIPTION)) skipItems(1) - initialState.eventSink.invoke(BugReportEvents.SendBugReport) + initialState.eventSink.invoke(BugReportEvent.SendBugReport) skipItems(1) val progressState = awaitItem() assertThat(progressState.sending).isEqualTo(AsyncAction.Loading) @@ -170,9 +170,9 @@ class BugReportPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) + initialState.eventSink.invoke(BugReportEvent.SetDescription(A_LONG_DESCRIPTION)) skipItems(1) - initialState.eventSink.invoke(BugReportEvents.SendBugReport) + initialState.eventSink.invoke(BugReportEvent.SendBugReport) skipItems(1) val progressState = awaitItem() assertThat(progressState.sending).isEqualTo(AsyncAction.Loading) @@ -182,7 +182,7 @@ class BugReportPresenterTest { assertThat(awaitItem().sendingProgress).isEqualTo(0f) assertThat((awaitItem().sending as AsyncAction.Failure).error.message).isEqualTo(A_FAILURE_REASON) // Reset failure - initialState.eventSink.invoke(BugReportEvents.ClearError) + initialState.eventSink.invoke(BugReportEvent.ClearError) val lastItem = awaitItem() assertThat(lastItem.sendingProgress).isEqualTo(0f) assertThat(lastItem.sending).isInstanceOf(AsyncAction.Uninitialized::class.java) @@ -194,13 +194,13 @@ class BugReportPresenterTest { val presenter = createPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetDescription(A_SHORT_DESCRIPTION)) + initialState.eventSink.invoke(BugReportEvent.SetDescription(A_SHORT_DESCRIPTION)) skipItems(1) - initialState.eventSink.invoke(BugReportEvents.SendBugReport) + initialState.eventSink.invoke(BugReportEvent.SendBugReport) val errorState = awaitItem() assertThat(errorState.sending).isEqualTo(AsyncAction.Failure(BugReportFormError.DescriptionTooShort)) // Reset failure - initialState.eventSink.invoke(BugReportEvents.ClearError) + initialState.eventSink.invoke(BugReportEvent.ClearError) val lastItem = awaitItem() assertThat(lastItem.sending).isInstanceOf(AsyncAction.Uninitialized::class.java) } @@ -215,9 +215,9 @@ class BugReportPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) + initialState.eventSink.invoke(BugReportEvent.SetDescription(A_LONG_DESCRIPTION)) skipItems(1) - initialState.eventSink.invoke(BugReportEvents.SendBugReport) + initialState.eventSink.invoke(BugReportEvent.SendBugReport) skipItems(1) val progressState = awaitItem() assertThat(progressState.sending).isEqualTo(AsyncAction.Loading) diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvent.kt similarity index 65% rename from features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt rename to features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvent.kt index f7185f7fcd..e4df50fa7c 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.reportroom.impl -sealed interface ReportRoomEvents { - data class UpdateReason(val reason: String) : ReportRoomEvents - data object ToggleLeaveRoom : ReportRoomEvents - data object Report : ReportRoomEvents - data object ClearReportAction : ReportRoomEvents +sealed interface ReportRoomEvent { + data class UpdateReason(val reason: String) : ReportRoomEvent + data object ToggleLeaveRoom : ReportRoomEvent + data object Report : ReportRoomEvent + data object ClearReportAction : ReportRoomEvent } diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt index 422cf42718..25f9b56d59 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt @@ -44,16 +44,16 @@ class ReportRoomPresenter( val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: ReportRoomEvents) { + fun handleEvent(event: ReportRoomEvent) { when (event) { - ReportRoomEvents.Report -> coroutineScope.reportRoom(reason, leaveRoom, reportAction) - ReportRoomEvents.ToggleLeaveRoom -> { + ReportRoomEvent.Report -> coroutineScope.reportRoom(reason, leaveRoom, reportAction) + ReportRoomEvent.ToggleLeaveRoom -> { leaveRoom = !leaveRoom } - is ReportRoomEvents.UpdateReason -> { + is ReportRoomEvent.UpdateReason -> { reason = event.reason } - ReportRoomEvents.ClearReportAction -> { + ReportRoomEvent.ClearReportAction -> { reportAction.value = AsyncAction.Uninitialized } } diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt index fa09306991..28427705b0 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt @@ -14,7 +14,7 @@ data class ReportRoomState( val reason: String, val leaveRoom: Boolean, val reportAction: AsyncAction, - val eventSink: (ReportRoomEvents) -> Unit + val eventSink: (ReportRoomEvent) -> Unit ) { val canReport: Boolean = reason.isNotBlank() } diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStatePreviewParam.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStatePreviewParam.kt index 11d2777446..421c55f0e3 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStatePreviewParam.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStatePreviewParam.kt @@ -30,7 +30,7 @@ fun aReportRoomState( reason: String = "", leaveRoom: Boolean = false, reportAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (ReportRoomEvents) -> Unit = {} + eventSink: (ReportRoomEvent) -> Unit = {} ) = ReportRoomState( reason = reason, leaveRoom = leaveRoom, diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt index 7b614b13cb..df67b44b22 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt @@ -66,9 +66,9 @@ fun ReportRoomView( } }, onRetry = { - state.eventSink(ReportRoomEvents.Report) + state.eventSink(ReportRoomEvent.Report) }, - onErrorDismiss = { state.eventSink(ReportRoomEvents.ClearReportAction) } + onErrorDismiss = { state.eventSink(ReportRoomEvent.ClearReportAction) } ) Scaffold( @@ -93,7 +93,7 @@ fun ReportRoomView( ) { TextField( value = state.reason, - onValueChange = { state.eventSink(ReportRoomEvents.UpdateReason(it)) }, + onValueChange = { state.eventSink(ReportRoomEvent.UpdateReason(it)) }, placeholder = stringResource(R.string.screen_report_room_reason_placeholder), minLines = 3, enabled = !isReporting, @@ -112,7 +112,7 @@ fun ReportRoomView( Text(text = stringResource(CommonStrings.action_leave_room)) }, onClick = { - state.eventSink(ReportRoomEvents.ToggleLeaveRoom) + state.eventSink(ReportRoomEvent.ToggleLeaveRoom) }, trailingContent = ListItemContent.Switch(checked = state.leaveRoom) ) @@ -126,7 +126,7 @@ fun ReportRoomView( showProgress = isReporting, onClick = { focusManager.clearFocus(force = true) - state.eventSink(ReportRoomEvents.Report) + state.eventSink(ReportRoomEvent.Report) }, modifier = Modifier .fillMaxWidth() diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt index 6bcaeac02d..87b3c49b47 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt @@ -43,13 +43,13 @@ class ReportRoomPresenterTest { assertThat(state.reason).isEmpty() assertThat(state.canReport).isFalse() assertThat(state.leaveRoom).isFalse() - state.eventSink(ReportRoomEvents.UpdateReason("Spam")) + state.eventSink(ReportRoomEvent.UpdateReason("Spam")) } awaitItem().also { state -> assertThat(state.reason).isEqualTo("Spam") assertThat(state.canReport).isTrue() assertThat(state.leaveRoom).isFalse() - state.eventSink(ReportRoomEvents.ToggleLeaveRoom) + state.eventSink(ReportRoomEvent.ToggleLeaveRoom) } awaitItem().also { state -> assertThat(state.leaveRoom).isTrue() @@ -68,8 +68,8 @@ class ReportRoomPresenterTest { ) val presenter = createReportRoomPresenter(roomId = roomId, reportRoom = reportRoom) presenter.test { - awaitItem().eventSink(ReportRoomEvents.ToggleLeaveRoom) - awaitItem().eventSink(ReportRoomEvents.Report) + awaitItem().eventSink(ReportRoomEvent.ToggleLeaveRoom) + awaitItem().eventSink(ReportRoomEvent.Report) awaitItem().also { state -> assertThat(state.reportAction).isInstanceOf(AsyncAction.Loading::class.java) } @@ -93,7 +93,7 @@ class ReportRoomPresenterTest { ) val presenter = createReportRoomPresenter(roomId = roomId, reportRoom = reportRoom) presenter.test { - awaitItem().eventSink(ReportRoomEvents.Report) + awaitItem().eventSink(ReportRoomEvent.Report) awaitItem().also { state -> assertThat(state.reportAction).isInstanceOf(AsyncAction.Loading::class.java) } @@ -117,14 +117,14 @@ class ReportRoomPresenterTest { ) val presenter = createReportRoomPresenter(roomId = roomId, reportRoom = reportRoom) presenter.test { - awaitItem().eventSink(ReportRoomEvents.ToggleLeaveRoom) - awaitItem().eventSink(ReportRoomEvents.Report) + awaitItem().eventSink(ReportRoomEvent.ToggleLeaveRoom) + awaitItem().eventSink(ReportRoomEvent.Report) awaitItem().also { state -> assertThat(state.reportAction).isInstanceOf(AsyncAction.Loading::class.java) } awaitItem().also { state -> assertThat(state.reportAction).isInstanceOf(AsyncAction.Failure::class.java) - state.eventSink(ReportRoomEvents.Report) + state.eventSink(ReportRoomEvent.Report) } awaitItem().also { state -> assertThat(state.reportAction).isInstanceOf(AsyncAction.Loading::class.java) diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt index 0e43cf6594..64c8744424 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt @@ -28,7 +28,7 @@ import org.junit.Test class ReportRoomViewTest : RobolectricTest() { @Test fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setReportRoomView( aReportRoomState( @@ -42,7 +42,7 @@ class ReportRoomViewTest : RobolectricTest() { @Test fun `clicking on report when enabled emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setReportRoomView( aReportRoomState( reason = "Spam", @@ -50,12 +50,12 @@ class ReportRoomViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_report) - eventsRecorder.assertSingle(ReportRoomEvents.Report) + eventsRecorder.assertSingle(ReportRoomEvent.Report) } @Test fun `clicking on decline when disabled does not emit event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setReportRoomView( aReportRoomState(eventSink = eventsRecorder), ) @@ -64,17 +64,17 @@ class ReportRoomViewTest : RobolectricTest() { @Test fun `clicking on leave room option emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setReportRoomView( aReportRoomState(eventSink = eventsRecorder), ) clickOn(CommonStrings.action_leave_room) - eventsRecorder.assertSingle(ReportRoomEvents.ToggleLeaveRoom) + eventsRecorder.assertSingle(ReportRoomEvent.ToggleLeaveRoom) } @Test fun `typing text in the reason field emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setReportRoomView( aReportRoomState( eventSink = eventsRecorder, @@ -82,7 +82,7 @@ class ReportRoomViewTest : RobolectricTest() { ), ) onNodeWithText("").performTextInput("Spam!") - eventsRecorder.assertSingle(ReportRoomEvents.UpdateReason("Spam!")) + eventsRecorder.assertSingle(ReportRoomEvent.UpdateReason("Spam!")) } } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvent.kt similarity index 63% rename from features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvent.kt index 69fad02c25..b250fee2d1 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvent.kt @@ -10,9 +10,9 @@ package io.element.android.features.rolesandpermissions.impl.root import io.element.android.libraries.matrix.api.room.RoomMember -sealed interface RolesAndPermissionsEvents { - data object ChangeOwnRole : RolesAndPermissionsEvents - data class DemoteSelfTo(val role: RoomMember.Role) : RolesAndPermissionsEvents - data object ResetPermissions : RolesAndPermissionsEvents - data object CancelPendingAction : RolesAndPermissionsEvents +sealed interface RolesAndPermissionsEvent { + data object ChangeOwnRole : RolesAndPermissionsEvent + data class DemoteSelfTo(val role: RoomMember.Role) : RolesAndPermissionsEvent + data object ResetPermissions : RolesAndPermissionsEvent + data object CancelPendingAction : RolesAndPermissionsEvent } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt index dd3a59b99e..1231089af0 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt @@ -63,20 +63,20 @@ class RolesAndPermissionsPresenter( val changeOwnRoleAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val resetPermissionsAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - fun handleEvent(event: RolesAndPermissionsEvents) { + fun handleEvent(event: RolesAndPermissionsEvent) { when (event) { - is RolesAndPermissionsEvents.ChangeOwnRole -> { + is RolesAndPermissionsEvent.ChangeOwnRole -> { changeOwnRoleAction.value = AsyncAction.ConfirmingNoParams } - is RolesAndPermissionsEvents.CancelPendingAction -> { + is RolesAndPermissionsEvent.CancelPendingAction -> { changeOwnRoleAction.value = AsyncAction.Uninitialized resetPermissionsAction.value = AsyncAction.Uninitialized } - is RolesAndPermissionsEvents.DemoteSelfTo -> coroutineScope.demoteSelfTo( + is RolesAndPermissionsEvent.DemoteSelfTo -> coroutineScope.demoteSelfTo( role = event.role, changeOwnRoleAction = changeOwnRoleAction, ) - is RolesAndPermissionsEvents.ResetPermissions -> if (resetPermissionsAction.value.isConfirming()) { + is RolesAndPermissionsEvent.ResetPermissions -> if (resetPermissionsAction.value.isConfirming()) { coroutineScope.resetPermissions(resetPermissionsAction) } else { resetPermissionsAction.value = AsyncAction.ConfirmingNoParams diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt index 626ad3b699..a1345b27c2 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt @@ -20,7 +20,7 @@ data class RolesAndPermissionsState( val availableSelfDemoteActions: ImmutableList, val changeOwnRoleAction: AsyncAction, val resetPermissionsAction: AsyncAction, - val eventSink: (RolesAndPermissionsEvents) -> Unit, + val eventSink: (RolesAndPermissionsEvent) -> Unit, ) { val canSelfDemote = availableSelfDemoteActions.isNotEmpty() } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStatePreviewParam.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStatePreviewParam.kt index 2fc39212d8..2229c185b9 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStatePreviewParam.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStatePreviewParam.kt @@ -58,7 +58,7 @@ internal fun aRolesAndPermissionsState( availableSelfDemoteActions: List = listOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember), changeOwnRoleAction: AsyncAction = AsyncAction.Uninitialized, resetPermissionsAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (RolesAndPermissionsEvents) -> Unit = {}, + eventSink: (RolesAndPermissionsEvent) -> Unit = {}, ) = RolesAndPermissionsState( roomSupportsOwnerRole = roomSupportsOwners, adminCount = adminCount, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt index d9c09bbc64..6c9c4396b2 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt @@ -80,7 +80,7 @@ fun RolesAndPermissionsView( if (state.canSelfDemote) { ListItem( content = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) }, - onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) }, + onClick = { state.eventSink(RolesAndPermissionsEvent.ChangeOwnRole) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit())) ) } @@ -94,7 +94,7 @@ fun RolesAndPermissionsView( ListItem( content = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Delete())), - onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, + onClick = { state.eventSink(RolesAndPermissionsEvent.ResetPermissions) }, style = ListItemStyle.Destructive, ) } @@ -107,12 +107,12 @@ fun RolesAndPermissionsView( content = stringResource(R.string.screen_room_roles_and_permissions_reset_confirm_description), submitText = stringResource(CommonStrings.action_reset), destructiveSubmit = true, - onSubmitClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, - onDismiss = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) }, + onSubmitClick = { state.eventSink(RolesAndPermissionsEvent.ResetPermissions) }, + onDismiss = { state.eventSink(RolesAndPermissionsEvent.CancelPendingAction) }, ) }, - onSuccess = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) }, - onErrorDismiss = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) } + onSuccess = { state.eventSink(RolesAndPermissionsEvent.CancelPendingAction) }, + onErrorDismiss = { state.eventSink(RolesAndPermissionsEvent.CancelPendingAction) } ) when (state.changeOwnRoleAction) { @@ -128,7 +128,7 @@ fun RolesAndPermissionsView( is AsyncAction.Failure -> { ErrorDialog( content = stringResource(CommonStrings.error_unknown), - onSubmit = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) } + onSubmit = { state.eventSink(RolesAndPermissionsEvent.CancelPendingAction) } ) } else -> Unit @@ -139,7 +139,7 @@ fun RolesAndPermissionsView( @Composable private fun ChangeOwnRoleBottomSheet( availableDemoteActions: ImmutableList, - eventSink: (RolesAndPermissionsEvents) -> Unit, + eventSink: (RolesAndPermissionsEvent) -> Unit, ) { val coroutineScope = rememberCoroutineScope() val sheetState = rememberBottomSheetState( @@ -148,7 +148,7 @@ private fun ChangeOwnRoleBottomSheet( ) fun dismiss() { sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.CancelPendingAction) + eventSink(RolesAndPermissionsEvent.CancelPendingAction) } } ModalBottomSheet( @@ -176,7 +176,7 @@ private fun ChangeOwnRoleBottomSheet( content = { Text(stringResource(demoteAction.titleRes)) }, onClick = { sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.DemoteSelfTo(demoteAction.role)) + eventSink(RolesAndPermissionsEvent.DemoteSelfTo(demoteAction.role)) } }, style = ListItemStyle.Destructive, diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt index 54bd6b7987..29ba0d368a 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt @@ -45,7 +45,7 @@ class RolesAndPermissionPresenterTest { val presenter = createRolesAndPermissionsPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) + initialState.eventSink(RolesAndPermissionsEvent.ChangeOwnRole) assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.ConfirmingNoParams) } } @@ -62,7 +62,7 @@ class RolesAndPermissionPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) + initialState.eventSink(RolesAndPermissionsEvent.DemoteSelfTo(RoomMember.Role.Moderator)) runCurrent() assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading) @@ -82,7 +82,7 @@ class RolesAndPermissionPresenterTest { val presenter = createRolesAndPermissionsPresenter(room = room, dispatchers = testCoroutineDispatchers()) presenter.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) + initialState.eventSink(RolesAndPermissionsEvent.DemoteSelfTo(RoomMember.Role.Moderator)) runCurrent() assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading) @@ -90,7 +90,7 @@ class RolesAndPermissionPresenterTest { runCurrent() assertThat(awaitItem().changeOwnRoleAction).isInstanceOf(AsyncAction.Failure::class.java) - initialState.eventSink(RolesAndPermissionsEvents.CancelPendingAction) + initialState.eventSink(RolesAndPermissionsEvent.CancelPendingAction) assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -100,8 +100,8 @@ class RolesAndPermissionPresenterTest { val presenter = createRolesAndPermissionsPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) - awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction) + initialState.eventSink(RolesAndPermissionsEvent.ChangeOwnRole) + awaitItem().eventSink(RolesAndPermissionsEvent.CancelPendingAction) assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Uninitialized) } @@ -119,9 +119,9 @@ class RolesAndPermissionPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions) + initialState.eventSink(RolesAndPermissionsEvent.ResetPermissions) // Confirmation - awaitItem().eventSink(RolesAndPermissionsEvents.ResetPermissions) + awaitItem().eventSink(RolesAndPermissionsEvent.ResetPermissions) assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Success(Unit)) @@ -134,8 +134,8 @@ class RolesAndPermissionPresenterTest { val presenter = createRolesAndPermissionsPresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions) - awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction) + initialState.eventSink(RolesAndPermissionsEvent.ResetPermissions) + awaitItem().eventSink(RolesAndPermissionsEvent.CancelPendingAction) assertThat(awaitItem().resetPermissionsAction).isEqualTo(AsyncAction.Uninitialized) } diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt index 8a3b6fd766..7e94468d48 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt @@ -92,19 +92,19 @@ class RolesAndPermissionsViewTest : RobolectricTest() { @Test @Config(qualifiers = "h640dp") fun `tapping on reset permissions triggers ResetPermissions event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setRolesAndPermissionsView( state = aRolesAndPermissionsState( eventSink = recorder, ), ) clickOn(R.string.screen_room_roles_and_permissions_reset) - recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions) + recorder.assertSingle(RolesAndPermissionsEvent.ResetPermissions) } @Test fun `tapping on Reset in the reset permissions confirmation dialog triggers ResetPermissions event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setRolesAndPermissionsView( state = aRolesAndPermissionsState( resetPermissionsAction = AsyncAction.ConfirmingNoParams, @@ -112,12 +112,12 @@ class RolesAndPermissionsViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_reset) - recorder.assertSingle(RolesAndPermissionsEvents.ResetPermissions) + recorder.assertSingle(RolesAndPermissionsEvent.ResetPermissions) } @Test fun `tapping on Cancel in the reset permissions confirmation dialog triggers CancelPendingAction event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setRolesAndPermissionsView( state = aRolesAndPermissionsState( resetPermissionsAction = AsyncAction.ConfirmingNoParams, @@ -125,12 +125,12 @@ class RolesAndPermissionsViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_cancel) - recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction) + recorder.assertSingle(RolesAndPermissionsEvent.CancelPendingAction) } @Test fun `tapping on 'Demote to moderator' in the demote self bottom sheet triggers the right event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setRolesAndPermissionsView( state = aRolesAndPermissionsState( changeOwnRoleAction = AsyncAction.ConfirmingNoParams, @@ -139,12 +139,12 @@ class RolesAndPermissionsViewTest : RobolectricTest() { ) clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator) mainClock.advanceTimeBy(1_000L) - recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) + recorder.assertSingle(RolesAndPermissionsEvent.DemoteSelfTo(RoomMember.Role.Moderator)) } @Test fun `tapping on 'Demote to member' in the demote self bottom sheet triggers the right event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setRolesAndPermissionsView( state = aRolesAndPermissionsState( changeOwnRoleAction = AsyncAction.ConfirmingNoParams, @@ -153,12 +153,12 @@ class RolesAndPermissionsViewTest : RobolectricTest() { ) clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member) mainClock.advanceTimeBy(1_000L) - recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User)) + recorder.assertSingle(RolesAndPermissionsEvent.DemoteSelfTo(RoomMember.Role.User)) } @Test fun `tapping on 'Cancel' in the demote self bottom sheet triggers the right event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setRolesAndPermissionsView( state = aRolesAndPermissionsState( changeOwnRoleAction = AsyncAction.ConfirmingNoParams, @@ -167,7 +167,7 @@ class RolesAndPermissionsViewTest : RobolectricTest() { ) clickOn(CommonStrings.action_cancel) mainClock.advanceTimeBy(1_000L) - recorder.assertSingle(RolesAndPermissionsEvents.CancelPendingAction) + recorder.assertSingle(RolesAndPermissionsEvent.CancelPendingAction) } } diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvent.kt similarity index 67% rename from features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt rename to features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvent.kt index 11b92ba244..6c528cea14 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.roomaliasresolver.impl -sealed interface RoomAliasResolverEvents { - data object Retry : RoomAliasResolverEvents - data object DismissError : RoomAliasResolverEvents +sealed interface RoomAliasResolverEvent { + data object Retry : RoomAliasResolverEvent + data object DismissError : RoomAliasResolverEvent } diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt index 1ec0ce0691..41a49d0fe1 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt @@ -48,10 +48,10 @@ class RoomAliasResolverPresenter( resolveAlias(resolveState) } - fun handleEvent(event: RoomAliasResolverEvents) { + fun handleEvent(event: RoomAliasResolverEvent) { when (event) { - RoomAliasResolverEvents.Retry -> coroutineScope.resolveAlias(resolveState) - RoomAliasResolverEvents.DismissError -> resolveState.value = AsyncData.Uninitialized + RoomAliasResolverEvent.Retry -> coroutineScope.resolveAlias(resolveState) + RoomAliasResolverEvent.DismissError -> resolveState.value = AsyncData.Uninitialized } } diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt index 82d13f16a5..1974fdff7c 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt @@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias data class RoomAliasResolverState( val roomAlias: RoomAlias, val resolveState: AsyncData, - val eventSink: (RoomAliasResolverEvents) -> Unit + val eventSink: (RoomAliasResolverEvent) -> Unit ) sealed class RoomAliasResolverFailures : Exception() { diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStatePreviewParam.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStatePreviewParam.kt index 6f93930b99..1bb4e03a45 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStatePreviewParam.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStatePreviewParam.kt @@ -30,7 +30,7 @@ open class RoomAliasResolverStatePreviewParam : PreviewParameterProvider = AsyncData.Uninitialized, - eventSink: (RoomAliasResolverEvents) -> Unit = {} + eventSink: (RoomAliasResolverEvent) -> Unit = {} ) = RoomAliasResolverState( roomAlias = roomAlias, resolveState = resolveState, diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt index 37960a50e2..dd002814b1 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt @@ -66,9 +66,9 @@ fun RoomAliasResolverView( ResolvedRoomAliasView( resolvedRoomAlias = state.resolveState, onSuccess = onSuccess, - onRetry = { state.eventSink(RoomAliasResolverEvents.Retry) }, + onRetry = { state.eventSink(RoomAliasResolverEvent.Retry) }, onDismissError = { - state.eventSink(RoomAliasResolverEvents.DismissError) + state.eventSink(RoomAliasResolverEvent.DismissError) onBackClick() } ) diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt index a50049446b..d480ffce11 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt @@ -91,7 +91,7 @@ class RoomAliasHelperPresenterTest { assertThat(awaitItem().resolveState.isLoading()).isTrue() val resultState = awaitItem() assertThat(resultState.resolveState.errorOrNull()).isEqualTo(AN_EXCEPTION) - resultState.eventSink(RoomAliasResolverEvents.Retry) + resultState.eventSink(RoomAliasResolverEvent.Retry) val retryLoadingState = awaitItem() assertThat(retryLoadingState.resolveState.isLoading()).isTrue() val retryState = awaitItem() diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt index ae33517732..18a97ebb69 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt @@ -30,7 +30,7 @@ import org.junit.Test class RoomAliasHelperViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setRoomAliasResolverView( aRoomAliasResolverState( @@ -44,7 +44,7 @@ class RoomAliasHelperViewTest : RobolectricTest() { @Test fun `clicking on Retry emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomAliasResolverView( aRoomAliasResolverState( resolveState = AsyncData.Failure(Exception("Error")), @@ -52,13 +52,13 @@ class RoomAliasHelperViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_retry) - eventsRecorder.assertSingle(RoomAliasResolverEvents.Retry) + eventsRecorder.assertSingle(RoomAliasResolverEvent.Retry) } @Test fun `success state invokes the expected Callback`() = runAndroidComposeUiTest { val result = aResolvedRoomAlias() - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(result) { setRoomAliasResolverView( aRoomAliasResolverState( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt index 3919817313..87d0ca7ace 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt @@ -20,7 +20,7 @@ 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.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.features.invitepeople.api.InvitePeoplePresenter import io.element.android.features.invitepeople.api.InvitePeopleRenderer import io.element.android.libraries.architecture.callback @@ -81,7 +81,7 @@ class RoomInviteMembersNode( ProgressDialog(text = stringResource(CommonStrings.common_creating_room)) }, onErrorDismiss = { - state.eventSink(InvitePeopleEvents.ClearError) + state.eventSink(InvitePeopleEvent.ClearError) } ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt index d00a4e159a..ba9d2b4498 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt @@ -22,7 +22,7 @@ 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.features.invitepeople.api.InvitePeopleEvents +import io.element.android.features.invitepeople.api.InvitePeopleEvent import io.element.android.features.invitepeople.api.InvitePeopleState import io.element.android.features.invitepeople.api.InvitePeopleStatePreviewParam import io.element.android.features.roomdetails.impl.R @@ -49,13 +49,13 @@ fun RoomInviteMembersView( RoomInviteMembersTopBar( onBackClick = { if (state.isSearchActive) { - state.eventSink(InvitePeopleEvents.CloseSearch) + state.eventSink(InvitePeopleEvent.CloseSearch) } else { onBackClick() } }, onSubmitClick = { - state.eventSink(InvitePeopleEvents.SendInvites) + state.eventSink(InvitePeopleEvent.SendInvites) }, canSend = state.canInvite, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index 37f107e784..402ff5ed02 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -21,7 +21,7 @@ import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode import io.element.android.features.roommembermoderation.api.ModerationAction -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.callback @@ -81,7 +81,7 @@ class RoomMemberListNode( onSelectAction = { action, target -> when (action) { is ModerationAction.DisplayProfile -> openRoomMemberDetails(target.userId) - else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) + else -> state.moderationState.eventSink(RoomMemberModerationEvent.ProcessAction(action, target)) } }, onAvatarClick = { user -> diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 674dd9aad9..a47f9af84f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -19,7 +19,7 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents.ShowActionsForUser +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent.ShowActionsForUser import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStatePreviewParam.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStatePreviewParam.kt index 59a26e6207..39e6ffdace 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStatePreviewParam.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStatePreviewParam.kt @@ -10,7 +10,7 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData @@ -115,7 +115,7 @@ fun aRoomMemberModerationState( canBan = canBan, canKick = canKick, ) - override val eventSink: (RoomMemberModerationEvents) -> Unit = {} + override val eventSink: (RoomMemberModerationEvent) -> Unit = {} } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index 9b18c88c93..85eca4fbae 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -17,7 +17,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfilePresenterFactory import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState @@ -112,12 +112,12 @@ class RoomMemberDetailsPresenter( } } - fun handleEvent(event: UserProfileEvents) { + fun handleEvent(event: UserProfileEvent) { when (event) { - UserProfileEvents.WithdrawVerification -> coroutineScope.launch { + UserProfileEvent.WithdrawVerification -> coroutineScope.launch { encryptionService.withdrawVerification(roomMemberId) } - is UserProfileEvents.CopyToClipboard -> { + is UserProfileEvent.CopyToClipboard -> { clipboardHelper.copyPlainText(event.text) snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_copied_to_clipboard)) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt index 34ac8d623f..53ec3b1491 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt @@ -11,7 +11,7 @@ package io.element.android.features.roomdetails.impl.members.details import com.google.common.truth.Truth.assertThat import io.element.android.features.roomdetails.impl.aJoinedRoom import io.element.android.features.roomdetails.impl.members.aRoomMember -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfilePresenterFactory import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.shared.aUserProfileState @@ -348,7 +348,7 @@ class RoomMemberDetailsPresenterTest { consumeItemsUntilPredicate { it.verificationState == UserProfileVerificationState.VERIFICATION_VIOLATION } - initialState.eventSink(UserProfileEvents.WithdrawVerification) + initialState.eventSink(UserProfileEvent.WithdrawVerification) withdrawVerificationResult.assertions().isCalledOnce() } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvent.kt similarity index 67% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvent.kt index 20e866f2bf..8eb82d8ea9 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.roomdirectory.impl.root -sealed interface RoomDirectoryEvents { - data class Search(val query: String) : RoomDirectoryEvents - data object LoadMore : RoomDirectoryEvents +sealed interface RoomDirectoryEvent { + data class Search(val query: String) : RoomDirectoryEvent + data object LoadMore : RoomDirectoryEvent } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 98338440a1..b99a31dced 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -63,12 +63,12 @@ class RoomDirectoryPresenter( loadingMore = false } } - fun handleEvent(event: RoomDirectoryEvents) { + fun handleEvent(event: RoomDirectoryEvent) { when (event) { - RoomDirectoryEvents.LoadMore -> { + RoomDirectoryEvent.LoadMore -> { loadingMore = true } - is RoomDirectoryEvents.Search -> { + is RoomDirectoryEvent.Search -> { searchQuery = event.query } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index a3647381dc..4066ff785a 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -15,7 +15,7 @@ data class RoomDirectoryState( val query: String, val roomDescriptions: ImmutableList, val displayLoadMoreIndicator: Boolean, - val eventSink: (RoomDirectoryEvents) -> Unit + val eventSink: (RoomDirectoryEvent) -> Unit ) { val displayEmptyState = roomDescriptions.isEmpty() && !displayLoadMoreIndicator } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStatePreviewParam.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStatePreviewParam.kt index d7869f3d06..89dde9605b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStatePreviewParam.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStatePreviewParam.kt @@ -35,7 +35,7 @@ fun aRoomDirectoryState( query: String = "", displayLoadMoreIndicator: Boolean = false, roomDescriptions: ImmutableList = persistentListOf(), - eventSink: (RoomDirectoryEvents) -> Unit = {}, + eventSink: (RoomDirectoryEvent) -> Unit = {}, ) = RoomDirectoryState( query = query, roomDescriptions = roomDescriptions, diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index b880a4d28e..3e9c6e05c4 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -107,7 +107,7 @@ private fun RoomDirectoryContent( Column(modifier = modifier) { SearchTextField( query = state.query, - onQueryChange = { state.eventSink(RoomDirectoryEvents.Search(it)) }, + onQueryChange = { state.eventSink(RoomDirectoryEvent.Search(it)) }, placeholder = stringResource(id = CommonStrings.action_search), modifier = Modifier.fillMaxWidth(), ) @@ -116,7 +116,7 @@ private fun RoomDirectoryContent( displayLoadMoreIndicator = state.displayLoadMoreIndicator, displayEmptyState = state.displayEmptyState, onResultClick = onResultClick, - onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) }, + onReachedLoadMore = { state.eventSink(RoomDirectoryEvent.LoadMore) }, ) } } diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt index 6295e4beb6..7d75703587 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt @@ -91,7 +91,7 @@ class RoomDirectoryPresenterTest { val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) presenter.test { awaitItem().also { state -> - state.eventSink(RoomDirectoryEvents.Search("test")) + state.eventSink(RoomDirectoryEvent.Search("test")) } awaitItem().also { state -> assertThat(state.query).isEqualTo("test") @@ -112,7 +112,7 @@ class RoomDirectoryPresenterTest { val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) presenter.test { awaitItem().also { state -> - state.eventSink(RoomDirectoryEvents.LoadMore) + state.eventSink(RoomDirectoryEvent.LoadMore) } advanceUntilIdle() cancelAndIgnoreRemainingEvents() diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt index b3b935596f..1c26290f10 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt @@ -30,7 +30,7 @@ import org.junit.Test class RoomDirectoryViewTest : RobolectricTest() { @Test fun `typing text in search field emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomDirectoryView( state = aRoomDirectoryState( eventSink = eventsRecorder, @@ -39,12 +39,12 @@ class RoomDirectoryViewTest : RobolectricTest() { onNodeWithTag(TestTags.searchTextField.value).performTextInput( text = "Test" ) - eventsRecorder.assertSingle(RoomDirectoryEvents.Search("Test")) + eventsRecorder.assertSingle(RoomDirectoryEvent.Search("Test")) } @Test fun `clicking on room item then onResultClick lambda is called once`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aRoomDirectoryState( roomDescriptions = aRoomDescriptionList(), eventSink = eventsRecorder, @@ -61,13 +61,13 @@ class RoomDirectoryViewTest : RobolectricTest() { @Test fun `composing load more indicator emits expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aRoomDirectoryState( displayLoadMoreIndicator = true, eventSink = eventsRecorder, ) setRoomDirectoryView(state = state) - eventsRecorder.assertSingle(RoomDirectoryEvents.LoadMore) + eventsRecorder.assertSingle(RoomDirectoryEvent.LoadMore) } } diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvent.kt similarity index 84% rename from features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt rename to features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvent.kt index d452ff4e66..403268a18d 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvent.kt @@ -13,7 +13,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser /** * Events sent to the moderation presenter through [RoomMemberModerationState.eventSink]. */ -interface RoomMemberModerationEvents { - data class ShowActionsForUser(val user: MatrixUser) : RoomMemberModerationEvents - data class ProcessAction(val action: ModerationAction, val targetUser: MatrixUser) : RoomMemberModerationEvents +interface RoomMemberModerationEvent { + data class ShowActionsForUser(val user: MatrixUser) : RoomMemberModerationEvent + data class ProcessAction(val action: ModerationAction, val targetUser: MatrixUser) : RoomMemberModerationEvent } diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt index 9227c77edf..8c73aaee0e 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt @@ -19,7 +19,7 @@ interface RoomMemberModerationState { val permissions: RoomMemberModerationPermissions /** Where the host screen sends its events to open the actions of a member or run one of them. */ - val eventSink: (RoomMemberModerationEvents) -> Unit + val eventSink: (RoomMemberModerationEvent) -> Unit } data class ModerationActionState( diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvent.kt similarity index 71% rename from features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt rename to features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvent.kt index 2bc76db761..741cb42961 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvent.kt @@ -8,11 +8,11 @@ package io.element.android.features.roommembermoderation.impl -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent -sealed interface InternalRoomMemberModerationEvents : RoomMemberModerationEvents { - data class DoKickUser(val reason: String) : InternalRoomMemberModerationEvents - data class DoBanUser(val reason: String) : InternalRoomMemberModerationEvents - data class DoUnbanUser(val reason: String) : InternalRoomMemberModerationEvents - data object Reset : InternalRoomMemberModerationEvents +sealed interface InternalRoomMemberModerationEvent : RoomMemberModerationEvent { + data class DoKickUser(val reason: String) : InternalRoomMemberModerationEvent + data class DoBanUser(val reason: String) : InternalRoomMemberModerationEvent + data class DoUnbanUser(val reason: String) : InternalRoomMemberModerationEvent + data object Reset : InternalRoomMemberModerationEvent } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index f45a277992..adb9e84e1d 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -9,7 +9,7 @@ package io.element.android.features.roommembermoderation.impl import io.element.android.features.roommembermoderation.api.ModerationActionState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction @@ -23,7 +23,7 @@ data class InternalRoomMemberModerationState( val kickUserAsyncAction: AsyncAction, val banUserAsyncAction: AsyncAction, val unbanUserAsyncAction: AsyncAction, - override val eventSink: (RoomMemberModerationEvents) -> Unit, + override val eventSink: (RoomMemberModerationEvent) -> Unit, ) : RoomMemberModerationState { val canDisplayActions = actions.isNotEmpty() } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStatePreviewParam.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStatePreviewParam.kt index 78358da213..1da2392737 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStatePreviewParam.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStatePreviewParam.kt @@ -11,7 +11,7 @@ package io.element.android.features.roommembermoderation.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE @@ -93,7 +93,7 @@ fun aRoomMembersModerationState( kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, banUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, unbanUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (RoomMemberModerationEvents) -> Unit = {}, + eventSink: (RoomMemberModerationEvent) -> Unit = {}, ) = InternalRoomMemberModerationState( permissions = permissions, selectedUser = selectedUser, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 2a876fc484..49d7ced4d7 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -20,7 +20,7 @@ import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.roomMemberModerationPermissions @@ -77,9 +77,9 @@ class RoomMemberModerationPresenter( } val moderationActions = remember { mutableStateOf>(persistentListOf()) } - fun handleEvent(event: RoomMemberModerationEvents) { + fun handleEvent(event: RoomMemberModerationEvent) { when (event) { - is RoomMemberModerationEvents.ShowActionsForUser -> { + is RoomMemberModerationEvent.ShowActionsForUser -> { selectedUser = event.user val member = room.membersStateFlow.value.roomMembers()?.firstOrNull { it.userId == event.user.userId @@ -90,7 +90,7 @@ class RoomMemberModerationPresenter( currentUserPowerLevel = currentUserPowerLevel, ) } - is RoomMemberModerationEvents.ProcessAction -> { + is RoomMemberModerationEvent.ProcessAction -> { // First, hide any list of existing actions that could be displayed moderationActions.value = persistentListOf() @@ -110,25 +110,25 @@ class RoomMemberModerationPresenter( } } } - is InternalRoomMemberModerationEvents.DoKickUser -> { + is InternalRoomMemberModerationEvent.DoKickUser -> { selectedUser?.let { coroutineScope.kickUser(it.userId, event.reason, kickUserAsyncAction) } selectedUser = null } - is InternalRoomMemberModerationEvents.DoBanUser -> { + is InternalRoomMemberModerationEvent.DoBanUser -> { selectedUser?.let { coroutineScope.banUser(it.userId, event.reason, banUserAsyncAction) } selectedUser = null } - is InternalRoomMemberModerationEvents.DoUnbanUser -> { + is InternalRoomMemberModerationEvent.DoUnbanUser -> { selectedUser?.let { coroutineScope.unbanUser(it.userId, event.reason, unbanUserAsyncAction) } selectedUser = null } - is InternalRoomMemberModerationEvents.Reset -> { + is InternalRoomMemberModerationEvent.Reset -> { selectedUser = null moderationActions.value = persistentListOf() kickUserAsyncAction.value = AsyncAction.Uninitialized diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index f1c6cc8ed3..3ec00fd232 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -77,7 +77,7 @@ fun RoomMemberModerationView( actions = state.actions, onSelectAction = onSelectAction, onAvatarClick = onAvatarClick, - onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + onDismiss = { state.eventSink(InternalRoomMemberModerationEvent.Reset) }, ) } RoomMemberAsyncActions(state = state) @@ -102,9 +102,9 @@ private fun RoomMemberAsyncActions( destructiveSubmit = true, minLines = 2, onSubmit = { reason -> - state.eventSink(InternalRoomMemberModerationEvents.DoKickUser(reason = reason)) + state.eventSink(InternalRoomMemberModerationEvent.DoKickUser(reason = reason)) }, - onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvent.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), content = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_description), value = "", @@ -142,9 +142,9 @@ private fun RoomMemberAsyncActions( destructiveSubmit = true, minLines = 2, onSubmit = { reason -> - state.eventSink(InternalRoomMemberModerationEvents.DoBanUser(reason = reason)) + state.eventSink(InternalRoomMemberModerationEvent.DoBanUser(reason = reason)) }, - onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvent.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), content = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_description), value = "", @@ -185,9 +185,9 @@ private fun RoomMemberAsyncActions( asyncIndicatorState.enqueue { AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_unbanning_user, userDisplayName)) } - state.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser(reason = reason)) + state.eventSink(InternalRoomMemberModerationEvent.DoUnbanUser(reason = reason)) }, - onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvent.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), content = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_description), value = "", diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt index 3397f3a40a..1f13b7e68f 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt @@ -12,7 +12,7 @@ import app.cash.turbine.TurbineTestContext import com.google.common.truth.Truth.assertThat import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction @@ -73,7 +73,7 @@ class RoomMemberModerationPresenterTest { ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() - initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + initialState.eventSink(RoomMemberModerationEvent.ShowActionsForUser(targetUser)) skipItems(1) val updatedState = awaitState() assertThat(updatedState.selectedUser).isEqualTo(targetUser) @@ -93,7 +93,7 @@ class RoomMemberModerationPresenterTest { ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() - initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + initialState.eventSink(RoomMemberModerationEvent.ShowActionsForUser(targetUser)) skipItems(2) val updatedState = awaitState() assertThat(updatedState.selectedUser).isEqualTo(targetUser) @@ -115,7 +115,7 @@ class RoomMemberModerationPresenterTest { ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() - initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + initialState.eventSink(RoomMemberModerationEvent.ShowActionsForUser(targetUser)) skipItems(2) val updatedState = awaitState() assertThat(updatedState.selectedUser).isEqualTo(targetUser) @@ -137,7 +137,7 @@ class RoomMemberModerationPresenterTest { ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() - initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + initialState.eventSink(RoomMemberModerationEvent.ShowActionsForUser(targetUser)) skipItems(2) val updatedState = awaitState() assertThat(updatedState.selectedUser).isEqualTo(targetUser) @@ -159,7 +159,7 @@ class RoomMemberModerationPresenterTest { ) createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() - initialState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(targetUser)) + initialState.eventSink(RoomMemberModerationEvent.ShowActionsForUser(targetUser)) skipItems(2) val updatedState = awaitState() assertThat(updatedState.selectedUser).isEqualTo(targetUser) @@ -175,7 +175,7 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = aJoinedRoom()).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.KickUser ) @@ -192,7 +192,7 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = aJoinedRoom()).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.BanUser ) @@ -209,7 +209,7 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = aJoinedRoom()).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.UnbanUser ) @@ -235,13 +235,13 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.KickUser ) ) skipItems(2) - initialState.eventSink(InternalRoomMemberModerationEvents.DoKickUser("Reason")) + initialState.eventSink(InternalRoomMemberModerationEvent.DoKickUser("Reason")) skipItems(1) val loadingState = awaitState() assertThat(loadingState.kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -265,13 +265,13 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.BanUser ) ) skipItems(2) - initialState.eventSink(InternalRoomMemberModerationEvents.DoBanUser("Reason")) + initialState.eventSink(InternalRoomMemberModerationEvent.DoBanUser("Reason")) skipItems(1) val loadingState = awaitState() assertThat(loadingState.banUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -295,13 +295,13 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.UnbanUser ) ) skipItems(2) - initialState.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser("Reason")) + initialState.eventSink(InternalRoomMemberModerationEvent.DoUnbanUser("Reason")) skipItems(1) val loadingState = awaitState() assertThat(loadingState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -320,13 +320,13 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction( + RoomMemberModerationEvent.ProcessAction( targetUser = targetUser, action = ModerationAction.KickUser ) ) skipItems(2) - initialState.eventSink(InternalRoomMemberModerationEvents.DoKickUser("Reason")) + initialState.eventSink(InternalRoomMemberModerationEvent.DoKickUser("Reason")) skipItems(1) val loadingState = awaitState() assertThat(loadingState.kickUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -340,10 +340,10 @@ class RoomMemberModerationPresenterTest { createRoomMemberModerationPresenter(room = aJoinedRoom()).test { val initialState = awaitState() initialState.eventSink( - RoomMemberModerationEvents.ProcessAction(targetUser = targetUser, action = ModerationAction.BanUser) + RoomMemberModerationEvent.ProcessAction(targetUser = targetUser, action = ModerationAction.BanUser) ) skipItems(2) - initialState.eventSink(InternalRoomMemberModerationEvents.Reset) + initialState.eventSink(InternalRoomMemberModerationEvent.Reset) skipItems(1) val resetState = awaitState() assertThat(resetState.selectedUser).isNull() diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt index f99e517767..ff7183ed5f 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.v2.runAndroidComposeUiTest import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvent import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.testtags.TestTags @@ -34,7 +34,7 @@ class RoomMemberModerationViewTest : RobolectricTest() { @Test fun `clicking on display profile action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.DisplayProfile, user) { callback -> setRoomMemberModerationView( aRoomMembersModerationState( @@ -55,7 +55,7 @@ class RoomMemberModerationViewTest : RobolectricTest() { @Test fun `clicking on kick user action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.KickUser, user) { callback -> setRoomMemberModerationView( aRoomMembersModerationState( @@ -76,7 +76,7 @@ class RoomMemberModerationViewTest : RobolectricTest() { @Test fun `clicking on ban user action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.BanUser, user) { callback -> setRoomMemberModerationView( aRoomMembersModerationState( @@ -97,7 +97,7 @@ class RoomMemberModerationViewTest : RobolectricTest() { @Test fun `clicking on unban user action calls onSelectAction`() = runAndroidComposeUiTest { val user = anAlice() - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithTwoParams(ModerationAction.UnbanUser, user) { callback -> setRoomMemberModerationView( aRoomMembersModerationState( @@ -117,7 +117,7 @@ class RoomMemberModerationViewTest : RobolectricTest() { @Test fun `clicking submit on kick confirmation dialog sends DoKickUser event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), @@ -126,12 +126,12 @@ class RoomMemberModerationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoKickUser(reason = "")) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvent.DoKickUser(reason = "")) } @Test fun `clicking dismiss on kick confirmation dialog sends Reset event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), @@ -140,12 +140,12 @@ class RoomMemberModerationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogNegative.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvent.Reset) } @Test fun `clicking submit on ban confirmation dialog sends DoBanUser event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), @@ -154,12 +154,12 @@ class RoomMemberModerationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoBanUser(reason = "")) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvent.DoBanUser(reason = "")) } @Test fun `clicking dismiss on ban confirmation dialog sends Reset event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), @@ -168,12 +168,12 @@ class RoomMemberModerationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogNegative.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvent.Reset) } @Test fun `clicking confirm on unban confirmation dialog sends DoUnbanUser event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), @@ -182,12 +182,12 @@ class RoomMemberModerationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoUnbanUser("")) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvent.DoUnbanUser("")) } @Test fun `clicking dismiss on unban confirmation dialog sends Reset event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), @@ -196,12 +196,12 @@ class RoomMemberModerationViewTest : RobolectricTest() { ), ) pressTag(TestTags.dialogNegative.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.Reset) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvent.Reset) } @Test fun `disabled actions are not clickable`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setRoomMemberModerationView( aRoomMembersModerationState( selectedUser = anAlice(), diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvent.kt similarity index 65% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvent.kt index 05d05a4840..330faa47f5 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.securebackup.impl.disable -sealed interface SecureBackupDisableEvents { - data object DisableBackup : SecureBackupDisableEvents - data object DismissDialogs : SecureBackupDisableEvents +sealed interface SecureBackupDisableEvent { + data object DisableBackup : SecureBackupDisableEvent + data object DismissDialogs : SecureBackupDisableEvent } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt index 67b6816097..13a2882fa8 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt @@ -37,10 +37,10 @@ class SecureBackupDisablePresenter( Timber.tag(loggerTagDisable.value).d("backupState: $backupState") val disableAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: SecureBackupDisableEvents) { + fun handleEvent(event: SecureBackupDisableEvent) { when (event) { - is SecureBackupDisableEvents.DisableBackup -> coroutineScope.disableBackup(disableAction) - SecureBackupDisableEvents.DismissDialogs -> { + is SecureBackupDisableEvent.DisableBackup -> coroutineScope.disableBackup(disableAction) + SecureBackupDisableEvent.DismissDialogs -> { disableAction.value = AsyncAction.Uninitialized } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt index a4f6b210ab..1e1b1ec35e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt @@ -15,5 +15,5 @@ data class SecureBackupDisableState( val backupState: BackupState, val disableAction: AsyncAction, val appName: String, - val eventSink: (SecureBackupDisableEvents) -> Unit + val eventSink: (SecureBackupDisableEvent) -> Unit ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt index 423e2ed5da..10019caae6 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt @@ -55,7 +55,7 @@ fun SecureBackupDisableView( async = state.disableAction, progressDialog = {}, errorMessage = { it.message ?: it.toString() }, - onErrorDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) }, + onErrorDismiss = { state.eventSink.invoke(SecureBackupDisableEvent.DismissDialogs) }, onSuccess = { onSuccess() }, ) } @@ -69,7 +69,7 @@ private fun ColumnScope.Buttons( showProgress = state.disableAction.isLoading(), destructive = true, modifier = Modifier.fillMaxWidth(), - onClick = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup) } + onClick = { state.eventSink.invoke(SecureBackupDisableEvent.DisableBackup) } ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvent.kt similarity index 62% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvent.kt index 644756b957..ac5aa751b6 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.securebackup.impl.enter -sealed interface SecureBackupEnterRecoveryKeyEvents { - data class OnRecoveryKeyChange(val recoveryKey: String) : SecureBackupEnterRecoveryKeyEvents - data class ChangeRecoveryKeyFieldContentsVisibility(val visible: Boolean) : SecureBackupEnterRecoveryKeyEvents - data object Submit : SecureBackupEnterRecoveryKeyEvents - data object ClearDialog : SecureBackupEnterRecoveryKeyEvents +sealed interface SecureBackupEnterRecoveryKeyEvent { + data class OnRecoveryKeyChange(val recoveryKey: String) : SecureBackupEnterRecoveryKeyEvent + data class ChangeRecoveryKeyFieldContentsVisibility(val visible: Boolean) : SecureBackupEnterRecoveryKeyEvent + data object Submit : SecureBackupEnterRecoveryKeyEvent + data object ClearDialog : SecureBackupEnterRecoveryKeyEvent } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt index 9a71cf1727..bbc61cc283 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt @@ -45,12 +45,12 @@ class SecureBackupEnterRecoveryKeyPresenter( mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvent(event: SecureBackupEnterRecoveryKeyEvents) { + fun handleEvent(event: SecureBackupEnterRecoveryKeyEvent) { when (event) { - SecureBackupEnterRecoveryKeyEvents.ClearDialog -> { + SecureBackupEnterRecoveryKeyEvent.ClearDialog -> { submitAction.value = AsyncAction.Uninitialized } - is SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange -> { + is SecureBackupEnterRecoveryKeyEvent.OnRecoveryKeyChange -> { val previousRecoveryKey = recoveryKey recoveryKey = if (previousRecoveryKey.isEmpty() && recoveryKeyTools.isRecoveryKeyFormatValid(event.recoveryKey)) { // A Recovery key has been entered, remove the spaces for a better rendering @@ -60,11 +60,11 @@ class SecureBackupEnterRecoveryKeyPresenter( event.recoveryKey } } - SecureBackupEnterRecoveryKeyEvents.Submit -> { + SecureBackupEnterRecoveryKeyEvent.Submit -> { // No need to remove the spaces, the SDK will do it. coroutineScope.submitRecoveryKey(recoveryKey, submitAction) } - is SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility -> { + is SecureBackupEnterRecoveryKeyEvent.ChangeRecoveryKeyFieldContentsVisibility -> { displayRecoveryKeyFieldContents = event.visible } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt index 45a6989c15..188419553a 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt @@ -15,5 +15,5 @@ data class SecureBackupEnterRecoveryKeyState( val recoveryKeyViewState: RecoveryKeyViewState, val isSubmitEnabled: Boolean, val submitAction: AsyncAction, - val eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit + val eventSink: (SecureBackupEnterRecoveryKeyEvent) -> Unit ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStatePreviewParam.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStatePreviewParam.kt index 5041f4ab92..90a040106c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStatePreviewParam.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStatePreviewParam.kt @@ -30,7 +30,7 @@ fun aSecureBackupEnterRecoveryKeyState( isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(), displayTextFieldContents: Boolean = true, submitAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit = {}, + eventSink: (SecureBackupEnterRecoveryKeyEvent) -> Unit = {}, ) = SecureBackupEnterRecoveryKeyState( recoveryKeyViewState = RecoveryKeyViewState( recoveryKeyUserStory = RecoveryKeyUserStory.Enter, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt index 823bbf5c80..db3f228f8b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt @@ -41,7 +41,7 @@ fun SecureBackupEnterRecoveryKeyView( progressDialog = { }, errorTitle = { stringResource(id = R.string.screen_recovery_key_confirm_error_title) }, errorMessage = { stringResource(id = R.string.screen_recovery_key_confirm_error_content) }, - onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) }, + onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvent.ClearDialog) }, ) FlowStepPage( @@ -68,13 +68,13 @@ private fun Content( state = state.recoveryKeyViewState, onClick = null, onChange = { - state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange(it)) + state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvent.OnRecoveryKeyChange(it)) }, onSubmit = { - state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit) + state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvent.Submit) }, toggleRecoveryKeyVisibility = { - state.eventSink(SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility(it)) + state.eventSink(SecureBackupEnterRecoveryKeyEvent.ChangeRecoveryKeyFieldContentsVisibility(it)) } ) } @@ -89,7 +89,7 @@ private fun ColumnScope.Buttons( showProgress = state.submitAction.isLoading(), modifier = Modifier.fillMaxWidth(), onClick = { - state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit) + state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvent.Submit) } ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvent.kt similarity index 62% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvent.kt index 9d371e3c30..91bcf67575 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.securebackup.impl.root -sealed interface SecureBackupRootEvents { - data object RetryKeyBackupState : SecureBackupRootEvents - data object EnableKeyStorage : SecureBackupRootEvents - data object DisplayKeyStorageDisabledError : SecureBackupRootEvents - data object DismissDialog : SecureBackupRootEvents +sealed interface SecureBackupRootEvent { + data object RetryKeyBackupState : SecureBackupRootEvent + data object EnableKeyStorage : SecureBackupRootEvent + data object DisplayKeyStorageDisabledError : SecureBackupRootEvent + data object DismissDialog : SecureBackupRootEvent } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt index 9a97bfac25..602fd773da 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt @@ -59,15 +59,15 @@ class SecureBackupRootPresenter( } } - fun handleEvent(event: SecureBackupRootEvents) { + fun handleEvent(event: SecureBackupRootEvent) { when (event) { - SecureBackupRootEvents.RetryKeyBackupState -> localCoroutineScope.getKeyBackupStatus(doesBackupExistOnServerAction) - SecureBackupRootEvents.EnableKeyStorage -> localCoroutineScope.enableBackup(enableAction) - SecureBackupRootEvents.DismissDialog -> { + SecureBackupRootEvent.RetryKeyBackupState -> localCoroutineScope.getKeyBackupStatus(doesBackupExistOnServerAction) + SecureBackupRootEvent.EnableKeyStorage -> localCoroutineScope.enableBackup(enableAction) + SecureBackupRootEvent.DismissDialog -> { enableAction.value = AsyncAction.Uninitialized displayKeyStorageDisabledError = false } - SecureBackupRootEvents.DisplayKeyStorageDisabledError -> displayKeyStorageDisabledError = true + SecureBackupRootEvent.DisplayKeyStorageDisabledError -> displayKeyStorageDisabledError = true } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt index 1cbf917ca5..f528910fe2 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt @@ -22,7 +22,7 @@ data class SecureBackupRootState( val appName: String, val displayKeyStorageDisabledError: Boolean, val snackbarMessage: SnackbarMessage?, - val eventSink: (SecureBackupRootEvents) -> Unit, + val eventSink: (SecureBackupRootEvent) -> Unit, ) { val isKeyStorageEnabled: Boolean get() = when (backupState) { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt index 8059329cd0..fb45228ce4 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt @@ -114,12 +114,12 @@ fun SecureBackupRootView( if (state.doesBackupExistOnServer.data) { onDisableClick() } else { - state.eventSink.invoke(SecureBackupRootEvents.EnableKeyStorage) + state.eventSink.invoke(SecureBackupRootEvent.EnableKeyStorage) } } is AsyncData.Loading, AsyncData.Uninitialized -> Unit - is AsyncData.Failure -> state.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) + is AsyncData.Failure -> state.eventSink.invoke(SecureBackupRootEvent.RetryKeyBackupState) } } BackupState.CREATING, @@ -154,7 +154,7 @@ fun SecureBackupRootView( if (state.isKeyStorageEnabled) { onSetupClick() } else { - state.eventSink.invoke(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + state.eventSink.invoke(SecureBackupRootEvent.DisplayKeyStorageDisabledError) } }, ) @@ -177,7 +177,7 @@ fun SecureBackupRootView( if (state.isKeyStorageEnabled) { onChangeClick() } else { - state.eventSink.invoke(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + state.eventSink.invoke(SecureBackupRootEvent.DisplayKeyStorageDisabledError) } }, ) @@ -201,7 +201,7 @@ fun SecureBackupRootView( if (state.isKeyStorageEnabled) { onConfirmRecoveryKeyClick() } else { - state.eventSink.invoke(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + state.eventSink.invoke(SecureBackupRootEvent.DisplayKeyStorageDisabledError) } }, ) @@ -212,13 +212,13 @@ fun SecureBackupRootView( async = state.enableAction, progressDialog = { }, onSuccess = { }, - onErrorDismiss = { state.eventSink.invoke(SecureBackupRootEvents.DismissDialog) } + onErrorDismiss = { state.eventSink.invoke(SecureBackupRootEvent.DismissDialog) } ) if (state.displayKeyStorageDisabledError) { ErrorDialog( title = null, content = stringResource(id = R.string.screen_chat_backup_key_storage_disabled_error), - onSubmit = { state.eventSink.invoke(SecureBackupRootEvents.DismissDialog) }, + onSubmit = { state.eventSink.invoke(SecureBackupRootEvent.DismissDialog) }, ) } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvent.kt similarity index 63% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvent.kt index f61e65ba61..68221a29e4 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.securebackup.impl.setup -sealed interface SecureBackupSetupEvents { - data object CreateRecoveryKey : SecureBackupSetupEvents - data object RecoveryKeyHasBeenSaved : SecureBackupSetupEvents - data object Done : SecureBackupSetupEvents - data object DismissDialog : SecureBackupSetupEvents +sealed interface SecureBackupSetupEvent { + data object CreateRecoveryKey : SecureBackupSetupEvent + data object RecoveryKeyHasBeenSaved : SecureBackupSetupEvent + data object Done : SecureBackupSetupEvent + data object DismissDialog : SecureBackupSetupEvent } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt index 9f27bc9566..538128a0d5 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt @@ -53,18 +53,18 @@ class SecureBackupSetupPresenter( } var showSaveConfirmationDialog by remember { mutableStateOf(false) } - fun handleEvent(event: SecureBackupSetupEvents) { + fun handleEvent(event: SecureBackupSetupEvent) { when (event) { - SecureBackupSetupEvents.CreateRecoveryKey -> { + SecureBackupSetupEvent.CreateRecoveryKey -> { coroutineScope.createOrChangeRecoveryKey(stateAndDispatch) } - SecureBackupSetupEvents.RecoveryKeyHasBeenSaved -> + SecureBackupSetupEvent.RecoveryKeyHasBeenSaved -> stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.UserSavedKey) - SecureBackupSetupEvents.DismissDialog -> { + SecureBackupSetupEvent.DismissDialog -> { showSaveConfirmationDialog = false stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.ClearError) } - SecureBackupSetupEvents.Done -> { + SecureBackupSetupEvent.Done -> { showSaveConfirmationDialog = true } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt index 752b5e4851..ecec6e3f6a 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt @@ -15,7 +15,7 @@ data class SecureBackupSetupState( val recoveryKeyViewState: RecoveryKeyViewState, val showSaveConfirmationDialog: Boolean, val setupState: SetupState, - val eventSink: (SecureBackupSetupEvents) -> Unit + val eventSink: (SecureBackupSetupEvent) -> Unit ) sealed interface SetupState { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt index cab1e0995c..c22bd44c99 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt @@ -56,7 +56,7 @@ fun SecureBackupSetupView( title = stringResource(id = CommonStrings.common_something_went_wrong), content = stringResource(id = CommonStrings.common_something_went_wrong_message), onSubmit = { - state.eventSink.invoke(SecureBackupSetupEvents.DismissDialog) + state.eventSink.invoke(SecureBackupSetupEvent.DismissDialog) }, ) } @@ -68,7 +68,7 @@ fun SecureBackupSetupView( submitText = stringResource(id = CommonStrings.action_continue), onSubmitClick = onSuccess, onDismiss = { - state.eventSink.invoke(SecureBackupSetupEvents.DismissDialog) + state.eventSink.invoke(SecureBackupSetupEvent.DismissDialog) } ) } @@ -123,12 +123,12 @@ private fun Content( text = formattedRecoveryKey, toastMessage = toastMessage, ) - state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) + state.eventSink.invoke(SecureBackupSetupEvent.RecoveryKeyHasBeenSaved) } } else { if (!state.recoveryKeyViewState.inProgress) { { - state.eventSink.invoke(SecureBackupSetupEvents.CreateRecoveryKey) + state.eventSink.invoke(SecureBackupSetupEvent.CreateRecoveryKey) } } else { null @@ -174,7 +174,7 @@ private fun ColumnScope.Buttons( chooserTitle = chooserTitle, text = state.setupState.recoveryKey()!!, ) - state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) + state.eventSink.invoke(SecureBackupSetupEvent.RecoveryKeyHasBeenSaved) }, ) Button( @@ -184,7 +184,7 @@ private fun ColumnScope.Buttons( if (state.setupState is SetupState.CreatedAndSaved) { onFinish() } else { - state.eventSink.invoke(SecureBackupSetupEvents.Done) + state.eventSink.invoke(SecureBackupSetupEvent.Done) } }, ) diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt index 899726d806..69f54fc253 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt @@ -47,7 +47,7 @@ class SecureBackupDisablePresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(SecureBackupDisableEvents.DisableBackup) + initialState.eventSink(SecureBackupDisableEvent.DisableBackup) val loadingState = awaitItem() assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java) val finalState = awaitItem() @@ -68,12 +68,12 @@ class SecureBackupDisablePresenterTest { }.test { val initialState = awaitItem() assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(SecureBackupDisableEvents.DisableBackup) + initialState.eventSink(SecureBackupDisableEvent.DisableBackup) val loadingState = awaitItem() assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() assertThat(errorState.disableAction).isInstanceOf(AsyncAction.Failure::class.java) - errorState.eventSink(SecureBackupDisableEvents.DismissDialogs) + errorState.eventSink(SecureBackupDisableEvent.DismissDialogs) val finalState = awaitItem() assertThat(finalState.disableAction).isEqualTo(AsyncAction.Uninitialized) } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt index a6ffb57dc4..a1c7d697d6 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt @@ -56,7 +56,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink(SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange("1234")) + initialState.eventSink(SecureBackupEnterRecoveryKeyEvent.OnRecoveryKeyChange("1234")) val withRecoveryKeyState = awaitItem() assertThat(withRecoveryKeyState.isSubmitEnabled).isTrue() assertThat(withRecoveryKeyState.recoveryKeyViewState).isEqualTo( @@ -68,19 +68,19 @@ class SecureBackupEnterRecoveryKeyPresenterTest { ) ) encryptionService.givenRecoverFailure(AN_EXCEPTION) - withRecoveryKeyState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit) + withRecoveryKeyState.eventSink(SecureBackupEnterRecoveryKeyEvent.Submit) val loadingState = awaitItem() assertThat(loadingState.submitAction).isEqualTo(AsyncAction.Loading) assertThat(loadingState.isSubmitEnabled).isFalse() val errorState = awaitItem() assertThat(errorState.submitAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) assertThat(errorState.isSubmitEnabled).isFalse() - errorState.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) + errorState.eventSink(SecureBackupEnterRecoveryKeyEvent.ClearDialog) val clearedState = awaitItem() assertThat(clearedState.submitAction).isEqualTo(AsyncAction.Uninitialized) assertThat(clearedState.isSubmitEnabled).isTrue() encryptionService.givenRecoverFailure(null) - clearedState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit) + clearedState.eventSink(SecureBackupEnterRecoveryKeyEvent.Submit) val loadingState2 = awaitItem() assertThat(loadingState2.submitAction).isEqualTo(AsyncAction.Loading) assertThat(loadingState2.isSubmitEnabled).isFalse() diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt index fcf050f925..5bc880a685 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt @@ -58,32 +58,32 @@ class SecureBackupEnterRecoveryKeyViewTest : RobolectricTest() { @Test @Config(qualifiers = "h1024dp") fun `tapping on Continue when key is valid - calls expected action`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder), ) clickOn(CommonStrings.action_continue) - recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.Submit) + recorder.assertSingle(SecureBackupEnterRecoveryKeyEvent.Submit) } @Test fun `entering a char emits the expected event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val keyValue = aFormattedRecoveryKey() setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder), ) onNodeWithText(keyValue).performTextInput("X") recorder.assertSingle( - SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange("X$keyValue") + SecureBackupEnterRecoveryKeyEvent.OnRecoveryKeyChange("X$keyValue") ) } @Test @Config(qualifiers = "h1024dp") fun `toggling the visibility of the textfield changes it`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val keyValue = aFormattedRecoveryKey() setSecureBackupEnterRecoveryKeyView(aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder)) @@ -94,18 +94,18 @@ class SecureBackupEnterRecoveryKeyViewTest : RobolectricTest() { waitForIdle() - recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.ChangeRecoveryKeyFieldContentsVisibility(false)) + recorder.assertSingle(SecureBackupEnterRecoveryKeyEvent.ChangeRecoveryKeyFieldContentsVisibility(false)) } @Test fun `validating from keyboard emits the expected event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val keyValue = aFormattedRecoveryKey() setSecureBackupEnterRecoveryKeyView( aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder), ) onNodeWithText(keyValue).performImeAction() - recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.Submit) + recorder.assertSingle(SecureBackupEnterRecoveryKeyEvent.Submit) } @Test diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt index 63adfc789e..bbee28803e 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt @@ -66,7 +66,7 @@ class SecureBackupRootPresenterTest { val errorState = awaitItem() assertThat(errorState.doesBackupExistOnServer).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) encryptionService.givenDoesBackupExistOnServerResult(Result.success(false)) - errorState.eventSink.invoke(SecureBackupRootEvents.RetryKeyBackupState) + errorState.eventSink.invoke(SecureBackupRootEvent.RetryKeyBackupState) val loadingState2 = awaitItem() assertThat(loadingState2.doesBackupExistOnServer).isInstanceOf(AsyncData.Loading::class.java) val finalState = awaitItem() @@ -82,9 +82,9 @@ class SecureBackupRootPresenterTest { }.test { skipItems(2) val initialState = awaitItem() - initialState.eventSink(SecureBackupRootEvents.DisplayKeyStorageDisabledError) + initialState.eventSink(SecureBackupRootEvent.DisplayKeyStorageDisabledError) assertThat(awaitItem().displayKeyStorageDisabledError).isTrue() - initialState.eventSink(SecureBackupRootEvents.DismissDialog) + initialState.eventSink(SecureBackupRootEvent.DismissDialog) assertThat(awaitItem().displayKeyStorageDisabledError).isFalse() } } @@ -97,7 +97,7 @@ class SecureBackupRootPresenterTest { }.test { skipItems(2) val initialState = awaitItem() - initialState.eventSink(SecureBackupRootEvents.EnableKeyStorage) + initialState.eventSink(SecureBackupRootEvent.EnableKeyStorage) assertThat(awaitItem().enableAction.isLoading()).isTrue() assertThat(awaitItem().enableAction.isSuccess()).isTrue() } diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt index 4aede3c534..6ab0b257a2 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt @@ -58,7 +58,7 @@ class SecureBackupSetupPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(SecureBackupSetupEvents.CreateRecoveryKey) + initialState.eventSink.invoke(SecureBackupSetupEvent.CreateRecoveryKey) val creatingState = awaitItem() assertThat(creatingState.setupState).isEqualTo(SetupState.Creating) assertThat(creatingState.recoveryKeyViewState).isEqualTo( @@ -80,13 +80,13 @@ class SecureBackupSetupPresenterTest { inProgress = false, ) ) - createdState.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) + createdState.eventSink.invoke(SecureBackupSetupEvent.RecoveryKeyHasBeenSaved) val createdAndSaveState = awaitItem() assertThat(createdAndSaveState.setupState).isInstanceOf(SetupState.CreatedAndSaved::class.java) - createdAndSaveState.eventSink.invoke(SecureBackupSetupEvents.Done) + createdAndSaveState.eventSink.invoke(SecureBackupSetupEvent.Done) val doneState = awaitItem() assertThat(doneState.showSaveConfirmationDialog).isTrue() - doneState.eventSink.invoke(SecureBackupSetupEvents.DismissDialog) + doneState.eventSink.invoke(SecureBackupSetupEvent.DismissDialog) val doneStateCancelled = awaitItem() assertThat(doneStateCancelled.showSaveConfirmationDialog).isFalse() } @@ -130,12 +130,12 @@ class SecureBackupSetupPresenterTest { assertThat(initialState.isChangeRecoveryKeyUserStory).isFalse() assertThat(initialState.setupState).isEqualTo(SetupState.Init) - initialState.eventSink(SecureBackupSetupEvents.CreateRecoveryKey) + initialState.eventSink(SecureBackupSetupEvent.CreateRecoveryKey) val creatingState = awaitItem() assertThat(creatingState.setupState).isEqualTo(SetupState.Creating) val failedState = awaitItem() assertThat(failedState.setupState).isInstanceOf(SetupState.Error::class.java) - failedState.eventSink(SecureBackupSetupEvents.DismissDialog) + failedState.eventSink(SecureBackupSetupEvent.DismissDialog) val finalState = awaitItem() assertThat(finalState.setupState).isEqualTo(SetupState.Init) @@ -153,7 +153,7 @@ class SecureBackupSetupPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(SecureBackupSetupEvents.CreateRecoveryKey) + initialState.eventSink.invoke(SecureBackupSetupEvent.CreateRecoveryKey) val creatingState = awaitItem() assertThat(creatingState.setupState).isEqualTo(SetupState.Creating) assertThat(creatingState.recoveryKeyViewState).isEqualTo( @@ -174,13 +174,13 @@ class SecureBackupSetupPresenterTest { inProgress = false, ) ) - createdState.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) + createdState.eventSink.invoke(SecureBackupSetupEvent.RecoveryKeyHasBeenSaved) val createdAndSaveState = awaitItem() assertThat(createdAndSaveState.setupState).isInstanceOf(SetupState.CreatedAndSaved::class.java) - createdAndSaveState.eventSink.invoke(SecureBackupSetupEvents.Done) + createdAndSaveState.eventSink.invoke(SecureBackupSetupEvent.Done) val doneState = awaitItem() assertThat(doneState.showSaveConfirmationDialog).isTrue() - doneState.eventSink.invoke(SecureBackupSetupEvents.DismissDialog) + doneState.eventSink.invoke(SecureBackupSetupEvent.DismissDialog) val doneStateCancelled = awaitItem() assertThat(doneStateCancelled.showSaveConfirmationDialog).isFalse() } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvents.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvent.kt similarity index 70% rename from features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvents.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvent.kt index e42236d31c..e9cac38806 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvents.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvent.kt @@ -8,8 +8,8 @@ package io.element.android.features.securityandprivacy.impl.editroomaddress -sealed interface EditRoomAddressEvents { - data object Save : EditRoomAddressEvents - data object DismissError : EditRoomAddressEvents - data class RoomAddressChanged(val roomAddress: String) : EditRoomAddressEvents +sealed interface EditRoomAddressEvent { + data object Save : EditRoomAddressEvent + data object DismissError : EditRoomAddressEvent + data class RoomAddressChanged(val roomAddress: String) : EditRoomAddressEvent } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt index 8dcea0d880..df4c4a88e1 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt @@ -63,17 +63,17 @@ class EditRoomAddressPresenter( ) } - fun handleEvent(event: EditRoomAddressEvents) { + fun handleEvent(event: EditRoomAddressEvent) { when (event) { - EditRoomAddressEvents.Save -> coroutineScope.save( + EditRoomAddressEvent.Save -> coroutineScope.save( saveAction = saveAction, serverName = homeserverName, newRoomAddress = newRoomAddress ) - is EditRoomAddressEvents.RoomAddressChanged -> { + is EditRoomAddressEvent.RoomAddressChanged -> { newRoomAddress = event.roomAddress } - EditRoomAddressEvents.DismissError -> { + EditRoomAddressEvent.DismissError -> { saveAction.value = AsyncAction.Uninitialized } } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt index 89315ab8ce..a9a4b9c669 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt @@ -16,7 +16,7 @@ data class EditRoomAddressState( val roomAddress: String, val roomAddressValidity: RoomAddressValidity, val saveAction: AsyncAction, - val eventSink: (EditRoomAddressEvents) -> Unit + val eventSink: (EditRoomAddressEvent) -> Unit ) { val canBeSaved = roomAddressValidity == RoomAddressValidity.Valid } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStatePreviewParam.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStatePreviewParam.kt index 096e9c50c5..577bfc9957 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStatePreviewParam.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStatePreviewParam.kt @@ -28,7 +28,7 @@ fun anEditRoomAddressState( roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Unknown, homeserverName: String = ":myserver.org", saveAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (EditRoomAddressEvents) -> Unit = {} + eventSink: (EditRoomAddressEvent) -> Unit = {} ) = EditRoomAddressState( roomAddress = roomAddress, roomAddressValidity = roomAddressValidity, diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt index c1156df209..9eb15b7af9 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt @@ -46,7 +46,7 @@ fun EditRoomAddressView( isSaveActionEnabled = state.canBeSaved, onBackClick = onBackClick, onSaveClick = { - state.eventSink(EditRoomAddressEvents.Save) + state.eventSink(EditRoomAddressEvent.Save) }, ) } @@ -63,7 +63,7 @@ fun EditRoomAddressView( homeserverName = state.homeserverName, addressValidity = state.roomAddressValidity, onAddressChange = { - state.eventSink(EditRoomAddressEvents.RoomAddressChanged(it)) + state.eventSink(EditRoomAddressEvent.RoomAddressChanged(it)) }, label = stringResource(R.string.screen_edit_room_address_title), supportingText = stringResource(R.string.screen_edit_room_address_room_address_section_footer), @@ -81,8 +81,8 @@ fun EditRoomAddressView( }, onSuccess = {}, errorMessage = { stringResource(CommonStrings.error_unknown) }, - onRetry = { state.eventSink(EditRoomAddressEvents.Save) }, - onErrorDismiss = { state.eventSink(EditRoomAddressEvents.DismissError) }, + onRetry = { state.eventSink(EditRoomAddressEvent.Save) }, + onErrorDismiss = { state.eventSink(EditRoomAddressEvent.DismissError) }, ) } } diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt index aac25ec018..9d2b7f9228 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt @@ -97,7 +97,7 @@ class EditRoomAddressPresenterTest { val presenter = createEditRoomAddressPresenter(roomAliasHelper = roomAliasHelper) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("invalid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("invalid")) } with(awaitItem()) { assertThat(roomAddress).isEqualTo("invalid") @@ -115,7 +115,7 @@ class EditRoomAddressPresenterTest { val presenter = createEditRoomAddressPresenter() presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("valid")) } with(awaitItem()) { assertThat(roomAddress).isEqualTo("valid") @@ -134,7 +134,7 @@ class EditRoomAddressPresenterTest { val presenter = createEditRoomAddressPresenter(client = client) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("valid")) } with(awaitItem()) { assertThat(roomAddress).isEqualTo("valid") @@ -163,13 +163,13 @@ class EditRoomAddressPresenterTest { val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("valid")) } skipItems(1) with(awaitItem()) { assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) assertThat(canBeSaved).isTrue() - eventSink(EditRoomAddressEvents.Save) + eventSink(EditRoomAddressEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -212,13 +212,13 @@ class EditRoomAddressPresenterTest { val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("valid")) } skipItems(1) with(awaitItem()) { assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) assertThat(canBeSaved).isTrue() - eventSink(EditRoomAddressEvents.Save) + eventSink(EditRoomAddressEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -263,13 +263,13 @@ class EditRoomAddressPresenterTest { val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("valid")) } skipItems(1) with(awaitItem()) { assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) assertThat(canBeSaved).isTrue() - eventSink(EditRoomAddressEvents.Save) + eventSink(EditRoomAddressEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -309,13 +309,13 @@ class EditRoomAddressPresenterTest { ) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.RoomAddressChanged("valid")) + eventSink(EditRoomAddressEvent.RoomAddressChanged("valid")) } skipItems(1) with(awaitItem()) { assertThat(roomAddressValidity).isEqualTo(RoomAddressValidity.Valid) assertThat(canBeSaved).isTrue() - eventSink(EditRoomAddressEvents.Save) + eventSink(EditRoomAddressEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -339,12 +339,12 @@ class EditRoomAddressPresenterTest { ) presenter.test { with(awaitItem()) { - eventSink(EditRoomAddressEvents.Save) + eventSink(EditRoomAddressEvent.Save) } assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) with(awaitItem()) { assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) - eventSink(EditRoomAddressEvents.DismissError) + eventSink(EditRoomAddressEvent.DismissError) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt index 1d0886f52a..beeab1cd14 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt @@ -39,7 +39,7 @@ class EditRoomAddressViewTest : RobolectricTest() { @Test fun `click on disabled save doesn't emit event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder(expectEvents = false) + val recorder = EventsRecorder(expectEvents = false) val state = anEditRoomAddressState(eventSink = recorder) setEditRoomAddressView(state) clickOn(CommonStrings.action_save) @@ -48,7 +48,7 @@ class EditRoomAddressViewTest : RobolectricTest() { @Test fun `click on enabled save emits the expected event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "room", roomAddressValidity = RoomAddressValidity.Valid, @@ -56,12 +56,12 @@ class EditRoomAddressViewTest : RobolectricTest() { ) setEditRoomAddressView(state) clickOn(CommonStrings.action_save) - recorder.assertSingle(EditRoomAddressEvents.Save) + recorder.assertSingle(EditRoomAddressEvent.Save) } @Test fun `text changes on text field emits the expected event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "", eventSink = recorder @@ -69,12 +69,12 @@ class EditRoomAddressViewTest : RobolectricTest() { setEditRoomAddressView(state) onNodeWithTag(TestTags.roomAddressField.value).performTextInput("alias") - recorder.assertSingle(EditRoomAddressEvents.RoomAddressChanged("alias")) + recorder.assertSingle(EditRoomAddressEvent.RoomAddressChanged("alias")) } @Test fun `click on dismiss error emits the expected event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "", saveAction = AsyncAction.Failure(IllegalStateException()), @@ -82,12 +82,12 @@ class EditRoomAddressViewTest : RobolectricTest() { ) setEditRoomAddressView(state) clickOn(CommonStrings.action_cancel) - recorder.assertSingle(EditRoomAddressEvents.DismissError) + recorder.assertSingle(EditRoomAddressEvent.DismissError) } @Test fun `click on retry error emits the expected event`() = runAndroidComposeUiTest { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = anEditRoomAddressState( roomAddress = "", saveAction = AsyncAction.Failure(IllegalStateException()), @@ -95,7 +95,7 @@ class EditRoomAddressViewTest : RobolectricTest() { ) setEditRoomAddressView(state) clickOn(CommonStrings.action_retry) - recorder.assertSingle(EditRoomAddressEvents.Save) + recorder.assertSingle(EditRoomAddressEvent.Save) } } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvent.kt similarity index 80% rename from features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt rename to features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvent.kt index d0e246fe44..5137c50f2d 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.share.impl -sealed interface ShareEvents { - data object ClearError : ShareEvents +sealed interface ShareEvent { + data object ClearError : ShareEvent } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt index 93a9ae3bf4..3bd9b98227 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt @@ -55,9 +55,9 @@ class SharePresenter( @Composable override fun present(): ShareState { - fun handleEvent(event: ShareEvents) { + fun handleEvent(event: ShareEvent) { when (event) { - ShareEvents.ClearError -> shareActionState.value = AsyncAction.Uninitialized + ShareEvent.ClearError -> shareActionState.value = AsyncAction.Uninitialized } } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt index fd98d58ba3..7538309941 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt @@ -13,5 +13,5 @@ import io.element.android.libraries.matrix.api.core.RoomId data class ShareState( val shareAction: AsyncAction>, - val eventSink: (ShareEvents) -> Unit + val eventSink: (ShareEvent) -> Unit ) diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStatePreviewParam.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStatePreviewParam.kt index 97135acb71..18258f1b95 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStatePreviewParam.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStatePreviewParam.kt @@ -32,7 +32,7 @@ open class ShareStatePreviewParam : PreviewParameterProvider { fun aShareState( shareAction: AsyncAction> = AsyncAction.Uninitialized, - eventSink: (ShareEvents) -> Unit = {} + eventSink: (ShareEvent) -> Unit = {} ) = ShareState( shareAction = shareAction, eventSink = eventSink diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt index 5e5130965f..2516c31a66 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt @@ -26,7 +26,7 @@ fun ShareView( onShareSuccess(it) }, onErrorDismiss = { - state.eventSink(ShareEvents.ClearError) + state.eventSink(ShareEvent.ClearError) }, ) } diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt index ab3a260c01..44ccea92d6 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt @@ -65,7 +65,7 @@ class SharePresenterTest : RobolectricTest() { assertThat(awaitItem().shareAction.isLoading()).isTrue() val failure = awaitItem() assertThat(failure.shareAction.isFailure()).isTrue() - failure.eventSink.invoke(ShareEvents.ClearError) + failure.eventSink.invoke(ShareEvent.ClearError) assertThat(awaitItem().shareAction.isUninitialized()).isTrue() } } diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvent.kt similarity index 78% rename from features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt rename to features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvent.kt index 3b78af9918..1e774488e5 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.signedout.impl -sealed interface SignedOutEvents { - data object SignInAgain : SignedOutEvents +sealed interface SignedOutEvent { + data object SignInAgain : SignedOutEvent } diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt index 1aa13d9b8d..1f1a9be1d7 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt @@ -43,9 +43,9 @@ class SignedOutPresenter( }.collectAsState(initial = null) val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: SignedOutEvents) { + fun handleEvent(event: SignedOutEvent) { when (event) { - SignedOutEvents.SignInAgain -> coroutineScope.launch { + SignedOutEvent.SignInAgain -> coroutineScope.launch { sessionStore.removeSession(sessionId.value) } } diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt index bc1ee18f5a..aeff7c7d2f 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt @@ -13,5 +13,5 @@ import io.element.android.libraries.sessionstorage.api.SessionData data class SignedOutState( val appName: String, val signedOutSession: SessionData?, - val eventSink: (SignedOutEvents) -> Unit, + val eventSink: (SignedOutEvent) -> Unit, ) diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt index 9dcb4585e5..73e854c836 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt @@ -39,7 +39,7 @@ fun SignedOutView( state: SignedOutState, modifier: Modifier = Modifier, ) { - BackHandler(onBack = { state.eventSink(SignedOutEvents.SignInAgain) }) + BackHandler(onBack = { state.eventSink(SignedOutEvent.SignInAgain) }) HeaderFooterPage( modifier = modifier .fillMaxSize() @@ -49,7 +49,7 @@ fun SignedOutView( content = { SignedOutContent() }, footer = { SignedOutFooter( - onSignInAgain = { state.eventSink(SignedOutEvents.SignInAgain) }, + onSignInAgain = { state.eventSink(SignedOutEvent.SignInAgain) }, ) } ) diff --git a/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt b/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt index 519016b1e0..b72ebbcdba 100644 --- a/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt +++ b/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt @@ -60,7 +60,7 @@ class SignedOutPresenterTest { assertThat(initialState.signedOutSession).isEqualTo(aSessionData) assertThat(sessionStore.getAllSessions()).isNotEmpty() assertThat(sessionStore.numberOfSessions()).isEqualTo(1) - initialState.eventSink(SignedOutEvents.SignInAgain) + initialState.eventSink(SignedOutEvent.SignInAgain) assertThat(awaitItem().signedOutSession).isNull() assertThat(sessionStore.getAllSessions()).isEmpty() assertThat(sessionStore.numberOfSessions()).isEqualTo(0) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvent.kt similarity index 59% rename from features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt rename to features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvent.kt index 8bc96c428c..debec67151 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvent.kt @@ -10,11 +10,11 @@ package io.element.android.features.space.impl.leave import io.element.android.libraries.matrix.api.core.RoomId -sealed interface LeaveSpaceEvents { - data object Retry : LeaveSpaceEvents - data object SelectAllRooms : LeaveSpaceEvents - data object DeselectAllRooms : LeaveSpaceEvents - data class ToggleRoomSelection(val roomId: RoomId) : LeaveSpaceEvents - data object LeaveSpace : LeaveSpaceEvents - data object CloseError : LeaveSpaceEvents +sealed interface LeaveSpaceEvent { + data object Retry : LeaveSpaceEvent + data object SelectAllRooms : LeaveSpaceEvent + data object DeselectAllRooms : LeaveSpaceEvent + data class ToggleRoomSelection(val roomId: RoomId) : LeaveSpaceEvent + data object LeaveSpace : LeaveSpaceEvent + data object CloseError : LeaveSpaceEvent } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt index e0876c255c..91a4552903 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt @@ -99,33 +99,33 @@ class LeaveSpacePresenter( } } - fun handleEvent(event: LeaveSpaceEvents) { + fun handleEvent(event: LeaveSpaceEvent) { when (event) { - LeaveSpaceEvents.Retry -> { + LeaveSpaceEvent.Retry -> { leaveSpaceRooms = AsyncData.Loading() retryCount += 1 } - LeaveSpaceEvents.DeselectAllRooms -> { + LeaveSpaceEvent.DeselectAllRooms -> { selectedRoomIds = persistentSetOf() } - LeaveSpaceEvents.SelectAllRooms -> { + LeaveSpaceEvent.SelectAllRooms -> { selectedRoomIds = selectableSpaceRooms.dataOrNull() .orEmpty() .filter { it.isLastOwner.not() } .map { it.spaceRoom.roomId } } - is LeaveSpaceEvents.ToggleRoomSelection -> { + is LeaveSpaceEvent.ToggleRoomSelection -> { selectedRoomIds = if (selectedRoomIds.contains(event.roomId)) { selectedRoomIds - event.roomId } else { selectedRoomIds + event.roomId } } - LeaveSpaceEvents.LeaveSpace -> coroutineScope.leaveSpace( + LeaveSpaceEvent.LeaveSpace -> coroutineScope.leaveSpace( leaveSpaceAction = leaveSpaceAction, selectedRoomIds = selectedRoomIds, ) - LeaveSpaceEvents.CloseError -> { + LeaveSpaceEvent.CloseError -> { leaveSpaceAction.value = AsyncAction.Uninitialized } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt index 9bf029cb15..5f0423721e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt @@ -19,7 +19,7 @@ data class LeaveSpaceState( val areCreatorsPrivileged: Boolean, val selectableSpaceRooms: AsyncData>, val leaveSpaceAction: AsyncAction, - val eventSink: (LeaveSpaceEvents) -> Unit, + val eventSink: (LeaveSpaceEvent) -> Unit, ) { private val rooms = selectableSpaceRooms.dataOrNull().orEmpty().toImmutableList() private val lastAdminRooms: ImmutableList 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 992240cac2..e9e13c2c0e 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 @@ -92,7 +92,7 @@ fun LeaveSpaceView( showLeaveButton = state.showLeaveButton, selectedRoomsCount = state.selectedRoomsCount, onLeaveSpace = { - state.eventSink(LeaveSpaceEvents.LeaveSpace) + state.eventSink(LeaveSpaceEvent.LeaveSpace) }, onCancel = onCancel, showRolesAndPermissionsButton = state.needsOwnerChange && !state.areCreatorsPrivileged, @@ -115,7 +115,7 @@ fun LeaveSpaceView( selectableSpaceRoom = selectableSpaceRoom, showCheckBox = state.hasOnlyLastAdminRoom.not(), onClick = { - state.eventSink(LeaveSpaceEvents.ToggleRoomSelection(selectableSpaceRoom.spaceRoom.roomId)) + state.eventSink(LeaveSpaceEvent.ToggleRoomSelection(selectableSpaceRoom.spaceRoom.roomId)) } ) } @@ -125,7 +125,7 @@ fun LeaveSpaceView( AsyncFailure( throwable = state.selectableSpaceRooms.error, onRetry = { - state.eventSink(LeaveSpaceEvents.Retry) + state.eventSink(LeaveSpaceEvent.Retry) }, ) } @@ -143,7 +143,7 @@ fun LeaveSpaceView( async = state.leaveSpaceAction, onSuccess = { /* Nothing to do, the screen will be dismissed automatically */ }, errorMessage = { stringResource(CommonStrings.error_unknown) }, - onErrorDismiss = { state.eventSink(LeaveSpaceEvents.CloseError) }, + onErrorDismiss = { state.eventSink(LeaveSpaceEvent.CloseError) }, ) } @@ -184,11 +184,11 @@ private fun LeaveSpaceHeader( if (state.showQuickAction) { if (state.areAllSelected) { QuickActionButton(CommonStrings.action_deselect_all) { - state.eventSink(LeaveSpaceEvents.DeselectAllRooms) + state.eventSink(LeaveSpaceEvent.DeselectAllRooms) } } else { QuickActionButton(resId = CommonStrings.action_select_all) { - state.eventSink(LeaveSpaceEvents.SelectAllRooms) + state.eventSink(LeaveSpaceEvent.SelectAllRooms) } } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvent.kt similarity index 54% rename from features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt rename to features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvent.kt index 0f17a2f6f7..1761ec0846 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvent.kt @@ -11,21 +11,21 @@ package io.element.android.features.space.impl.root import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom -sealed interface SpaceEvents { - data object LoadMore : SpaceEvents - data class Join(val spaceRoom: SpaceRoom) : SpaceEvents - data object ClearFailures : SpaceEvents - data class AcceptInvite(val spaceRoom: SpaceRoom) : SpaceEvents - data class DeclineInvite(val spaceRoom: SpaceRoom) : SpaceEvents +sealed interface SpaceEvent { + data object LoadMore : SpaceEvent + data class Join(val spaceRoom: SpaceRoom) : SpaceEvent + data object ClearFailures : SpaceEvent + data class AcceptInvite(val spaceRoom: SpaceRoom) : SpaceEvent + data class DeclineInvite(val spaceRoom: SpaceRoom) : SpaceEvent - data class ShowTopicViewer(val topic: String) : SpaceEvents - data object HideTopicViewer : SpaceEvents + data class ShowTopicViewer(val topic: String) : SpaceEvent + data object HideTopicViewer : SpaceEvent // Manage mode events - data object EnterManageMode : SpaceEvents - data object ExitManageMode : SpaceEvents - data class ToggleRoomSelection(val roomId: RoomId) : SpaceEvents - data object ConfirmRoomRemoval : SpaceEvents - data object RemoveSelectedRooms : SpaceEvents - data object ClearRemoveAction : SpaceEvents + data object EnterManageMode : SpaceEvent + data object ExitManageMode : SpaceEvent + data class ToggleRoomSelection(val roomId: RoomId) : SpaceEvent + data object ConfirmRoomRemoval : SpaceEvent + data object RemoveSelectedRooms : SpaceEvent + data object ClearRemoveAction : SpaceEvent } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt index de30ddb6c1..d42501d002 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt @@ -22,7 +22,7 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.JoinedRoom.Trigger import io.element.android.features.invite.api.SeenInvitesStore -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.toInviteData import io.element.android.libraries.architecture.AsyncAction @@ -150,51 +150,51 @@ class SpacePresenter( } } - fun handleEvent(event: SpaceEvents) { + fun handleEvent(event: SpaceEvent) { when (event) { // SpaceRoomList is loaded automatically as backend is really slow. Event is kept for future. - SpaceEvents.LoadMore -> Unit - is SpaceEvents.Join -> { + SpaceEvent.LoadMore -> Unit + is SpaceEvent.Join -> { sessionCoroutineScope.joinRoom(event.spaceRoom, joinActions, setJoinActions) } - SpaceEvents.ClearFailures -> { + SpaceEvent.ClearFailures -> { val failedActions = joinActions .filterValues { it is AsyncAction.Failure } .mapValues { AsyncAction.Uninitialized } setJoinActions(joinActions + failedActions) } - is SpaceEvents.AcceptInvite -> { + is SpaceEvent.AcceptInvite -> { acceptDeclineInviteState.eventSink( - AcceptDeclineInviteEvents.AcceptInvite(event.spaceRoom.toInviteData()) + AcceptDeclineInviteEvent.AcceptInvite(event.spaceRoom.toInviteData()) ) } - is SpaceEvents.DeclineInvite -> { + is SpaceEvent.DeclineInvite -> { acceptDeclineInviteState.eventSink( - AcceptDeclineInviteEvents.DeclineInvite(invite = event.spaceRoom.toInviteData(), shouldConfirm = true, blockUser = false) + AcceptDeclineInviteEvent.DeclineInvite(invite = event.spaceRoom.toInviteData(), shouldConfirm = true, blockUser = false) ) } - SpaceEvents.HideTopicViewer -> topicViewerState = TopicViewerState.Hidden - is SpaceEvents.ShowTopicViewer -> topicViewerState = TopicViewerState.Shown(event.topic) + SpaceEvent.HideTopicViewer -> topicViewerState = TopicViewerState.Hidden + is SpaceEvent.ShowTopicViewer -> topicViewerState = TopicViewerState.Shown(event.topic) // Manage mode events - SpaceEvents.EnterManageMode -> { + SpaceEvent.EnterManageMode -> { isManageMode = true selectedRoomIds = emptySet() } - SpaceEvents.ExitManageMode -> { + SpaceEvent.ExitManageMode -> { localCoroutineScope.launch { exitManageMode(shouldReset = removedRoomIds.isNotEmpty()) } } - is SpaceEvents.ToggleRoomSelection -> { + is SpaceEvent.ToggleRoomSelection -> { selectedRoomIds = if (event.roomId in selectedRoomIds) { selectedRoomIds - event.roomId } else { selectedRoomIds + event.roomId } } - SpaceEvents.RemoveSelectedRooms -> { + SpaceEvent.RemoveSelectedRooms -> { removeRoomsAction = AsyncAction.ConfirmingNoParams } - SpaceEvents.ConfirmRoomRemoval -> { + SpaceEvent.ConfirmRoomRemoval -> { localCoroutineScope.launch { removeRoomsAction = AsyncAction.Loading val spaceId = spaceRoomList.spaceId @@ -219,7 +219,7 @@ class SpacePresenter( } } } - SpaceEvents.ClearRemoveAction -> { + SpaceEvent.ClearRemoveAction -> { removeRoomsAction = AsyncAction.Uninitialized } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt index a669c294b5..a782b9fa71 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt @@ -32,7 +32,7 @@ data class SpaceState( val selectedRoomIds: ImmutableSet, val canEditSpaceGraph: Boolean, val removeRoomsAction: AsyncAction, - val eventSink: (SpaceEvents) -> Unit + val eventSink: (SpaceEvent) -> Unit ) { fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading fun isSelected(spaceId: RoomId): Boolean = selectedRoomIds.contains(spaceId) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStatePreviewParam.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStatePreviewParam.kt index 34d6e4534a..ee44c8c17e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStatePreviewParam.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStatePreviewParam.kt @@ -80,7 +80,7 @@ fun aSpaceState( selectedRoomIds: Set = emptySet(), canManageRooms: Boolean = true, removeRoomsAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (SpaceEvents) -> Unit = { }, + eventSink: (SpaceEvent) -> Unit = { }, ) = SpaceState( spaceInfo = spaceInfo, children = children.toImmutableList(), 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 f1cddf5e0f..069b00492c 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 @@ -114,7 +114,7 @@ fun SpaceView( var handledBack by remember { mutableStateOf(false) } BackHandler(enabled = !handledBack) { if (state.isManageMode) { - state.eventSink(SpaceEvents.ExitManageMode) + state.eventSink(SpaceEvent.ExitManageMode) } else { handledBack = true onBackClick() @@ -133,8 +133,8 @@ fun SpaceView( ManageModeTopBar( selectedCount = state.selectedCount, isRemoveButtonEnabled = state.isRemoveButtonEnabled, - onCancelClick = { state.eventSink(SpaceEvents.ExitManageMode) }, - onRemoveClick = { state.eventSink(SpaceEvents.RemoveSelectedRooms) }, + onCancelClick = { state.eventSink(SpaceEvent.ExitManageMode) }, + onRemoveClick = { state.eventSink(SpaceEvent.RemoveSelectedRooms) }, ) } AnimatedVisibility( @@ -152,7 +152,7 @@ fun SpaceView( onSettingsClick = onSettingsClick, onShareSpace = onShareSpace, onViewMembersClick = onViewMembersClick, - onManageRoomsClick = { state.eventSink(SpaceEvents.EnterManageMode) }, + onManageRoomsClick = { state.eventSink(SpaceEvent.EnterManageMode) }, onAddRoomClick = onAddRoomClick, onCreateRoomClick = onCreateRoomClick, ) @@ -168,13 +168,13 @@ fun SpaceView( state = state, onRoomClick = { spaceRoom -> if (state.isManageMode) { - state.eventSink(SpaceEvents.ToggleRoomSelection(spaceRoom.roomId)) + state.eventSink(SpaceEvent.ToggleRoomSelection(spaceRoom.roomId)) } else { onRoomClick(spaceRoom) } }, onTopicClick = { topic -> - state.eventSink(SpaceEvents.ShowTopicViewer(topic)) + state.eventSink(SpaceEvent.ShowTopicViewer(topic)) }, onCreateRoomClick = onCreateRoomClick, onAddRoomClick = onAddRoomClick, @@ -187,8 +187,8 @@ fun SpaceView( spaceDisplayName = state.spaceInfo.name ?: state.spaceInfo.id.value, removeRoomsAction = state.removeRoomsAction, selectedCount = state.selectedCount, - onConfirm = { state.eventSink(SpaceEvents.ConfirmRoomRemoval) }, - onDismiss = { state.eventSink(SpaceEvents.ClearRemoveAction) }, + onConfirm = { state.eventSink(SpaceEvent.ConfirmRoomRemoval) }, + onDismiss = { state.eventSink(SpaceEvent.ClearRemoveAction) }, ) acceptDeclineInviteView() } @@ -198,7 +198,7 @@ fun SpaceView( TopicViewerBottomSheet( topicViewerState = state.topicViewerState, onDismiss = { - state.eventSink(SpaceEvents.HideTopicViewer) + state.eventSink(SpaceEvent.HideTopicViewer) } ) } @@ -207,7 +207,7 @@ fun SpaceView( @Composable private fun JoinFailuresEffect( hasAnyFailure: Boolean, - eventSink: (SpaceEvents) -> Unit, + eventSink: (SpaceEvent) -> Unit, ) { val asyncIndicatorState = rememberAsyncIndicatorState() val updatedEventSink by rememberUpdatedState(eventSink) @@ -218,7 +218,7 @@ private fun JoinFailuresEffect( AsyncIndicator.Failure(text = stringResource(CommonStrings.common_something_went_wrong)) } delay(AsyncIndicator.DURATION_SHORT) - updatedEventSink(SpaceEvents.ClearFailures) + updatedEventSink(SpaceEvent.ClearFailures) } else { asyncIndicatorState.clear() } @@ -317,7 +317,7 @@ private fun SpaceViewContent( } } else { spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { - state.eventSink(SpaceEvents.Join(spaceRoom)) + state.eventSink(SpaceEvent.Join(spaceRoom)) } }, bottomAction = if (state.isManageMode) { @@ -325,10 +325,10 @@ private fun SpaceViewContent( } else { spaceRoom.inviteButtons( onAcceptClick = { - state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) + state.eventSink(SpaceEvent.AcceptInvite(spaceRoom)) }, onDeclineClick = { - state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) + state.eventSink(SpaceEvent.DeclineInvite(spaceRoom)) } ) } @@ -384,7 +384,7 @@ private fun EmptySpaceView( @Composable private fun LoadingMoreIndicator( - eventSink: (SpaceEvents) -> Unit, + eventSink: (SpaceEvent) -> Unit, modifier: Modifier = Modifier ) { Box( @@ -397,7 +397,7 @@ private fun LoadingMoreIndicator( ) val latestEventSink by rememberUpdatedState(eventSink) LaunchedEffect(Unit) { - latestEventSink(SpaceEvents.LoadMore) + latestEventSink(SpaceEvent.LoadMore) } } } 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/SpaceSettingsEvent.kt similarity index 88% rename from features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt rename to features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvent.kt index 3c2b1e6609..d85191ed73 100644 --- 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/SpaceSettingsEvent.kt @@ -8,4 +8,4 @@ package io.element.android.features.space.impl.settings -sealed interface SpaceSettingsEvents +sealed interface SpaceSettingsEvent 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 index 5c7e9b5c6e..01f65e4fdf 100644 --- 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 @@ -20,5 +20,5 @@ data class SpaceSettingsState( val canEditDetails: Boolean, val showRolesAndPermissions: Boolean, val showSecurityAndPrivacy: Boolean, - val eventSink: (SpaceSettingsEvents) -> Unit + val eventSink: (SpaceSettingsEvent) -> Unit ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStatePreviewParam.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStatePreviewParam.kt index a68a3ce850..d61b1b656c 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStatePreviewParam.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStatePreviewParam.kt @@ -32,7 +32,7 @@ fun aSpaceSettingsState( showRolesAndPermissions: Boolean = false, showSecurityAndPrivacy: Boolean = false, canEditDetails: Boolean = false, - eventSink: (SpaceSettingsEvents) -> Unit = {}, + eventSink: (SpaceSettingsEvent) -> Unit = {}, ) = SpaceSettingsState( roomId = roomId, name = name, diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt index 021f52defd..4f7bb78c80 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt @@ -61,7 +61,7 @@ class LeaveSpacePresenterTest { val stateError = awaitItem() assertThat(stateError.selectableSpaceRooms.isFailure()).isTrue() // Retry - stateError.eventSink(LeaveSpaceEvents.Retry) + stateError.eventSink(LeaveSpaceEvent.Retry) skipItems(1) val stateLoadingAgain = awaitItem() assertThat(stateLoadingAgain.selectableSpaceRooms.isLoading()).isTrue() @@ -121,7 +121,7 @@ class LeaveSpacePresenterTest { assertThat(finalState.selectableSpaceRooms.dataOrNull()!!.map { it.spaceRoom.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_3) assertThat(finalState.selectedRoomsCount).isEqualTo(2) // Leaving the space will not include the DM - finalState.eventSink(LeaveSpaceEvents.LeaveSpace) + finalState.eventSink(LeaveSpaceEvent.LeaveSpace) val stateLeaving = awaitItem() assertThat(stateLeaving.leaveSpaceAction).isEqualTo(AsyncAction.Loading) val stateLeft = awaitItem() @@ -165,32 +165,32 @@ class LeaveSpacePresenterTest { assertThat(room2.isSelected).isFalse() assertThat(room2.isLastOwner).isTrue() // Deselect all - state.eventSink(LeaveSpaceEvents.DeselectAllRooms) + state.eventSink(LeaveSpaceEvent.DeselectAllRooms) skipItems(1) val stateAllDeselected = awaitItem() val dataAllDeselected = stateAllDeselected.selectableSpaceRooms.dataOrNull()!! assertThat(dataAllDeselected.any { it.isSelected }).isFalse() // Select all - stateAllDeselected.eventSink(LeaveSpaceEvents.SelectAllRooms) + stateAllDeselected.eventSink(LeaveSpaceEvent.SelectAllRooms) skipItems(1) val stateAllSelected = awaitItem() val dataAllSelected = stateAllSelected.selectableSpaceRooms.dataOrNull()!! // The last admin room should not be selected assertThat(dataAllSelected.count { it.isSelected }).isEqualTo(1) // Toggle selection of the first room - stateAllSelected.eventSink(LeaveSpaceEvents.ToggleRoomSelection(A_ROOM_ID)) + stateAllSelected.eventSink(LeaveSpaceEvent.ToggleRoomSelection(A_ROOM_ID)) skipItems(1) val stateOneDeselected = awaitItem() val dataOneDeselected = stateOneDeselected.selectableSpaceRooms.dataOrNull()!! assertThat(dataOneDeselected[0].isSelected).isFalse() // Toggle selection of the first room - stateOneDeselected.eventSink(LeaveSpaceEvents.ToggleRoomSelection(A_ROOM_ID)) + stateOneDeselected.eventSink(LeaveSpaceEvent.ToggleRoomSelection(A_ROOM_ID)) skipItems(1) val stateOneSelected = awaitItem() val dataOneSelected = stateOneSelected.selectableSpaceRooms.dataOrNull()!! assertThat(dataOneSelected[0].isSelected).isTrue() // Leave space - stateOneSelected.eventSink(LeaveSpaceEvents.LeaveSpace) + stateOneSelected.eventSink(LeaveSpaceEvent.LeaveSpace) val stateLeaving = awaitItem() assertThat(stateLeaving.leaveSpaceAction).isEqualTo(AsyncAction.Loading) val stateLeft = awaitItem() @@ -215,13 +215,13 @@ class LeaveSpacePresenterTest { presenter.test { skipItems(3) val state = awaitItem() - state.eventSink(LeaveSpaceEvents.LeaveSpace) + state.eventSink(LeaveSpaceEvent.LeaveSpace) val stateLeaving = awaitItem() assertThat(stateLeaving.leaveSpaceAction).isEqualTo(AsyncAction.Loading) val stateError = awaitItem() assertThat(stateError.leaveSpaceAction.isFailure()).isTrue() // Close error - stateError.eventSink(LeaveSpaceEvents.CloseError) + stateError.eventSink(LeaveSpaceEvent.CloseError) val stateErrorClosed = awaitItem() assertThat(stateErrorClosed.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized) } diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt index 2de52a600b..627ea4483d 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt @@ -15,7 +15,7 @@ import com.google.testing.junit.testparameterinjector.KotlinTestParameters.named import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import io.element.android.features.invite.api.SeenInvitesStore -import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvent import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState import io.element.android.features.invite.api.toInviteData @@ -106,7 +106,7 @@ class SpacePresenterTest { presenter.test { val state = awaitItem() // LoadMore event should not cause any state change - state.eventSink(SpaceEvents.LoadMore) + state.eventSink(SpaceEvent.LoadMore) expectNoEvents() } } @@ -181,7 +181,7 @@ class SpacePresenterTest { skipItems(1) val state = awaitItem() assertThat(state.joinActions[A_ROOM_ID_2]).isNull() - state.eventSink(SpaceEvents.Join(aNotJoinedRoom)) + state.eventSink(SpaceEvent.Join(aNotJoinedRoom)) val joiningState = awaitItem() assertThat(joiningState.joinActions[A_ROOM_ID_2]).isEqualTo(AsyncAction.Loading) // Let the joinRoom call complete @@ -234,14 +234,14 @@ class SpacePresenterTest { skipItems(1) val state = awaitItem() assertThat(state.joinActions[A_ROOM_ID_2]).isNull() - state.eventSink(SpaceEvents.Join(aNotJoinedRoom)) + state.eventSink(SpaceEvent.Join(aNotJoinedRoom)) val joiningState = awaitItem() assertThat(joiningState.joinActions[A_ROOM_ID_2]).isEqualTo(AsyncAction.Loading) val errorState = awaitItem() // Joined room is removed from the join actions assertThat(errorState.joinActions[A_ROOM_ID_2]!!.isFailure()).isTrue() // Clear error - errorState.eventSink(SpaceEvents.ClearFailures) + errorState.eventSink(SpaceEvent.ClearFailures) val clearedState = awaitItem() assertThat(clearedState.joinActions[A_ROOM_ID_2]).isEqualTo(AsyncAction.Uninitialized) } @@ -258,9 +258,9 @@ class SpacePresenterTest { val state = awaitItem() assertThat(state.topicViewerState).isEqualTo(TopicViewerState.Hidden) advanceUntilIdle() - state.eventSink(SpaceEvents.ShowTopicViewer("topic")) + state.eventSink(SpaceEvent.ShowTopicViewer("topic")) assertThat(awaitItem().topicViewerState).isEqualTo(TopicViewerState.Shown("topic")) - state.eventSink(SpaceEvents.HideTopicViewer) + state.eventSink(SpaceEvent.HideTopicViewer) assertThat(awaitItem().topicViewerState).isEqualTo(TopicViewerState.Hidden) } } @@ -272,7 +272,7 @@ class SpacePresenterTest { "decline" to false, ), ) = runTest { - val eventRecorder = EventsRecorder() + val eventRecorder = EventsRecorder() val anInvitedRoom = aSpaceRoom( roomId = A_ROOM_ID_2, state = CurrentUserMembership.INVITED, @@ -300,16 +300,16 @@ class SpacePresenterTest { val state = awaitItem() assertThat(state.joinActions[A_ROOM_ID_2]).isNull() if (acceptInvite) { - state.eventSink(SpaceEvents.AcceptInvite(anInvitedRoom)) + state.eventSink(SpaceEvent.AcceptInvite(anInvitedRoom)) eventRecorder.assertSingle( - AcceptDeclineInviteEvents.AcceptInvite( + AcceptDeclineInviteEvent.AcceptInvite( invite = anInvitedRoom.toInviteData(), ) ) } else { - state.eventSink(SpaceEvents.DeclineInvite(anInvitedRoom)) + state.eventSink(SpaceEvent.DeclineInvite(anInvitedRoom)) eventRecorder.assertSingle( - AcceptDeclineInviteEvents.DeclineInvite( + AcceptDeclineInviteEvent.DeclineInvite( invite = anInvitedRoom.toInviteData(), shouldConfirm = true, blockUser = false, @@ -325,7 +325,7 @@ class SpacePresenterTest { presenter.test { val state = awaitItem() assertThat(state.isManageMode).isFalse() - state.eventSink(SpaceEvents.EnterManageMode) + state.eventSink(SpaceEvent.EnterManageMode) val manageModeState = awaitItem() assertThat(manageModeState.isManageMode).isTrue() assertThat(manageModeState.selectedRoomIds).isEmpty() @@ -342,9 +342,9 @@ class SpacePresenterTest { val presenter = createSpacePresenter(spaceRoomList = fakeSpaceRoomList) presenter.test { val initialState = awaitItem() - initialState.eventSink(SpaceEvents.EnterManageMode) - initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) - initialState.eventSink(SpaceEvents.ExitManageMode) + initialState.eventSink(SpaceEvent.EnterManageMode) + initialState.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) + initialState.eventSink(SpaceEvent.ExitManageMode) advanceUntilIdle() val finalState = expectMostRecentItem() assertThat(finalState.isManageMode).isFalse() @@ -359,13 +359,13 @@ class SpacePresenterTest { val presenter = createSpacePresenter() presenter.test { val initialState = awaitItem() - initialState.eventSink(SpaceEvents.EnterManageMode) + initialState.eventSink(SpaceEvent.EnterManageMode) // Select a room - initialState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) + initialState.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) var latestState = expectMostRecentItem() assertThat(latestState.selectedRoomIds).containsExactly(A_ROOM_ID) // Deselect the room - latestState.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) + latestState.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) latestState = expectMostRecentItem() assertThat(latestState.selectedRoomIds).isEmpty() } @@ -397,10 +397,10 @@ class SpacePresenterTest { advanceUntilIdle() val stateWithChildren = awaitItem() assertThat(stateWithChildren.children).hasSize(1) - stateWithChildren.eventSink(SpaceEvents.EnterManageMode) - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) - stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms) - stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval) + stateWithChildren.eventSink(SpaceEvent.EnterManageMode) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) + stateWithChildren.eventSink(SpaceEvent.RemoveSelectedRooms) + stateWithChildren.eventSink(SpaceEvent.ConfirmRoomRemoval) advanceUntilIdle() val successState = expectMostRecentItem() assertThat(successState.removeRoomsAction).isEqualTo(AsyncAction.Success(Unit)) @@ -442,11 +442,11 @@ class SpacePresenterTest { advanceUntilIdle() val stateWithChildren = awaitItem() assertThat(stateWithChildren.children).hasSize(2) - stateWithChildren.eventSink(SpaceEvents.EnterManageMode) - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID_2)) - stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms) - stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval) + stateWithChildren.eventSink(SpaceEvent.EnterManageMode) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID_2)) + stateWithChildren.eventSink(SpaceEvent.RemoveSelectedRooms) + stateWithChildren.eventSink(SpaceEvent.ConfirmRoomRemoval) advanceUntilIdle() val failureState = expectMostRecentItem() assertThat(failureState.removeRoomsAction.isFailure()).isTrue() @@ -492,16 +492,16 @@ class SpacePresenterTest { awaitItem() // Initial empty state advanceUntilIdle() val stateWithChildren = awaitItem() - stateWithChildren.eventSink(SpaceEvents.EnterManageMode) - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID_2)) - stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms) - stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval) + stateWithChildren.eventSink(SpaceEvent.EnterManageMode) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID_2)) + stateWithChildren.eventSink(SpaceEvent.RemoveSelectedRooms) + stateWithChildren.eventSink(SpaceEvent.ConfirmRoomRemoval) advanceUntilIdle() val failureState = expectMostRecentItem() assertThat(failureState.removeRoomsAction.isFailure()).isTrue() // Exit manage mode after partial failure - reset should be called - failureState.eventSink(SpaceEvents.ExitManageMode) + failureState.eventSink(SpaceEvent.ExitManageMode) advanceUntilIdle() expectMostRecentItem() assert(resetResult).isCalledOnce() @@ -530,7 +530,7 @@ class SpacePresenterTest { // Both room and space visible initially assertThat(stateWithChildren.children).hasSize(2) assertThat(stateWithChildren.isManageMode).isFalse() - stateWithChildren.eventSink(SpaceEvents.EnterManageMode) + stateWithChildren.eventSink(SpaceEvent.EnterManageMode) val manageModeState = expectMostRecentItem() // Only rooms visible in manage mode assertThat(manageModeState.children).hasSize(1) @@ -648,12 +648,12 @@ class SpacePresenterTest { awaitItem() // Initial empty state advanceUntilIdle() val stateWithChildren = awaitItem() - stateWithChildren.eventSink(SpaceEvents.EnterManageMode) + stateWithChildren.eventSink(SpaceEvent.EnterManageMode) // Select both rooms for removal - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) - stateWithChildren.eventSink(SpaceEvents.ToggleRoomSelection(A_ROOM_ID_2)) - stateWithChildren.eventSink(SpaceEvents.RemoveSelectedRooms) - stateWithChildren.eventSink(SpaceEvents.ConfirmRoomRemoval) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) + stateWithChildren.eventSink(SpaceEvent.ToggleRoomSelection(A_ROOM_ID_2)) + stateWithChildren.eventSink(SpaceEvent.RemoveSelectedRooms) + stateWithChildren.eventSink(SpaceEvent.ConfirmRoomRemoval) advanceUntilIdle() val failureState = expectMostRecentItem() assertThat(failureState.removeRoomsAction.isFailure()).isTrue() diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt index 042d6a00ff..218e635e0c 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt @@ -42,7 +42,7 @@ import org.robolectric.annotation.Config class SpaceViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setSpaceView( aSpaceState( @@ -58,7 +58,7 @@ class SpaceViewTest : RobolectricTest() { @Test fun `clicking on a room name invokes the expected callback`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, displayName = A_ROOM_NAME) - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(aSpaceRoom) { setSpaceView( aSpaceState( @@ -75,7 +75,7 @@ class SpaceViewTest : RobolectricTest() { @Test fun `clicking on Join room emits the expected Event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = null) - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( children = listOf(aSpaceRoom), @@ -84,14 +84,14 @@ class SpaceViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_join) - eventsRecorder.assertSingle(SpaceEvents.Join(aSpaceRoom)) + eventsRecorder.assertSingle(SpaceEvent.Join(aSpaceRoom)) } @Config(qualifiers = "h1024dp") @Test fun `clicking on accept invite emits the expected Event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED) - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( hasMoreToLoad = false, @@ -100,14 +100,14 @@ class SpaceViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_accept) - eventsRecorder.assertSingle(SpaceEvents.AcceptInvite(aSpaceRoom)) + eventsRecorder.assertSingle(SpaceEvent.AcceptInvite(aSpaceRoom)) } @Config(qualifiers = "h1024dp") @Test fun `clicking on decline invite emits the expected Event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, state = CurrentUserMembership.INVITED) - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( hasMoreToLoad = false, @@ -116,13 +116,13 @@ class SpaceViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_decline) - eventsRecorder.assertSingle(SpaceEvents.DeclineInvite(aSpaceRoom)) + eventsRecorder.assertSingle(SpaceEvent.DeclineInvite(aSpaceRoom)) } @Config(qualifiers = "h1024dp") @Test fun `clicking on topic emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( spaceInfo = aRoomInfo(topic = A_ROOM_TOPIC), @@ -131,12 +131,12 @@ class SpaceViewTest : RobolectricTest() { ) ) onNodeWithText(A_ROOM_TOPIC).performClick() - eventsRecorder.assertSingle(SpaceEvents.ShowTopicViewer(A_ROOM_TOPIC)) + eventsRecorder.assertSingle(SpaceEvent.ShowTopicViewer(A_ROOM_TOPIC)) } @Test fun `clicking back in manage mode emits ExitManageMode event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( hasMoreToLoad = false, @@ -145,13 +145,13 @@ class SpaceViewTest : RobolectricTest() { ) ) pressBackKey() - eventsRecorder.assertSingle(SpaceEvents.ExitManageMode) + eventsRecorder.assertSingle(SpaceEvent.ExitManageMode) } @Test fun `clicking on room in manage mode emits ToggleRoomSelection event`() = runAndroidComposeUiTest { val aSpaceRoom = aSpaceRoom(roomId = A_ROOM_ID, displayName = A_ROOM_NAME) - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( children = listOf(aSpaceRoom), @@ -161,12 +161,12 @@ class SpaceViewTest : RobolectricTest() { ) ) onNodeWithText(A_ROOM_NAME).performClick() - eventsRecorder.assertSingle(SpaceEvents.ToggleRoomSelection(A_ROOM_ID)) + eventsRecorder.assertSingle(SpaceEvent.ToggleRoomSelection(A_ROOM_ID)) } @Test fun `clicking remove button emits RemoveSelectedRooms event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( children = listOf(aSpaceRoom(roomId = A_ROOM_ID)), @@ -177,13 +177,13 @@ class SpaceViewTest : RobolectricTest() { ) ) clickOn(CommonStrings.action_remove) - eventsRecorder.assertSingle(SpaceEvents.RemoveSelectedRooms) + eventsRecorder.assertSingle(SpaceEvent.RemoveSelectedRooms) } @Config(qualifiers = "h1024dp") @Test fun `clicking confirm in removal dialog emits ConfirmRoomRemoval event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setSpaceView( aSpaceState( children = listOf(aSpaceRoom(roomId = A_ROOM_ID)), @@ -196,7 +196,7 @@ class SpaceViewTest : RobolectricTest() { ) // Click on the Remove button in the confirmation dialog clickOn(CommonStrings.action_remove, inDialog = true) - eventsRecorder.assertSingle(SpaceEvents.ConfirmRoomRemoval) + eventsRecorder.assertSingle(SpaceEvent.ConfirmRoomRemoval) } @Test diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt index 4b62820ef4..e067e13995 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt @@ -24,7 +24,7 @@ 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.features.startchat.impl.userlist.UserListEvents +import io.element.android.features.startchat.impl.userlist.UserListEvent import io.element.android.features.startchat.impl.userlist.UserListState import io.element.android.features.startchat.impl.userlist.UserListStatePreviewParam import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -63,13 +63,13 @@ fun UserListView( showLoader = state.showSearchLoader, isMultiSelectionEnable = state.isMultiSelectionEnabled, showBackButton = showBackButton, - onActiveChange = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) }, + onActiveChange = { state.eventSink(UserListEvent.OnSearchActiveChanged(it)) }, onUserSelect = { - state.eventSink(UserListEvents.AddToSelection(it)) + state.eventSink(UserListEvent.AddToSelection(it)) onSelectUser(it) }, onUserDeselect = { - state.eventSink(UserListEvents.RemoveFromSelection(it)) + state.eventSink(UserListEvent.RemoveFromSelection(it)) onDeselectUser(it) }, ) @@ -80,7 +80,7 @@ fun UserListView( selectedUsers = state.selectedUsers, autoScroll = true, onUserRemove = { - state.eventSink(UserListEvents.RemoveFromSelection(it)) + state.eventSink(UserListEvent.RemoveFromSelection(it)) onDeselectUser(it) }, ) @@ -104,10 +104,10 @@ fun UserListView( checked = isSelected, onCheckedChange = { if (isSelected) { - state.eventSink(UserListEvents.RemoveFromSelection(recentDirectRoom.matrixUser)) + state.eventSink(UserListEvent.RemoveFromSelection(recentDirectRoom.matrixUser)) onDeselectUser(recentDirectRoom.matrixUser) } else { - state.eventSink(UserListEvents.AddToSelection(recentDirectRoom.matrixUser)) + state.eventSink(UserListEvent.AddToSelection(recentDirectRoom.matrixUser)) onSelectUser(recentDirectRoom.matrixUser) } }, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvent.kt similarity index 82% rename from features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt rename to features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvent.kt index 66e135b45c..68f50c41ee 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvent.kt @@ -10,7 +10,7 @@ package io.element.android.features.startchat.impl.root import io.element.android.libraries.matrix.api.user.MatrixUser -sealed interface StartChatEvents { - data class StartDM(val matrixUser: MatrixUser) : StartChatEvents - data object CancelStartDM : StartChatEvents +sealed interface StartChatEvent { + data class StartDM(val matrixUser: MatrixUser) : StartChatEvent + data object CancelStartDM : StartChatEvent } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt index 079ece4180..8b1f92222e 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt @@ -49,16 +49,16 @@ class StartChatPresenter( val localCoroutineScope = rememberCoroutineScope() val startDmActionState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvent(event: StartChatEvents) { + fun handleEvent(event: StartChatEvent) { when (event) { - is StartChatEvents.StartDM -> localCoroutineScope.launch { + is StartChatEvent.StartDM -> localCoroutineScope.launch { startDMAction.execute( matrixUser = event.matrixUser, createIfDmDoesNotExist = startDmActionState.value is AsyncAction.Confirming, actionState = startDmActionState, ) } - StartChatEvents.CancelStartDM -> startDmActionState.value = AsyncAction.Uninitialized + StartChatEvent.CancelStartDM -> startDmActionState.value = AsyncAction.Uninitialized } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt index e6746e1302..167e4f444a 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt @@ -16,5 +16,5 @@ data class StartChatState( val applicationName: String, val userListState: UserListState, val startDmAction: AsyncAction, - val eventSink: (StartChatEvents) -> Unit, + val eventSink: (StartChatEvent) -> Unit, ) diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStatePreviewParam.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStatePreviewParam.kt index e8ac1d55a7..bd7c24da41 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStatePreviewParam.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStatePreviewParam.kt @@ -72,7 +72,7 @@ fun aCreateRoomRootState( applicationName: String = "Element X Preview", userListState: UserListState = aUserListState(), startDmAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (StartChatEvents) -> Unit = {}, + eventSink: (StartChatEvent) -> Unit = {}, ) = StartChatState( applicationName = applicationName, userListState = userListState, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt index 350cf15757..3ef807b777 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt @@ -94,7 +94,7 @@ fun StartChatView( onSelectUser = { coroutineScope.launch { view.hideKeyboardAndAwaitAnimation() - state.eventSink(StartChatEvents.StartDM(it)) + state.eventSink(StartChatEvent.StartDM(it)) } }, onDeselectUser = { }, @@ -124,21 +124,21 @@ fun StartChatView( errorMessage = { stringResource(R.string.screen_start_chat_error_starting_chat) }, onRetry = { state.userListState.selectedUsers.firstOrNull() - ?.let { state.eventSink(StartChatEvents.StartDM(it)) } + ?.let { state.eventSink(StartChatEvent.StartDM(it)) } // Cancel start DM if there is no more selected user (should not happen) - ?: state.eventSink(StartChatEvents.CancelStartDM) + ?: state.eventSink(StartChatEvent.CancelStartDM) }, - onErrorDismiss = { state.eventSink(StartChatEvents.CancelStartDM) }, + onErrorDismiss = { state.eventSink(StartChatEvent.CancelStartDM) }, confirmationDialog = { data -> if (data is ConfirmingStartDmWithMatrixUser) { CreateDmConfirmationBottomSheet( matrixUser = data.matrixUser, isUserIdentityUnknown = data.isUserIdentityUnknown, onSendInvite = { - state.eventSink(StartChatEvents.StartDM(data.matrixUser)) + state.eventSink(StartChatEvent.StartDM(data.matrixUser)) }, onDismiss = { - state.eventSink(StartChatEvents.CancelStartDM) + state.eventSink(StartChatEvent.CancelStartDM) }, ) } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt index 286f0abe68..adbab19180 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt @@ -85,11 +85,11 @@ class DefaultUserListPresenter( }.launchIn(this) } - fun handleEvent(event: UserListEvents) { + fun handleEvent(event: UserListEvent) { when (event) { - is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active - is UserListEvents.AddToSelection -> userListDataStore.selectUser(event.matrixUser) - is UserListEvents.RemoveFromSelection -> userListDataStore.removeUserFromSelection(event.matrixUser) + is UserListEvent.OnSearchActiveChanged -> isSearchActive = event.active + is UserListEvent.AddToSelection -> userListDataStore.selectUser(event.matrixUser) + is UserListEvent.RemoveFromSelection -> userListDataStore.removeUserFromSelection(event.matrixUser) } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvent.kt similarity index 88% rename from features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt rename to features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvent.kt index f7601f6d36..098e4411d2 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvent.kt @@ -10,8 +10,8 @@ package io.element.android.features.startchat.impl.userlist import io.element.android.libraries.matrix.api.user.MatrixUser -sealed interface UserListEvents { - data class AddToSelection(val matrixUser: MatrixUser) : UserListEvents - data class RemoveFromSelection(val matrixUser: MatrixUser) : UserListEvents - data class OnSearchActiveChanged(val active: Boolean) : UserListEvents +sealed interface UserListEvent { + data class AddToSelection(val matrixUser: MatrixUser) : UserListEvent + data class RemoveFromSelection(val matrixUser: MatrixUser) : UserListEvent + data class OnSearchActiveChanged(val active: Boolean) : UserListEvent } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt index 6a507f8a75..6ce5bafddf 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt @@ -23,7 +23,7 @@ data class UserListState( val isSearchActive: Boolean, val selectionMode: SelectionMode, val recentDirectRooms: ImmutableList, - val eventSink: (UserListEvents) -> Unit, + val eventSink: (UserListEvent) -> Unit, ) { val isMultiSelectionEnabled = selectionMode == SelectionMode.Multiple } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStatePreviewParam.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStatePreviewParam.kt index 190ff23ad2..a84927be62 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStatePreviewParam.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStatePreviewParam.kt @@ -68,7 +68,7 @@ fun aUserListState( showSearchLoader: Boolean = false, selectionMode: SelectionMode = SelectionMode.Single, recentDirectRooms: List = emptyList(), - eventSink: (UserListEvents) -> Unit = {}, + eventSink: (UserListEvent) -> Unit = {}, ) = UserListState( searchQuery = TextFieldState(initialText = searchQuery), isSearchActive = isSearchActive, diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt index 57f6cd2333..39d6f34b21 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt @@ -53,7 +53,7 @@ class StartChatPresenterTest { assertThat(initialState.userListState.isSearchActive).isFalse() assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse() val matrixUser = MatrixUser(UserId("@name:domain")) - initialState.eventSink(StartChatEvents.StartDM(matrixUser)) + initialState.eventSink(StartChatEvent.StartDM(matrixUser)) awaitItem().also { state -> assertThat(state.startDmAction).isEqualTo(startDMFailureResult) executeResult.assertions().isCalledOnce().with( @@ -61,7 +61,7 @@ class StartChatPresenterTest { value(false), any(), ) - state.eventSink(StartChatEvents.CancelStartDM) + state.eventSink(StartChatEvent.CancelStartDM) } awaitItem().also { state -> assertThat(state.startDmAction.isUninitialized()).isTrue() @@ -85,7 +85,7 @@ class StartChatPresenterTest { assertThat(initialState.userListState.isSearchActive).isFalse() assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse() val matrixUser = MatrixUser(UserId("@name:domain")) - initialState.eventSink(StartChatEvents.StartDM(matrixUser)) + initialState.eventSink(StartChatEvent.StartDM(matrixUser)) awaitItem().also { state -> assertThat(state.startDmAction).isEqualTo(startDMSuccessResult) executeResult.assertions().isCalledOnce().with( @@ -109,7 +109,7 @@ class StartChatPresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - initialState.eventSink(StartChatEvents.StartDM(matrixUser)) + initialState.eventSink(StartChatEvent.StartDM(matrixUser)) val confirmingState = awaitItem() assertThat(confirmingState.startDmAction).isEqualTo(startDMConfirmationResult) executeResult.assertions().isCalledOnce().with( @@ -118,7 +118,7 @@ class StartChatPresenterTest { any(), ) // Cancelling should not create the DM - confirmingState.eventSink(StartChatEvents.CancelStartDM) + confirmingState.eventSink(StartChatEvent.CancelStartDM) val finalState = awaitItem() assertThat(finalState.startDmAction.isUninitialized()).isTrue() executeResult.assertions().isCalledExactly(1) @@ -137,7 +137,7 @@ class StartChatPresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - initialState.eventSink(StartChatEvents.StartDM(matrixUser)) + initialState.eventSink(StartChatEvent.StartDM(matrixUser)) val confirmingState = awaitItem() assertThat(confirmingState.startDmAction).isEqualTo(startDMConfirmationResult) executeResult.assertions().isCalledOnce().with( @@ -146,7 +146,7 @@ class StartChatPresenterTest { any(), ) // Start DM again should invoke the action with createIfDmDoesNotExist = true - confirmingState.eventSink(StartChatEvents.StartDM(matrixUser)) + confirmingState.eventSink(StartChatEvent.StartDM(matrixUser)) executeResult.assertions().isCalledExactly(2).withSequence( listOf(value(matrixUser), value(false), any()), listOf(value(matrixUser), value(true), any()), diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt index 8b5e1da8e6..7b7c046bb2 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt @@ -36,7 +36,7 @@ import org.robolectric.annotation.Config class StartChatViewTest : RobolectricTest() { @Test fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setStartChatView( aCreateRoomRootState( @@ -50,7 +50,7 @@ class StartChatViewTest : RobolectricTest() { @Test fun `clicking on New room invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setStartChatView( aCreateRoomRootState( @@ -65,7 +65,7 @@ class StartChatViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on Invite people invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setStartChatView( aCreateRoomRootState( @@ -84,7 +84,7 @@ class StartChatViewTest : RobolectricTest() { fun `clicking on a user suggestion invokes the expected callback`() = runAndroidComposeUiTest { val recentDirectRoomList = aRecentDirectRoomList() val firstRoom = recentDirectRoomList[0] - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnceWithParam(firstRoom.roomId) { setStartChatView( aCreateRoomRootState( @@ -102,7 +102,7 @@ class StartChatViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on Join room by address invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setStartChatView( aCreateRoomRootState( @@ -116,7 +116,7 @@ class StartChatViewTest : RobolectricTest() { @Test fun `clicking on room directory invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setStartChatView( aCreateRoomRootState( diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt index 55fd30a84e..0366ec79bd 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt @@ -80,7 +80,7 @@ class DefaultUserListPresenterTest { presenter.test { val initialState = awaitItem() - initialState.eventSink(UserListEvents.OnSearchActiveChanged(true)) + initialState.eventSink(UserListEvent.OnSearchActiveChanged(true)) assertThat(awaitItem().isSearchActive).isTrue() val matrixIdQuery = "@name:matrix.org" @@ -93,7 +93,7 @@ class DefaultUserListPresenterTest { assertThat(awaitItem().searchQuery.text.toString()).isEqualTo(notMatrixIdQuery) assertThat(userRepository.providedQuery).isEqualTo(notMatrixIdQuery) - initialState.eventSink(UserListEvents.OnSearchActiveChanged(false)) + initialState.eventSink(UserListEvent.OnSearchActiveChanged(false)) assertThat(awaitItem().isSearchActive).isFalse() } } @@ -190,22 +190,22 @@ class DefaultUserListPresenterTest { val userABis = aMatrixUser("@userA:domain", "A") val userC = aMatrixUser("@userC:domain", "C") - initialState.eventSink(UserListEvents.AddToSelection(userA)) + initialState.eventSink(UserListEvent.AddToSelection(userA)) assertThat(awaitItem().selectedUsers).containsExactly(userA) - initialState.eventSink(UserListEvents.AddToSelection(userB)) + initialState.eventSink(UserListEvent.AddToSelection(userB)) assertThat(awaitItem().selectedUsers).containsExactly(userA, userB) - initialState.eventSink(UserListEvents.AddToSelection(userABis)) - initialState.eventSink(UserListEvents.AddToSelection(userC)) + initialState.eventSink(UserListEvent.AddToSelection(userABis)) + initialState.eventSink(UserListEvent.AddToSelection(userC)) // duplicated users should be ignored assertThat(awaitItem().selectedUsers).containsExactly(userA, userB, userC) - initialState.eventSink(UserListEvents.RemoveFromSelection(userB)) + initialState.eventSink(UserListEvent.RemoveFromSelection(userB)) assertThat(awaitItem().selectedUsers).containsExactly(userA, userC) - initialState.eventSink(UserListEvents.RemoveFromSelection(userA)) + initialState.eventSink(UserListEvent.RemoveFromSelection(userA)) assertThat(awaitItem().selectedUsers).containsExactly(userC) - initialState.eventSink(UserListEvents.RemoveFromSelection(userC)) + initialState.eventSink(UserListEvent.RemoveFromSelection(userC)) assertThat(awaitItem().selectedUsers).isEmpty() } } diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvent.kt similarity index 57% rename from features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt rename to features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvent.kt index 55c9c40f74..24265f0d9b 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvent.kt @@ -8,13 +8,13 @@ package io.element.android.features.userprofile.api -sealed interface UserProfileEvents { - data object StartDM : UserProfileEvents - data object ClearStartDMState : UserProfileEvents - data class BlockUser(val needsConfirmation: Boolean = false) : UserProfileEvents - data class UnblockUser(val needsConfirmation: Boolean = false) : UserProfileEvents - data object ClearBlockUserError : UserProfileEvents - data object ClearConfirmationDialog : UserProfileEvents - data object WithdrawVerification : UserProfileEvents - data class CopyToClipboard(val text: String) : UserProfileEvents +sealed interface UserProfileEvent { + data object StartDM : UserProfileEvent + data object ClearStartDMState : UserProfileEvent + data class BlockUser(val needsConfirmation: Boolean = false) : UserProfileEvent + data class UnblockUser(val needsConfirmation: Boolean = false) : UserProfileEvent + data object ClearBlockUserError : UserProfileEvent + data object ClearConfirmationDialog : UserProfileEvent + data object WithdrawVerification : UserProfileEvent + data class CopyToClipboard(val text: String) : UserProfileEvent } diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt index 4de0d50ca4..bdc1c5a5c7 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt @@ -28,7 +28,7 @@ data class UserProfileState( val canCall: Boolean, val snackbarMessage: SnackbarMessage?, val displayedStatus: DisplayedStatus?, - val eventSink: (UserProfileEvents) -> Unit + val eventSink: (UserProfileEvent) -> Unit ) { enum class ConfirmationDialog { Block, diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt index 24977713c0..08251981c9 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt @@ -23,7 +23,7 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.startchat.api.StartDMAction -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileState.ConfirmationDialog import io.element.android.features.userprofile.api.UserProfileVerificationState @@ -101,9 +101,9 @@ class UserProfilePresenter( } val userProfile by produceState(null) { value = client.getProfile(userId).getOrNull() } - fun handleEvent(event: UserProfileEvents) { + fun handleEvent(event: UserProfileEvent) { when (event) { - is UserProfileEvents.BlockUser -> { + is UserProfileEvent.BlockUser -> { if (event.needsConfirmation) { confirmationDialog = ConfirmationDialog.Block } else { @@ -111,7 +111,7 @@ class UserProfilePresenter( coroutineScope.blockUser(isBlocked) } } - is UserProfileEvents.UnblockUser -> { + is UserProfileEvent.UnblockUser -> { if (event.needsConfirmation) { confirmationDialog = ConfirmationDialog.Unblock } else { @@ -119,11 +119,11 @@ class UserProfilePresenter( coroutineScope.unblockUser(isBlocked) } } - UserProfileEvents.ClearConfirmationDialog -> confirmationDialog = null - UserProfileEvents.ClearBlockUserError -> { + UserProfileEvent.ClearConfirmationDialog -> confirmationDialog = null + UserProfileEvent.ClearBlockUserError -> { isBlocked.value = AsyncData.Success(isBlocked.value.dataOrNull().orFalse()) } - UserProfileEvents.StartDM -> { + UserProfileEvent.StartDM -> { coroutineScope.launch { startDMAction.execute( matrixUser = userProfile ?: MatrixUser(userId), @@ -132,12 +132,12 @@ class UserProfilePresenter( ) } } - UserProfileEvents.ClearStartDMState -> { + UserProfileEvent.ClearStartDMState -> { startDmActionState.value = AsyncAction.Uninitialized } // Do nothing for other event as they are handled by the RoomMemberDetailsPresenter if needed - UserProfileEvents.WithdrawVerification, - is UserProfileEvents.CopyToClipboard -> Unit + UserProfileEvent.WithdrawVerification, + is UserProfileEvent.CopyToClipboard -> Unit } } diff --git a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt index bbdc698f17..57a7ae273f 100644 --- a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt +++ b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt @@ -18,7 +18,7 @@ import io.element.android.features.enterprise.test.FakeSessionEnterpriseService import io.element.android.features.invitepeople.test.FakeStartDMAction import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser import io.element.android.features.startchat.api.StartDMAction -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.impl.root.UserProfilePresenter @@ -182,12 +182,12 @@ class UserProfilePresenterTest { val presenter = createUserProfilePresenter() presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = true)) + initialState.eventSink(UserProfileEvent.BlockUser(needsConfirmation = true)) val dialogState = awaitItem() assertThat(dialogState.displayConfirmationDialog).isEqualTo(UserProfileState.ConfirmationDialog.Block) - dialogState.eventSink(UserProfileEvents.ClearConfirmationDialog) + dialogState.eventSink(UserProfileEvent.ClearConfirmationDialog) assertThat(awaitItem().displayConfirmationDialog).isNull() } } @@ -202,12 +202,12 @@ class UserProfilePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false)) + initialState.eventSink(UserProfileEvent.BlockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() ignoredUsersFlow.emit(persistentListOf(A_USER_ID)) assertThat(awaitItem().isBlocked.dataOrNull()).isTrue() - initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false)) + initialState.eventSink(UserProfileEvent.UnblockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() ignoredUsersFlow.emit(persistentListOf()) assertThat(awaitItem().isBlocked.dataOrNull()).isFalse() @@ -222,12 +222,12 @@ class UserProfilePresenterTest { val presenter = createUserProfilePresenter(client = matrixClient) presenter.test { val initialState = awaitFirstItem(count = 2) - initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false)) + initialState.eventSink(UserProfileEvent.BlockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() val errorState = awaitItem() assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(AN_EXCEPTION) // Clear error - initialState.eventSink(UserProfileEvents.ClearBlockUserError) + initialState.eventSink(UserProfileEvent.ClearBlockUserError) assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(false)) } } @@ -240,12 +240,12 @@ class UserProfilePresenterTest { val presenter = createUserProfilePresenter(client = matrixClient) presenter.test { val initialState = awaitFirstItem(count = 2) - initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false)) + initialState.eventSink(UserProfileEvent.UnblockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() val errorState = awaitItem() assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(AN_EXCEPTION) // Clear error - initialState.eventSink(UserProfileEvents.ClearBlockUserError) + initialState.eventSink(UserProfileEvent.ClearBlockUserError) assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(true)) } } @@ -255,12 +255,12 @@ class UserProfilePresenterTest { val presenter = createUserProfilePresenter() presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = true)) + initialState.eventSink(UserProfileEvent.UnblockUser(needsConfirmation = true)) val dialogState = awaitItem() assertThat(dialogState.displayConfirmationDialog).isEqualTo(UserProfileState.ConfirmationDialog.Unblock) - dialogState.eventSink(UserProfileEvents.ClearConfirmationDialog) + dialogState.eventSink(UserProfileEvent.ClearConfirmationDialog) assertThat(awaitItem().displayConfirmationDialog).isNull() } } @@ -279,7 +279,7 @@ class UserProfilePresenterTest { val initialState = awaitFirstItem() assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java) val matrixUser = MatrixUser(UserId("@alice:server.org")) - initialState.eventSink(UserProfileEvents.StartDM) + initialState.eventSink(UserProfileEvent.StartDM) awaitItem().also { state -> assertThat(state.startDmActionState).isEqualTo(startDMFailureResult) executeResult.assertions().isCalledOnce().with( @@ -287,7 +287,7 @@ class UserProfilePresenterTest { value(false), any(), ) - state.eventSink(UserProfileEvents.ClearStartDMState) + state.eventSink(UserProfileEvent.ClearStartDMState) } awaitItem().also { state -> assertThat(state.startDmActionState.isUninitialized()).isTrue() @@ -309,7 +309,7 @@ class UserProfilePresenterTest { val initialState = awaitFirstItem() assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java) val matrixUser = MatrixUser(UserId("@alice:server.org")) - initialState.eventSink(UserProfileEvents.StartDM) + initialState.eventSink(UserProfileEvent.StartDM) awaitItem().also { state -> assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult) executeResult.assertions().isCalledOnce().with( @@ -335,7 +335,7 @@ class UserProfilePresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java) - initialState.eventSink(UserProfileEvents.StartDM) + initialState.eventSink(UserProfileEvent.StartDM) val confirmingState = awaitItem() assertThat(confirmingState.startDmActionState).isEqualTo(startDMConfirmationResult) executeResult.assertions().isCalledOnce().with( @@ -344,7 +344,7 @@ class UserProfilePresenterTest { any(), ) // Cancelling should not create the DM - confirmingState.eventSink(UserProfileEvents.ClearStartDMState) + confirmingState.eventSink(UserProfileEvent.ClearStartDMState) val finalState = awaitItem() assertThat(finalState.startDmActionState.isUninitialized()).isTrue() executeResult.assertions().isCalledExactly(1) @@ -365,7 +365,7 @@ class UserProfilePresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java) - initialState.eventSink(UserProfileEvents.StartDM) + initialState.eventSink(UserProfileEvent.StartDM) val confirmingState = awaitItem() assertThat(confirmingState.startDmActionState).isEqualTo(startDMConfirmationResult) executeResult.assertions().isCalledOnce().with( @@ -374,7 +374,7 @@ class UserProfilePresenterTest { any(), ) // Start DM again should invoke the action with createIfDmDoesNotExist = true - confirmingState.eventSink(UserProfileEvents.StartDM) + confirmingState.eventSink(UserProfileEvent.StartDM) executeResult.assertions().isCalledExactly(2).withSequence( listOf(value(matrixUser), value(false), any()), listOf(value(matrixUser), value(true), any()), diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStatePreviewParam.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStatePreviewParam.kt index 84a95e0b14..bfb3df4ac6 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStatePreviewParam.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStatePreviewParam.kt @@ -10,7 +10,7 @@ package io.element.android.features.userprofile.shared import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.libraries.architecture.AsyncAction @@ -50,7 +50,7 @@ fun aUserProfileState( canCall: Boolean = false, snackbarMessage: SnackbarMessage? = null, displayedStatus: DisplayedStatus? = null, - eventSink: (UserProfileEvents) -> Unit = {}, + eventSink: (UserProfileEvent) -> Unit = {}, ) = UserProfileState( userId = userId, userName = userName, diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt index b9f43db36d..643db456b1 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs @@ -83,15 +83,15 @@ fun UserProfileView( openAvatarPreview(state.userName ?: state.userId.value, avatarUrl) }, onUserIdClick = { - state.eventSink(UserProfileEvents.CopyToClipboard(state.userId.value)) + state.eventSink(UserProfileEvent.CopyToClipboard(state.userId.value)) }, - withdrawVerificationClick = { state.eventSink(UserProfileEvents.WithdrawVerification) }, + withdrawVerificationClick = { state.eventSink(UserProfileEvent.WithdrawVerification) }, ) UserProfileMainActionsSection( isCurrentUser = state.isCurrentUser, canCall = state.canCall, onShareUser = onShareUser, - onStartDM = { state.eventSink(UserProfileEvents.StartDM) }, + onStartDM = { state.eventSink(UserProfileEvent.StartDM) }, onCall = { intent -> state.dmRoomId?.let { onStartCall(it, intent) } } ) Spacer(modifier = Modifier.height(26.dp)) @@ -109,18 +109,18 @@ fun UserProfileView( }, onSuccess = onOpenDm, errorMessage = { stringResource(R.string.screen_start_chat_error_starting_chat) }, - onRetry = { state.eventSink(UserProfileEvents.StartDM) }, - onErrorDismiss = { state.eventSink(UserProfileEvents.ClearStartDMState) }, + onRetry = { state.eventSink(UserProfileEvent.StartDM) }, + onErrorDismiss = { state.eventSink(UserProfileEvent.ClearStartDMState) }, confirmationDialog = { data -> if (data is ConfirmingStartDmWithMatrixUser) { CreateDmConfirmationBottomSheet( matrixUser = data.matrixUser, isUserIdentityUnknown = data.isUserIdentityUnknown, onSendInvite = { - state.eventSink(UserProfileEvents.StartDM) + state.eventSink(UserProfileEvent.StartDM) }, onDismiss = { - state.eventSink(UserProfileEvents.ClearStartDMState) + state.eventSink(UserProfileEvent.ClearStartDMState) }, ) } diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt index 2ba17b947b..220118c5a7 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt @@ -10,7 +10,7 @@ package io.element.android.features.userprofile.shared.blockuser import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.shared.R import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog @@ -23,24 +23,24 @@ fun BlockUserDialogs(state: UserProfileState) { BlockConfirmationDialog( onBlockAction = { state.eventSink( - UserProfileEvents.BlockUser( + UserProfileEvent.BlockUser( needsConfirmation = false ) ) }, - onDismiss = { state.eventSink(UserProfileEvents.ClearConfirmationDialog) } + onDismiss = { state.eventSink(UserProfileEvent.ClearConfirmationDialog) } ) } UserProfileState.ConfirmationDialog.Unblock -> { UnblockConfirmationDialog( onUnblockAction = { state.eventSink( - UserProfileEvents.UnblockUser( + UserProfileEvent.UnblockUser( needsConfirmation = false ) ) }, - onDismiss = { state.eventSink(UserProfileEvents.ClearConfirmationDialog) } + onDismiss = { state.eventSink(UserProfileEvent.ClearConfirmationDialog) } ) } } diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt index 30f8c28607..e8210997b0 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt @@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.shared.R import io.element.android.libraries.architecture.AsyncData @@ -50,13 +50,13 @@ fun BlockUserSection( if (isBlocked is AsyncData.Failure) { RetryDialog( content = stringResource(CommonStrings.error_unknown), - onDismiss = { state.eventSink(UserProfileEvents.ClearBlockUserError) }, + onDismiss = { state.eventSink(UserProfileEvent.ClearBlockUserError) }, onRetry = { val event = when (isBlocked.prevData) { - true -> UserProfileEvents.UnblockUser(needsConfirmation = false) - false -> UserProfileEvents.BlockUser(needsConfirmation = false) + true -> UserProfileEvent.UnblockUser(needsConfirmation = false) + false -> UserProfileEvent.BlockUser(needsConfirmation = false) // null case Should not happen - null -> UserProfileEvents.ClearBlockUserError + null -> UserProfileEvent.ClearBlockUserError } state.eventSink(event) }, @@ -68,7 +68,7 @@ fun BlockUserSection( private fun PreferenceBlockUser( isBlocked: Boolean?, isLoading: Boolean, - eventSink: (UserProfileEvents) -> Unit, + eventSink: (UserProfileEvent) -> Unit, ) { val loadingCurrentValue = @Composable { _: Boolean -> CircularProgressIndicator( @@ -82,7 +82,7 @@ private fun PreferenceBlockUser( ListItem( content = { Text(stringResource(R.string.screen_dm_details_unblock_user)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), - onClick = { if (!isLoading) eventSink(UserProfileEvents.UnblockUser(needsConfirmation = true)) }, + onClick = { if (!isLoading) eventSink(UserProfileEvent.UnblockUser(needsConfirmation = true)) }, trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null, ) } else { @@ -90,7 +90,7 @@ private fun PreferenceBlockUser( content = { Text(stringResource(R.string.screen_dm_details_block_user)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())), style = ListItemStyle.Destructive, - onClick = { if (!isLoading) eventSink(UserProfileEvents.BlockUser(needsConfirmation = true)) }, + onClick = { if (!isLoading) eventSink(UserProfileEvent.BlockUser(needsConfirmation = true)) }, trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null, ) } diff --git a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt index 63930affee..9c6aa66670 100644 --- a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt +++ b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.v2.runAndroidComposeUiTest -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.api.UserProfileVerificationState import io.element.android.features.userprofile.shared.R @@ -89,7 +89,7 @@ class UserProfileViewTest : RobolectricTest() { @Test fun `on Message clicked - the StartDm event is emitted`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( dmRoomId = A_ROOM_ID, @@ -97,7 +97,7 @@ class UserProfileViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_message) - eventsRecorder.assertSingle(UserProfileEvents.StartDM) + eventsRecorder.assertSingle(UserProfileEvent.StartDM) } @Test @@ -131,19 +131,19 @@ class UserProfileViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `on Block user clicked - a BlockUser event is emitted with needsConfirmation`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( eventSink = eventsRecorder, ), ) clickOn(R.string.screen_dm_details_block_user) - eventsRecorder.assertSingle(UserProfileEvents.BlockUser(needsConfirmation = true)) + eventsRecorder.assertSingle(UserProfileEvent.BlockUser(needsConfirmation = true)) } @Test fun `on confirming block user - a BlockUser event is emitted without needsConfirmation`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, @@ -151,12 +151,12 @@ class UserProfileViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_dm_details_block_alert_action) - eventsRecorder.assertSingle(UserProfileEvents.BlockUser(needsConfirmation = false)) + eventsRecorder.assertSingle(UserProfileEvent.BlockUser(needsConfirmation = false)) } @Test fun `on canceling blocking a user - a ClearConfirmationDialog event is emitted`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block, @@ -164,13 +164,13 @@ class UserProfileViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) + eventsRecorder.assertSingle(UserProfileEvent.ClearConfirmationDialog) } @Config(qualifiers = "h1024dp") @Test fun `on Unblock user clicked - an UnblockUser event is emitted with needsConfirmation`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( isBlocked = AsyncData.Success(true), @@ -178,12 +178,12 @@ class UserProfileViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_dm_details_unblock_user) - eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(needsConfirmation = true)) + eventsRecorder.assertSingle(UserProfileEvent.UnblockUser(needsConfirmation = true)) } @Test fun `on confirming Unblock user - an UnblockUser event is emitted without needsConfirmation`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( isBlocked = AsyncData.Success(true), @@ -192,12 +192,12 @@ class UserProfileViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_dm_details_unblock_alert_action) - eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(needsConfirmation = false)) + eventsRecorder.assertSingle(UserProfileEvent.UnblockUser(needsConfirmation = false)) } @Test fun `on canceling unblocking a user - a ClearConfirmationDialog event is emitted`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setUserProfileView( state = aUserProfileState( isBlocked = AsyncData.Success(true), @@ -206,7 +206,7 @@ class UserProfileViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) + eventsRecorder.assertSingle(UserProfileEvent.ClearConfirmationDialog) } @Test diff --git a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt index b0a2590e8a..575c9ff7a0 100644 --- a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt +++ b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt @@ -12,7 +12,7 @@ package io.element.android.features.userprofile.shared.blockuser import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.v2.runAndroidComposeUiTest -import io.element.android.features.userprofile.api.UserProfileEvents +import io.element.android.features.userprofile.api.UserProfileEvent import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.shared.R import io.element.android.features.userprofile.shared.aUserProfileState @@ -25,7 +25,7 @@ import org.junit.Test class BlockUserDialogsTest : RobolectricTest() { @Test fun `confirm block user emit expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setContent { BlockUserDialogs( state = aUserProfileState( @@ -35,12 +35,12 @@ class BlockUserDialogsTest : RobolectricTest() { ) } clickOn(R.string.screen_dm_details_block_alert_action) - eventsRecorder.assertSingle(UserProfileEvents.BlockUser(false)) + eventsRecorder.assertSingle(UserProfileEvent.BlockUser(false)) } @Test fun `cancel block user emit expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setContent { BlockUserDialogs( state = aUserProfileState( @@ -50,12 +50,12 @@ class BlockUserDialogsTest : RobolectricTest() { ) } clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) + eventsRecorder.assertSingle(UserProfileEvent.ClearConfirmationDialog) } @Test fun `confirm unblock user emit expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setContent { BlockUserDialogs( state = aUserProfileState( @@ -65,12 +65,12 @@ class BlockUserDialogsTest : RobolectricTest() { ) } clickOn(R.string.screen_dm_details_unblock_alert_action) - eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(false)) + eventsRecorder.assertSingle(UserProfileEvent.UnblockUser(false)) } @Test fun `cancel unblock user emit expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setContent { BlockUserDialogs( state = aUserProfileState( @@ -80,6 +80,6 @@ class BlockUserDialogsTest : RobolectricTest() { ) } clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) + eventsRecorder.assertSingle(UserProfileEvent.ClearConfirmationDialog) } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt index aa991a54bc..4cde3f927f 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt @@ -108,18 +108,18 @@ class IncomingVerificationPresenter( } } - fun handleEvent(event: IncomingVerificationViewEvents) { + fun handleEvent(event: IncomingVerificationViewEvent) { Timber.d("Verification user action: ${event::class.simpleName}") when (event) { - IncomingVerificationViewEvents.StartVerification -> + IncomingVerificationViewEvent.StartVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptIncomingRequest) - IncomingVerificationViewEvents.IgnoreVerification -> + IncomingVerificationViewEvent.IgnoreVerification -> navigator.onFinish() - IncomingVerificationViewEvents.ConfirmVerification -> + IncomingVerificationViewEvent.ConfirmVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptChallenge) - IncomingVerificationViewEvents.DeclineVerification -> + IncomingVerificationViewEvent.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge) - IncomingVerificationViewEvents.GoBack -> { + IncomingVerificationViewEvent.GoBack -> { when (val verificationStep = step) { is Step.Initial -> if (verificationStep.isWaiting) { stateAndDispatch.dispatchAction(StateMachineEvent.Cancel) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt index 311fc13e74..e3663800b2 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt @@ -16,7 +16,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest data class IncomingVerificationState( val step: Step, val request: VerificationRequest.Incoming, - val eventSink: (IncomingVerificationViewEvents) -> Unit, + val eventSink: (IncomingVerificationViewEvent) -> Unit, ) { @Stable sealed interface Step { diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt index c8d70e03c0..68672be025 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStatePreviewParam.kt @@ -87,7 +87,7 @@ internal fun anIncomingUserVerificationRequest() = VerificationRequest.Incoming. internal fun anIncomingVerificationState( step: Step = aStepInitial(), verificationRequest: VerificationRequest.Incoming = anIncomingSessionVerificationRequest(), - eventSink: (IncomingVerificationViewEvents) -> Unit = {}, + eventSink: (IncomingVerificationViewEvent) -> Unit = {}, ) = IncomingVerificationState( step = step, request = verificationRequest, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt index 64adc78aa0..00b430fb53 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt @@ -62,7 +62,7 @@ fun IncomingVerificationView( val step = state.step BackHandler { - state.eventSink(IncomingVerificationViewEvents.GoBack) + state.eventSink(IncomingVerificationViewEvent.GoBack) } HeaderFooterPage( modifier = modifier, @@ -70,7 +70,7 @@ fun IncomingVerificationView( TopAppBar( title = {}, navigationIcon = { - BackButton(onClick = { state.eventSink(IncomingVerificationViewEvents.GoBack) }) + BackButton(onClick = { state.eventSink(IncomingVerificationViewEvent.GoBack) }) }, colors = topAppBarColors(containerColor = Color.Transparent), ) @@ -217,13 +217,13 @@ private fun IncomingVerificationBottomMenu( text = stringResource(CommonStrings.action_start_verification), enabled = !step.isWaiting, showProgress = step.isWaiting, - onClick = { eventSink(IncomingVerificationViewEvents.StartVerification) }, + onClick = { eventSink(IncomingVerificationViewEvent.StartVerification) }, ) TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_ignore), enabled = !step.isWaiting, - onClick = { eventSink(IncomingVerificationViewEvents.IgnoreVerification) }, + onClick = { eventSink(IncomingVerificationViewEvent.IgnoreVerification) }, ) } } @@ -235,14 +235,14 @@ private fun IncomingVerificationBottomMenu( enabled = !step.isWaiting, showProgress = step.isWaiting, onClick = { - eventSink(IncomingVerificationViewEvents.ConfirmVerification) + eventSink(IncomingVerificationViewEvent.ConfirmVerification) }, ) TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.screen_session_verification_they_dont_match), enabled = !step.isWaiting, - onClick = { eventSink(IncomingVerificationViewEvents.DeclineVerification) }, + onClick = { eventSink(IncomingVerificationViewEvent.DeclineVerification) }, ) } } @@ -254,7 +254,7 @@ private fun IncomingVerificationBottomMenu( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_done), onClick = { - eventSink(IncomingVerificationViewEvents.GoBack) + eventSink(IncomingVerificationViewEvent.GoBack) }, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvent.kt similarity index 82% rename from features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt rename to features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvent.kt index 383354860b..26a05b44cc 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvent.kt @@ -8,10 +8,10 @@ package io.element.android.features.verifysession.impl.incoming -sealed interface IncomingVerificationViewEvents { - data object GoBack : IncomingVerificationViewEvents - data object StartVerification : IncomingVerificationViewEvents - data object IgnoreVerification : IncomingVerificationViewEvents - data object ConfirmVerification : IncomingVerificationViewEvents - data object DeclineVerification : IncomingVerificationViewEvents +sealed interface IncomingVerificationViewEvent { + data object GoBack : IncomingVerificationViewEvent + data object StartVerification : IncomingVerificationViewEvent + data object IgnoreVerification : IncomingVerificationViewEvent + data object ConfirmVerification : IncomingVerificationViewEvent + data object DeclineVerification : IncomingVerificationViewEvent } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt index 90e8a3e598..5ecc500725 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt @@ -93,16 +93,16 @@ class OutgoingVerificationPresenter( observeVerificationService() } - fun handleEvent(event: OutgoingVerificationViewEvents) { + fun handleEvent(event: OutgoingVerificationViewEvent) { Timber.d("Verification user action: ${event::class.simpleName}") when (event) { // Just relay the event to the state machine - OutgoingVerificationViewEvents.RequestVerification -> StateMachineEvent.RequestVerification(verificationRequest) - OutgoingVerificationViewEvents.StartSasVerification -> StateMachineEvent.StartSasVerification - OutgoingVerificationViewEvents.ConfirmVerification -> StateMachineEvent.AcceptChallenge - OutgoingVerificationViewEvents.DeclineVerification -> StateMachineEvent.DeclineChallenge - OutgoingVerificationViewEvents.Cancel -> StateMachineEvent.Cancel - OutgoingVerificationViewEvents.Reset -> StateMachineEvent.Reset + OutgoingVerificationViewEvent.RequestVerification -> StateMachineEvent.RequestVerification(verificationRequest) + OutgoingVerificationViewEvent.StartSasVerification -> StateMachineEvent.StartSasVerification + OutgoingVerificationViewEvent.ConfirmVerification -> StateMachineEvent.AcceptChallenge + OutgoingVerificationViewEvent.DeclineVerification -> StateMachineEvent.DeclineChallenge + OutgoingVerificationViewEvent.Cancel -> StateMachineEvent.Cancel + OutgoingVerificationViewEvent.Reset -> StateMachineEvent.Reset }.let { stateMachineEvent -> stateAndDispatch.dispatchAction(stateMachineEvent) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt index 6ba8a03a7f..5f1d7aa921 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt @@ -16,7 +16,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest data class OutgoingVerificationState( val step: Step, val request: VerificationRequest.Outgoing, - val eventSink: (OutgoingVerificationViewEvents) -> Unit, + val eventSink: (OutgoingVerificationViewEvent) -> Unit, ) { @Stable sealed interface Step { diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt index c97bf2cd2e..05851437cc 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt @@ -23,9 +23,11 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.timeout +import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import com.freeletics.flowredux.dsl.State as MachineState @@ -36,6 +38,11 @@ class OutgoingVerificationStateMachine( ) : FlowReduxStateMachine( initialState = State.Initial, ) { + companion object { + /** Lifetime the Matrix specification gives a verification request; past it the other session can no longer accept ours. */ + private val VERIFICATION_REQUEST_TIMEOUT = 10.minutes + } + init { spec { inState { @@ -44,11 +51,15 @@ class OutgoingVerificationStateMachine( } } inState { - onEnterEffect { event -> - when (event.verificationRequest) { + onEnter { state -> + val verificationRequest = state.snapshot.verificationRequest + when (verificationRequest) { is VerificationRequest.Outgoing.CurrentSession -> sessionVerificationService.requestDeviceVerification() - is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(event.verificationRequest.userId) + is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(verificationRequest.userId) } + delay(VERIFICATION_REQUEST_TIMEOUT) + sessionVerificationService.cancelVerification() + state.override { State.Canceled.andLogStateChange() } } on { _, state -> state.override { State.VerificationRequestAccepted.andLogStateChange() } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStatePreviewParam.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStatePreviewParam.kt index ee3d5dd7a3..50abe14e9f 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStatePreviewParam.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStatePreviewParam.kt @@ -79,7 +79,7 @@ internal fun anOutgoingSessionVerificationRequest() = VerificationRequest.Outgoi internal fun anOutgoingVerificationState( step: Step = Step.Initial, request: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(), - eventSink: (OutgoingVerificationViewEvents) -> Unit = {}, + eventSink: (OutgoingVerificationViewEvent) -> Unit = {}, ) = OutgoingVerificationState( step = step, request = request, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt index 978ced6587..c9dc4ef45c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt @@ -65,13 +65,13 @@ fun OutgoingVerificationView( val step = state.step fun cancelOrResetFlow() { when (step) { - is Step.Canceled -> state.eventSink(OutgoingVerificationViewEvents.Reset) + is Step.Canceled -> state.eventSink(OutgoingVerificationViewEvent.Reset) Step.Initial -> onBack() Step.Completed -> onFinish() - Step.Ready, is Step.AwaitingOtherDeviceResponse -> state.eventSink(OutgoingVerificationViewEvents.Cancel) + Step.Ready, is Step.AwaitingOtherDeviceResponse -> state.eventSink(OutgoingVerificationViewEvent.Cancel) is Step.Verifying -> { if (!step.state.isLoading()) { - state.eventSink(OutgoingVerificationViewEvents.DeclineVerification) + state.eventSink(OutgoingVerificationViewEvent.DeclineVerification) } } else -> Unit @@ -261,7 +261,7 @@ private fun OutgoingVerificationBottomMenu( text = stringResource(CommonStrings.action_start_verification), enabled = !isWaiting, showProgress = isWaiting, - onClick = { eventSink(OutgoingVerificationViewEvents.RequestVerification) }, + onClick = { eventSink(OutgoingVerificationViewEvent.RequestVerification) }, ) InvisibleButton() } @@ -281,7 +281,7 @@ private fun OutgoingVerificationBottomMenu( Button( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_start), - onClick = { eventSink(OutgoingVerificationViewEvents.StartSasVerification) }, + onClick = { eventSink(OutgoingVerificationViewEvent.StartSasVerification) }, ) TextButton( modifier = Modifier.fillMaxWidth(), @@ -299,7 +299,7 @@ private fun OutgoingVerificationBottomMenu( enabled = !isVerifying, showProgress = isVerifying, onClick = { - eventSink(OutgoingVerificationViewEvents.ConfirmVerification) + eventSink(OutgoingVerificationViewEvent.ConfirmVerification) }, ) TextButton( @@ -307,7 +307,7 @@ private fun OutgoingVerificationBottomMenu( text = stringResource(R.string.screen_session_verification_they_dont_match), enabled = !isVerifying, onClick = { - eventSink(OutgoingVerificationViewEvents.DeclineVerification) + eventSink(OutgoingVerificationViewEvent.DeclineVerification) }, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvent.kt similarity index 75% rename from features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt rename to features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvent.kt index 9faab7e790..2edfa21b0c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvent.kt @@ -8,11 +8,11 @@ package io.element.android.features.verifysession.impl.outgoing -sealed interface OutgoingVerificationViewEvents { - data object RequestVerification : OutgoingVerificationViewEvents - data object StartSasVerification : OutgoingVerificationViewEvents - data object ConfirmVerification : OutgoingVerificationViewEvents - data object DeclineVerification : OutgoingVerificationViewEvents - data object Cancel : OutgoingVerificationViewEvents - data object Reset : OutgoingVerificationViewEvents +sealed interface OutgoingVerificationViewEvent { + data object RequestVerification : OutgoingVerificationViewEvent + data object StartSasVerification : OutgoingVerificationViewEvent + data object ConfirmVerification : OutgoingVerificationViewEvent + data object DeclineVerification : OutgoingVerificationViewEvent + data object Cancel : OutgoingVerificationViewEvent + data object Reset : OutgoingVerificationViewEvent } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt index a8d3802268..45d5c623c4 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt @@ -72,7 +72,7 @@ class IncomingVerificationPresenterTest { acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() // User accept the incoming verification - initialState.eventSink(IncomingVerificationViewEvents.StartVerification) + initialState.eventSink(IncomingVerificationViewEvent.StartVerification) skipItems(1) val initialWaitingState = awaitItem() assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue() @@ -96,7 +96,7 @@ class IncomingVerificationPresenterTest { ) ) // User claims that the emoji matches - emojiState.eventSink(IncomingVerificationViewEvents.ConfirmVerification) + emojiState.eventSink(IncomingVerificationViewEvent.ConfirmVerification) val emojiWaitingItem = awaitItem() assertThat((emojiWaitingItem.step as IncomingVerificationState.Step.Verifying).isWaiting).isTrue() advanceUntilIdle() @@ -141,7 +141,7 @@ class IncomingVerificationPresenterTest { acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() // User accept the incoming verification - initialState.eventSink(IncomingVerificationViewEvents.StartVerification) + initialState.eventSink(IncomingVerificationViewEvent.StartVerification) skipItems(1) val initialWaitingState = awaitItem() assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue() @@ -159,7 +159,7 @@ class IncomingVerificationPresenterTest { ) val emojiState = awaitItem() // User claims that the emojis do not match - emojiState.eventSink(IncomingVerificationViewEvents.DeclineVerification) + emojiState.eventSink(IncomingVerificationViewEvent.DeclineVerification) val emojiWaitingItem = awaitItem() assertThat((emojiWaitingItem.step as IncomingVerificationState.Step.Verifying).isWaiting).isTrue() advanceUntilIdle() @@ -241,7 +241,7 @@ class IncomingVerificationPresenterTest { acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() // User accept the incoming verification - initialState.eventSink(IncomingVerificationViewEvents.StartVerification) + initialState.eventSink(IncomingVerificationViewEvent.StartVerification) skipItems(1) val initialWaitingState = awaitItem() assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue() @@ -259,7 +259,7 @@ class IncomingVerificationPresenterTest { ) val emojiState = awaitItem() // User goes back - emojiState.eventSink(IncomingVerificationViewEvents.GoBack) + emojiState.eventSink(IncomingVerificationViewEvent.GoBack) val emojiWaitingItem = awaitItem() assertThat((emojiWaitingItem.step as IncomingVerificationState.Step.Verifying).isWaiting).isTrue() advanceUntilIdle() @@ -289,7 +289,7 @@ class IncomingVerificationPresenterTest { navigator = IncomingVerificationNavigator(navigatorLambda), ).test { val initialState = awaitItem() - initialState.eventSink(IncomingVerificationViewEvents.IgnoreVerification) + initialState.eventSink(IncomingVerificationViewEvent.IgnoreVerification) skipItems(1) navigatorLambda.assertions().isCalledOnce() } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt index 5947d1d0a5..c5c64585bc 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt @@ -27,7 +27,7 @@ class IncomingVerificationViewTest : RobolectricTest() { // region step Initial @Test fun `back key pressed - ignore the verification`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial(), @@ -35,12 +35,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } @Test fun `ignore incoming verification emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial(), @@ -48,12 +48,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_ignore) - eventsRecorder.assertSingle(IncomingVerificationViewEvents.IgnoreVerification) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.IgnoreVerification) } @Test fun `start incoming verification emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial(), @@ -61,12 +61,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_start_verification) - eventsRecorder.assertSingle(IncomingVerificationViewEvents.StartVerification) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.StartVerification) } @Test fun `back key pressed - when awaiting response cancels the verification`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = aStepInitial( @@ -76,14 +76,14 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } // endregion step Initial // region step Verifying @Test fun `back key pressed - when ready to verify cancels the verification`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( @@ -94,12 +94,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } @Test fun `back key pressed - when verifying and loading emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( @@ -110,12 +110,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } @Test fun `clicking on they do not match emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( @@ -126,12 +126,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_session_verification_they_dont_match) - eventsRecorder.assertSingle(IncomingVerificationViewEvents.DeclineVerification) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.DeclineVerification) } @Test fun `clicking on they match emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Verifying( @@ -142,14 +142,14 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_session_verification_they_match) - eventsRecorder.assertSingle(IncomingVerificationViewEvents.ConfirmVerification) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.ConfirmVerification) } // endregion // region step Failure @Test fun `back key pressed - when failure resets the flow`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Failure, @@ -157,12 +157,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } @Test fun `click on done - when failure resets the flow`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Failure, @@ -170,7 +170,7 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_done) - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } // endregion @@ -178,7 +178,7 @@ class IncomingVerificationViewTest : RobolectricTest() { // region step Completed @Test fun `back key pressed - on Completed step emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Completed, @@ -186,12 +186,12 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } @Test fun `when flow is completed and the user clicks on the done button, the expected event is emitted`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setIncomingVerificationView( anIncomingVerificationState( step = IncomingVerificationState.Step.Completed, @@ -199,7 +199,7 @@ class IncomingVerificationViewTest : RobolectricTest() { ), ) clickOn(CommonStrings.action_done) - eventsRecorder.assertSingle(IncomingVerificationViewEvents.GoBack) + eventsRecorder.assertSingle(IncomingVerificationViewEvent.GoBack) } // endregion diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt index 577683d12e..7dfacc40f1 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt @@ -31,10 +31,12 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import kotlin.time.Duration.Companion.minutes @ExperimentalCoroutinesApi class OutgoingVerificationPresenterTest { @@ -102,7 +104,7 @@ class OutgoingVerificationPresenterTest { val initialState = awaitItem() assertThat(initialState.step).isEqualTo(Step.Initial) val eventSink = initialState.eventSink - eventSink(OutgoingVerificationViewEvents.Cancel) + eventSink(OutgoingVerificationViewEvent.Cancel) assertThat(awaitItem().step).isEqualTo(Step.Exit) } @@ -118,7 +120,7 @@ class OutgoingVerificationPresenterTest { val presenter = createOutgoingVerificationPresenter(service) presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification) + state.eventSink(OutgoingVerificationViewEvent.ConfirmVerification) // Cancelling assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java) service.emitVerificationFlowState(VerificationFlowState.DidFail) @@ -134,13 +136,33 @@ class OutgoingVerificationPresenterTest { ) val presenter = createOutgoingVerificationPresenter(service) presenter.test { - awaitItem().eventSink(OutgoingVerificationViewEvents.RequestVerification) + awaitItem().eventSink(OutgoingVerificationViewEvent.RequestVerification) service.emitVerificationFlowState(VerificationFlowState.DidFail) assertThat(awaitItem().step).isInstanceOf(Step.AwaitingOtherDeviceResponse::class.java) assertThat(awaitItem().step).isEqualTo(Step.Canceled) } } + @Test + fun `present - A verification request the other session never accepts is canceled`() = runTest { + val cancelVerificationRecorder = lambdaRecorder {} + val service = unverifiedSessionService( + requestDeviceVerificationLambda = { }, + cancelVerificationLambda = cancelVerificationRecorder, + ) + val presenter = createOutgoingVerificationPresenter(service) + presenter.test { + awaitItem().eventSink(OutgoingVerificationViewEvent.RequestVerification) + advanceTimeBy(1.minutes) + assertThat(awaitItem().step).isEqualTo(Step.AwaitingOtherDeviceResponse) + advanceTimeBy(9.minutes) + expectNoEvents() + advanceTimeBy(2.minutes) + assertThat(awaitItem().step).isEqualTo(Step.Canceled) + cancelVerificationRecorder.assertions().isCalledOnce() + } + } + @Test fun `present - Canceling the flow once it's verifying cancels it`() = runTest { val service = unverifiedSessionService( @@ -151,7 +173,7 @@ class OutgoingVerificationPresenterTest { val presenter = createOutgoingVerificationPresenter(service) presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(OutgoingVerificationViewEvents.Cancel) + state.eventSink(OutgoingVerificationViewEvent.Cancel) assertThat(awaitItem().step).isEqualTo(Step.Canceled) } } @@ -181,7 +203,7 @@ class OutgoingVerificationPresenterTest { val state = requestVerificationAndAwaitVerifyingState(service) service.emitVerificationFlowState(VerificationFlowState.DidCancel) assertThat(awaitItem().step).isEqualTo(Step.Canceled) - state.eventSink(OutgoingVerificationViewEvents.Reset) + state.eventSink(OutgoingVerificationViewEvent.Reset) // Went back to initial state assertThat(awaitItem().step).isEqualTo(Step.Initial) cancelAndIgnoreRemainingEvents() @@ -204,7 +226,7 @@ class OutgoingVerificationPresenterTest { service, SessionVerificationData.Emojis(emojis) ) - state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification) + state.eventSink(OutgoingVerificationViewEvent.ConfirmVerification) assertThat(awaitItem().step).isEqualTo( Step.Verifying( SessionVerificationData.Emojis(emojis), @@ -235,7 +257,7 @@ class OutgoingVerificationPresenterTest { ) presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification) + state.eventSink(OutgoingVerificationViewEvent.ConfirmVerification) assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java) service.emitVerificationFlowState(VerificationFlowState.DidFinish) assertThat(awaitItem().step).isEqualTo(Step.Completed) @@ -261,7 +283,7 @@ class OutgoingVerificationPresenterTest { ) presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification) + state.eventSink(OutgoingVerificationViewEvent.ConfirmVerification) assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java) service.emitVerificationFlowState(VerificationFlowState.DidFinish) assertThat(awaitItem().step).isEqualTo(Step.Completed) @@ -286,7 +308,7 @@ class OutgoingVerificationPresenterTest { ) presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification) + state.eventSink(OutgoingVerificationViewEvent.ConfirmVerification) assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java) service.emitVerificationFlowState(VerificationFlowState.DidFinish) // The recovery state never leaves UNKNOWN, so the 10 seconds timeout fires and the flow completes anyway @@ -306,7 +328,7 @@ class OutgoingVerificationPresenterTest { val presenter = createOutgoingVerificationPresenter(service) presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(OutgoingVerificationViewEvents.DeclineVerification) + state.eventSink(OutgoingVerificationViewEvent.DeclineVerification) assertThat(awaitItem().step).isEqualTo( Step.Verifying( SessionVerificationData.Emojis(emptyList()), @@ -362,8 +384,8 @@ class OutgoingVerificationPresenterTest { ): OutgoingVerificationState { var state = awaitItem() assertThat(state.step).isEqualTo(Step.Initial) - state.eventSink(OutgoingVerificationViewEvents.RequestVerification) - testScope.advanceUntilIdle() + state.eventSink(OutgoingVerificationViewEvent.RequestVerification) + testScope.advanceTimeBy(1.minutes) // Await for other device response: fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest) state = awaitItem() @@ -371,7 +393,7 @@ class OutgoingVerificationPresenterTest { // Await for the state to be Ready state = awaitItem() assertThat(state.step).isEqualTo(Step.Ready) - state.eventSink(OutgoingVerificationViewEvents.StartSasVerification) + state.eventSink(OutgoingVerificationViewEvent.StartSasVerification) testScope.advanceUntilIdle() // Await for other device response (again): fakeService.emitVerificationFlowState(VerificationFlowState.DidStartSasVerification) diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt index 2b5ec63ced..a186c56f3f 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt @@ -29,7 +29,7 @@ import org.junit.Test class OutgoingVerificationViewTest : RobolectricTest() { @Test fun `back key pressed - when canceled resets the flow`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Canceled, @@ -37,12 +37,12 @@ class OutgoingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Reset) + eventsRecorder.assertSingle(OutgoingVerificationViewEvent.Reset) } @Test fun `back key pressed - when awaiting response cancels the verification`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.AwaitingOtherDeviceResponse, @@ -50,12 +50,12 @@ class OutgoingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Cancel) + eventsRecorder.assertSingle(OutgoingVerificationViewEvent.Cancel) } @Test fun `back key pressed - when ready to verify cancels the verification`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Ready, @@ -63,12 +63,12 @@ class OutgoingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(OutgoingVerificationViewEvents.Cancel) + eventsRecorder.assertSingle(OutgoingVerificationViewEvent.Cancel) } @Test fun `back key pressed - when verifying and not loading declines the verification`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( @@ -79,12 +79,12 @@ class OutgoingVerificationViewTest : RobolectricTest() { ), ) pressBackKey() - eventsRecorder.assertSingle(OutgoingVerificationViewEvents.DeclineVerification) + eventsRecorder.assertSingle(OutgoingVerificationViewEvent.DeclineVerification) } @Test fun `back key pressed - when verifying and loading does nothing`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( @@ -113,7 +113,7 @@ class OutgoingVerificationViewTest : RobolectricTest() { @Test fun `when flow is completed and the user clicks on the done button, the expected callback is invoked`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> setOutgoingVerificationView( anOutgoingVerificationState( @@ -128,7 +128,7 @@ class OutgoingVerificationViewTest : RobolectricTest() { @Test fun `clicking on they match emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( @@ -139,12 +139,12 @@ class OutgoingVerificationViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_session_verification_they_match) - eventsRecorder.assertSingle(OutgoingVerificationViewEvents.ConfirmVerification) + eventsRecorder.assertSingle(OutgoingVerificationViewEvent.ConfirmVerification) } @Test fun `clicking on they do not match emits the expected event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setOutgoingVerificationView( anOutgoingVerificationState( step = OutgoingVerificationState.Step.Verifying( @@ -155,7 +155,7 @@ class OutgoingVerificationViewTest : RobolectricTest() { ), ) clickOn(R.string.screen_session_verification_they_dont_match) - eventsRecorder.assertSingle(OutgoingVerificationViewEvents.DeclineVerification) + eventsRecorder.assertSingle(OutgoingVerificationViewEvent.DeclineVerification) } private fun AndroidComposeUiTest.setOutgoingVerificationView( diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt index 03f442f36e..add55cee36 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt @@ -16,6 +16,7 @@ import android.provider.MediaStore import androidx.annotation.RequiresApi import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.androidutils.file.saveWithUniqueFileName import io.element.android.libraries.androidutils.system.toast import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -25,6 +26,7 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File import java.io.FileOutputStream +import java.io.IOException interface FileSave { suspend fun save( @@ -61,18 +63,18 @@ class DefaultFileSave( @RequiresApi(Build.VERSION_CODES.Q) private fun saveOnDiskUsingMediaStore(path: String) { val file = File(path) - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, file.name) - put(MediaStore.MediaColumns.MIME_TYPE, MimeTypes.OctetStream) - put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) - } val resolver = context.contentResolver - val outputUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) - if (outputUri != null) { - file.inputStream().use { input -> - resolver.openOutputStream(outputUri).use { output -> - input.copyTo(output!!, DEFAULT_BUFFER_SIZE) - } + val outputUri = saveWithUniqueFileName(file.name) { displayName -> + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) + put(MediaStore.MediaColumns.MIME_TYPE, MimeTypes.OctetStream) + put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) + } + resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) + } ?: throw IOException("Unable to create the destination file") + file.inputStream().use { input -> + resolver.openOutputStream(outputUri).use { output -> + input.copyTo(output!!, DEFAULT_BUFFER_SIZE) } } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvent.kt similarity index 72% rename from features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt rename to features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvent.kt index 2cc4f3a601..2ff6faf4ef 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.viewfolder.impl.file -sealed interface ViewFileEvents { - data object SaveOnDisk : ViewFileEvents - data object Share : ViewFileEvents +sealed interface ViewFileEvent { + data object SaveOnDisk : ViewFileEvent + data object Share : ViewFileEvent } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt index dd294bd53c..571f3eb053 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt @@ -44,10 +44,10 @@ class ViewFilePresenter( val coroutineScope = rememberCoroutineScope() val colorationMode = remember { name.toColorationMode() } - fun handleEvent(event: ViewFileEvents) { + fun handleEvent(event: ViewFileEvent) { when (event) { - ViewFileEvents.Share -> coroutineScope.share(path) - ViewFileEvents.SaveOnDisk -> coroutineScope.save(path) + ViewFileEvent.Share -> coroutineScope.share(path) + ViewFileEvent.SaveOnDisk -> coroutineScope.save(path) } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt index 979e188b65..e4f9255a14 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt @@ -14,7 +14,7 @@ data class ViewFileState( val name: String, val lines: AsyncData>, val colorationMode: ColorationMode, - val eventSink: (ViewFileEvents) -> Unit, + val eventSink: (ViewFileEvent) -> Unit, ) enum class ColorationMode { diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt index 8b44f12ba8..3c4ce9674c 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt @@ -48,7 +48,7 @@ fun ViewFileView( actions = { IconButton( onClick = { - state.eventSink(ViewFileEvents.Share) + state.eventSink(ViewFileEvent.Share) }, ) { Icon( @@ -58,7 +58,7 @@ fun ViewFileView( } IconButton( onClick = { - state.eventSink(ViewFileEvents.SaveOnDisk) + state.eventSink(ViewFileEvent.SaveOnDisk) }, ) { Icon( diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/file/DefaultFileSaveTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/file/DefaultFileSaveTest.kt new file mode 100644 index 0000000000..a991a11f76 --- /dev/null +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/file/DefaultFileSaveTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026 Element Creations 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.viewfolder.impl.file + +import com.google.common.truth.Truth.assertThat +import io.element.android.tests.testutils.fake.registerFakeMediaStoreContentProvider +import io.element.android.tests.testutils.robolectric.RobolectricTest +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.robolectric.RuntimeEnvironment +import java.io.File + +class DefaultFileSaveTest : RobolectricTest() { + @Test + fun `save inserts the file in the Downloads collection under its own name`() = runTest { + val mediaStore = registerFakeMediaStoreContentProvider() + createFileSave().save(aFileToSave().path) + assertThat(mediaStore.insertedDisplayNames).containsExactly("a-file.txt") + assertThat(mediaStore.savedContent("a-file.txt")).isEqualTo("Some content") + } + + @Test + fun `save retries with a suffixed name when the name cannot be made unique`() = runTest { + val mediaStore = registerFakeMediaStoreContentProvider().apply { + conflictingDisplayNames = setOf("a-file.txt") + } + createFileSave().save(aFileToSave().path) + assertThat(mediaStore.insertedDisplayNames).hasSize(2) + assertThat(mediaStore.insertedDisplayNames.last()).matches("a-file_\\d+\\.txt") + assertThat(mediaStore.savedContent(mediaStore.insertedDisplayNames.last())).isEqualTo("Some content") + } + + @Test + fun `save writes nothing when the destination cannot be created`() = runTest { + val mediaStore = registerFakeMediaStoreContentProvider().apply { + returnsNoUriOnInsert = true + } + createFileSave().save(aFileToSave().path) + assertThat(mediaStore.insertedDisplayNames).containsExactly("a-file.txt") + assertThat(mediaStore.savedFileNames()).isEmpty() + } + + private fun aFileToSave(): File { + return File(RuntimeEnvironment.getApplication().cacheDir, "a-file.txt").apply { + writeText("Some content") + } + } + + private fun TestScope.createFileSave() = DefaultFileSave( + context = RuntimeEnvironment.getApplication(), + dispatchers = testCoroutineDispatchers(), + ) +} diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt index 1a031ade28..c3be8d84b9 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt @@ -16,7 +16,7 @@ import io.element.android.features.viewfolder.impl.file.ColorationMode import io.element.android.features.viewfolder.impl.file.FileContentReader import io.element.android.features.viewfolder.impl.file.FileSave import io.element.android.features.viewfolder.impl.file.FileShare -import io.element.android.features.viewfolder.impl.file.ViewFileEvents +import io.element.android.features.viewfolder.impl.file.ViewFileEvent import io.element.android.features.viewfolder.impl.file.ViewFilePresenter import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.test.AN_EXCEPTION @@ -86,7 +86,7 @@ class ViewFilePresenterTest { }.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink(ViewFileEvents.Share) + initialState.eventSink(ViewFileEvent.Share) assertThat(fileShare.hasBeenCalled).isTrue() assertThat(fileSave.hasBeenCalled).isFalse() } @@ -120,7 +120,7 @@ class ViewFilePresenterTest { }.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink(ViewFileEvents.SaveOnDisk) + initialState.eventSink(ViewFileEvent.SaveOnDisk) assertThat(fileShare.hasBeenCalled).isFalse() assertThat(fileSave.hasBeenCalled).isTrue() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46890d253f..395d5bfc62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -84,7 +84,7 @@ kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:34.17.0" +google_firebase_bom = "com.google.firebase:firebase-bom:34.18.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } @@ -175,7 +175,7 @@ test_truth = "com.google.truth:truth:1.4.5" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.22" test_robolectric = "org.robolectric:robolectric:4.16.1" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } -test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.9.2" +test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.9.3" test_detekt_api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version.ref = "detekt" } @@ -185,7 +185,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # All new features should not be implemented in the pull request that upgrades the version, developers should # only fix API breaks and may add some TODOs. -matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.08.19" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.08.25" # Others coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } @@ -208,7 +208,7 @@ matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } -sqlcipher = "net.zetetic:sqlcipher-android:4.17.0" +sqlcipher = "net.zetetic:sqlcipher-android:4.18.0" sqlite = "androidx.sqlite:sqlite-ktx:2.7.0" unifiedpush = "org.unifiedpush.android:connector:3.3.3" vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" @@ -222,7 +222,7 @@ maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" opusencoder = "io.element.android:opusencoder:1.2.0" zxing_cpp = "io.github.zxing-cpp:android:3.1.1" google_zxing = "com.google.zxing:core:3.5.4" -google_guava = "com.google.guava:guava:33.7.0-android" +google_guava = "com.google.guava:guava:33.7.1-android" haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" } haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt index 21306ff192..7c5f0450e9 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt @@ -42,6 +42,32 @@ fun File.safeRenameTo(dest: File) { ) } +/** + * Returns this file name with [suffix] inserted before its extension, i.e. `"image.png"` with the + * suffix `"_1"` gives `"image_1.png"`. A name without extension simply gets the suffix appended. + */ +fun String.withFileNameSuffix(suffix: String): String { + val extension = substringAfterLast('.', "") + return if (extension.isEmpty()) { + "$this$suffix" + } else { + "${substringBeforeLast('.')}$suffix.$extension" + } +} + +fun saveWithUniqueFileName( + fileName: String, + uniqueSuffix: () -> String = { "_${System.currentTimeMillis()}" }, + saveAs: (String) -> T, +): T { + return try { + saveAs(fileName) + } catch (nameConflict: IllegalStateException) { + Timber.w(nameConflict, "Unable to build a unique file name, retrying with a suffixed one") + saveAs(fileName.withFileNameSuffix(uniqueSuffix())) + } +} + fun Context.createTmpFile(baseDir: File = cacheDir, extension: String? = null): File { val suffix = extension?.let { ".$extension" } return File.createTempFile(UUID.randomUUID().toString(), suffix, baseDir).apply { mkdirs() } diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/file/FileTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/file/FileTest.kt new file mode 100644 index 0000000000..b2e28f6249 --- /dev/null +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/file/FileTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2026 Element Creations 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.androidutils.file + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class FileTest { + @Test + fun `withFileNameSuffix - inserts the suffix before the extension`() { + assertThat("image.png".withFileNameSuffix("_1")).isEqualTo("image_1.png") + } + + @Test + fun `withFileNameSuffix - appends the suffix when there is no extension`() { + assertThat("README".withFileNameSuffix("_1")).isEqualTo("README_1") + } + + @Test + fun `withFileNameSuffix - only considers the last extension`() { + assertThat("archive.tar.gz".withFileNameSuffix("_1")).isEqualTo("archive.tar_1.gz") + } + + @Test + fun `withFileNameSuffix - handles a name starting with a dot`() { + assertThat(".hidden".withFileNameSuffix("_1")).isEqualTo("_1.hidden") + } + + @Test + fun `saveWithUniqueFileName - uses the original name when it works`() { + val usedNames = mutableListOf() + + val result = saveWithUniqueFileName( + fileName = "image.png", + uniqueSuffix = { "_1" }, + ) { name -> + usedNames.add(name) + "saved as $name" + } + + assertThat(result).isEqualTo("saved as image.png") + assertThat(usedNames).containsExactly("image.png") + } + + @Test + fun `saveWithUniqueFileName - retries with a suffixed name when the name cannot be made unique`() { + val usedNames = mutableListOf() + + val result = saveWithUniqueFileName( + fileName = "image.png", + uniqueSuffix = { "_1" }, + ) { name -> + usedNames.add(name) + if (name == "image.png") { + error("Failed to build unique file") + } + "saved as $name" + } + + assertThat(result).isEqualTo("saved as image_1.png") + assertThat(usedNames).containsExactly("image.png", "image_1.png").inOrder() + } + + @Test + fun `saveWithUniqueFileName - does not retry more than once`() { + val usedNames = mutableListOf() + + val error = try { + saveWithUniqueFileName( + fileName = "image.png", + uniqueSuffix = { "_1" }, + ) { name -> + usedNames.add(name) + error("Failed to build unique file") + } + null + } catch (nameConflict: IllegalStateException) { + nameConflict + } + + assertThat(error).isInstanceOf(IllegalStateException::class.java) + assertThat(usedNames).containsExactly("image.png", "image_1.png").inOrder() + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt index 740605fe88..4188827a8a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.designsystem.atomic.atoms import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import io.element.android.compound.theme.ElementTheme @@ -20,11 +21,12 @@ fun RoomPreviewTitleAtom( title: String, modifier: Modifier = Modifier, fontStyle: FontStyle? = null, + style: TextStyle = ElementTheme.typography.fontHeadingLgBold ) { Text( modifier = modifier, text = title, - style = ElementTheme.typography.fontHeadingLgBold, + style = style, textAlign = TextAlign.Center, fontStyle = fontStyle, color = ElementTheme.colors.textPrimary, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AdaptativeTitle.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AdaptativeTitle.kt new file mode 100644 index 0000000000..f73347d67f --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AdaptativeTitle.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2026 Element Creations 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.text + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +/** + * Render the title of a screen, but adapt the style if it does not fit on a single line, + * so that a wrapped title does not look oversized. + * + * @param title the title to render. + * @param style the style to use to render the [title]. + * @param twoLinesStyle the style to use instead of [style] when the [title] does not fit on a single line with [style], + * so that a wrapped title does not look oversized. + * @param modifier the [Modifier] to apply to this layout. + */ +@Composable +fun AdaptativeTitle( + title: String, + style: TextStyle, + twoLinesStyle: TextStyle, + modifier: Modifier = Modifier, +) { + // If style and twoLinesStyle are the same, we don't need to measure the text. + if (style == twoLinesStyle) { + Text( + modifier = modifier.semantics { + heading() + }, + style = style, + maxLines = 2, + text = title, + ) + } else { + val textMeasurer = rememberTextMeasurer() + BoxWithConstraints(modifier = modifier) { + // Measure the title before rendering it, so that the style is picked in a single layout pass. + val fitsOnASingleLine = textMeasurer.measure( + text = title, + style = style, + constraints = constraints, + ).lineCount == 1 + Text( + modifier = Modifier.semantics { + heading() + }, + style = if (fitsOnASingleLine) style else twoLinesStyle, + maxLines = 2, + text = title, + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun AdaptativeTitlePreview() = ElementPreview { + @Composable + fun AdaptativeTitleItem( + text: String, + content: @Composable () -> Unit, + ) { + Column( + modifier = Modifier + .width(180.dp) + .background(ElementTheme.colors.bgSubtlePrimary), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = text, + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + ) + content() + } + } + Column( + modifier = Modifier.padding(4.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + // Fits on a single line: the large style is kept. + AdaptativeTitleItem("Adaptative, 1 line:") { + AdaptativeTitle( + title = "Chats", + style = ElementTheme.typography.fontHeadingLgBold, + twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium, + ) + } + // Would be rendered on two lines with the large style: the smaller style is used instead. + AdaptativeTitleItem("Adaptative, 2 lines:") { + AdaptativeTitle( + title = "Tutte le conversazioni:", + style = ElementTheme.typography.fontHeadingLgBold, + twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium, + ) + } + // Would be rendered on two lines with the large style: the smaller style is used instead + // and it's finally rendered on a single line with the smaller style. + AdaptativeTitleItem("Adaptative, but 1 line:") { + AdaptativeTitle( + title = "Tous les chats", + style = ElementTheme.typography.fontHeadingLgBold, + twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium, + ) + } + // Short title, with small font, 1 line. + AdaptativeTitleItem("Always small, 1 line:") { + AdaptativeTitle( + title = "My space", + style = ElementTheme.typography.fontHeadingSmMedium, + twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium, + ) + } + // Space name, which can still be rendered on two lines. + AdaptativeTitleItem("Always small, 2 lines:") { + AdaptativeTitle( + title = "A very long space name", + style = ElementTheme.typography.fontHeadingSmMedium, + twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium, + ) + } + } +} diff --git a/libraries/encrypted-db/build.gradle.kts b/libraries/encrypted-db/build.gradle.kts index a2150945a5..d48d4aefd1 100644 --- a/libraries/encrypted-db/build.gradle.kts +++ b/libraries/encrypted-db/build.gradle.kts @@ -5,6 +5,8 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ +import extension.testCommonDependencies + plugins { id("io.element.android-library") } @@ -27,4 +29,6 @@ dependencies { implementation(libs.google.tink) implementation(projects.libraries.androidutils) + + testCommonDependencies(libs) } diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt index 84ab2d2a18..0f2e954b1f 100644 --- a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/SqlCipherDriverFactory.kt @@ -51,12 +51,28 @@ class SqlCipherDriverFactory( is ClientSecret.Passphrase -> secret.formattedAsString().hexToByteArray() } val factory = SupportOpenHelperFactory(key) - return AndroidSqliteDriver(schema = schema, context = context, name = name, factory = factory, callback = object : AndroidSqliteDriver.Callback( - schema - ) { - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - onUpgradeCallback?.invoke(db, oldVersion, newVersion) - } - }) + return AndroidSqliteDriver( + schema = schema, + context = context, + name = name, + factory = factory, + callback = MigrationCallback(schema, onUpgradeCallback), + ) + } +} + +/** + * Runs [onUpgradeCallback] and then the schema migrations themselves. + * + * @param schema The SQLite DB schema, whose migrations are applied by [AndroidSqliteDriver.Callback.onUpgrade]. + * @param onUpgradeCallback Called before the migrations run, so that the database key can be replaced first. + */ +internal class MigrationCallback( + schema: SqlSchema>, + private val onUpgradeCallback: ((driver: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) -> Unit)?, +) : AndroidSqliteDriver.Callback(schema) { + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + onUpgradeCallback?.invoke(db, oldVersion, newVersion) + super.onUpgrade(db, oldVersion, newVersion) } } diff --git a/libraries/encrypted-db/src/test/kotlin/io/element/encrypteddb/MigrationCallbackTest.kt b/libraries/encrypted-db/src/test/kotlin/io/element/encrypteddb/MigrationCallbackTest.kt new file mode 100644 index 0000000000..5e16184951 --- /dev/null +++ b/libraries/encrypted-db/src/test/kotlin/io/element/encrypteddb/MigrationCallbackTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2026 Element Creations 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.encrypteddb + +import app.cash.sqldelight.db.AfterVersion +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.db.SqlSchema +import com.google.common.truth.Truth.assertThat +import io.mockk.mockk +import org.junit.Test + +class MigrationCallbackTest { + @Test + fun `onUpgrade migrates the schema`() { + val schema = RecordingSchema() + MigrationCallback(schema, onUpgradeCallback = null).onUpgrade(mockk(relaxed = true), 9, 11) + assertThat(schema.migrations).containsExactly(9L to 11L) + } + + @Test + fun `onUpgrade replaces the database key before migrating the schema`() { + val order = mutableListOf() + val schema = RecordingSchema(onMigrate = { order.add("migrate") }) + MigrationCallback(schema, onUpgradeCallback = { _, _, _ -> order.add("rekey") }) + .onUpgrade(mockk(relaxed = true), 9, 11) + assertThat(order).containsExactly("rekey", "migrate").inOrder() + } +} + +private class RecordingSchema( + private val onMigrate: () -> Unit = {}, +) : SqlSchema> { + val migrations = mutableListOf>() + + override val version = 11L + + override fun create(driver: SqlDriver) = QueryResult.Unit + + override fun migrate( + driver: SqlDriver, + oldVersion: Long, + newVersion: Long, + vararg callbacks: AfterVersion, + ): QueryResult.Value { + migrations.add(oldVersion to newVersion) + onMigrate() + return QueryResult.Unit + } +} diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt index 1311c524e4..8025591070 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatter.kt @@ -56,7 +56,7 @@ class DefaultPinnedMessagesBannerFormatter( is PollContent -> { content.question.prefixWith(CommonStrings.a11y_poll) } - RedactedContent -> { + is RedactedContent -> { sp.getString(CommonStrings.common_message_removed) } else -> { diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatter.kt index e7bbdb1e7f..bd519b60c2 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatter.kt @@ -89,7 +89,7 @@ class DefaultRoomLatestEventFormatter( ): CharSequence? { return when (content) { is MessageContent -> content.process(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) - RedactedContent -> { + is RedactedContent -> { val message = sp.getString(CommonStrings.common_message_removed) message.prefixIfNeeded(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) } diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt index ff5cce7a59..ffb98fe2ec 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -62,7 +62,7 @@ class DefaultTimelineEventFormatter( is CallNotifyContent -> { sp.getString(CommonStrings.common_call_started) } - RedactedContent, + is RedactedContent, is LegacyCallInviteContent, is StickerContent, is PollContent, diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt index 198da2bf7d..38517dab33 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt @@ -81,7 +81,7 @@ class DefaultPinnedMessagesBannerFormatterTest : RobolectricTest() { fun `Redacted content`() { val expected = "Message removed" val senderName = "Someone" - val message = createRoomEvent(false, senderName, RedactedContent) + val message = createRoomEvent(false, senderName, RedactedContent(threadInfo = null)) val result = formatter.format(message) assertThat(result).isEqualTo(expected) } diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt index ecd0509269..c293b67975 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt @@ -85,7 +85,7 @@ class DefaultRoomLatestEventFormatterTest : RobolectricTest() { val expected = "Message removed" val senderName = "Someone" sequenceOf(false, true).forEach { isDm -> - val message = createLatestEvent(false, senderName, RedactedContent) + val message = createLatestEvent(false, senderName, RedactedContent(threadInfo = null)) val result = formatter.format(message, isDm) if (isDm) { assertThat(result).isEqualTo(expected) diff --git a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt index 5e1a056a6c..de1b40e14c 100644 --- a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt +++ b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt @@ -14,11 +14,16 @@ import io.element.android.libraries.matrix.api.roomlist.LatestEventValue class FakeRoomLatestEventFormatter : RoomLatestEventFormatter { private var result: CharSequence? = null + var lastIsDmRoom: Boolean? = null + private set + override fun format(latestEvent: LatestEventValue.Local, isDmRoom: Boolean): CharSequence? { + lastIsDmRoom = isDmRoom return result } override fun format(latestEvent: LatestEventValue.Remote, isDmRoom: Boolean): CharSequence? { + lastIsDmRoom = isDmRoom return result } diff --git a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvent.kt similarity index 62% rename from libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt rename to libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvent.kt index 1c5bc4b6a0..fc39eb96a9 100644 --- a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt +++ b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvent.kt @@ -8,7 +8,7 @@ package io.element.android.libraries.fullscreenintent.api -sealed interface FullScreenIntentPermissionsEvents { - data object Dismiss : FullScreenIntentPermissionsEvents - data object OpenSettings : FullScreenIntentPermissionsEvents +sealed interface FullScreenIntentPermissionsEvent { + data object Dismiss : FullScreenIntentPermissionsEvent + data object OpenSettings : FullScreenIntentPermissionsEvent } diff --git a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt index 5b1ddb91ed..238e11bd6c 100644 --- a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt +++ b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt @@ -11,5 +11,5 @@ package io.element.android.libraries.fullscreenintent.api data class FullScreenIntentPermissionsState( val permissionGranted: Boolean, val shouldDisplayBanner: Boolean, - val eventSink: (FullScreenIntentPermissionsEvents) -> Unit, + val eventSink: (FullScreenIntentPermissionsEvent) -> Unit, ) diff --git a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt index 7248a24414..2e42908851 100644 --- a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt +++ b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt @@ -11,7 +11,7 @@ package io.element.android.libraries.fullscreenintent.api fun aFullScreenIntentPermissionsState( permissionGranted: Boolean = true, shouldDisplay: Boolean = false, - eventSink: (FullScreenIntentPermissionsEvents) -> Unit = {}, + eventSink: (FullScreenIntentPermissionsEvent) -> Unit = {}, ) = FullScreenIntentPermissionsState( permissionGranted = permissionGranted, shouldDisplayBanner = shouldDisplay, diff --git a/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt b/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt index d4a3e75d1a..7d726ccdcc 100644 --- a/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt +++ b/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt @@ -25,7 +25,7 @@ import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvents +import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvent import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher @@ -64,12 +64,12 @@ class FullScreenIntentPermissionsPresenter( val isGranted = notificationManagerCompat.canUseFullScreenIntent() val isBannerDismissed by isFullScreenIntentBannerDismissed.collectAsState(initial = true) - fun handleEvent(event: FullScreenIntentPermissionsEvents) { + fun handleEvent(event: FullScreenIntentPermissionsEvent) { when (event) { - FullScreenIntentPermissionsEvents.Dismiss -> coroutineScope.launch { + FullScreenIntentPermissionsEvent.Dismiss -> coroutineScope.launch { dismissFullScreenIntentBanner() } - FullScreenIntentPermissionsEvents.OpenSettings -> openFullScreenIntentSettings() + FullScreenIntentPermissionsEvent.OpenSettings -> openFullScreenIntentSettings() } } diff --git a/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt b/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt index 44a5579e86..08bc2ed885 100644 --- a/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt +++ b/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt @@ -16,7 +16,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvents +import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsEvent import io.element.android.libraries.fullscreenintent.impl.FullScreenIntentPermissionsPresenter import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFactory @@ -78,7 +78,7 @@ class FullScreenIntentPermissionsPresenterTest { }.test { skipItems(1) val loadedItem = awaitItem() - loadedItem.eventSink(FullScreenIntentPermissionsEvents.Dismiss) + loadedItem.eventSink(FullScreenIntentPermissionsEvent.Dismiss) runCurrent() assertThat(awaitItem().shouldDisplayBanner).isFalse() } @@ -94,7 +94,7 @@ class FullScreenIntentPermissionsPresenterTest { }.test { skipItems(1) val loadedItem = awaitItem() - loadedItem.eventSink(FullScreenIntentPermissionsEvents.OpenSettings) + loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings) launchLambda.assertions().isCalledOnce() cancelAndIgnoreRemainingEvents() } @@ -113,7 +113,7 @@ class FullScreenIntentPermissionsPresenterTest { }.test { skipItems(1) val loadedItem = awaitItem() - loadedItem.eventSink(FullScreenIntentPermissionsEvents.OpenSettings) + loadedItem.eventSink(FullScreenIntentPermissionsEvent.OpenSettings) launchLambda.assertions().isNeverCalled() cancelAndIgnoreRemainingEvents() } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt index 7bfc5dc6bb..2994683a61 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt @@ -35,6 +35,7 @@ sealed interface StateEventType { data object RoomImagePack : StateEventType data object RoomLanguage : StateEventType data object RoomPolicy : StateEventType + data object RoomRetention : StateEventType data class Custom(val type: String) : StateEventType } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index c2d388ecb0..132b3b3504 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -32,7 +32,9 @@ data class MessageContent( val type: MessageType, ) : EventContent -data object RedactedContent : EventContent +data class RedactedContent( + val threadInfo: EventThreadInfo?, +) : EventContent data class StickerContent( val filename: String, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 91703f7fa4..8a3221677d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -51,6 +51,7 @@ data class EventTimelineItem( is PollContent -> content.threadInfo is StickerContent -> content.threadInfo is UnableToDecryptContent -> content.threadInfo + is RedactedContent -> content.threadInfo else -> null } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 0d593626bb..b1d93fd903 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -99,7 +99,6 @@ class RustMatrixClientFactory( isMessageSearchAvailable = isMessageSearchAvailable, ) .homeserverUrl(sessionData.homeserverUrl) - .username(sessionData.userId) .let { (clientBuilderEnterpriseHook(RustMatrixClientBuilder(it), SessionId(sessionData.userId)) as RustMatrixClientBuilder).inner } .use { it.build() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt index 13a040525e..e64eb87570 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt @@ -39,6 +39,7 @@ fun StateEventType.map(): RustStateEventType = when (this) { StateEventType.RoomImagePack -> RustStateEventType.RoomImagePack StateEventType.RoomLanguage -> RustStateEventType.RoomLanguage StateEventType.RoomPolicy -> RustStateEventType.RoomPolicy + StateEventType.RoomRetention -> RustStateEventType.RoomRetention is StateEventType.Custom -> stateEventTypeFromString(type) } @@ -69,5 +70,6 @@ fun RustStateEventType.map(): StateEventType = when (this) { RustStateEventType.RoomImagePack -> StateEventType.RoomImagePack RustStateEventType.RoomLanguage -> StateEventType.RoomLanguage RustStateEventType.RoomPolicy -> StateEventType.RoomPolicy + RustStateEventType.RoomRetention -> StateEventType.RoomRetention is RustStateEventType.Custom -> StateEventType.Custom(this.toString()) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 98efa505d0..c8434e207a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -81,7 +81,7 @@ class TimelineEventContentMapper( ) } is MsgLikeKind.Redacted -> { - RedactedContent + RedactedContent(threadInfo = extractThreadInfo(it.content)) } is MsgLikeKind.Poll -> { PollContent( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt index 623fe9a8cf..ba010ed728 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt @@ -42,7 +42,7 @@ class FakeFfiClientBuilder( override fun setSessionDelegate(sessionDelegate: ClientSessionDelegate) = this override fun slidingSyncVersionBuilder(versionBuilder: SlidingSyncVersionBuilder) = this override fun userAgent(userAgent: String) = this - override fun username(username: String) = this + override fun serverNameFromUserId(userId: String): ClientBuilder = this override fun enableShareHistoryOnInvite(enableShareHistoryOnInvite: Boolean): ClientBuilder = this override fun threadsEnabled(enabled: Boolean, threadSubscriptions: Boolean): ClientBuilder = this override fun sqliteStore(config: SqliteStoreBuilder): ClientBuilder = this diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt index 803c9d8f42..6e57100781 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt @@ -30,6 +30,7 @@ fun aNotificationData( senderIsNameAmbiguous: Boolean = false, roomDisplayName: String? = A_ROOM_NAME, roomJoinRule: JoinRule? = null, + isNoisy: Boolean = false, ): NotificationData { return NotificationData( sessionId = A_SESSION_ID, @@ -45,7 +46,7 @@ fun aNotificationData( isDm = false, isSpace = isSpace, isEncrypted = false, - isNoisy = false, + isNoisy = isNoisy, timestamp = timestamp, content = content, hasMention = hasMention, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index b61009a5c6..da8b872ef0 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.Receipt +import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -137,6 +138,10 @@ fun aStickerContent( threadInfo = threadInfo, ) +fun aRedactedContent( + threadInfo: EventThreadInfo? = null, +) = RedactedContent(threadInfo = threadInfo) + fun aTimelineItemDebugInfo( model: String = "Rust(Model())", originalJson: String? = null, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt index 1e8fb864a7..2b5e8c6fce 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt @@ -46,7 +46,7 @@ fun SpaceHeaderRootView( ) Text( text = stringResource(CommonStrings.screen_space_list_title), - style = ElementTheme.typography.fontHeadingLgBold, + style = ElementTheme.typography.fontHeadingMdBold, color = ElementTheme.colors.textPrimary, textAlign = TextAlign.Center, ) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt index 7aee62902f..8a833cee44 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.tooling.preview.datasource.LoremIpsum import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewAliasAtom import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewDescriptionAtom import io.element.android.libraries.designsystem.atomic.atoms.RoomPreviewTitleAtom @@ -68,11 +69,15 @@ fun SpaceHeaderView( }, title = { if (name != null) { - RoomPreviewTitleAtom(title = name) + RoomPreviewTitleAtom( + title = name, + style = ElementTheme.typography.fontHeadingMdBold, + ) } else { RoomPreviewTitleAtom( title = stringResource(id = CommonStrings.common_no_space_name), - fontStyle = FontStyle.Italic + fontStyle = FontStyle.Italic, + style = ElementTheme.typography.fontHeadingMdBold, ) } }, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsPreviewParam.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsPreviewParam.kt index 1b48e0eb9b..ce9fd884eb 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsPreviewParam.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsPreviewParam.kt @@ -118,7 +118,7 @@ class InReplyToDetailsDisambiguatedPreviewParam : InReplyToDetailsPreviewParam() class InReplyToDetailsInformativePreviewParam : InReplyToDetailsPreviewParam() { override val values: Sequence get() = sequenceOf( - RedactedContent, + RedactedContent(threadInfo = null), UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null), ).map { aInReplyToDetails( diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt index 3fcb2f8f18..64e647ab19 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt @@ -656,7 +656,7 @@ class InReplyToMetadataKtTest : RobolectricTest() { fun `redacted content`() = runTest { moleculeFlow(RecompositionMode.Immediate) { anInReplyToDetailsReady( - eventContent = RedactedContent + eventContent = RedactedContent(threadInfo = null) ).metadata(hideImage = false) }.test { awaitItem().let { diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/FileViewerEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/FileViewerEntryPoint.kt new file mode 100644 index 0000000000..8f90a5802b --- /dev/null +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/FileViewerEntryPoint.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.architecture.NodeInputs + +/** + * Renders content which is not a Matrix media as a file, the same way the media viewer does. + * + * The content is provided by the caller and written to a temporary file, so that it can be + * displayed, shared and saved on disk. + */ +interface FileViewerEntryPoint : FeatureEntryPoint { + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node + + /** + * @param filename the name of the file, used as the screen title and for the shared and saved files. + * @param mimeType the mime type of [content], it drives how the content is rendered. + * @param content the whole content of the file. + */ + data class Params( + val filename: String, + val mimeType: String, + val content: String, + ) : NodeInputs + + interface Callback : Plugin { + fun onDone() + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt index 40a9a0351d..f140fc6628 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt @@ -79,7 +79,7 @@ class EventItemFactory( LegacyCallInviteContent, is PollContent, is ProfileChangeContent, - RedactedContent, + is RedactedContent, is RoomMembershipContent, is StateContent, is StickerContent, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/DefaultFileViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/DefaultFileViewerEntryPoint.kt new file mode 100644 index 0000000000..2692e8461d --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/DefaultFileViewerEntryPoint.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint + +@ContributesBinding(AppScope::class) +class DefaultFileViewerEntryPoint : FileViewerEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: FileViewerEntryPoint.Params, + callback: FileViewerEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback), + ) + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerEvent.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerEvent.kt new file mode 100644 index 0000000000..f36e1b87cf --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerEvent.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +sealed interface FileViewerEvent { + data object Share : FileViewerEvent + data object SaveOnDisk : FileViewerEvent +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerNode.kt new file mode 100644 index 0000000000..ee1b35c990 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerNode.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.viewfolder.api.TextFileViewer +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint + +@ContributesNode(AppScope::class) +@AssistedInject +class FileViewerNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: FileViewerPresenter.Factory, + private val textFileViewer: TextFileViewer, +) : Node(buildContext, plugins = plugins) { + private val callback: FileViewerEntryPoint.Callback = callback() + private val inputs = inputs() + + private val presenter = presenterFactory.create(params = inputs) + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + FileViewerView( + state = state, + textFileViewer = textFileViewer, + onBackClick = callback::onDone, + modifier = modifier, + ) + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerPresenter.kt new file mode 100644 index 0000000000..61be47ebb2 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerPresenter.kt @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import android.content.ActivityNotFoundException +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.core.net.toUri +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject +import io.element.android.libraries.androidutils.filesize.FileSizeFormatter +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState +import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor +import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import io.element.android.libraries.androidutils.R as UtilsR + +@AssistedInject +class FileViewerPresenter( + @Assisted private val params: FileViewerEntryPoint.Params, + @ApplicationContext private val context: Context, + private val coroutineDispatchers: CoroutineDispatchers, + private val fileSizeFormatter: FileSizeFormatter, + private val fileExtensionExtractor: FileExtensionExtractor, + private val localMediaActions: LocalMediaActions, +) : Presenter { + @AssistedFactory + fun interface Factory { + fun create(params: FileViewerEntryPoint.Params): FileViewerPresenter + } + + // Use a local snackbarDispatcher, because this screen can be rendered from any Node. + private val snackbarDispatcher = SnackbarDispatcher() + + @Composable + override fun present(): FileViewerState { + val coroutineScope = rememberCoroutineScope() + val localMedia = remember { mutableStateOf>(AsyncData.Uninitialized) } + val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() + + localMediaActions.Configure() + + LaunchedEffect(Unit) { + suspend { createLocalMedia() }.runCatchingUpdatingState(localMedia) + } + + fun handleEvent(event: FileViewerEvent) { + when (event) { + FileViewerEvent.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value) + FileViewerEvent.Share -> coroutineScope.share(localMedia.value) + } + } + + return FileViewerState( + filename = params.filename, + localMedia = localMedia.value, + snackbarMessage = snackbarMessage, + eventSink = ::handleEvent, + ) + } + + /** + * Write the content to a temporary file, so that it can be rendered, shared and saved on disk. + * The file is always written to the same location, so it does not need to be cleaned up. + */ + private suspend fun createLocalMedia(): LocalMedia = withContext(coroutineDispatchers.io) { + val folder = File(context.cacheDir, "temp/fileviewer").apply { mkdirs() } + val file = File(folder, params.filename) + file.writeText(params.content) + LocalMedia( + uri = file.toUri(), + info = MediaInfo( + filename = params.filename, + caption = null, + mimeType = params.mimeType, + fileSize = file.length(), + formattedFileSize = fileSizeFormatter.format(file.length()), + fileExtension = fileExtensionExtractor.extractFromName(params.filename), + senderId = null, + senderName = null, + senderAvatar = null, + dateSent = null, + dateSentFull = null, + waveform = null, + duration = null, + ), + ) + } + + private fun CoroutineScope.saveOnDisk(localMedia: AsyncData) = launch { + if (localMedia is AsyncData.Success) { + localMediaActions.saveOnDisk(localMedia.data) + .onSuccess { + snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_file_saved_on_disk_android)) + } + .onFailure { + snackbarDispatcher.post(SnackbarMessage(mediaActionsError(it))) + } + } + } + + private fun CoroutineScope.share(localMedia: AsyncData) = launch { + if (localMedia is AsyncData.Success) { + localMediaActions.share(localMedia.data) + .onFailure { + snackbarDispatcher.post(SnackbarMessage(mediaActionsError(it))) + } + } + } + + private fun mediaActionsError(throwable: Throwable): Int { + return if (throwable is ActivityNotFoundException) { + UtilsR.string.error_no_compatible_app_found + } else { + CommonStrings.error_unknown + } + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerState.kt new file mode 100644 index 0000000000..0edc198205 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerState.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.mediaviewer.api.local.LocalMedia + +data class FileViewerState( + val filename: String, + val localMedia: AsyncData, + val snackbarMessage: SnackbarMessage?, + val eventSink: (FileViewerEvent) -> Unit, +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerStatePreviewParam.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerStatePreviewParam.kt new file mode 100644 index 0000000000..3f48dbdc31 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerStatePreviewParam.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import android.net.Uri +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.local.LocalMedia + +private const val A_FILENAME = "push_rules@alice_server.org.json" + +open class FileViewerStatePreviewParam : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aFileViewerState(), + aFileViewerState(localMedia = AsyncData.Failure(Exception("A failure"))), + aFileViewerState( + localMedia = AsyncData.Success( + LocalMedia( + uri = Uri.EMPTY, + info = aJsonMediaInfo(), + ) + ) + ), + ) +} + +fun aFileViewerState( + filename: String = A_FILENAME, + localMedia: AsyncData = AsyncData.Uninitialized, + eventSink: (FileViewerEvent) -> Unit = {}, +) = FileViewerState( + filename = filename, + localMedia = localMedia, + snackbarMessage = null, + eventSink = eventSink, +) + +fun aJsonMediaInfo( + filename: String = A_FILENAME, +) = MediaInfo( + filename = filename, + caption = null, + mimeType = MimeTypes.Json, + fileSize = 2 * 1024, + formattedFileSize = "2kB", + fileExtension = "json", + senderId = null, + senderName = null, + senderAvatar = null, + dateSent = null, + dateSentFull = null, + waveform = null, + duration = null, +) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerView.kt new file mode 100644 index 0000000000..613e3af269 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerView.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalMaterial3Api::class) + +package io.element.android.libraries.mediaviewer.impl.fileviewer + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +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.viewfolder.api.TextFileViewer +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.components.async.AsyncFailure +import io.element.android.libraries.designsystem.components.async.AsyncLoading +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost +import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState +import io.element.android.libraries.mediaviewer.impl.local.LocalMediaView +import io.element.android.libraries.mediaviewer.impl.util.bgCanvasWithTransparency +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun FileViewerView( + state: FileViewerState, + textFileViewer: TextFileViewer, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) + Scaffold( + modifier = modifier, + topBar = { + FileViewerTopBar( + filename = state.filename, + actionsEnabled = state.localMedia.isSuccess(), + onBackClick = onBackClick, + onShareClick = { state.eventSink(FileViewerEvent.Share) }, + onSaveClick = { state.eventSink(FileViewerEvent.SaveOnDisk) }, + ) + }, + snackbarHost = { SnackbarHost(snackbarHostState) }, + ) { padding -> + when (val localMedia = state.localMedia) { + AsyncData.Uninitialized, + is AsyncData.Loading -> AsyncLoading( + modifier = Modifier.padding(padding), + ) + is AsyncData.Failure -> AsyncFailure( + modifier = Modifier.padding(padding), + throwable = localMedia.error, + onRetry = null, + ) + is AsyncData.Success -> { + // Do not apply the padding, the content is rendered behind the translucent top bar, + // the same way the media viewer does. + LocalMediaView( + localMedia = localMedia.data, + bottomPaddingInPixels = 0, + audioFocus = null, + onClick = {}, + onOpenWith = null, + textFileViewer = textFileViewer, + forPreview = false, + modifier = Modifier.fillMaxSize(), + ) + } + } + } +} + +@Composable +private fun FileViewerTopBar( + filename: String, + actionsEnabled: Boolean, + onBackClick: () -> Unit, + onShareClick: () -> Unit, + onSaveClick: () -> Unit, +) { + TopAppBar( + titleStr = filename, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = bgCanvasWithTransparency, + ), + navigationIcon = { BackButton(onClick = onBackClick) }, + actions = { + IconButton( + onClick = onShareClick, + enabled = actionsEnabled, + ) { + Icon( + imageVector = CompoundIcons.ShareAndroid(), + contentDescription = stringResource(id = CommonStrings.action_share), + ) + } + IconButton( + onClick = onSaveClick, + enabled = actionsEnabled, + ) { + Icon( + imageVector = CompoundIcons.Download(), + contentDescription = stringResource(id = CommonStrings.action_download), + ) + } + }, + ) +} + +@PreviewsDayNight +@Composable +internal fun FileViewerViewPreview( + @PreviewParameter(FileViewerStatePreviewParam::class) state: FileViewerState, +) = ElementPreview { + FileViewerView( + state = state, + textFileViewer = { _, _ -> }, + onBackClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt index 071e0dba64..4364020052 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt @@ -32,6 +32,7 @@ import androidx.core.content.PermissionChecker import androidx.core.net.toFile import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.androidutils.file.saveWithUniqueFileName import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -44,6 +45,7 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File import java.io.FileOutputStream +import java.io.IOException import java.io.InputStream @ContributesBinding(AppScope::class) @@ -172,18 +174,18 @@ class AndroidLocalMediaActions( @RequiresApi(Build.VERSION_CODES.Q) private fun saveOnDiskUsingMediaStore(localMedia: LocalMedia) { - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, localMedia.info.filename) - put(MediaStore.MediaColumns.MIME_TYPE, localMedia.info.mimeType) - put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) - } val resolver = context.contentResolver - val outputUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) - if (outputUri != null) { - localMedia.openStream()?.use { input -> - resolver.openOutputStream(outputUri).use { output -> - input.copyTo(output!!, DEFAULT_BUFFER_SIZE) - } + val outputUri = saveWithUniqueFileName(localMedia.info.filename) { displayName -> + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) + put(MediaStore.MediaColumns.MIME_TYPE, localMedia.info.mimeType) + put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) + } + resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) + } ?: throw IOException("Unable to create the destination file") + localMedia.openStream()?.use { input -> + resolver.openOutputStream(outputUri).use { output -> + input.copyTo(output!!, DEFAULT_BUFFER_SIZE) } } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 01b44f1246..e370361696 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -59,7 +59,7 @@ fun LocalMediaView( forPreview = forPreview, modifier = modifier, ) - mimeType == MimeTypes.PlainText -> TextFileView( + mimeType == MimeTypes.PlainText || mimeType == MimeTypes.Json -> TextFileView( localMedia = localMedia, textFileViewer = textFileViewer, modifier = modifier, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt index 4ed89e8923..4f02637a62 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessa import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherState -import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -49,6 +48,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent +import io.element.android.libraries.matrix.test.timeline.aRedactedContent import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent @@ -80,7 +80,7 @@ class EventItemFactoryTest { LegacyCallInviteContent, aPollContent(), aProfileChangeMessageContent(), - RedactedContent, + aRedactedContent(), aRoomMembershipContent( userId = A_USER_ID, ), diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/DefaultFileViewerEntryPointTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/DefaultFileViewerEntryPointTest.kt new file mode 100644 index 0000000000..2a84ad00b8 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/DefaultFileViewerEntryPointTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.testing.junit4.util.MainDispatcherRule +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint +import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions +import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.node.TestParentNode +import io.element.android.tests.testutils.testCoroutineDispatchers +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class DefaultFileViewerEntryPointTest { + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + + @Test + fun `test node builder`() = runTest { + val entryPoint = DefaultFileViewerEntryPoint() + val parentNode = TestParentNode.create { buildContext, plugins -> + FileViewerNode( + buildContext = buildContext, + plugins = plugins, + presenterFactory = { nodeParams -> + FileViewerPresenter( + params = nodeParams, + context = mockk(relaxed = true), + coroutineDispatchers = testCoroutineDispatchers(), + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), + localMediaActions = FakeLocalMediaActions(), + ) + }, + textFileViewer = { _, _ -> lambdaError() }, + ) + } + val params = FileViewerEntryPoint.Params( + filename = "a file.json", + mimeType = MimeTypes.Json, + content = "{}", + ) + val callback = object : FileViewerEntryPoint.Callback { + override fun onDone() = lambdaError() + } + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) + assertThat(result).isInstanceOf(FileViewerNode::class.java) + assertThat(result.plugins).contains(params) + assertThat(result.plugins).contains(callback) + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerPresenterTest.kt new file mode 100644 index 0000000000..7a8a85d69e --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerPresenterTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.impl.fileviewer + +import android.content.ActivityNotFoundException +import androidx.core.net.toFile +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions +import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions +import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractorWithoutValidation +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.robolectric.RobolectricTest +import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.robolectric.RuntimeEnvironment +import io.element.android.libraries.androidutils.R as UtilsR + +private const val A_FILENAME = "push_rules@alice_server.org.json" +private const val A_CONTENT = """{"global":{"override":[]}}""" + +class FileViewerPresenterTest : RobolectricTest() { + @Test + fun `present - writes the content to a file and exposes it as a local media`() = runTest { + val presenter = createFileViewerPresenter() + presenter.test { + assertThat(awaitItem().localMedia).isEqualTo(AsyncData.Uninitialized) + consumeItemsUntilPredicate { it.localMedia.isSuccess() }.last().also { state -> + assertThat(state.filename).isEqualTo(A_FILENAME) + val localMedia = state.localMedia.dataOrNull()!! + assertThat(localMedia.info.filename).isEqualTo(A_FILENAME) + assertThat(localMedia.info.mimeType).isEqualTo(MimeTypes.Json) + assertThat(localMedia.info.fileExtension).isEqualTo("json") + val file = localMedia.uri.toFile() + assertThat(file.name).isEqualTo(A_FILENAME) + assertThat(file.readText()).isEqualTo(A_CONTENT) + } + } + } + + @Test + fun `present - SaveOnDisk event saves the file and posts a snackbar message`() = runTest { + val saveOnDiskResult = lambdaRecorder> { Result.success(Unit) } + val presenter = createFileViewerPresenter( + localMediaActions = FakeLocalMediaActions(saveOnDiskResult = saveOnDiskResult), + ) + presenter.test { + consumeItemsUntilPredicate { it.localMedia.isSuccess() }.last().eventSink(FileViewerEvent.SaveOnDisk) + consumeItemsUntilPredicate { it.snackbarMessage != null }.last().also { state -> + assertThat(state.snackbarMessage?.messageResId).isEqualTo(CommonStrings.common_file_saved_on_disk_android) + } + saveOnDiskResult.assertions().isCalledOnce() + } + } + + @Test + fun `present - Share event failure posts a snackbar message`() = runTest { + val shareResult = lambdaRecorder> { Result.failure(ActivityNotFoundException()) } + val presenter = createFileViewerPresenter( + localMediaActions = FakeLocalMediaActions(shareResult = shareResult), + ) + presenter.test { + consumeItemsUntilPredicate { it.localMedia.isSuccess() }.last().eventSink(FileViewerEvent.Share) + consumeItemsUntilPredicate { it.snackbarMessage != null }.last().also { state -> + assertThat(state.snackbarMessage?.messageResId).isEqualTo(UtilsR.string.error_no_compatible_app_found) + } + shareResult.assertions().isCalledOnce() + } + } + + private fun TestScope.createFileViewerPresenter( + params: FileViewerEntryPoint.Params = FileViewerEntryPoint.Params( + filename = A_FILENAME, + mimeType = MimeTypes.Json, + content = A_CONTENT, + ), + localMediaActions: LocalMediaActions = FakeLocalMediaActions(), + ) = FileViewerPresenter( + params = params, + context = RuntimeEnvironment.getApplication(), + coroutineDispatchers = testCoroutineDispatchers(), + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), + localMediaActions = localMediaActions, + ) +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerViewTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerViewTest.kt new file mode 100644 index 0000000000..96f5de7007 --- /dev/null +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/fileviewer/FileViewerViewTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalTestApi::class) + +package io.element.android.libraries.mediaviewer.impl.fileviewer + +import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.annotation.StringRes +import androidx.compose.ui.test.AndroidComposeUiTest +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasClickAction +import androidx.compose.ui.test.hasContentDescription +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.v2.runAndroidComposeUiTest +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.mediaviewer.api.local.LocalMedia +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.robolectric.RobolectricTest +import org.junit.Test + +class FileViewerViewTest : RobolectricTest() { + @Test + fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + setFileViewerView( + state = aFileViewerState(eventSink = eventsRecorder), + onBackClick = it, + ) + pressBack() + } + } + + @Test + fun `clicking on share emits the expected event`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() + setFileViewerView( + state = aFileViewerState( + localMedia = AsyncData.Success(LocalMedia(Uri.EMPTY, aJsonMediaInfo())), + eventSink = eventsRecorder, + ), + ) + clickOnContentDescription(CommonStrings.action_share) + eventsRecorder.assertSingle(FileViewerEvent.Share) + } + + @Test + fun `clicking on download emits the expected event`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() + setFileViewerView( + state = aFileViewerState( + localMedia = AsyncData.Success(LocalMedia(Uri.EMPTY, aJsonMediaInfo())), + eventSink = eventsRecorder, + ), + ) + clickOnContentDescription(CommonStrings.action_download) + eventsRecorder.assertSingle(FileViewerEvent.SaveOnDisk) + } +} + +private fun AndroidComposeUiTest.clickOnContentDescription(@StringRes res: Int) { + val text = activity!!.getString(res) + onNode(hasContentDescription(text) and hasClickAction()).performClick() +} + +private fun AndroidComposeUiTest.setFileViewerView( + state: FileViewerState, + onBackClick: () -> Unit = EnsureNeverCalled(), +) { + setContent { + FileViewerView( + state = state, + textFileViewer = { _, _ -> }, + onBackClick = onBackClick, + ) + } +} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt index cdc5f7220d..ab1b193e26 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt @@ -12,18 +12,22 @@ import android.net.Uri import androidx.activity.compose.LocalActivityResultRegistryOwner import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext +import androidx.core.net.toUri import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia +import io.element.android.tests.testutils.fake.registerFakeMediaStoreContentProvider import io.element.android.tests.testutils.robolectric.RobolectricTest import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.robolectric.RuntimeEnvironment +import java.io.File +import java.io.IOException class AndroidLocalMediaActionsTest : RobolectricTest() { @Test @@ -62,6 +66,43 @@ class AndroidLocalMediaActionsTest : RobolectricTest() { assertThat(result.exceptionOrNull()).isNotNull() } + @Test + fun `test AndroidLocalMediaAction save on disk inserts the media under its own name`() = runTest { + val mediaStore = registerFakeMediaStoreContentProvider() + val sut = createAndroidLocalMediaActions() + val result = sut.saveOnDisk(aLocalMedia(aMediaFile().toUri())) + assertThat(result.exceptionOrNull()).isNull() + assertThat(mediaStore.insertedDisplayNames).containsExactly("an image file.jpg") + } + + @Test + fun `test AndroidLocalMediaAction save on disk retries with a suffixed name when the name cannot be made unique`() = runTest { + val mediaStore = registerFakeMediaStoreContentProvider().apply { + conflictingDisplayNames = setOf("an image file.jpg") + } + val sut = createAndroidLocalMediaActions() + val result = sut.saveOnDisk(aLocalMedia(aMediaFile().toUri())) + assertThat(result.exceptionOrNull()).isNull() + assertThat(mediaStore.insertedDisplayNames).hasSize(2) + assertThat(mediaStore.insertedDisplayNames.last()).matches("an image file_\\d+\\.jpg") + } + + @Test + fun `test AndroidLocalMediaAction save on disk fails when the destination cannot be created`() = runTest { + registerFakeMediaStoreContentProvider().apply { + returnsNoUriOnInsert = true + } + val sut = createAndroidLocalMediaActions() + val result = sut.saveOnDisk(aLocalMedia(aMediaFile().toUri())) + assertThat(result.exceptionOrNull()).isInstanceOf(IOException::class.java) + } + + private fun aMediaFile(): File { + return File(RuntimeEnvironment.getApplication().cacheDir, "an image file.jpg").apply { + writeText("Some content") + } + } + private fun TestScope.createAndroidLocalMediaActions() = AndroidLocalMediaActions( RuntimeEnvironment.getApplication(), testCoroutineDispatchers(), diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeFileViewerEntryPoint.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeFileViewerEntryPoint.kt new file mode 100644 index 0000000000..0379bff47d --- /dev/null +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeFileViewerEntryPoint.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Element Creations 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.mediaviewer.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeFileViewerEntryPoint : FileViewerEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: FileViewerEntryPoint.Params, + callback: FileViewerEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvent.kt similarity index 77% rename from libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt rename to libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvent.kt index b7a01cc61f..56e6a9caa7 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvent.kt @@ -8,7 +8,7 @@ package io.element.android.libraries.push.api.battery -sealed interface BatteryOptimizationEvents { - data object Dismiss : BatteryOptimizationEvents - data object RequestDisableOptimizations : BatteryOptimizationEvents +sealed interface BatteryOptimizationEvent { + data object Dismiss : BatteryOptimizationEvent + data object RequestDisableOptimizations : BatteryOptimizationEvent } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt index 6732f7c17d..6a938eaec1 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt @@ -10,5 +10,5 @@ package io.element.android.libraries.push.api.battery data class BatteryOptimizationState( val shouldDisplayBanner: Boolean, - val eventSink: (BatteryOptimizationEvents) -> Unit, + val eventSink: (BatteryOptimizationEvent) -> Unit, ) diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt index 684185f40a..66531fb13b 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt @@ -10,7 +10,7 @@ package io.element.android.libraries.push.api.battery fun aBatteryOptimizationState( shouldDisplayBanner: Boolean = false, - eventSink: (BatteryOptimizationEvents) -> Unit = {}, + eventSink: (BatteryOptimizationEvent) -> Unit = {}, ) = BatteryOptimizationState( shouldDisplayBanner = shouldDisplayBanner, eventSink = eventSink, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt index e9af494686..d2f421775c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt @@ -18,7 +18,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.compose.LifecycleResumeEffect import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents +import io.element.android.libraries.push.api.battery.BatteryOptimizationEvent import io.element.android.libraries.push.api.battery.BatteryOptimizationState import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.store.PushDataStore @@ -48,12 +48,12 @@ class BatteryOptimizationPresenter( onPauseOrDispose {} } - fun handleEvent(event: BatteryOptimizationEvents) { + fun handleEvent(event: BatteryOptimizationEvent) { when (event) { - BatteryOptimizationEvents.Dismiss -> coroutineScope.launch { + BatteryOptimizationEvent.Dismiss -> coroutineScope.launch { mutableBatteryOptimizationStore.onOptimizationBannerDismissed() } - BatteryOptimizationEvents.RequestDisableOptimizations -> { + BatteryOptimizationEvent.RequestDisableOptimizations -> { isRequestSent = true if (batteryOptimization.requestDisablingBatteryOptimization().not()) { // If not able to perform the request, ensure that we do not display the banner again diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index fdffad3049..e22ae1f7d1 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -298,6 +298,7 @@ class DefaultNotifiableEventResolver( roomId = roomId, eventId = eventId, cause = "Unable to decrypt event content", + noisy = isNoisy, ) ResolvedPushEvent.Event(fallbackNotifiableEvent) } 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 b6d15dac4e..6834f404ed 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 @@ -274,6 +274,12 @@ private fun NotifiableEvent.toFallbackNotifiableEvent(): FallbackNotifiableEvent is FallbackNotifiableEvent -> timestamp is NotifiableRingingCallEvent -> return null } + val noisy = when (this) { + is NotifiableMessageEvent -> noisy + is InviteNotifiableEvent -> noisy + is SimpleNotifiableEvent -> noisy + is FallbackNotifiableEvent -> noisy + } return FallbackNotifiableEvent( sessionId = sessionId, roomId = roomId, @@ -283,6 +289,7 @@ private fun NotifiableEvent.toFallbackNotifiableEvent(): FallbackNotifiableEvent canBeReplaced = false, isRedacted = false, isUpdated = false, + noisy = noisy, timestamp = timestamp, cause = null, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt index 970f19fd45..d2083f7333 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt @@ -24,6 +24,7 @@ class FallbackNotificationFactory( roomId: RoomId, eventId: EventId, cause: String?, + noisy: Boolean, ): FallbackNotifiableEvent = FallbackNotifiableEvent( sessionId = sessionId, roomId = roomId, @@ -32,6 +33,7 @@ class FallbackNotificationFactory( canBeReplaced = true, isRedacted = false, isUpdated = false, + noisy = noisy, timestamp = clock.epochMillis(), description = "", cause = cause, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt index a97937f5d0..88ba6c5ff9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResultProcessor.kt @@ -170,6 +170,7 @@ class DefaultNotificationResultProcessor( roomId = RoomId(request.roomId), eventId = EventId(request.eventId), cause = exception.message, + noisy = false, ) ) } 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 afbbe4af4f..26412abebc 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 @@ -317,7 +317,7 @@ class DefaultNotificationCreator( val fallbackNotifiableEvent = fallbackNotifiableEvents.first() val channelId = notificationChannels.getChannelIdForMessage( sessionId = fallbackNotifiableEvent.sessionId, - noisy = false, + noisy = fallbackNotifiableEvents.any { it.noisy }, ) val existingCounter = existingNotification ?.extras diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt index 97f66face0..3b3210a580 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt @@ -25,6 +25,7 @@ data class FallbackNotifiableEvent( override val canBeReplaced: Boolean, override val isRedacted: Boolean, override val isUpdated: Boolean, + val noisy: Boolean, val timestamp: Long, val cause: String?, ) : NotifiableEvent diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt index d3b2c8da2b..65bc3cf155 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt @@ -10,7 +10,7 @@ package io.element.android.libraries.push.impl.battery import androidx.lifecycle.Lifecycle import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents +import io.element.android.libraries.push.api.battery.BatteryOptimizationEvent import io.element.android.libraries.push.impl.push.FakeMutableBatteryOptimizationStore import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.store.InMemoryPushDataStore @@ -98,7 +98,7 @@ class BatteryOptimizationPresenterTest { assertThat(initialState.shouldDisplayBanner).isFalse() val displayedItem = awaitItem() assertThat(displayedItem.shouldDisplayBanner).isTrue() - displayedItem.eventSink(BatteryOptimizationEvents.Dismiss) + displayedItem.eventSink(BatteryOptimizationEvent.Dismiss) onOptimizationBannerDismissedResult.assertions().isCalledOnce() } } @@ -124,7 +124,7 @@ class BatteryOptimizationPresenterTest { assertThat(initialState.shouldDisplayBanner).isFalse() val displayedItem = awaitItem() assertThat(displayedItem.shouldDisplayBanner).isTrue() - displayedItem.eventSink(BatteryOptimizationEvents.RequestDisableOptimizations) + displayedItem.eventSink(BatteryOptimizationEvent.RequestDisableOptimizations) requestDisablingBatteryOptimizationResult.assertions().isCalledOnce() onOptimizationBannerDismissedResult.assertions().isCalledOnce() } @@ -150,7 +150,7 @@ class BatteryOptimizationPresenterTest { assertThat(initialState.shouldDisplayBanner).isFalse() val displayedItem = awaitItem() assertThat(displayedItem.shouldDisplayBanner).isTrue() - displayedItem.eventSink(BatteryOptimizationEvents.RequestDisableOptimizations) + displayedItem.eventSink(BatteryOptimizationEvent.RequestDisableOptimizations) requestDisablingBatteryOptimizationResult.assertions().isCalledOnce() batteryOptimization.isIgnoringBatteryOptimizationsResult = true lifeCycleOwner.givenState(Lifecycle.State.RESUMED) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt index 24e88e72d2..5918329c40 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt @@ -712,6 +712,41 @@ class DefaultNotifiableEventResolverTest : RobolectricTest() { canBeReplaced = true, isRedacted = false, isUpdated = false, + noisy = false, + timestamp = A_FAKE_TIMESTAMP, + cause = "Unable to decrypt event content", + ) + ) + assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult)) + } + + @Test + fun `resolve RoomEncrypted noisy`() = runTest { + val sut = createDefaultNotifiableEventResolver( + notificationResult = Result.success( + mapOf( + AN_EVENT_ID to Result.success( + aNotificationData( + content = NotificationContent.MessageLike.RoomEncrypted, + isNoisy = true, + ) + ) + ) + ) + ) + val request = aPushRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase") + val result = sut.resolveEvents(A_SESSION_ID, listOf(request)) + val expectedResult = ResolvedPushEvent.Event( + FallbackNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + editedEventId = null, + description = "", + canBeReplaced = true, + isRedacted = false, + isUpdated = false, + noisy = true, timestamp = A_FAKE_TIMESTAMP, cause = "Unable to decrypt event content", ) 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 4e2685e63a..cdc6a01fad 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 @@ -15,28 +15,35 @@ import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.test.FakeLockScreenService +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.A_ROOM_ID 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.A_THREAD_ID import io.element.android.libraries.matrix.test.A_THREAD_ID_2 +import io.element.android.libraries.matrix.test.A_TIMESTAMP 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.media.test.FakeImageLoaderHolder 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.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.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.fixtures.aFallbackNotifiableEvent 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.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.observer.SessionObserver @@ -48,6 +55,8 @@ import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.services.appnavstate.test.aNavigationState import io.element.android.services.appnavstate.test.anAppNavigationState +import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP +import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -472,6 +481,137 @@ class DefaultNotificationDrawerManagerTest { extraInvocationsForNotificationSummary = 1, ) + @Test + fun `when the app is locked, a noisy message event produces a noisy fallback notification`() = testAppLockedNotifiableEventReceived( + notifiableEvent = aNotifiableMessageEvent(noisy = true), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = A_TIMESTAMP), + ) + + @Test + fun `when the app is locked, a silent message event produces a silent fallback notification`() = testAppLockedNotifiableEventReceived( + notifiableEvent = aNotifiableMessageEvent(noisy = false), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = A_TIMESTAMP), + ) + + @Test + fun `when the app is locked, a noisy invite event produces a noisy fallback notification`() = testAppLockedNotifiableEventReceived( + notifiableEvent = anInviteNotifiableEvent(noisy = true), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = 0), + ) + + @Test + fun `when the app is locked, a silent invite event produces a silent fallback notification`() = testAppLockedNotifiableEventReceived( + notifiableEvent = anInviteNotifiableEvent(noisy = false), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = 0), + ) + + @Test + fun `when the app is locked, a noisy simple event produces a noisy fallback notification`() = testAppLockedNotifiableEventReceived( + notifiableEvent = aSimpleNotifiableEvent(noisy = true), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = 0), + ) + + @Test + fun `when the app is locked, a silent simple event produces a silent fallback notification`() = testAppLockedNotifiableEventReceived( + notifiableEvent = aSimpleNotifiableEvent(noisy = false), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = 0), + ) + + @Test + fun `when the app is locked, a noisy fallback event stays noisy`() = testAppLockedNotifiableEventReceived( + notifiableEvent = aFallbackNotifiableEvent(noisy = true), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = A_FAKE_TIMESTAMP), + ) + + @Test + fun `when the app is locked, a silent fallback event stays silent`() = testAppLockedNotifiableEventReceived( + notifiableEvent = aFallbackNotifiableEvent(noisy = false), + expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = A_FAKE_TIMESTAMP), + ) + + @Test + fun `when the app is locked, all the events are grouped into a single fallback notification`() = runTest { + val createFallbackNotificationResult = aCreateFallbackNotificationRecorder() + val sut = createAppLockedNotificationDrawerManager(createFallbackNotificationResult) + sut.onNotifiableEventsReceived( + listOf( + aNotifiableMessageEvent(noisy = false), + aNotifiableMessageEvent(eventId = AN_EVENT_ID_2, noisy = true), + ) + ) + createFallbackNotificationResult.assertions() + .isCalledOnce() + .with( + value(null), + any(), + value( + listOf( + anExpectedFallbackNotifiableEvent(noisy = false, timestamp = A_TIMESTAMP), + anExpectedFallbackNotifiableEvent(eventId = AN_EVENT_ID_2, noisy = true, timestamp = A_TIMESTAMP), + ) + ), + ) + } + + @Test + fun `when the app is not locked, no fallback notification is created`() = runTest { + val createFallbackNotificationResult = aCreateFallbackNotificationRecorder() + val sut = createDefaultNotificationDrawerManager( + appNavigationStateService = FakeAppNavigationStateService( + initialAppNavigationState = anAppNavigationState(isInForeground = false), + ), + notificationCreator = FakeNotificationCreator( + createFallbackNotificationResult = createFallbackNotificationResult, + ), + lockScreenService = FakeLockScreenService().apply { setIsPinSetup(false) }, + ) + sut.onNotifiableEventsReceived(listOf(aNotifiableMessageEvent(noisy = true))) + createFallbackNotificationResult.assertions().isNeverCalled() + } + + /** + * The content of the original event must be stripped, only the noisy flag, the ids and the timestamp are kept. + */ + private fun anExpectedFallbackNotifiableEvent( + eventId: EventId = AN_EVENT_ID, + noisy: Boolean, + timestamp: Long, + ) = aFallbackNotifiableEvent( + eventId = eventId, + description = null, + canBeReplaced = false, + noisy = noisy, + timestamp = timestamp, + cause = null, + ) + + private fun aCreateFallbackNotificationRecorder() = + lambdaRecorder, Notification> { _, _, _ -> A_NOTIFICATION } + + private fun TestScope.createAppLockedNotificationDrawerManager( + createFallbackNotificationResult: LambdaThreeParamsRecorder, Notification>, + ) = createDefaultNotificationDrawerManager( + appNavigationStateService = FakeAppNavigationStateService( + initialAppNavigationState = anAppNavigationState(isInForeground = false), + ), + notificationCreator = FakeNotificationCreator( + createFallbackNotificationResult = createFallbackNotificationResult, + ), + lockScreenService = FakeLockScreenService().apply { setIsPinSetup(true) }, + ) + + private fun testAppLockedNotifiableEventReceived( + notifiableEvent: NotifiableEvent, + expectedFallbackEvent: FallbackNotifiableEvent, + ) = runTest { + val createFallbackNotificationResult = aCreateFallbackNotificationRecorder() + val sut = createAppLockedNotificationDrawerManager(createFallbackNotificationResult) + sut.onNotifiableEventsReceived(listOf(notifiableEvent)) + createFallbackNotificationResult.assertions() + .isCalledOnce() + .with(value(null), any(), value(listOf(expectedFallbackEvent))) + } + private fun testOnNotifiableEventReceived( appNavigationState: AppNavigationState, notifiableEvents: List, @@ -503,6 +643,7 @@ class DefaultNotificationDrawerManagerTest { fun TestScope.createDefaultNotificationDrawerManager( notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), notificationRenderer: NotificationRenderer? = null, + notificationCreator: NotificationCreator = FakeNotificationCreator(), appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), @@ -519,7 +660,7 @@ fun TestScope.createDefaultNotificationDrawerManager( notificationRenderer = notificationRenderer ?: NotificationRenderer( notificationDisplayer = notificationDisplayer, notificationDataFactory = DefaultNotificationDataFactory( - notificationCreator = FakeNotificationCreator(), + notificationCreator = notificationCreator, roomGroupMessageCreator = roomGroupMessageCreator, summaryGroupMessageCreator = summaryGroupMessageCreator, activeNotificationsProvider = activeNotificationsProvider, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt index 91f29dd28e..21a2c6cb91 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationResultProcessorTest.kt @@ -267,6 +267,42 @@ class DefaultNotificationResultProcessorTest { assertThat(receivedFallbackEvent).isTrue() } + @Test + fun `when an event cannot be resolved, the generated fallback event is silent`() = runTest { + val onNotifiableEventsReceived = lambdaRecorder, Unit> {} + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } + val processor = createDefaultNotificationResultProcessor( + mutableBatteryOptimizationStore = FakeMutableBatteryOptimizationStore( + showBatteryOptimizationBannerResult = lambdaRecorder {}, + ), + pushHistoryService = FakePushHistoryService( + onPushReceivedResult = onPushReceivedResult, + ), + onNotifiableEventsReceived = onNotifiableEventsReceived, + ) + + runningProcessor(processor) { + emit(mapOf(aPushRequest() to Result.failure(NotificationResolverException.UnknownError("Unable to resolve event")))) + } + + advanceTimeBy(300.milliseconds) + + onNotifiableEventsReceived.assertions() + .isCalledOnce() + .with( + value( + listOf( + aFallbackNotifiableEvent( + description = "", + canBeReplaced = true, + noisy = false, + cause = "Unable to resolve event", + ) + ) + ) + ) + } + private suspend fun TestScope.runningProcessor(processor: NotificationResultProcessor, block: suspend NotificationResultProcessor.() -> Unit) { processor.start() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactoryTest.kt new file mode 100644 index 0000000000..58825f07b9 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactoryTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 Element Creations 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 + +import com.google.common.truth.Truth.assertThat +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.push.impl.notifications.fixtures.aFallbackNotifiableEvent +import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import org.junit.Test + +class FallbackNotificationFactoryTest { + @Test + fun `create silent event`() { + val sut = FallbackNotificationFactory(FakeSystemClock()) + val result = sut.create( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + cause = "A cause", + noisy = false, + ) + assertThat(result).isEqualTo( + aFallbackNotifiableEvent( + description = "", + canBeReplaced = true, + noisy = false, + cause = "A cause", + ) + ) + } + + @Test + fun `create noisy event`() { + val sut = FallbackNotificationFactory(FakeSystemClock()) + val result = sut.create( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + cause = "A cause", + noisy = true, + ) + assertThat(result).isEqualTo( + aFallbackNotifiableEvent( + description = "", + canBeReplaced = true, + noisy = true, + cause = "A cause", + ) + ) + } +} 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 299c5ad213..ead16c02d7 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 @@ -18,6 +18,7 @@ import io.element.android.appconfig.NotificationConfig import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.A_COLOR_INT @@ -33,6 +34,7 @@ import io.element.android.libraries.push.impl.notifications.DefaultNotificationB import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo import io.element.android.libraries.push.impl.notifications.channels.DefaultNotificationChannels +import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory @@ -47,6 +49,8 @@ import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvid import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.robolectric.RobolectricTest import kotlinx.coroutines.test.runTest import org.junit.Test @@ -82,7 +86,10 @@ class DefaultNotificationCreatorTest : RobolectricTest() { @Test fun `test createFallbackNotification`() { - val sut = createNotificationCreator() + val channelIdForMessage = lambdaRecorder { _, _ -> A_CHANNEL_ID } + val sut = createNotificationCreator( + notificationChannels = FakeNotificationChannels(channelIdForMessage = channelIdForMessage), + ) val result = sut.createFallbackNotification( existingNotification = null, notificationAccountParams = aNotificationAccountParams(), @@ -93,6 +100,50 @@ class DefaultNotificationCreatorTest : RobolectricTest() { result.commonAssertions( expectedCategory = null, ) + assertThat(result.channelId).isEqualTo(A_CHANNEL_ID) + // The notification must be posted on the silent channel. + channelIdForMessage.assertions().isCalledOnce().with(value(A_SESSION_ID), value(false)) + } + + @Test + fun `test createFallbackNotification noisy`() { + val channelIdForMessage = lambdaRecorder { _, _ -> A_CHANNEL_ID } + val sut = createNotificationCreator( + notificationChannels = FakeNotificationChannels(channelIdForMessage = channelIdForMessage), + ) + val result = sut.createFallbackNotification( + existingNotification = null, + notificationAccountParams = aNotificationAccountParams(), + fallbackNotifiableEvents = listOf( + aFallbackNotifiableEvent(noisy = true), + ) + ) + result.commonAssertions( + expectedCategory = null, + ) + assertThat(result.channelId).isEqualTo(A_CHANNEL_ID) + // The notification must be posted on the noisy channel. + channelIdForMessage.assertions().isCalledOnce().with(value(A_SESSION_ID), value(true)) + } + + @Test + fun `test createFallbackNotification is noisy when at least one event is noisy`() { + val channelIdForMessage = lambdaRecorder { _, _ -> A_CHANNEL_ID } + val sut = createNotificationCreator( + notificationChannels = FakeNotificationChannels(channelIdForMessage = channelIdForMessage), + ) + val result = sut.createFallbackNotification( + existingNotification = null, + notificationAccountParams = aNotificationAccountParams(), + fallbackNotifiableEvents = listOf( + aFallbackNotifiableEvent(noisy = false), + aFallbackNotifiableEvent(eventId = AN_EVENT_ID_2, noisy = true), + ) + ) + result.commonAssertions( + expectedCategory = null, + ) + channelIdForMessage.assertions().isCalledOnce().with(value(A_SESSION_ID), value(true)) } @Test @@ -389,6 +440,8 @@ class DefaultNotificationCreatorTest : RobolectricTest() { } } +private const val A_CHANNEL_ID = "aChannelId" + const val MARK_AS_READ_ACTION_TITLE = "MarkAsReadAction" const val QUICK_REPLY_ACTION_TITLE = "QuickReplyAction" const val ACCEPT_INVITATION_ACTION_TITLE = "AcceptInvitationAction" diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt index 4242632fe5..cb4bffcfb3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt @@ -39,13 +39,14 @@ fun aSimpleNotifiableEvent( type: String? = null, isRedacted: Boolean = false, canBeReplaced: Boolean = false, - editedEventId: EventId? = null + editedEventId: EventId? = null, + noisy: Boolean = false, ) = SimpleNotifiableEvent( sessionId = sessionId, roomId = roomId, eventId = eventId, editedEventId = editedEventId, - noisy = false, + noisy = noisy, title = "title", description = "description", type = type, @@ -59,14 +60,15 @@ fun anInviteNotifiableEvent( sessionId: SessionId = A_SESSION_ID, roomId: RoomId = A_ROOM_ID, eventId: EventId = AN_EVENT_ID, - isRedacted: Boolean = false + isRedacted: Boolean = false, + noisy: Boolean = false, ) = InviteNotifiableEvent( sessionId = sessionId, eventId = eventId, roomId = roomId, roomName = A_ROOM_NAME, editedEventId = null, - noisy = false, + noisy = noisy, title = "title", description = "description", type = null, @@ -89,11 +91,12 @@ fun aNotifiableMessageEvent( senderId: UserId = A_USER_ID_2, senderDisambiguatedDisplayName: String = A_USER_NAME_2, roomName: String? = A_ROOM_NAME, + noisy: Boolean = false, ) = NotifiableMessageEvent( sessionId = sessionId, eventId = eventId, editedEventId = null, - noisy = false, + noisy = noisy, timestamp = timestamp, senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, senderId = senderId, @@ -149,15 +152,23 @@ fun aNotifiableCallEvent( fun aFallbackNotifiableEvent( sessionId: SessionId = A_SESSION_ID, + roomId: RoomId = A_ROOM_ID, + eventId: EventId = AN_EVENT_ID, + description: String? = "A fallback notification", + canBeReplaced: Boolean = false, + noisy: Boolean = false, + timestamp: Long = A_FAKE_TIMESTAMP, + cause: String? = "Unable to decrypt event", ) = FallbackNotifiableEvent( sessionId = sessionId, - roomId = A_ROOM_ID, - eventId = AN_EVENT_ID, + roomId = roomId, + eventId = eventId, editedEventId = null, - description = "A fallback notification", - canBeReplaced = false, + description = description, + canBeReplaced = canBeReplaced, isRedacted = false, isUpdated = false, - timestamp = A_FAKE_TIMESTAMP, - cause = "Unable to decrypt event", + noisy = noisy, + timestamp = timestamp, + cause = cause, ) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvent.kt similarity index 76% rename from libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvent.kt index 94330907ea..a8ac210dd8 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvent.kt @@ -8,8 +8,8 @@ package io.element.android.libraries.troubleshoot.impl -sealed interface TroubleshootNotificationsEvents { - data object StartTests : TroubleshootNotificationsEvents - data object RetryFailedTests : TroubleshootNotificationsEvents - data class QuickFix(val testIndex: Int) : TroubleshootNotificationsEvents +sealed interface TroubleshootNotificationsEvent { + data object StartTests : TroubleshootNotificationsEvent + data object RetryFailedTests : TroubleshootNotificationsEvent + data class QuickFix(val testIndex: Int) : TroubleshootNotificationsEvent } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt index 00958b1f99..a2c3cc9a58 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt @@ -38,19 +38,19 @@ class TroubleshootNotificationsPresenter( } val testSuiteState by troubleshootTestSuite.state.collectAsState() - fun handleEvent(event: TroubleshootNotificationsEvents) { + fun handleEvent(event: TroubleshootNotificationsEvent) { when (event) { - TroubleshootNotificationsEvents.StartTests -> coroutineScope.launch { + TroubleshootNotificationsEvent.StartTests -> coroutineScope.launch { troubleshootTestSuite.runTestSuite(this) } - is TroubleshootNotificationsEvents.QuickFix -> coroutineScope.launch { + is TroubleshootNotificationsEvent.QuickFix -> coroutineScope.launch { troubleshootTestSuite.quickFix( testIndex = event.testIndex, coroutineScope = this, navigator = navigator, ) } - TroubleshootNotificationsEvents.RetryFailedTests -> coroutineScope.launch { + TroubleshootNotificationsEvent.RetryFailedTests -> coroutineScope.launch { troubleshootTestSuite.retryFailedTest(this) } } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt index 92f4d668c4..b6ce940a9f 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt @@ -10,7 +10,7 @@ package io.element.android.libraries.troubleshoot.impl data class TroubleshootNotificationsState( val testSuiteState: TroubleshootTestSuiteState, - val eventSink: (TroubleshootNotificationsEvents) -> Unit, + val eventSink: (TroubleshootNotificationsEvent) -> Unit, ) { val hasFailedTests: Boolean = testSuiteState.mainState.isFailure() } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStatePreviewParam.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStatePreviewParam.kt index 54dd40617f..1f1aef6767 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStatePreviewParam.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStatePreviewParam.kt @@ -78,7 +78,7 @@ open class TroubleshootNotificationsStatePreviewParam : PreviewParameterProvider fun aTroubleshootNotificationsState( tests: List = emptyList(), - eventSink: (TroubleshootNotificationsEvents) -> Unit = {}, + eventSink: (TroubleshootNotificationsEvent) -> Unit = {}, ) = TroubleshootNotificationsState( eventSink = eventSink, testSuiteState = TroubleshootTestSuiteState( diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt index d168cfd764..f6b085bc14 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt @@ -44,7 +44,7 @@ fun TroubleshootNotificationsView( when (event) { Lifecycle.Event.ON_RESUME -> { if (state.hasFailedTests) { - state.eventSink(TroubleshootNotificationsEvents.RetryFailedTests) + state.eventSink(TroubleshootNotificationsEvent.RetryFailedTests) } } else -> Unit @@ -129,7 +129,7 @@ private fun ColumnScope.TroubleshootNotificationsContent(state: TroubleshootNoti TestSuiteView( testSuiteState = state.testSuiteState, onQuickFixClick = { - state.eventSink(TroubleshootNotificationsEvents.QuickFix(it)) + state.eventSink(TroubleshootNotificationsEvent.QuickFix(it)) } ) } @@ -181,7 +181,7 @@ private fun RunTestButton(state: TroubleshootNotificationsState) { } ), onClick = { - state.eventSink(TroubleshootNotificationsEvents.StartTests) + state.eventSink(TroubleshootNotificationsEvent.StartTests) }, modifier = Modifier.fillMaxWidth(), ) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvent.kt similarity index 80% rename from libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvent.kt index 5116e0e101..a73c466275 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvent.kt @@ -12,9 +12,9 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId -sealed interface PushHistoryEvents { - data class SetShowOnlyErrors(val showOnlyErrors: Boolean) : PushHistoryEvents - data class Reset(val requiresConfirmation: Boolean) : PushHistoryEvents - data class NavigateTo(val sessionId: SessionId, val roomId: RoomId, val eventId: EventId) : PushHistoryEvents - data object ClearDialog : PushHistoryEvents +sealed interface PushHistoryEvent { + data class SetShowOnlyErrors(val showOnlyErrors: Boolean) : PushHistoryEvent + data class Reset(val requiresConfirmation: Boolean) : PushHistoryEvent + data class NavigateTo(val sessionId: SessionId, val roomId: RoomId, val eventId: EventId) : PushHistoryEvent + data object ClearDialog : PushHistoryEvent } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt index ad1878051d..94e220a726 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt @@ -62,12 +62,12 @@ class PushHistoryPresenter( var resetAction: AsyncAction by remember { mutableStateOf(AsyncAction.Uninitialized) } var showNotSameAccountError by remember { mutableStateOf(false) } - fun handleEvent(event: PushHistoryEvents) { + fun handleEvent(event: PushHistoryEvent) { when (event) { - is PushHistoryEvents.SetShowOnlyErrors -> { + is PushHistoryEvent.SetShowOnlyErrors -> { showOnlyErrors = event.showOnlyErrors } - is PushHistoryEvents.Reset -> { + is PushHistoryEvent.Reset -> { if (event.requiresConfirmation) { resetAction = AsyncAction.ConfirmingNoParams } else { @@ -78,11 +78,11 @@ class PushHistoryPresenter( } } } - PushHistoryEvents.ClearDialog -> { + PushHistoryEvent.ClearDialog -> { resetAction = AsyncAction.Uninitialized showNotSameAccountError = false } - is PushHistoryEvents.NavigateTo -> { + is PushHistoryEvent.NavigateTo -> { if (event.sessionId != sessionId) { showNotSameAccountError = true } else { diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt index dbc98bdbe3..7afa567aa1 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt @@ -18,5 +18,5 @@ data class PushHistoryState( val showOnlyErrors: Boolean, val resetAction: AsyncAction, val showNotSameAccountError: Boolean, - val eventSink: (PushHistoryEvents) -> Unit, + val eventSink: (PushHistoryEvent) -> Unit, ) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStatePreviewParam.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStatePreviewParam.kt index 968de5534c..cec0069237 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStatePreviewParam.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStatePreviewParam.kt @@ -53,7 +53,7 @@ fun aPushHistoryState( showOnlyErrors: Boolean = false, resetAction: AsyncAction = AsyncAction.Uninitialized, showNotSameAccountError: Boolean = false, - eventSink: (PushHistoryEvents) -> Unit = {}, + eventSink: (PushHistoryEvent) -> Unit = {}, ) = PushHistoryState( pushCounter = pushCounter, pushHistoryItems = pushHistoryItems.toImmutableList(), diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt index 7315cb4e5a..be10a2845e 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt @@ -100,14 +100,14 @@ fun PushHistoryView( }, onClick = { showMenu = false - state.eventSink(PushHistoryEvents.SetShowOnlyErrors(state.showOnlyErrors.not())) + state.eventSink(PushHistoryEvent.SetShowOnlyErrors(state.showOnlyErrors.not())) }, ) DropdownMenuItem( text = { Text(stringResource(id = CommonStrings.action_reset)) }, onClick = { showMenu = false - state.eventSink(PushHistoryEvents.Reset(requiresConfirmation = true)) + state.eventSink(PushHistoryEvent.Reset(requiresConfirmation = true)) }, ) } @@ -132,8 +132,8 @@ fun PushHistoryView( title = stringResource(CommonStrings.dialog_title_confirmation), submitText = stringResource(CommonStrings.action_reset), cancelText = stringResource(CommonStrings.action_cancel), - onSubmitClick = { state.eventSink(PushHistoryEvents.Reset(requiresConfirmation = false)) }, - onDismiss = { state.eventSink(PushHistoryEvents.ClearDialog) }, + onSubmitClick = { state.eventSink(PushHistoryEvent.Reset(requiresConfirmation = false)) }, + onDismiss = { state.eventSink(PushHistoryEvent.ClearDialog) }, ) }, onErrorDismiss = {}, @@ -142,7 +142,7 @@ fun PushHistoryView( if (state.showNotSameAccountError) { ErrorDialog( content = "Please switch account first to navigate to the event.", - onSubmit = { state.eventSink(PushHistoryEvents.ClearDialog) } + onSubmit = { state.eventSink(PushHistoryEvent.ClearDialog) } ) } } @@ -175,7 +175,7 @@ private fun PushHistoryContent( val roomId = pushHistory.roomId val eventId = pushHistory.eventId if (sessionId != null && roomId != null && eventId != null) { - state.eventSink(PushHistoryEvents.NavigateTo(sessionId, roomId, eventId)) + state.eventSink(PushHistoryEvent.NavigateTo(sessionId, roomId, eventId)) } } ) diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt index c8f8382ecf..32ab07883b 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt @@ -42,7 +42,7 @@ class TroubleshootNotificationsPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(TroubleshootNotificationsEvents.StartTests) + initialState.eventSink(TroubleshootNotificationsEvent.StartTests) skipItems(1) val stateAfterStart = awaitItem() assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) @@ -63,7 +63,7 @@ class TroubleshootNotificationsPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(TroubleshootNotificationsEvents.RetryFailedTests) + initialState.eventSink(TroubleshootNotificationsEvent.RetryFailedTests) skipItems(1) val stateAfterStart = awaitItem() assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) @@ -160,7 +160,7 @@ class TroubleshootNotificationsPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.testSuiteState.mainState).isInstanceOf(AsyncAction.Failure::class.java) - initialState.eventSink(TroubleshootNotificationsEvents.QuickFix(0)) + initialState.eventSink(TroubleshootNotificationsEvent.QuickFix(0)) val stateAfterStart = awaitItem() assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) } diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt index ea98aeabc6..b685eb2238 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt @@ -27,7 +27,7 @@ import org.robolectric.annotation.Config class TroubleshootNotificationsViewTest : RobolectricTest() { @Test fun `press menu back invokes the expected callback`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { setTroubleshootNotificationsView( state = aTroubleshootNotificationsState( @@ -41,20 +41,20 @@ class TroubleshootNotificationsViewTest : RobolectricTest() { @Test fun `clicking on run test emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setTroubleshootNotificationsView( aTroubleshootNotificationsState( eventSink = eventsRecorder ), ) onNodeWithText("Run tests").performClick() - eventsRecorder.assertSingle(TroubleshootNotificationsEvents.StartTests) + eventsRecorder.assertSingle(TroubleshootNotificationsEvent.StartTests) } @Config(qualifiers = "h1024dp") @Test fun `clicking on run test again emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setTroubleshootNotificationsView( aTroubleshootNotificationsState( tests = listOf( @@ -68,8 +68,8 @@ class TroubleshootNotificationsViewTest : RobolectricTest() { onNodeWithText("Run tests again").performClick() eventsRecorder.assertList( listOf( - TroubleshootNotificationsEvents.RetryFailedTests, - TroubleshootNotificationsEvents.StartTests, + TroubleshootNotificationsEvent.RetryFailedTests, + TroubleshootNotificationsEvent.StartTests, ) ) } @@ -77,7 +77,7 @@ class TroubleshootNotificationsViewTest : RobolectricTest() { @Config(qualifiers = "h1024dp") @Test fun `clicking on quick fix emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setTroubleshootNotificationsView( aTroubleshootNotificationsState( tests = listOf( @@ -91,8 +91,8 @@ class TroubleshootNotificationsViewTest : RobolectricTest() { onNodeWithText("Attempt to fix").performClick() eventsRecorder.assertList( listOf( - TroubleshootNotificationsEvents.RetryFailedTests, - TroubleshootNotificationsEvents.QuickFix(0), + TroubleshootNotificationsEvent.RetryFailedTests, + TroubleshootNotificationsEvent.QuickFix(0), ) ) } diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt index 15fb6b5f25..e378cc9af6 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt @@ -73,9 +73,9 @@ class PushHistoryPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(PushHistoryEvents.Reset(requiresConfirmation = true)) + initialState.eventSink(PushHistoryEvent.Reset(requiresConfirmation = true)) assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink(PushHistoryEvents.ClearDialog) + initialState.eventSink(PushHistoryEvent.ClearDialog) assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.Uninitialized) resetPushHistoryResult.assertions().isNeverCalled() } @@ -92,9 +92,9 @@ class PushHistoryPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(PushHistoryEvents.Reset(requiresConfirmation = true)) + initialState.eventSink(PushHistoryEvent.Reset(requiresConfirmation = true)) assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.ConfirmingNoParams) - initialState.eventSink(PushHistoryEvents.Reset(requiresConfirmation = false)) + initialState.eventSink(PushHistoryEvent.Reset(requiresConfirmation = false)) assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.Uninitialized) resetPushHistoryResult.assertions().isCalledOnce() @@ -115,13 +115,13 @@ class PushHistoryPresenterTest { pushService.emitPushHistoryItems(listOf(item, itemError)) awaitItem().let { state -> assertThat(state.pushHistoryItems).containsExactly(item, itemError) - state.eventSink(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = true)) + state.eventSink(PushHistoryEvent.SetShowOnlyErrors(showOnlyErrors = true)) } skipItems(1) awaitItem().let { state -> assertThat(state.showOnlyErrors).isTrue() assertThat(state.pushHistoryItems).containsExactly(itemError) - state.eventSink(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = false)) + state.eventSink(PushHistoryEvent.SetShowOnlyErrors(showOnlyErrors = false)) } skipItems(1) awaitItem().let { state -> @@ -142,7 +142,7 @@ class PushHistoryPresenterTest { presenter.test { val initialState = awaitItem() initialState.eventSink( - PushHistoryEvents.NavigateTo( + PushHistoryEvent.NavigateTo( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, eventId = AN_EVENT_ID, @@ -160,7 +160,7 @@ class PushHistoryPresenterTest { presenter.test { val initialState = awaitItem() initialState.eventSink( - PushHistoryEvents.NavigateTo( + PushHistoryEvent.NavigateTo( sessionId = A_SESSION_ID_2, roomId = A_ROOM_ID, eventId = AN_EVENT_ID, @@ -168,7 +168,7 @@ class PushHistoryPresenterTest { ) assertThat(awaitItem().showNotSameAccountError).isTrue() // Reset error - initialState.eventSink(PushHistoryEvents.ClearDialog) + initialState.eventSink(PushHistoryEvent.ClearDialog) assertThat(awaitItem().showNotSameAccountError).isFalse() } } diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt index b9f615d68e..989f7bdc10 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt @@ -30,8 +30,8 @@ import org.junit.Test class PushHistoryViewTest : RobolectricTest() { @Test - fun `clicking on Reset sends a PushHistoryEvents`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on Reset sends a PushHistoryEvent`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setPushHistoryView( aPushHistoryState( pushCounter = 123, @@ -41,14 +41,14 @@ class PushHistoryViewTest : RobolectricTest() { val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) onNodeWithContentDescription(menuContentDescription).performClick() clickOn(CommonStrings.action_reset) - eventsRecorder.assertSingle(PushHistoryEvents.Reset(requiresConfirmation = true)) + eventsRecorder.assertSingle(PushHistoryEvent.Reset(requiresConfirmation = true)) // Also check that the push counter is rendered onNodeWithText("123").assertExists() } @Test - fun `clicking on show only errors sends a PushHistoryEvents(true)`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on show only errors sends a PushHistoryEvent(true)`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setPushHistoryView( aPushHistoryState( showOnlyErrors = false, @@ -58,12 +58,12 @@ class PushHistoryViewTest : RobolectricTest() { val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) onNodeWithContentDescription(menuContentDescription).performClick() onNodeWithText("Show only errors").performClick() - eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = true)) + eventsRecorder.assertSingle(PushHistoryEvent.SetShowOnlyErrors(showOnlyErrors = true)) } @Test - fun `clicking on show only errors sends a PushHistoryEvents(false)`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + fun `clicking on show only errors sends a PushHistoryEvent(false)`() = runAndroidComposeUiTest { + val eventsRecorder = EventsRecorder() setPushHistoryView( aPushHistoryState( showOnlyErrors = true, @@ -73,12 +73,12 @@ class PushHistoryViewTest : RobolectricTest() { val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu) onNodeWithContentDescription(menuContentDescription).performClick() onNodeWithText("Show only errors").performClick() - eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = false)) + eventsRecorder.assertSingle(PushHistoryEvent.SetShowOnlyErrors(showOnlyErrors = false)) } @Test fun `clicking on an invalid event has no effect`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) setPushHistoryView( aPushHistoryState( pushHistoryItems = listOf( @@ -95,7 +95,7 @@ class PushHistoryViewTest : RobolectricTest() { @Test fun `clicking on a valid event emits the expected Event`() = runAndroidComposeUiTest { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() setPushHistoryView( aPushHistoryState( pushHistoryItems = listOf( @@ -111,7 +111,7 @@ class PushHistoryViewTest : RobolectricTest() { ) onNodeWithText(A_FORMATTED_DATE).performClick() eventsRecorder.assertSingle( - PushHistoryEvents.NavigateTo( + PushHistoryEvent.NavigateTo( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, eventId = AN_EVENT_ID, diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 89ad4a63fc..0c2885aaa1 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -45,7 +45,7 @@ private const val versionMonth = 8 * 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 = 2 +private const val versionReleaseNumber = 4 object Versions { /** diff --git a/screenshots/html/data.js b/screenshots/html/data.js index 62cea77dcf..8cf60a31ef 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,4 +1,5 @@ // Generated file, do not edit +export const buildDate = 1787667600; export const screenshots = [ ["en","en-dark","de",], ["features.messages.impl.timeline.components.event_ATimelineItemEventRow_Day_0_en","features.messages.impl.timeline.components.event_ATimelineItemEventRow_Night_0_en",20675,], diff --git a/screenshots/html/screenshots.css b/screenshots/html/screenshots.css index f946c82e81..90842b7ac9 100644 --- a/screenshots/html/screenshots.css +++ b/screenshots/html/screenshots.css @@ -150,6 +150,11 @@ input { height: 40px; } +.build-date { + margin-right: 20px; + font-size: 13px; +} + a:link { color: white; font-size: 14px; diff --git a/screenshots/html/script.js b/screenshots/html/script.js index 4a9586d591..44b2522a5d 100644 --- a/screenshots/html/script.js +++ b/screenshots/html/script.js @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -import { screenshots } from './data.js'; +import { buildDate, screenshots } from './data.js'; const URL_PARAM_LANGUAGES = "l"; const URL_PARAM_IMAGE_WIDTH = "w"; @@ -360,5 +360,13 @@ function addTable() { document.getElementById('lines').textContent = `${linesCounter} lines`; } +function addBuildDate() { + const element = document.createElement('div'); + element.className = 'build-date'; + element.textContent = `Built on ${new Date(buildDate * 1000).toLocaleDateString()}`; + document.getElementById('footer').appendChild(element); +} + addForm(); +addBuildDate(); addTable(); diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt index c0e62482fa..26f9ef50a3 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt @@ -49,12 +49,12 @@ class KonsistArchitectureTest { } @Test - fun `Events MUST be sealed interface`() { + fun `Event MUST be sealed interface`() { Konsist.scopeFromProject() .classes() .withSealedModifier() - .withNameEndingWith("Events") - .assertEmpty(additionalMessage = "Events class MUST be sealed interface") + .withNameEndingWith("Event") + .assertEmpty(additionalMessage = "Event class MUST be sealed interface") } @Test diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index 53cdbbf225..14a3f24f3c 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -103,6 +103,7 @@ class KonsistClassNameTest { "FakeImageLoader", "FakeKeyStore", "FakeListenableFuture", + "FakeMediaStoreContentProvider", "FakeParentNode", ) .assertTrue { diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt index ceb0058124..8534c50b6e 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt @@ -67,11 +67,14 @@ class KonsistImmutableTest { } @Test - fun `Immutable annotation is not used on sealed interface for Presenter Events`() { + fun `Immutable annotation is not used on sealed interface for Presenter Event`() { Konsist .scopeFromProduction() .interfaces() - .withNameEndingWith("Events") + .withoutName( + "LatestEvent", + ) + .withNameEndingWith("Event") .withAnnotationOf(Immutable::class) .assertEmpty() } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index a661beef0f..339ba1023a 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -134,6 +134,7 @@ class KonsistPreviewTest { "SecureBackupSetupViewChangePreview", "SelectedUserCannotRemovePreview", "SpaceMembersViewNoHeroesPreview", + "SyncStateViewServerUnreachablePreview", "TextComposerAddCaptionPreview", "TextComposerCaptionPreview", "TextComposerEditCaptionPreview", diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeMediaStoreContentProvider.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeMediaStoreContentProvider.kt new file mode 100644 index 0000000000..13077db51f --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeMediaStoreContentProvider.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026 Element Creations 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.tests.testutils.fake + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.net.Uri +import android.os.ParcelFileDescriptor +import android.provider.MediaStore +import androidx.test.core.app.ApplicationProvider +import org.robolectric.shadows.ShadowContentResolver +import java.io.File + +/** + * Stands in for the MediaStore provider: it records the display names an insert is attempted with, + * writes what is copied into it to a temporary directory, and can make any display name collide the + * way MediaStore does once a directory already holds 32 files with the same name. + */ +class FakeMediaStoreContentProvider : ContentProvider() { + private val directory: File = File.createTempFile("fake-media-store", null).apply { + delete() + mkdir() + } + + val insertedDisplayNames = mutableListOf() + + var conflictingDisplayNames: Set = emptySet() + + /** When true, an insert reports that it created nothing, the way a rejected insert does. */ + var returnsNoUriOnInsert: Boolean = false + + fun savedFileNames(): List = directory.list().orEmpty().toList() + + fun savedContent(displayName: String): String = File(directory, displayName).readText() + + override fun onCreate() = true + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + val displayName = values?.getAsString(MediaStore.MediaColumns.DISPLAY_NAME).orEmpty() + insertedDisplayNames += displayName + check(displayName !in conflictingDisplayNames) { "Failed to build unique file" } + return if (returnsNoUriOnInsert) null else uri.buildUpon().appendPath(displayName).build() + } + + override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor { + val file = File(directory, uri.lastPathSegment.orEmpty()) + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_CREATE or ParcelFileDescriptor.MODE_READ_WRITE) + } + + override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? = null + + override fun getType(uri: Uri): String? = null + + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?) = 0 + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?) = 0 +} + +fun registerFakeMediaStoreContentProvider(): FakeMediaStoreContentProvider { + return FakeMediaStoreContentProvider().apply { + attachInfo( + ApplicationProvider.getApplicationContext(), + ProviderInfo().apply { authority = MediaStore.AUTHORITY } + ) + ShadowContentResolver.registerProviderInternal(MediaStore.AUTHORITY, this) + } +} diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_2_en.png index 6082c07349..ae687af6c7 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:062413bedff67d6264f7c498db69e38babdfad4cc5b5c0a581f9031dcd03ae10 -size 36968 +oid sha256:fdce2c0a9c41b04870e79cc5e0413bf09ddea7907650d798cc4206ece881933f +size 9782 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_3_en.png index ad2925d791..6082c07349 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ed6c634f81fca7a0d8cf242f294d37fe44b4dd5f0832c1bb1e29df245f06546 -size 24161 +oid sha256:062413bedff67d6264f7c498db69e38babdfad4cc5b5c0a581f9031dcd03ae10 +size 36968 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_4_en.png index 5adfe35003..ad2925d791 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:965cd4692e88a413e5e0a83e16a190386e8f199716fdd7288f4e05b8c623a8b2 -size 33756 +oid sha256:8ed6c634f81fca7a0d8cf242f294d37fe44b4dd5f0832c1bb1e29df245f06546 +size 24161 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_5_en.png index bf9aed6830..5adfe35003 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:771c01d4563687aff208b0f279c9fb98192b0cd2b2ba56d43627f6c785e92b90 -size 32956 +oid sha256:965cd4692e88a413e5e0a83e16a190386e8f199716fdd7288f4e05b8c623a8b2 +size 33756 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_6_en.png new file mode 100644 index 0000000000..bf9aed6830 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:771c01d4563687aff208b0f279c9fb98192b0cd2b2ba56d43627f6c785e92b90 +size 32956 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_2_en.png index 72eca81304..5439563841 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e612d46ec9d5234d26768bf68f7f58877f9344858184bd54c6d6be893e972fa -size 34955 +oid sha256:5b11f9ba2fda961d1571b46c1bcbd7a7ccde6efb4a320ead276779c391421223 +size 8543 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_3_en.png index 8e38bbe539..72eca81304 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb9ef99adff85610ec2808bc468a5f8161db436283e51bde77fc2d6f5b61e214 -size 22502 +oid sha256:8e612d46ec9d5234d26768bf68f7f58877f9344858184bd54c6d6be893e972fa +size 34955 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_4_en.png index 75fdd8172d..8e38bbe539 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f2d01f11bcca2ba3465b6308f7368797c6fd9712bf90f4e9493b30dbece8277 -size 31907 +oid sha256:bb9ef99adff85610ec2808bc468a5f8161db436283e51bde77fc2d6f5b61e214 +size 22502 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_5_en.png index 28fd0cfc21..75fdd8172d 100644 --- a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:484173d52fcb0acff42f62d28f88cd544e205bde49370ccc8230c30b193e1959 -size 31173 +oid sha256:8f2d01f11bcca2ba3465b6308f7368797c6fd9712bf90f4e9493b30dbece8277 +size 31907 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_6_en.png new file mode 100644 index 0000000000..28fd0cfc21 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_LoggedInView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:484173d52fcb0acff42f62d28f88cd544e205bde49370ccc8230c30b193e1959 +size 31173 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_SyncStateViewServerUnreachable_Day_0_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_SyncStateViewServerUnreachable_Day_0_en.png new file mode 100644 index 0000000000..d1e672507d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_SyncStateViewServerUnreachable_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:433fd969f604ec432cc0b324109c5aa3f60abb44c692c6965731314c66377924 +size 10200 diff --git a/tests/uitests/src/test/snapshots/images/appnav.loggedin_SyncStateViewServerUnreachable_Night_0_en.png b/tests/uitests/src/test/snapshots/images/appnav.loggedin_SyncStateViewServerUnreachable_Night_0_en.png new file mode 100644 index 0000000000..95c1b90315 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/appnav.loggedin_SyncStateViewServerUnreachable_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab1c87fe0b7d5c7e5f1ccad62fba68c2a12607bad342046e58ae11abe981a383 +size 8834 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Day_0_en.png index d68c73c3dd..5f17df2b70 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32ad73a97cbf59783c6f5ba2ba216c180af3b8ff30280b712b9b936293a2ecf1 -size 22034 +oid sha256:16eff51daa024855e4843c78ac5d8f381e0ca66d7fd4102319ae74bc5298d4bd +size 22758 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Night_0_en.png index 3df4db4b5f..a381f7a081 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarMultiAccount_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13fac4328987e056ff03284d8d8814d762e8134ea8e3c33ccd95036d1c662b16 -size 20306 +oid sha256:41a251131b2d236b7e5d24730def468e9bf1861409dd1a7ef8eaf9c3b5ab2226 +size 20909 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Day_0_en.png index 690fad4569..0f3475b8ce 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:499d48e33c188e42e066e30644d2ce4d1a2516b6b26d15c385d613234777d738 -size 12105 +oid sha256:a6b9a6700cc66e74dced47f658cc8a6bff533e29e026dfada6c733180b72a19b +size 9126 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Night_0_en.png index 82f1da2494..c5e641590e 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarSpaces_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f9d7c7900c41fbc10b27a68bf079e4750c28cfdcc4d96db35a79f3606cd5796 -size 11205 +oid sha256:e4db5dc9d61a6c3e7f19d79efe8b247e91f77d04885b1b63a86682137d009023 +size 8232 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Day_0_en.png index 2a515ca368..1fd8dfd36f 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd697e25338f15e3dfeb82370f1726cc337fa74a5e65240cad22c404ec949976 -size 22485 +oid sha256:d5accca8cea12a266d137db293d48261e7bb92ae84e38167dc287ed39a34df5c +size 23207 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Night_0_en.png index 7dd2daea6e..91b11e062a 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithIndicator_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0567d8419972054d7dce9a080b856324a1bc97529ff2d15776aba6c023eb90e2 -size 20730 +oid sha256:3bc4c15cdee0758dfb100efb9c2571152940f0a7724b8e3838ef223c0bd122c8 +size 21325 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png index 4fa1c78188..54b10704ee 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd896bfc1111dcb04d94584c19a395e69612dd9ceda8beea6432c14aa4f18023 -size 23126 +oid sha256:6a38ac7b1ae8a91f08a8010d684204474e815fb967bbabe3a1080d152d802d44 +size 23742 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png index 9111455bec..a41049657f 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBarWithStatus_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:262622cd1124902ab45ccd645d33193dd8d0e4a99ba7e4d2cff786393b350118 -size 21299 +oid sha256:520288193ac12637e44d96ce99ca2f44d447699ae840674c146ec05fe41493d1 +size 21902 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Day_0_en.png index 539216dd6b..495b8b894b 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1f98cbd7fb3856f01738bca57088ee72c859aef8f09ae8f597b7769ef372ed7 -size 22144 +oid sha256:1981c2ba67db5027a3dad0a477dd33ee2947044574c2b89a55cf7972ab64b3bf +size 22863 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Night_0_en.png index 353ee965dc..2b154c8425 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.components_HomeTopBar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:038445401184aa84312acebbcc52327202ca0613a88d2667591db00b0121b287 -size 20378 +oid sha256:b31e258cd95a44e39943224285751a1b0ca51b9d490dc70479bf11bb63385dae +size 20974 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png index c69cba2125..fb9ed3f101 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09c0613d19abd7673223bc5eac12b4e87c73e66e7ff9d437c5f97ae8bcaf9303 -size 85239 +oid sha256:43b2800a5fb8014557801f35210a98752529c3c71ec285e95be1d405d2910327 +size 84602 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png index bd9fa30de2..82f5491b50 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df0dc04b3946a58dda5c3499d965ab23cd3c172eedcb8a23461201c2aca95934 -size 38713 +oid sha256:9640707e06cf5ac5b4f60305c2166b3562f072bcfa13b56b1ed8e6bdd446d24e +size 37854 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_2_en.png index 03d7d97186..be9847ac64 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecd79d3f8c4efbb54efa6a5b8dee79274ae9957b01146025dab42a45fc328fc0 -size 24740 +oid sha256:e3f09104202b0d1ba857ef376c32733bb598bebe1dbb47fd2765f7bbf5f756eb +size 22218 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png index 8ec9b1585e..f1a441a0b3 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e28f120a919db7c7a0ced5ca0d56239197785a0ecfbd37af0c5dacb5f8316c1f -size 83583 +oid sha256:330103af450379bc1ea3bd9815779324384d6025a68ba8e3bb9a76b2565fa2dc +size 82909 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png index 72e173c493..29f23a6b80 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc37ee6efade5947d0fdc25f6925cf45ba55442eb3fb715f942178513a00e8fc -size 37416 +oid sha256:8d6216bd19d1fae4b5cb70aefe2f3959a18d08f0e1f0ae82e174c10427df400f +size 36596 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_2_en.png index 049aa93a8a..3a37cae365 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fd37517b8913e2bac3275f717aa15bed18055a7525e0404b25b43cb8bd8422d -size 23840 +oid sha256:037171836a783617d4c6907e73e4738c38bdef6c328302a14f8dcb5535105e41 +size 21553 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png index aca66273b4..fbb9365c88 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeViewA11y_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9cf305c0e52881fb40fee2ba7bdcdc3dfa5c899577b3e252af19e37ee2efacf7 -size 143213 +oid sha256:610593d1acf3fa39f937f787a5e6531c3dd0a2262bc1abf63b8489f227917ba5 +size 144141 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png index b5f80f2faf..091b77a218 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8247dedc9811f2b325568973a2b714cd4b46322bcd0bf89af0e662518fa77747 -size 67694 +oid sha256:265d1fd77f393724a26a410e343b1dde4ce8e986db16ba00bb07def24aa52139 +size 68363 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png index f106244405..e2b3622d86 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c393b13532fba4d4ef25dda45b6ca3e1f6da6d49c5b2de412beded261a4f9311 -size 36164 +oid sha256:3c3e85ad3d267db2fa0964b4d28ef9fd50ce4989259646d5d07bdea7e24c7a4c +size 36872 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png index f2a69ea475..48b80d3c0c 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b493bc5151f15d6ba91bfb0c27b41f3abcf13ee7d511aa59db171cecdbd95f3 -size 31958 +oid sha256:3c3580427877cca8f59538bccd47ee19f57b4bdce28d60135da7de185e17f8f1 +size 32636 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png index f4dff45422..80e9263c7e 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a33681fe064d5e12f53273bfd9262af8c7b902e237cb8d0641573b0645e291c -size 94687 +oid sha256:7758cf4023fa542f29fd8ade54bab1f2707666c640ad62ed8a6805c8ec2256a3 +size 95336 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png index bdad242551..2d5574b88a 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:022954bdca89572b74bc70f6d91820e99eef8826409c0a06e6145a5c3ec8eaf4 -size 87268 +oid sha256:60eec8085688c228b28cef3f1922b984f2cdc54a9207ea04c27c44900fb4d7ee +size 87932 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png index edf07a66f3..0f310c78a8 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af36d0e3383935d256b3730e6f955e858ae42a82f54485b879e7a6a71fc25f6c -size 55237 +oid sha256:0f2280adb5d99a20fbe98b8d097e97c2de0c05a083382a905075bfb5795a2290 +size 55890 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png index aa313049f5..17ff86bf54 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e55407ac7116b3127a979f630777d0d528a349c38d37829d8bc0c37a316c7c5b -size 42526 +oid sha256:2e2d1b5923dd2941bd7e60a9019a7770ed3383529ad9a8f3572c9e3d450310e0 +size 37461 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png index b5f80f2faf..091b77a218 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8247dedc9811f2b325568973a2b714cd4b46322bcd0bf89af0e662518fa77747 -size 67694 +oid sha256:265d1fd77f393724a26a410e343b1dde4ce8e986db16ba00bb07def24aa52139 +size 68363 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png index b5f80f2faf..091b77a218 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8247dedc9811f2b325568973a2b714cd4b46322bcd0bf89af0e662518fa77747 -size 67694 +oid sha256:265d1fd77f393724a26a410e343b1dde4ce8e986db16ba00bb07def24aa52139 +size 68363 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png index e01335907a..f4db0cfcf2 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25e5ac35ddf79fdc78c21dbd031f506da04caac7bdd53f279c88fd4574f72a82 -size 64644 +oid sha256:564360123154706aceed6daf43f33c96c7e3c49b787222ecc236aabb1349e3a1 +size 65323 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png index fe41a54fcc..9eb0a6a43d 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c1245da0a4cf56aba8e102e7f297996ed58fc8c50165288dea11ca86fbc0dcc -size 57235 +oid sha256:0227ce3fa0664ff4a99649532683837835733d1bb26dcd05b7f6277964b5115c +size 54342 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png index b5f80f2faf..091b77a218 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8247dedc9811f2b325568973a2b714cd4b46322bcd0bf89af0e662518fa77747 -size 67694 +oid sha256:265d1fd77f393724a26a410e343b1dde4ce8e986db16ba00bb07def24aa52139 +size 68363 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png index 0894037dc4..c301dfd550 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e389bce27c241cc13f60c247fea578fa4cd1ecc7448c9bf3ac3af1179e693216 -size 54701 +oid sha256:de11af4e521b9ea166dd422b94e1a6405753a8c00a9b637c76dd3b0e14774294 +size 55355 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png index c4630c3e45..05b71c486e 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d56802b3e6dd21b20c4e92d47d9b880085f499feac855a46af601a5eb0bfc2f -size 54510 +oid sha256:6596062bc9bcbd81f51e1c7118426ec389c3bcae65df0793754c1a319399998f +size 55159 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png index ce7b5ea699..85ef4f3937 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef1246daff44b29ee4c2b50538a3a948c2593749c70e15e053b8258a377c5756 -size 52745 +oid sha256:af598c1be693235acf29785c487fe69f0bdfc32408bddc948be78bc5fd8ce558 +size 53394 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png index b6779aca93..f3460a5dd4 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0769e243dc60de52c16df23a86eb62167bb60cb756d113f88b9b476c025e402b -size 87117 +oid sha256:d2151f61b19e6d1710c9b9074abbcf2a927fca510cc6d3ef08746dcc4cf8e14a +size 87791 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png index fe171e26b7..226f054223 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a7b2a6ccb43dc572140e2ce658967cd273d1122bf0216269a648920099a68d0 -size 62572 +oid sha256:a4c896b9025d301961b134c22ec1af858cec8a7b0eb1d823936f5af3cad38271 +size 63094 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png index 420cb7c631..fc6b74a2f6 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d56c1af9fc73f4aaeed33dd8b6113ec64ddbfdf63506aa31ae6996732870baa2 -size 31127 +oid sha256:588859e742ca76818510abb9326969da5c227d0946ea6aac9de216e1a62a7d6a +size 31657 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png index f36b5c2f83..6f2a17627d 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c3fb8ae530989e5a6d6e4193fbac4d4356af34076b684b782a0f8fec31226f1 -size 26657 +oid sha256:827036f5c685629037b5bb7ae3265b3261a52ab3d8b912344cd06137839512be +size 27266 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png index 48b07fc9bd..d905171a03 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d302be6dbaef08b501f4f33d3e01338e068767702a7018bd5bb24ade6eee2cb -size 88558 +oid sha256:3691f9a0e8e90001011735c7496cc977995347eee2333d0f9bb37bd57b488fb8 +size 89113 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png index 5cb7d766cd..a4ffe24031 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_14_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6be5cd35eec917bf1c96d03079df8235d0bc7b52159f54c8a177a64f648e58c2 -size 81212 +oid sha256:649be03a09cc9f34c0d59f035cdf26096b54b04d9dcb06f53882aae7a6c098cb +size 81789 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png index 2691d1c1df..2e8b7314f6 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66d241a4270bef0a1f80c62c302cb5b40b4370f73e8175a746d32c90d1f98208 -size 49882 +oid sha256:422c10006716ea99ae5d3222334f4fe74a002e42fa2ab8b7890dff31a60ca8f7 +size 50444 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png index 451ca0c3ec..a26f43f289 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_16_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1838a9839badf62d4d427a66a15a7d220996bdb0b60f0373249d62e4afae2910 -size 37390 +oid sha256:858ed6993ba8d322e9acc40ca3e759372e19cd3aae9854709f374c0c8d1c4429 +size 32549 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png index fe171e26b7..226f054223 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a7b2a6ccb43dc572140e2ce658967cd273d1122bf0216269a648920099a68d0 -size 62572 +oid sha256:a4c896b9025d301961b134c22ec1af858cec8a7b0eb1d823936f5af3cad38271 +size 63094 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png index fe171e26b7..226f054223 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a7b2a6ccb43dc572140e2ce658967cd273d1122bf0216269a648920099a68d0 -size 62572 +oid sha256:a4c896b9025d301961b134c22ec1af858cec8a7b0eb1d823936f5af3cad38271 +size 63094 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png index 89c4934dd9..b8280a1e69 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8172ca867a8c2e827ead36a22cefe967a5b57c69cae6d5aca1c941e4dd2ba16f -size 60160 +oid sha256:6329073993661030954c535849498a33dc3a95700ef3b1fae9f80cf0b5e6ffaa +size 60728 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png index 390c9f6818..95f3da33c4 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13740b4fc4cbd24075606e45bf070a8fbb762d298857bc071399def91dda7175 -size 52189 +oid sha256:a04f8865034c36f32f0f7dd098fd6bf3e80f96869036f965c755a7a48286a071 +size 49232 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png index fe171e26b7..226f054223 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a7b2a6ccb43dc572140e2ce658967cd273d1122bf0216269a648920099a68d0 -size 62572 +oid sha256:a4c896b9025d301961b134c22ec1af858cec8a7b0eb1d823936f5af3cad38271 +size 63094 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png index 0950a6881e..75d8b98f01 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:842d5e0a5cc1102a2e320b43ea5ffda6cf63e94acdbf42adc39edaa1fb355812 -size 51742 +oid sha256:18d684eb1d4bbb7c3eb5de88a5123a60ff66ae625809761ae5f3624e443961c4 +size 52297 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png index b29ccd3a11..0c77cb1a94 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82b75ce20a535cf1062c4b3ad86d08dc9044cf58d8035a5fdc68c48119157aae -size 51477 +oid sha256:6d4567aa39bf44dbf66c8c81f9195f0520364b1478b0bf9ef499923e0c267b9f +size 52015 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png index 1b1e2edb98..f9b5ae643f 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bde8e9c5618ffc2b3019ce10db6fca44e4f23f3b987c222b7e838beb0dcb488 -size 49749 +oid sha256:729160a038512d10dbce06e6bdf8db15a2aca6b22624a096ade6687ca9c8b7de +size 50335 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png index 1092dcd1b9..3ae5a4a153 100644 --- a/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.home.impl_HomeView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bce0bc8590037705369735e83e576f0818f4528298b4b2face999c8802f855b9 -size 81081 +oid sha256:76035b0f36179e93f4e35b9c59d51acee15e7db9d02249ee102656da88e04341 +size 81635 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_3_en.png new file mode 100644 index 0000000000..da05331f32 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a5a8e393b6c77066e69b25e41a82850f86d0b8b283cecc7f7c8416d612974d7 +size 43097 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_3_en.png new file mode 100644 index 0000000000..0fe722a849 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81cffb805bfeda8d0ce58f6c54fb298cc2b35d0ce210f514fd7ccb2fa23ab82f +size 40978 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png index 2c15fd6a37..82f344f714 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7260abe16fc2ab4f6ada97ea0f756dc5c9908ff3b61d6ec1723ffde302bb3dc5 -size 11955 +oid sha256:8d96eefa01a5de803b0cc9d9109c20df86eaff01b73ff24a91945b43d53d5d01 +size 11960 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png index 7c422591ad..fdd8fcd5a5 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e16fc1daa0f284c442f66591e1d1f59161272a67998d26740b0b403bfe82495 -size 11866 +oid sha256:22678a2a8505e1d9d5bfcb44459b6fac69c11a4bf72fae09c48ffa2e54f5a110 +size 12128 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png index acb8afa74a..f6e27475da 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bed7565420e11c43acc06896ef298683d2f2dd1dc16c430eab7018e9ead9937a -size 11652 +oid sha256:9ee89826c8aa2ac0371ed31959243f62bf9968806a7b27117cd7b4d849423a33 +size 11667 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png index e0a4cb1c97..b1bb69efb8 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.userstatus_UserStatusView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:87811df502c760816e74beb006341627857c39380ce715f1a747ac9fce77b226 -size 11516 +oid sha256:e27cb9e501ccbac54926f0252b4098867643a2d2683c5b267f86ad99edf6f5d7 +size 11817 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png index 5c597300e4..73b8b8e50c 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 -size 3853 +oid sha256:e8a3ad99763929d2783147814e44fa9ec7e7efb7b99949e3580f23c38fb5d971 +size 9398 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png index f8a1f24b34..5c597300e4 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1342b6427f5b2e3075ae3ccee60bf1d3b11afaccf3e152643d6ef2d65c21b54e -size 9108 +oid sha256:21aee17ee9a292bb2f0d3d2ec2db565ebb8d5df753454a5a002f033ed5ed39c5 +size 3853 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png index 4ae7279fe7..d47bf90664 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:410a8596d4c70b9a3856d959d6d71ade8e831e8581ff410c9900957a93b3eb50 -size 8114 +oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c +size 3664 diff --git a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png index d47bf90664..a87d89c701 100644 --- a/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:520cae2544153ba45f8fe4b021286c2d22da06aa1b60be7f1b13879723ff994c -size 3664 +oid sha256:0f3a0cb5d9bae6725a1c527f3d9575aabf85d3edf63a28d9faa23f4f0b65e2d0 +size 7624 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png index cf3ad87af2..1ba6334833 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bf5bde86a7f7e873a6871d359f04f2139099724f9977601f5077b2590e97965 -size 46711 +oid sha256:a4e87a86ec5448c005cc236354c08668380023f3f734413edd38130af7ac9846 +size 46183 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png index a31ad79f82..e165278f84 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:078079bf6b37471c7a16363c9d49f7deb5bd9d044ed0591ab86ac8a20800ba03 -size 46795 +oid sha256:9d7ecf7e48fc351e1936d9dcb7485873c93941248a5c63af8334fa4967bcca5b +size 46285 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png index 5f77c0fa62..5b3e741c3f 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d36b97324b3794ff1d01fc13e9e59afb010130d6e022dcc2d7cc9387a13a787e -size 48387 +oid sha256:9db9d270801acdc43a87955bc055e5a25b9b1617de7e2aad93e4d390a36ac653 +size 47797 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png index 5f67807828..8b83d0555c 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:986cfe49b91c522cf3a85d0425c0677a6929c44dca1531923bff2c1766ce46ae -size 59583 +oid sha256:373cfbe1fbd3d9e06bc2079b7ceadccce999efd0cbb1d43f7e791450f3882cad +size 59876 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png index bc27628ac1..6febc55310 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f8ef2430abd11b31b3e90976f85bd4578e667bd0acc0851544ee4d16358479d -size 60237 +oid sha256:5192794371b4b3d10d5e8035f5ccf5dad66b04f16abb686cb5820d4647f678ef +size 60550 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png index 490c357afb..b73534e92c 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7228db8e07c808d0a8780ed06f04f22ff8f1d620327f9243b1dedd476cecb902 -size 56896 +oid sha256:59c20c6ef29f76824f67bf26062586455a792bcbee79b7a0c648a1d69db08a65 +size 56400 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png index 039776804a..463df3804a 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d28be1c00397b6ac456ef93dab7b1da469a7665380ab2c326124efeef3a1c150 -size 45143 +oid sha256:13cf913c2a26e30ad4c9700b110802a00213a514d3c9fcfbce81a82f7316f9fe +size 44515 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png index 6847911e3c..3423750384 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81523eeba6cf9e4ea1e92cc819845bc95bbd76f9e977b462f2357fe1d2e3d335 -size 45230 +oid sha256:5a20e35cfde7c96c6f4ceb5fe51af915f7a5ebdd16dc4dc4a0b4187b0163c42a +size 44611 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png index 763fae759e..cb5e4713ba 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdff40a4ce4b77a98818798c2b6ce3ad18ee5117f40c47c075b30d7006626f10 -size 46703 +oid sha256:28698a3c2fbbee2f76b6a3fb6312596102e7fc8743dc95db62816606b4109be0 +size 46054 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png index 760d8a7533..8d9d32385a 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d331297c0912ffc278828cd00f886c926a71e59aadbee9fef994258ea56c822 -size 57757 +oid sha256:1ee744055473f7b652d874ff024ece68434e88ef085571dbab6466bcd3557c23 +size 58226 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png index 058f822302..2c24a43118 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8265b9acc3932df6f0e1eee5e85c2b16d13060c4164c3fbb5b1b0b598745770c -size 58326 +oid sha256:e3c991f23daeb792f580afc18f3f528649cc92f27555ed05cc1abedad8accc27 +size 58781 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png index 5f5b2315e6..abeece9d09 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:936cf07258d4a001b80b08df4b804c990b9e399bf8a5a544443beebf93caa298 -size 54825 +oid sha256:a95e67e4fd7d8886691d5df2571325254f49db24f8ab16335505b0c201cafb14 +size 54363 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.text_AdaptativeTitle_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.text_AdaptativeTitle_Day_0_en.png new file mode 100644 index 0000000000..87822df7c7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.text_AdaptativeTitle_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bbefd78bcd53367f72ca11d4f57ea87903a677ff24ba4bc80cc50ddd33108fa +size 34859 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.text_AdaptativeTitle_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.text_AdaptativeTitle_Night_0_en.png new file mode 100644 index 0000000000..57b121b8fa --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.text_AdaptativeTitle_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17941844b79274e31880c09b5ee8052693c4425eb7f09c3c2c8687bf2ef64863 +size 34113 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png index ee5c582240..58546b8522 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75de341f705bb22955f9399180519b65ad59c2e1917317d56e50ea4db78f35bc -size 12102 +oid sha256:30357bf18792db5ab20085010251170a724ffe8c00313facd04ee704e806af64 +size 10982 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png index 52fd9b9693..fedead0fa8 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbarNoFab_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58b9491aaa5554b89d86c62a5c9c44975367538c6baa5562f8dc5768f76d8264 -size 8705 +oid sha256:4811667dec0f32accc5e2c8a5dd5c0b34017fcff8d1e0376ee7a4708426bca0f +size 9838 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png index 38ad0e4b56..83302d6ba4 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:326d59cf768b2dbd96eb7964d54c8eda509a5cd57c6e62087625e3314da0bb4d -size 13658 +oid sha256:16dee1dfe746cd40dac9fe8f0af3ecb42cd14bab5cc490917b589beed065ed1b +size 14686 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png index 687e0d436a..4da2a98aca 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_HorizontalFloatingToolbar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d57df7110371c9224e232b707d6a1b10ce14afcb56c803a78220792f2a0d4cf2 -size 11354 +oid sha256:ef0dc4d53daeb986f378b6e4d85928b4b6729336c89cf21e642c88c53688a3ab +size 10292 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en.png index 3caed99b5a..7230e781b2 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53ec492b0a46d3add165e01c7ce540f304574791d10232a825e28cd15ae22248 -size 16903 +oid sha256:e582696ce8f1354353347853e8ad5e774b0c8fd2d34de5b1bc22460d372b2cf0 +size 16207 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en.png index c653ffd3fe..6f079aa16c 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:938b51a3e4e4466056da464fabd34066b716fbf55622b179f799181ff900819f -size 15978 +oid sha256:e34e3ba2f0c3bb8cbb0a21dea399dfa4ee34607c7ad4b2f86153d60f325696c3 +size 15232 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Day_0_en.png index f1bd04835c..341974f70a 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7853b79eda8a4b82cc49d553f78f545af62bc44b708a20a5bb5e4582ed07bc22 -size 63262 +oid sha256:72ea344a67f0c3cdb0037d63265414868bdc886ee0ef548ace738a7c95aa8ef6 +size 62034 diff --git a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Night_0_en.png index 9eba778594..e813b4086b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.matrix.ui.components_SpaceHeaderView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7daff199310c41900d0ca650f38ad2561c8db8542a4d48161a5a88f7fe976318 -size 61899 +oid sha256:0af67c43f3a8b3a5b322a9ab46a46468aa45df57e181e661b9a8c4489d737aff +size 61205 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_0_en.png new file mode 100644 index 0000000000..3cec94cb44 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bfd26e506a4e1693d0d0720add60ab9aa2b01558e0d14c8294bedd5b42ca2fb +size 15005 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_1_en.png new file mode 100644 index 0000000000..5d370f30d9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5103803369870f9cd661f1b5cbbb70023cef586276fca80f010432596b8ecbc3 +size 15026 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_2_en.png new file mode 100644 index 0000000000..a62a416966 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec16ff6ed3abbd4a04cf8d4b0664424e109736013868e790496afe5c7b106646 +size 14861 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_0_en.png new file mode 100644 index 0000000000..8fe23cb9be --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7351236d535315198d7d6d8d63d36deb26360af1ff3ed6f148e7c811ee69056 +size 14852 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_1_en.png new file mode 100644 index 0000000000..d679329fb8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bff0dd25d4ea06c23bc2fa8968892e8b07da65dfba57a61788d2955187b7c9c +size 14845 diff --git a/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_2_en.png new file mode 100644 index 0000000000..ac90f5b949 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.mediaviewer.impl.fileviewer_FileViewerView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ffc62fd0e9b765cfe43193cf0dd443fcf7199330f4a3f9691f9627e423c3f64 +size 14774 diff --git a/tools/manifest/gplay/release/aaptDump.txt b/tools/manifest/gplay/release/aaptDump.txt index 7eae1a5a78..ff8cec3036 100644 --- a/tools/manifest/gplay/release/aaptDump.txt +++ b/tools/manifest/gplay/release/aaptDump.txt @@ -1,4 +1,4 @@ -package: name='io.element.android.x' versionCode='202608020' versionName='26.08.2' platformBuildVersionName='17' platformBuildVersionCode='37' compileSdkVersion='37' compileSdkVersionCodename='17' +package: name='io.element.android.x' versionCode='202608040' versionName='26.08.4' platformBuildVersionName='17' platformBuildVersionCode='37' compileSdkVersion='37' compileSdkVersionCodename='17' sdkVersion:'24' targetSdkVersion:'37' uses-permission: name='android.permission.POST_NOTIFICATIONS' diff --git a/tools/release/releaseV2.sh b/tools/release/releaseV2.sh new file mode 100755 index 0000000000..cef4b7e6b0 --- /dev/null +++ b/tools/release/releaseV2.sh @@ -0,0 +1,544 @@ +#!/usr/bin/env bash + +# Copyright (c) 2026 Element Creations Ltd. +# +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +# Please see LICENSE files in the repository root for full details. + +# do not exit when any command fails (issue with git flow) +set +e + +printf "\n================================================================================\n" +printf "| Welcome to the release script (V2)! |\n" +printf "================================================================================\n" + +printf "Checking environment...\n" +envError=0 + +# Check that bundletool is installed +if ! command -v bundletool &> /dev/null +then + printf "Fatal: bundletool is not installed. You can install it running \`brew install bundletool\`\n" + envError=1 +fi + +# Check that the GitHub CLI is installed. It is expected to be already authenticated, see `gh auth login`. +if ! command -v gh &> /dev/null +then + printf "Fatal: gh (the GitHub CLI) is not installed. You can install it running \`brew install gh\`\n" + envError=1 +fi + +# Path of the key store (it's a file) +keyStorePath="${ELEMENT_X_KEYSTORE_PATH}" +if [[ -z "${keyStorePath}" ]]; then + printf "Fatal: ELEMENT_X_KEYSTORE_PATH is not defined in the environment.\n" + envError=1 +fi +# Keystore password +keyStorePassword="${ELEMENT_X_KEYSTORE_PASSWORD}" +if [[ -z "${keyStorePassword}" ]]; then + printf "Fatal: ELEMENT_X_KEYSTORE_PASSWORD is not defined in the environment.\n" + envError=1 +fi +# Key password +keyPassword="${ELEMENT_X_KEY_PASSWORD}" +if [[ -z "${keyPassword}" ]]; then + printf "Fatal: ELEMENT_X_KEY_PASSWORD is not defined in the environment.\n" + envError=1 +fi +# Android home +androidHome="${ANDROID_HOME}" +if [[ -z "${androidHome}" ]]; then + printf "Fatal: ANDROID_HOME is not defined in the environment.\n" + envError=1 +fi +# @elementbot:matrix.org matrix token / Not mandatory +elementBotToken="${ELEMENT_BOT_MATRIX_TOKEN}" +if [[ -z "${elementBotToken}" ]]; then + printf "Warning: ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment.\n" +fi + +if [ ${envError} == 1 ]; then + exit 1 +fi + +# Handle the result of a version check. ${checkError} must be set to 1 when a check has failed. +checkVersionResult() { + if [[ ${checkError} -ne 0 ]]; then + printf "\nThe check above has failed, this is not expected.\n" + read -r -p "Do you want to continue anyway (yes/no) default to no? " doContinue + doContinue=${doContinue:-no} + if [ "${doContinue}" != "yes" ]; then + exit 1 + fi + else + printf "The versions are correct.\n" + fi +} + +# Read minSdkVersion from file plugins/src/main/kotlin/Versions.kt +minSdkVersion=$(grep "MIN_SDK_FOSS =" ./plugins/src/main/kotlin/Versions.kt |cut -d '=' -f 2 |xargs) +# Read buildToolsVersion from file plugins/src/main/kotlin/Versions.kt +buildToolsVersion=$(grep "BUILD_TOOLS_VERSION =" ./plugins/src/main/kotlin/Versions.kt |cut -d '=' -f 2 |xargs) +buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}" + +if [[ ! -d ${buildToolsPath} ]]; then + printf "Fatal: %s folder not found, ensure that you have installed the SDK version %s.\n" "${buildToolsPath}" "${buildToolsVersion}" + exit 1 +fi + +# Check if git flow is enabled +gitFlowDevelop=$(git config gitflow.branch.develop) +if [[ ${gitFlowDevelop} != "" ]] +then + printf "Git flow is initialized\n" +else + printf "Git flow is not initialized. Initializing...\n" + ./tools/gitflow/gitflow-init.sh +fi + +printf "OK\n" + +printf "\n================================================================================\n" +printf "Ensuring main and develop branches are up to date...\n" + +git checkout main +git pull +git checkout develop +git pull + +printf "\n================================================================================\n" +# Guessing version to propose a default version +versionsFile="./plugins/src/main/kotlin/Versions.kt" +# The version of the release must match the date of next monday, where the release is supposed to go live +# The command below gets the date of next monday +nextMondayDateCommand="date -v +1w -v -monday" +# Get release year on 2 digits +versionYearCandidate=$(${nextMondayDateCommand} +%y) +currentVersionMonth=$(grep "val versionMonth" ${versionsFile} | cut -d " " -f6) +# Get release month on 2 digits +versionMonthCandidate=$(${nextMondayDateCommand} +%m) +versionMonthCandidateNoLeadingZero=${versionMonthCandidate/#0/} +currentVersionReleaseNumber=$(grep "val versionReleaseNumber" ${versionsFile} | cut -d " " -f6) +# if the release month is the same as the current version, we increment the release number, else we reset it to 0 +if [[ ${currentVersionMonth} -eq ${versionMonthCandidateNoLeadingZero} ]]; then + versionReleaseNumberCandidate=$((currentVersionReleaseNumber + 1)) +else + versionReleaseNumberCandidate=0 +fi +versionCandidate="${versionYearCandidate}.${versionMonthCandidate}.${versionReleaseNumberCandidate}" + +read -r -p "Please enter the release version (example: ${versionCandidate}). Format must be 'YY.MM.x' or 'YY.MM.xy', with year and month matching next Monday. Just press enter if ${versionCandidate} is correct. " version +version=${version:-${versionCandidate}} + +# extract year, month and release number for future use +versionYear=$(echo "${version}" | cut -d "." -f1) +versionMonth=$(echo "${version}" | cut -d "." -f2) +versionMonthNoLeadingZero=${versionMonth/#0/} +versionReleaseNumber=$(echo "${version}" | cut -d "." -f3) + +printf "\n================================================================================\n" +printf "Starting the release %s\n" "${version}" +git flow release start "${version}" + +# Note: in case the release is already started and the script is started again, checkout the release branch again. +ret=$? +if [[ $ret -ne 0 ]]; then + printf "Mmh, it seems that the release is already started. I'm displaying the changes now:\n" + git diff --stat "release/${version}" origin/main + printf "Do you want to continue the release using its contents?\n\n" + read -r -p "Continue (yes/no) default to yes? " doContinue + doContinue=${doContinue:-yes} + if [ "${doContinue}" == "no" ]; then + printf "OK, exiting, you can start the release again with the command 'git flow release start %s'\n" "${version}" + exit 1 + fi + git checkout "release/${version}" +fi + +# Ensure version is OK +versionsFileBak="${versionsFile}.bak" +cp ${versionsFile} ${versionsFileBak} +sed "s/private const val versionYear = .*/private const val versionYear = ${versionYear}/" ${versionsFileBak} > ${versionsFile} +sed "s/private const val versionMonth = .*/private const val versionMonth = ${versionMonthNoLeadingZero}/" ${versionsFile} > ${versionsFileBak} +sed "s/private const val versionReleaseNumber = .*/private const val versionReleaseNumber = ${versionReleaseNumber}/" ${versionsFileBak} > ${versionsFile} +rm ${versionsFileBak} + +printf -v versionReleaseNumber2Digits "%02d" "${versionReleaseNumber}" +versionCode="20${versionYear}${versionMonth}${versionReleaseNumber2Digits}0" + +# Update the file aaptDump.txt with the new version +aaptDumpFile="./tools/manifest/gplay/release/aaptDump.txt" +sed "s/versionCode='[0-9]*'/versionCode='${versionCode}'/" ${aaptDumpFile} > ${aaptDumpFile}.bak +sed "s/versionName='[0-9]*\.[0-9]*\.[0-9]*'/versionName='${version}'/" ${aaptDumpFile}.bak > ${aaptDumpFile} +rm ${aaptDumpFile}.bak + +git commit -a -m "Setting version for the release ${version}" + +printf "\n================================================================================\n" +printf "Creating fastlane file...\n" +fastlaneFile="${versionCode}.txt" +fastlanePathFile="./fastlane/metadata/android/en-US/changelogs/${fastlaneFile}" +printf "Main changes in this version: bug fixes and improvements.\nFull changelog: https://github.com/element-hq/element-x-android/releases" > "${fastlanePathFile}" + +read -r -p "I have created the file ${fastlanePathFile}, please edit it and press enter to continue. " +git add "${fastlanePathFile}" +git commit -a -m "Adding fastlane file for version ${version}" + +printf "\n================================================================================\n" +printf "OK, finishing the release...\n" +# GIT_MERGE_AUTOEDIT avoids opening the editor for the 2 merge commits, whose default message is +# always used, and -m provides the message of the annotated tag. git flow appends the tag name to +# it, so the tag message ends up being "Release v${version}". +GIT_MERGE_AUTOEDIT=no git flow release finish -m "Release" "${version}" + +printf "\n================================================================================\n" +read -r -p "Done, push the branch 'main' and the new tag (yes/no) default to yes? " doPush +doPush=${doPush:-yes} + +if [ "${doPush}" == "yes" ]; then + printf "Pushing branch 'main' and tag 'v%s'...\n" "${version}" + git push origin main + git push origin "v${version}" +else + printf "Not pushing, do not forget to push manually!\n" +fi + +printf "\n================================================================================\n" +printf "Checking out develop...\n" +git checkout develop + +printf "\n================================================================================\n" +printf "Downloading the artifacts...\n" + +targetPath="./tmp/Element/${version}" +fdroidTargetPath="${targetPath}/fdroid" +gplayTargetPath="${targetPath}/gplay" + +releaseCommit=$(git rev-parse --verify --quiet "v${version}^{commit}") +if [[ -z "${releaseCommit}" ]]; then + # Without a commit, `gh run list` would return the latest run of the workflow, which may be another one. + printf "Fatal: the tag v%s cannot be resolved.\n" "${version}" + exit 1 +fi + +printf "Looking for the run of the workflow release.yml for the commit %s...\n" "${releaseCommit}" + +runId="" + +# The run can take a few seconds to appear after the push, so retry for a couple of minutes. +for _ in $(seq 1 12); do + runId=$(gh run list --repo element-hq/element-x-android --workflow release.yml --commit "${releaseCommit}" --limit 1 --json databaseId -q '.[0].databaseId' 2> /dev/null) + if [[ -n "${runId}" ]]; then + break + fi + printf "No run found yet, waiting...\n" + sleep 10 +done + +if [[ -z "${runId}" ]]; then + printf "Fatal: no run of the workflow release.yml found for the commit %s.\n" "${releaseCommit}" + exit 1 +fi + +printf "Found the run https://github.com/element-hq/element-x-android/actions/runs/%s\n" "${runId}" +printf "Waiting for the run to complete...\n" +gh run watch "${runId}" --repo element-hq/element-x-android --compact --exit-status + +ret=1 + +while [[ $ret -ne 0 ]]; do + gh run download "${runId}" --repo element-hq/element-x-android \ + --dir "${gplayTargetPath}" \ + --name elementx-app-gplay-bundle-unsigned + + ret=$? + if [[ $ret -eq 0 ]]; then + gh run download "${runId}" --repo element-hq/element-x-android \ + --dir "${fdroidTargetPath}" \ + --name elementx-app-fdroid-apks-unsigned + + ret=$? + fi + if [[ $ret -ne 0 ]]; then + read -r -p "Error while downloading the artifacts. You may want to fix the issue and retry. Retry (yes/no) default to yes? " doRetry + doRetry=${doRetry:-yes} + if [ "${doRetry}" == "no" ]; then + exit 1 + fi + fi +done + +printf "\n================================================================================\n" +printf "Signing the FDroid APKs...\n" + +cp "${fdroidTargetPath}"/app-fdroid-arm64-v8a-release.apk \ + "${fdroidTargetPath}"/app-fdroid-arm64-v8a-release-signed.apk +"${buildToolsPath}"/apksigner sign \ + -v \ + --alignment-preserved true \ + --ks "${keyStorePath}" \ + --ks-pass pass:"${keyStorePassword}" \ + --ks-key-alias elementx \ + --key-pass pass:"${keyPassword}" \ + --min-sdk-version "${minSdkVersion}" \ + "${fdroidTargetPath}"/app-fdroid-arm64-v8a-release-signed.apk + +cp "${fdroidTargetPath}"/app-fdroid-armeabi-v7a-release.apk \ + "${fdroidTargetPath}"/app-fdroid-armeabi-v7a-release-signed.apk +"${buildToolsPath}"/apksigner sign \ + -v \ + --alignment-preserved true \ + --ks "${keyStorePath}" \ + --ks-pass pass:"${keyStorePassword}" \ + --ks-key-alias elementx \ + --key-pass pass:"${keyPassword}" \ + --min-sdk-version "${minSdkVersion}" \ + "${fdroidTargetPath}"/app-fdroid-armeabi-v7a-release-signed.apk + +cp "${fdroidTargetPath}"/app-fdroid-x86-release.apk \ + "${fdroidTargetPath}"/app-fdroid-x86-release-signed.apk +"${buildToolsPath}"/apksigner sign \ + -v \ + --alignment-preserved true \ + --ks "${keyStorePath}" \ + --ks-pass pass:"${keyStorePassword}" \ + --ks-key-alias elementx \ + --key-pass pass:"${keyPassword}" \ + --min-sdk-version "${minSdkVersion}" \ + "${fdroidTargetPath}"/app-fdroid-x86-release-signed.apk + +cp "${fdroidTargetPath}"/app-fdroid-x86_64-release.apk \ + "${fdroidTargetPath}"/app-fdroid-x86_64-release-signed.apk +"${buildToolsPath}"/apksigner sign \ + -v \ + --alignment-preserved true \ + --ks "${keyStorePath}" \ + --ks-pass pass:"${keyStorePassword}" \ + --ks-key-alias elementx \ + --key-pass pass:"${keyPassword}" \ + --min-sdk-version "${minSdkVersion}" \ + "${fdroidTargetPath}"/app-fdroid-x86_64-release-signed.apk + +printf "\n================================================================================\n" +printf "Checking the signed APKs...\n" + +checkError=0 + +# Each APK gets the version code of the app bundle, plus the code of its ABI. +# Must be kept in sync with the abiVersionCodes map in app/build.gradle.kts. +fdroidAbis="armeabi-v7a:1 arm64-v8a:2 x86:3 x86_64:4" + +for abiEntry in ${fdroidAbis}; do + abi="${abiEntry%%:*}" + abiCode="${abiEntry##*:}" + expectedApkVersionCode=$((versionCode + abiCode)) + apkFile="${fdroidTargetPath}/app-fdroid-${abi}-release-signed.apk" + apkBadging=$("${buildToolsPath}"/aapt dump badging "${apkFile}" | grep -m 1 "^package") + apkVersionCode=$(printf "%s" "${apkBadging}" | sed -n "s/.*versionCode='\([0-9]*\)'.*/\1/p") + apkVersionName=$(printf "%s" "${apkBadging}" | sed -n "s/.*versionName='\([^']*\)'.*/\1/p") + printf "File app-fdroid-%s-release-signed.apk: version code %s, version name %s\n" "${abi}" "${apkVersionCode}" "${apkVersionName}" + if [[ "${apkVersionCode}" != "${expectedApkVersionCode}" ]]; then + printf "Warning: was expecting the version code %s, but got %s.\n" "${expectedApkVersionCode}" "${apkVersionCode}" + checkError=1 + fi + if [[ "${apkVersionName}" != "${version}" ]]; then + printf "Warning: was expecting the version name %s, but got %s.\n" "${version}" "${apkVersionName}" + checkError=1 + fi +done + +checkVersionResult + +printf "\n================================================================================\n" +printf "The APKs in %s have been signed!\n" "${fdroidTargetPath}" + +unsignedBundlePath="${gplayTargetPath}/app-gplay-release.aab" +signedBundlePath="${gplayTargetPath}/app-gplay-release-signed.aab" +# The universal APK is downloaded manually from the GooglePlay console, and is named after the version code. +universalApkPath="${gplayTargetPath}/${versionCode}.apk" + +printf "\n================================================================================\n" +printf "Signing file %s with build-tools version %s for min SDK version %s...\n" "${unsignedBundlePath}" "${buildToolsVersion}" "${minSdkVersion}" + +cp "${unsignedBundlePath}" "${signedBundlePath}" + +"${buildToolsPath}"/apksigner sign \ + -v \ + --ks "${keyStorePath}" \ + --ks-pass pass:"${keyStorePassword}" \ + --ks-key-alias elementx \ + --key-pass pass:"${keyPassword}" \ + --min-sdk-version "${minSdkVersion}" \ + "${signedBundlePath}" + +printf "\n================================================================================\n" +printf "Checking the signed app bundle...\n" + +checkError=0 + +bundleVersionCode=$(bundletool dump manifest --bundle="${signedBundlePath}" --xpath=/manifest/@android:versionCode) +bundleVersionName=$(bundletool dump manifest --bundle="${signedBundlePath}" --xpath=/manifest/@android:versionName) +printf "File %s: version code %s, version name %s\n" "$(basename "${signedBundlePath}")" "${bundleVersionCode}" "${bundleVersionName}" +if [[ "${bundleVersionCode}" != "${versionCode}" ]]; then + printf "Warning: was expecting the version code %s, but got %s.\n" "${versionCode}" "${bundleVersionCode}" + checkError=1 +fi +if [[ "${bundleVersionName}" != "${version}" ]]; then + printf "Warning: was expecting the version name %s, but got %s.\n" "${version}" "${bundleVersionName}" + checkError=1 +fi + +checkVersionResult + +printf "\n================================================================================\n" +printf "The file %s has been signed and can be uploaded to the PlayStore!\n" "${signedBundlePath}" + +printf "\n================================================================================\n" +read -r -p "Do you want to build the APKs from the app bundle? You need to do this step if you want to install the application to your device. (yes/no) default to no " doBuildApks +doBuildApks=${doBuildApks:-no} + +if [ "${doBuildApks}" == "yes" ]; then + printf "Building apks...\n" + bundletool build-apks --bundle="${signedBundlePath}" --output="${gplayTargetPath}"/elementx.apks \ + --ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \ + --overwrite + + read -r -p "Do you want to install the application to your device? Make sure there is one (and only one!) connected device first. (yes/no) default to yes " doDeploy + doDeploy=${doDeploy:-yes} + if [ "${doDeploy}" == "yes" ]; then + printf "Installing apk for your device...\n" + bundletool install-apks --apks="${gplayTargetPath}"/elementx.apks + read -r -p "Please run the application on your phone to check that the upgrade went well. Press enter to continue. " + else + printf "APK will not be deployed!\n" + fi +else + printf "APKs will not be generated!\n" +fi + +printf "\n================================================================================\n" +printf "Create the open testing release on GooglePlay.\n" + +printf "On GooglePlay console, go the the open testing section and click on \"Create new release\" button, then:\n" +printf " - upload the file %s.\n" "${signedBundlePath}" +printf " - copy the release note from the fastlane file.\n" +printf " - download the universal APK, to be able to provide it to the GitHub release: click on the right arrow next to the \"App bundle\", then click on the \"Download\" tab, and download the \"Signed, universal APK\". Save it, without renaming it, to the folder %s. The expected final path is %s\n" "${gplayTargetPath}" "${universalApkPath}" +printf " - submit the release.\n" +read -r -p "Press enter to continue. " + +printf "You can then go to \"Publishing overview\" and send the new release for a review by Google.\n" +read -r -p "Press enter to continue. " + +printf "\n================================================================================\n" +printf "Creating the release on GitHub.\n" + +releaseAssets=( + "${signedBundlePath}" + "${universalApkPath}" + "${fdroidTargetPath}/app-fdroid-arm64-v8a-release-signed.apk" + "${fdroidTargetPath}/app-fdroid-armeabi-v7a-release-signed.apk" + "${fdroidTargetPath}/app-fdroid-x86-release-signed.apk" + "${fdroidTargetPath}/app-fdroid-x86_64-release-signed.apk" +) + +missingAsset=0 + +for releaseAsset in "${releaseAssets[@]}"; do + if [[ ! -f "${releaseAsset}" ]]; then + printf "Error: the file %s does not exist.\n" "${releaseAsset}" + missingAsset=1 + fi +done + +if [[ ${missingAsset} -ne 0 ]]; then + printf "Fatal: some files are missing, cannot create the GitHub release.\n" + exit 1 +fi + +printf "Creating the pre-release v%s and uploading the %d files, this can take a while...\n" "${version}" "${#releaseAssets[@]}" +gh release create "v${version}" \ + --repo element-hq/element-x-android \ + --title "Element X Android v${version}" \ + --generate-notes \ + --prerelease \ + --verify-tag \ + "${releaseAssets[@]}" + +if [[ $? -ne 0 ]]; then + printf "Fatal: error while creating the GitHub release.\n" + exit 1 +fi + +printf "The pre-release has been created: https://github.com/element-hq/element-x-android/releases/tag/v%s\n" "${version}" +printf "Please check the generated release notes, and optionally reorder items and fix typos.\n" +read -r -p "Press enter to continue. " + +printf "\n================================================================================\n" +printf "Update the project release notes:\n\n" + +printf "Getting the release notes from GitHub...\n" +releaseNotes=$(gh release view "v${version}" --repo element-hq/element-x-android --json body -q .body) + +if [[ $? -ne 0 || -z "${releaseNotes}" ]]; then + printf "Fatal: error while getting the release notes from GitHub.\n" + exit 1 +fi + +# GitHub returns the release notes with CRLF line endings, remove the CR. +releaseNotes="${releaseNotes//$'\r'/}" +changesTitle="Changes in Element X v${version}" +# Underline the title with as many '=' as there are characters in the title. +changesUnderline="${changesTitle//?/=}" +changesFile="./CHANGES.md" +changesFileBak="${changesFile}.bak" +mv "${changesFile}" "${changesFileBak}" +{ + printf "%s\n%s\n\n" "${changesTitle}" "${changesUnderline}" + printf "%s\n\n" "${releaseNotes}" + cat "${changesFileBak}" +} > "${changesFile}" +rm "${changesFileBak}" +printf "The file CHANGES.md has been updated.\n" +read -r -p "Please check the change and press enter to commit it. " + +printf "\n================================================================================\n" +printf "Committing...\n" +git commit -a -m "Changelog for version ${version}" + +printf "\n================================================================================\n" +read -r -p "Done, push the branch 'develop' (yes/no) default to yes? (A rebase may be necessary in case develop got new commits) " doPush +doPush=${doPush:-yes} + +if [ "${doPush}" == "yes" ]; then + printf "Pushing branch 'develop'...\n" + git push origin develop +else + printf "Not pushing, do not forget to push manually!\n" +fi + +printf "\n================================================================================\n" +printf "Message for the Android internal room:\n\n" +message="@room Element X Android ${version} is ready to be tested. You can get it from https://github.com/element-hq/element-x-android/releases/tag/v${version}. You can install the universal APK. If you want to install the application from the app bundle, you can follow instructions [here](https://github.com/element-hq/element-x-android/blob/develop/docs/install_from_github_release.md). Please report any feedback. Thanks!" +printf "%s\n\n" "${message}" + +if [[ -z "${elementBotToken}" ]]; then + read -r -p "ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment. Cannot send the message for you. Please send it manually, and press enter to continue. " +else + read -r -p "Send this message to the room (yes/no) default to yes? " doSend + doSend=${doSend:-yes} + if [ "${doSend}" == "yes" ]; then + printf "Sending message...\n" + transactionId=$(openssl rand -hex 16) + # Element Android internal + matrixRoomId="!LiSLXinTDCsepePiYW:matrix.org" + curl -X PUT --data "{\"msgtype\":\"m.text\",\"body\":\"${message}\"}" -H "Authorization: Bearer ${elementBotToken}" https://matrix-client.matrix.org/_matrix/client/r0/rooms/${matrixRoomId}/send/m.room.message/\$local."${transactionId}" + else + printf "Message not sent, please send it manually!\n" + fi +fi + +printf "\n================================================================================\n" +printf "Congratulation! Kudos for using this script! Have a nice day!\n" +printf "================================================================================\n" diff --git a/tools/test/generateAllScreenshots.py b/tools/test/generateAllScreenshots.py index e10a7abb74..a4e3468d80 100755 --- a/tools/test/generateAllScreenshots.py +++ b/tools/test/generateAllScreenshots.py @@ -167,6 +167,7 @@ def generateJavascriptFile(): with open("screenshots/html/data.js", "w") as f: f.write("// Generated file, do not edit\n") + f.write("export const buildDate = %d;\n" % int(time.time())) f.write("export const screenshots = [\n") for line in data: f.write("[")