From 73454b623236a87e47a07a9c1631c15475a7addc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 19 Feb 2025 16:25:32 +0100 Subject: [PATCH 01/19] Add support for starting verification of a user --- .../android/appnav/LoggedInFlowNode.kt | 7 +- features/ftue/impl/build.gradle.kts | 2 + .../features/ftue/impl/FtueFlowNode.kt | 3 +- .../features/ftue/impl/di/FtueModule.kt | 23 +++ .../FtueSessionVerificationFlowNode.kt | 58 +++++- .../ChooseSelfVerificationModeNode.kt | 59 ++++++ .../ChooseSelfVerificationModePresenter.kt | 58 ++++++ .../ChooseSelfVerificationModeView.kt | 108 ++++++++++ .../impl/src/main/res/values/localazy.xml | 11 + features/roomdetails/impl/build.gradle.kts | 2 + .../roomdetails/impl/RoomDetailsFlowNode.kt | 40 ++++ .../members/details/RoomMemberDetailsNode.kt | 1 + features/userprofile/impl/build.gradle.kts | 1 + .../userprofile/impl/UserProfileFlowNode.kt | 20 ++ .../userprofile/impl/root/UserProfileNode.kt | 1 + .../impl/root/UserProfilePresenter.kt | 6 +- .../shared/UserProfileNodeHelper.kt | 1 + .../userprofile/shared/UserProfileView.kt | 16 +- .../api/IncomingVerificationEntryPoint.kt | 3 +- .../api/VerifySessionEntryPoint.kt | 10 +- .../impl/incoming/IncomingVerificationNode.kt | 2 +- .../incoming/IncomingVerificationPresenter.kt | 14 +- .../incoming/IncomingVerificationState.kt | 2 + .../IncomingVerificationStateProvider.kt | 40 +++- .../impl/incoming/IncomingVerificationView.kt | 115 ++++++----- .../impl/outgoing/VerifySelfSessionNode.kt | 21 +- .../outgoing/VerifySelfSessionPresenter.kt | 130 +++++------- .../impl/outgoing/VerifySelfSessionState.kt | 12 +- .../outgoing/VerifySelfSessionStateMachine.kt | 84 ++++---- .../VerifySelfSessionStateProvider.kt | 54 +++-- .../impl/outgoing/VerifySelfSessionView.kt | 191 ++++++------------ .../outgoing/VerifySelfSessionViewEvents.kt | 3 - .../impl/ui/VerificationUserProfileContent.kt | 71 +++++++ .../IncomingVerificationPresenterTest.kt | 2 +- .../VerifySelfSessionPresenterTest.kt | 10 +- .../outgoing/VerifySelfSessionViewTest.kt | 2 +- libraries/designsystem/build.gradle.kts | 1 + .../designsystem/components/BigIcon.kt | 86 +++++--- .../components/avatar/AvatarSize.kt | 2 + .../designsystem/utils/OpenUrlInTabView.kt | 28 +++ .../SessionVerificationRequestDetails.kt | 12 +- .../SessionVerificationService.kt | 14 +- .../api/verification/VerificationRequest.kt | 30 +++ .../RustSessionVerificationService.kt | 33 ++- .../SessionVerificationRequestDetails.kt | 20 +- .../FakeSessionVerificationService.kt | 2 +- tools/localazy/config.json | 4 +- 47 files changed, 970 insertions(+), 445 deletions(-) create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt create mode 100644 features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 6526248ef81..3e0eef42631 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -75,6 +75,7 @@ import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -127,8 +128,8 @@ class LoggedInFlowNode @AssistedInject constructor( ) private val verificationListener = object : SessionVerificationServiceListener { - override fun onIncomingSessionRequest(sessionVerificationRequestDetails: SessionVerificationRequestDetails) { - backstack.singleTop(NavTarget.IncomingVerificationRequest(sessionVerificationRequestDetails)) + override fun onIncomingSessionRequest(verificationRequest: VerificationRequest.Incoming) { + backstack.singleTop(NavTarget.IncomingVerificationRequest(verificationRequest)) } } @@ -218,7 +219,7 @@ class LoggedInFlowNode @AssistedInject constructor( data object LogoutForNativeSlidingSyncMigrationNeeded : NavTarget @Parcelize - data class IncomingVerificationRequest(val data: SessionVerificationRequestDetails) : NavTarget + data class IncomingVerificationRequest(val data: VerificationRequest.Incoming) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index d9cd9930437..b2f02aaf3df 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(projects.libraries.uiStrings) implementation(projects.libraries.testtags) implementation(projects.features.analytics.api) + implementation(projects.features.logout.api) implementation(projects.features.securebackup.api) implementation(projects.features.verifysession.api) implementation(projects.services.analytics.api) @@ -37,6 +38,7 @@ dependencies { implementation(projects.libraries.permissions.api) implementation(projects.libraries.permissions.noop) implementation(projects.services.toolbox.api) + implementation(projects.appconfig) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index c5987508fe9..2c0ac486f1b 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -21,6 +21,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.replace import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -104,7 +105,7 @@ class FtueFlowNode @AssistedInject constructor( NavTarget.Placeholder -> { createNode(buildContext) } - NavTarget.SessionVerification -> { + is NavTarget.SessionVerification -> { val callback = object : FtueSessionVerificationFlowNode.Callback { override fun onDone() { moveToNextStepIfNeeded() diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt new file mode 100644 index 00000000000..24e118b0afd --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.di + +import com.squareup.anvil.annotations.ContributesTo +import dagger.Binds +import dagger.Module +import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModePresenter +import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModeState +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.SessionScope + +@ContributesTo(SessionScope::class) +@Module +interface FtueModule { + @Binds + fun bindChooseVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt index 4ff7e393b4a..43373f3a9a5 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt @@ -9,6 +9,7 @@ package io.element.android.features.ftue.impl.sessionverification import android.os.Parcelable import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext @@ -17,15 +18,21 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.appconfig.LearnMoreConfig +import io.element.android.features.ftue.impl.sessionverification.choosemode.ChooseSelfVerificationModeNode import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.designsystem.utils.OpenUrlInTabView import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -37,7 +44,7 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( private val secureBackupEntryPoint: SecureBackupEntryPoint, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.Root(showDeviceVerifiedScreen = false), + initialElement = NavTarget.Root, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -45,7 +52,10 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( ) { sealed interface NavTarget : Parcelable { @Parcelize - data class Root(val showDeviceVerifiedScreen: Boolean) : NavTarget + data object Root : NavTarget + + @Parcelize + data class UseAnotherDevice(val showDeviceVerifiedScreen: Boolean) : NavTarget @Parcelize data object EnterRecoveryKey : NavTarget @@ -62,7 +72,7 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( override fun onDone() { lifecycleScope.launch { // Move to the completed state view in the verification flow - backstack.newRoot(NavTarget.Root(showDeviceVerifiedScreen = true)) + backstack.newRoot(NavTarget.UseAnotherDevice(showDeviceVerifiedScreen = true)) } } } @@ -70,19 +80,43 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.Root -> { + val callback = object : ChooseSelfVerificationModeNode.Callback { + override fun onUseAnotherDevice() { + backstack.push(NavTarget.UseAnotherDevice(showDeviceVerifiedScreen = true)) + } + + override fun onUseRecoveryKey() { + backstack.push(NavTarget.EnterRecoveryKey) + } + + override fun onResetKey() { + backstack.push(NavTarget.ResetIdentity) + } + + override fun onLearnMoreAboutEncryption() { + learnMoreUrl.value = LearnMoreConfig.ENCRYPTION_URL + } + } + + createNode(buildContext, plugins = listOf(callback)) + } + is NavTarget.UseAnotherDevice -> { verifySessionEntryPoint.nodeBuilder(this, buildContext) - .params(VerifySessionEntryPoint.Params(navTarget.showDeviceVerifiedScreen)) + .params(VerifySessionEntryPoint.Params( + showDeviceVerifiedScreen = navTarget.showDeviceVerifiedScreen, + verificationRequest = VerificationRequest.Outgoing.CurrentSession, + )) .callback(object : VerifySessionEntryPoint.Callback { - override fun onEnterRecoveryKey() { - backstack.push(NavTarget.EnterRecoveryKey) - } - override fun onDone() { plugins().forEach { it.onDone() } } - override fun onResetKey() { - backstack.push(NavTarget.ResetIdentity) + override fun onBack() { + backstack.pop() + } + + override fun onLearnMoreAboutEncryption() { + learnMoreUrl.value = LearnMoreConfig.ENCRYPTION_URL } }) .build() @@ -106,8 +140,12 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( } } + private val learnMoreUrl = mutableStateOf(null) + @Composable override fun View(modifier: Modifier) { BackstackView() + + OpenUrlInTabView(learnMoreUrl) } } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt new file mode 100644 index 00000000000..1ad9d5427c0 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +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 com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.logout.api.direct.DirectLogoutView +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class ChooseSelfVerificationModeNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: Presenter, + private val directLogoutView: DirectLogoutView, +) : Node(buildContext, plugins = plugins) { + + interface Callback : Plugin { + fun onUseAnotherDevice() + fun onUseRecoveryKey() + fun onResetKey() + fun onLearnMoreAboutEncryption() + } + + private val callback = plugins().first() + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + + ChooseSelfVerificationModeView( + state = state, + onUseAnotherDevice = callback::onUseAnotherDevice, + onUseRecoveryKey = callback::onUseRecoveryKey, + onResetKey = callback::onResetKey, + onLearnMore = callback::onLearnMoreAboutEncryption, + modifier = modifier, + ) + + directLogoutView.Render( + state = state.directLogoutState, + openLogoutUrlIfPresent = true, + customOnSuccessLogout = {}, + ) + } +} 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 new file mode 100644 index 00000000000..ae7bf36c639 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import io.element.android.features.logout.api.direct.DirectLogoutEvents +import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.api.encryption.RecoveryState +import javax.inject.Inject + +class ChooseSelfVerificationModePresenter @Inject constructor( + private val encryptionService: EncryptionService, + private val directLogoutPresenter: Presenter, +) : Presenter { + @Composable + override fun present(): ChooseSelfVerificationModeState { + val isLastDevice by encryptionService.isLastDevice.collectAsState() + val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() + val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } } + + val directLogoutState = directLogoutPresenter.present() + + fun eventHandler(event: ChooseSelfVerificationModeEvent) { + when (event) { + ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) + } + } + + return ChooseSelfVerificationModeState( + isLastDevice = isLastDevice, + canEnterRecoveryKey = canEnterRecoveryKey, + directLogoutState = directLogoutState, + eventSink = ::eventHandler, + ) + } +} + +data class ChooseSelfVerificationModeState( + val isLastDevice: Boolean, + val canEnterRecoveryKey: Boolean, + val directLogoutState: DirectLogoutState, + val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, +) + +sealed interface ChooseSelfVerificationModeEvent { + object SignOut : ChooseSelfVerificationModeEvent +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt new file mode 100644 index 00000000000..a1158ab5ee5 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +import androidx.activity.compose.BackHandler +import androidx.activity.compose.LocalActivity +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.ftue.impl.R +import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule +import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.PageTitle +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.OutlinedButton +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChooseSelfVerificationModeView( + state: ChooseSelfVerificationModeState, + onUseAnotherDevice: () -> Unit, + onUseRecoveryKey: () -> Unit, + onResetKey: () -> Unit, + onLearnMore: () -> Unit, + modifier: Modifier = Modifier +) { + val activity = LocalActivity.current + BackHandler { + activity?.finish() + } + + HeaderFooterPage( + modifier = modifier, + topBar = { + TopAppBar( + title = {}, + actions = { + TextButton( + text = stringResource(CommonStrings.action_signout), + onClick = { state.eventSink(ChooseSelfVerificationModeEvent.SignOut) } + ) + } + ) + }, + header = { + PageTitle( + iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()), + title = stringResource(id = R.string.screen_identity_confirmation_title), + subtitle = stringResource(id = R.string.screen_identity_confirmation_subtitle) + ) + }, + footer = { + ButtonColumnMolecule( + modifier = modifier.padding(bottom = 16.dp) + ) { + if (state.isLastDevice.not()) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_identity_use_another_device), + onClick = onUseAnotherDevice, + ) + } + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_session_verification_enter_recovery_key), + onClick = onUseRecoveryKey, + ) + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_identity_confirmation_cannot_confirm), + onClick = onResetKey, + ) + } + } + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + Text( + modifier = Modifier + .clickable(onClick = onLearnMore) + .padding(vertical = 4.dp, horizontal = 16.dp), + text = stringResource(CommonStrings.action_learn_more), + style = ElementTheme.typography.fontBodyLgMedium + ) + } + } +} diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 3e8c86b7614..47214a93041 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -1,7 +1,18 @@ + "Can\'t confirm?" + "Create a new recovery key" + "Verify this device to set up secure messaging." + "Confirm your identity" + "Use another device" + "Use recovery key" + "Now you can read or send messages securely, and anyone you chat with can also trust this device." + "Device verified" + "Use another device" + "Waiting on other device…" "You can change your settings later." "Allow notifications and never miss a message" + "Enter recovery key" "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms isn’t available yet." "We’d love to hear from you, let us know what you think via the settings page." diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 1c092ec8053..315b00b1534 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -24,6 +24,7 @@ android { setupAnvil() dependencies { + implementation(projects.appconfig) implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) @@ -51,6 +52,7 @@ dependencies { implementation(projects.features.messages.api) implementation(projects.features.roomcall.api) implementation(projects.features.knockrequests.api) + implementation(projects.features.verifysession.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 774abfe8cdd..408a1df1c2e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -9,6 +9,7 @@ package io.element.android.features.roomdetails.impl import android.os.Parcelable import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -21,6 +22,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.Interaction import io.element.android.anvilannotations.ContributesNode +import io.element.android.appconfig.LearnMoreConfig import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint @@ -35,11 +37,14 @@ import io.element.android.features.roomdetails.impl.notificationsettings.RoomNot import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode import io.element.android.features.userprofile.shared.UserProfileNodeHelper +import io.element.android.features.verifysession.api.VerifySessionEntryPoint +import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.operation.hide import io.element.android.libraries.architecture.overlay.operation.show +import io.element.android.libraries.designsystem.utils.OpenUrlInTabView import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -47,6 +52,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.services.analytics.api.AnalyticsService @@ -65,6 +71,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, private val mediaViewerEntryPoint: MediaViewerEntryPoint, private val mediaGalleryEntryPoint: MediaGalleryEntryPoint, + private val verifySessionEntryPoint: VerifySessionEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), @@ -118,6 +125,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( @Parcelize data object SecurityAndPrivacy : NavTarget + + @Parcelize + data class VerifyUser(val userId: UserId) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -224,6 +234,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( override fun onStartCall(dmRoomId: RoomId) { elementCallEntryPoint.startCall(CallType.RoomCall(roomId = dmRoomId, sessionId = room.sessionId)) } + + override fun onVerifyUser(userId: UserId) { + backstack.push(NavTarget.VerifyUser(userId)) + } } val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId), callback) createNode(buildContext, plugins) @@ -301,11 +315,37 @@ class RoomDetailsFlowNode @AssistedInject constructor( NavTarget.SecurityAndPrivacy -> { createNode(buildContext) } + is NavTarget.VerifyUser -> { + val params = VerifySessionEntryPoint.Params( + showDeviceVerifiedScreen = true, + verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId,) + ) + verifySessionEntryPoint.nodeBuilder(this, buildContext) + .params(params) + .callback(object : VerifySessionEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + + override fun onBack() { + backstack.pop() + } + + override fun onLearnMoreAboutEncryption() { + learnMoreUrl.value = LearnMoreConfig.ENCRYPTION_URL + } + }) + .build() + } } } + private val learnMoreUrl = mutableStateOf(null) + @Composable override fun View(modifier: Modifier) { BackstackWithOverlayBox(modifier) + + OpenUrlInTabView(learnMoreUrl) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 7b5ce77f2be..28698b94513 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -79,6 +79,7 @@ class RoomMemberDetailsNode @AssistedInject constructor( onOpenDm = ::onStartDM, onStartCall = ::onStartCall, openAvatarPreview = callback::openAvatarPreview, + onVerifyClick = callback::onVerifyUser, ) } } diff --git a/features/userprofile/impl/build.gradle.kts b/features/userprofile/impl/build.gradle.kts index 2c94082d0c1..70769c986db 100644 --- a/features/userprofile/impl/build.gradle.kts +++ b/features/userprofile/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.mediaviewer.api) implementation(projects.features.call.api) + implementation(projects.features.verifysession.api) api(projects.features.userprofile.api) api(projects.features.userprofile.shared) implementation(libs.coil.compose) diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt index 489d82480cb..c09b582e6b4 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt @@ -25,6 +25,7 @@ import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.userprofile.api.UserProfileEntryPoint import io.element.android.features.userprofile.impl.root.UserProfileNode import io.element.android.features.userprofile.shared.UserProfileNodeHelper +import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode @@ -32,7 +33,9 @@ import io.element.android.libraries.architecture.inputs 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.core.UserId import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import kotlinx.parcelize.Parcelize @@ -43,6 +46,7 @@ class UserProfileFlowNode @AssistedInject constructor( private val elementCallEntryPoint: ElementCallEntryPoint, private val sessionIdHolder: CurrentSessionIdHolder, private val mediaViewerEntryPoint: MediaViewerEntryPoint, + private val verifySessionEntryPoint: VerifySessionEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Root, @@ -57,6 +61,9 @@ class UserProfileFlowNode @AssistedInject constructor( @Parcelize data class AvatarPreview(val name: String, val avatarUrl: String) : NavTarget + + @Parcelize + data class VerifyUser(val userId: UserId) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -74,6 +81,10 @@ class UserProfileFlowNode @AssistedInject constructor( override fun onStartCall(dmRoomId: RoomId) { elementCallEntryPoint.startCall(CallType.RoomCall(sessionId = sessionIdHolder.current, roomId = dmRoomId)) } + + override fun onVerifyUser(userId: UserId) { + backstack.push(NavTarget.VerifyUser(userId)) + } } val params = UserProfileNode.UserProfileInputs(userId = inputs().userId) createNode(buildContext, listOf(callback, params)) @@ -96,6 +107,15 @@ class UserProfileFlowNode @AssistedInject constructor( .callback(callback) .build() } + is NavTarget.VerifyUser -> { + val params = VerifySessionEntryPoint.Params( + showDeviceVerifiedScreen = false, + verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId) + ) + verifySessionEntryPoint.nodeBuilder(this, buildContext) + .params(params) + .build() + } } } diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt index 1b99e8409bf..af2e08212c1 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt @@ -75,6 +75,7 @@ class UserProfileNode @AssistedInject constructor( onOpenDm = ::onStartDM, onStartCall = callback::onStartCall, openAvatarPreview = callback::openAvatarPreview, + onVerifyClick = callback::onVerifyUser, ) } } 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 8189f75aa3e..a3378fee31b 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 @@ -73,7 +73,6 @@ class UserProfilePresenter @AssistedInject constructor( val coroutineScope = rememberCoroutineScope() val isCurrentUser = remember { client.isMe(userId) } var confirmationDialog by remember { mutableStateOf(null) } - var userProfile by remember { mutableStateOf(null) } val startDmActionState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val isBlocked: MutableState> = remember { mutableStateOf(AsyncData.Uninitialized) } val isVerified: MutableState> = remember { mutableStateOf(AsyncData.Uninitialized) } @@ -86,9 +85,8 @@ class UserProfilePresenter @AssistedInject constructor( .onEach { isBlocked.value = AsyncData.Success(it) } .launchIn(this) } - LaunchedEffect(Unit) { - userProfile = client.getProfile(userId).getOrNull() - } + val userProfile by produceState(null) { value = client.getProfile(userId).getOrNull() } + LaunchedEffect(Unit) { suspend { client.encryptionService().isUserVerified(userId).getOrThrow() diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt index deb72a5baba..61f46697690 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt @@ -24,6 +24,7 @@ class UserProfileNodeHelper( fun openAvatarPreview(username: String, avatarUrl: String) fun onStartDM(roomId: RoomId) fun onStartCall(dmRoomId: RoomId) + fun onVerifyUser(userId: UserId) } fun onShareUser( 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 dee44377d6f..76681a050cd 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 @@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.components.CreateDmConfirmationBottomSheet import io.element.android.libraries.ui.strings.CommonStrings @@ -50,6 +51,7 @@ fun UserProfileView( onStartCall: (RoomId) -> Unit, goBack: () -> Unit, openAvatarPreview: (username: String, url: String) -> Unit, + onVerifyClick: (UserId) -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -82,7 +84,7 @@ fun UserProfileView( ) Spacer(modifier = Modifier.height(26.dp)) if (!state.isCurrentUser) { - VerifyUserSection(state) + VerifyUserSection(state, onVerifyClick = { onVerifyClick(state.userId) }) BlockUserSection(state) BlockUserDialogs(state) } @@ -116,14 +118,15 @@ fun UserProfileView( } @Composable -private fun VerifyUserSection(state: UserProfileState) { +private fun VerifyUserSection( + state: UserProfileState, + onVerifyClick: () -> Unit, +) { if (state.isVerified.dataOrNull() == false) { ListItem( headlineContent = { Text(stringResource(CommonStrings.common_verify_identity)) }, - supportingContent = { Text(stringResource(R.string.screen_room_member_details_verify_button_subtitle)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), - enabled = false, - onClick = { }, + onClick = onVerifyClick, ) } } @@ -139,6 +142,7 @@ internal fun UserProfileViewPreview( goBack = {}, onOpenDm = {}, onStartCall = {}, - openAvatarPreview = { _, _ -> } + openAvatarPreview = { _, _ -> }, + onVerifyClick = {}, ) } diff --git a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt index 2d2a789b3a8..4a31a521563 100644 --- a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt +++ b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt @@ -13,10 +13,11 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails +import io.element.android.libraries.matrix.api.verification.VerificationRequest interface IncomingVerificationEntryPoint : FeatureEntryPoint { data class Params( - val sessionVerificationRequestDetails: SessionVerificationRequestDetails, + val verificationRequest: VerificationRequest.Incoming, ) : NodeInputs fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder diff --git a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt index 3bae35a3cf4..3dfc7a2962e 100644 --- a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt +++ b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt @@ -12,9 +12,13 @@ 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 +import io.element.android.libraries.matrix.api.verification.VerificationRequest interface VerifySessionEntryPoint : FeatureEntryPoint { - data class Params(val showDeviceVerifiedScreen: Boolean) : NodeInputs + data class Params( + val showDeviceVerifiedScreen: Boolean, + val verificationRequest: VerificationRequest.Outgoing, + ) : NodeInputs fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder @@ -25,8 +29,8 @@ interface VerifySessionEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onEnterRecoveryKey() - fun onResetKey() + fun onLearnMoreAboutEncryption() + fun onBack() fun onDone() } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt index f884f64510a..802eadb13a8 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt @@ -28,7 +28,7 @@ class IncomingVerificationNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins), IncomingVerificationNavigator { private val presenter = presenterFactory.create( - sessionVerificationRequestDetails = inputs().sessionVerificationRequestDetails, + verificationRequest = inputs().verificationRequest, navigator = this, ) 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 b3cec7def2a..21e2910025e 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 @@ -25,6 +25,7 @@ import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -34,7 +35,7 @@ import io.element.android.features.verifysession.impl.incoming.IncomingVerificat import io.element.android.features.verifysession.impl.incoming.IncomingVerificationStateMachine.State as StateMachineState class IncomingVerificationPresenter @AssistedInject constructor( - @Assisted private val sessionVerificationRequestDetails: SessionVerificationRequestDetails, + @Assisted private val verificationRequest: VerificationRequest.Incoming, @Assisted private val navigator: IncomingVerificationNavigator, private val sessionVerificationService: SessionVerificationService, private val stateMachine: IncomingVerificationStateMachine, @@ -43,7 +44,7 @@ class IncomingVerificationPresenter @AssistedInject constructor( @AssistedFactory interface Factory { fun create( - sessionVerificationRequestDetails: SessionVerificationRequestDetails, + verificationRequest: VerificationRequest.Incoming, navigator: IncomingVerificationNavigator, ): IncomingVerificationPresenter } @@ -56,19 +57,19 @@ class IncomingVerificationPresenter @AssistedInject constructor( cancelAnyPendingVerificationAttempt = false ) // Acknowledge the request right now - sessionVerificationService.acknowledgeVerificationRequest(sessionVerificationRequestDetails) + sessionVerificationService.acknowledgeVerificationRequest(verificationRequest) } val stateAndDispatch = stateMachine.rememberStateAndDispatch() val formattedSignInTime = remember { dateFormatter.format( - timestamp = sessionVerificationRequestDetails.firstSeenTimestamp, + timestamp = verificationRequest.details.firstSeenTimestamp, mode = DateFormatterMode.TimeOrDate, ) } val step by remember { derivedStateOf { stateAndDispatch.state.value.toVerificationStep( - sessionVerificationRequestDetails = sessionVerificationRequestDetails, + sessionVerificationRequestDetails = verificationRequest.details, formattedSignInTime = formattedSignInTime, ) } @@ -119,6 +120,7 @@ class IncomingVerificationPresenter @AssistedInject constructor( return IncomingVerificationState( step = step, + request = verificationRequest, eventSink = ::handleEvents, ) } @@ -133,7 +135,7 @@ class IncomingVerificationPresenter @AssistedInject constructor( IncomingVerificationStateMachine.State.RejectingIncomingVerification, null -> { Step.Initial( - deviceDisplayName = sessionVerificationRequestDetails.displayName ?: sessionVerificationRequestDetails.deviceId.value, + deviceDisplayName = sessionVerificationRequestDetails.senderProfile.displayName ?: sessionVerificationRequestDetails.deviceId.value, deviceId = sessionVerificationRequestDetails.deviceId, formattedSignInTime = formattedSignInTime, isWaiting = machineState == IncomingVerificationStateMachine.State.AcceptingIncomingVerification || 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 241ea8e5ad0..95f81af479d 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 @@ -11,10 +11,12 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.verification.SessionVerificationData +import io.element.android.libraries.matrix.api.verification.VerificationRequest @Immutable data class IncomingVerificationState( val step: Step, + val request: VerificationRequest.Incoming, val eventSink: (IncomingVerificationViewEvents) -> Unit, ) { @Stable diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt index d57f56385b2..95589b64def 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt @@ -12,16 +12,26 @@ import io.element.android.features.verifysession.impl.incoming.IncomingVerificat import io.element.android.features.verifysession.impl.ui.aDecimalsSessionVerificationData import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData import io.element.android.libraries.matrix.api.core.DeviceId +import io.element.android.libraries.matrix.api.core.FlowId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails +import io.element.android.libraries.matrix.api.verification.VerificationRequest open class IncomingVerificationStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( anIncomingVerificationState(), - anIncomingVerificationState(step = aStepInitial(isWaiting = true)), + anIncomingVerificationState(step = aStepInitial(isWaiting = false), verificationRequest = anIncomingSessionVerificationRequest()), + anIncomingVerificationState(step = aStepInitial(isWaiting = false), verificationRequest = anIncomingUserVerificationRequest()), + anIncomingVerificationState(step = aStepInitial(isWaiting = true), verificationRequest = anIncomingSessionVerificationRequest()), + anIncomingVerificationState(step = aStepInitial(isWaiting = true), verificationRequest = anIncomingUserVerificationRequest()), anIncomingVerificationState(step = Step.Verifying(data = aEmojisSessionVerificationData(), isWaiting = false)), + anIncomingVerificationState(step = Step.Verifying(data = aEmojisSessionVerificationData(), isWaiting = false), verificationRequest = anIncomingUserVerificationRequest()), anIncomingVerificationState(step = Step.Verifying(data = aEmojisSessionVerificationData(), isWaiting = true)), + anIncomingVerificationState(step = Step.Verifying(data = aEmojisSessionVerificationData(), isWaiting = true), verificationRequest = anIncomingUserVerificationRequest()), anIncomingVerificationState(step = Step.Verifying(data = aDecimalsSessionVerificationData(), isWaiting = false)), anIncomingVerificationState(step = Step.Completed), + anIncomingVerificationState(step = Step.Completed, verificationRequest = anIncomingUserVerificationRequest()), anIncomingVerificationState(step = Step.Failure), anIncomingVerificationState(step = Step.Canceled), // Add other state here @@ -37,10 +47,38 @@ internal fun aStepInitial( isWaiting = isWaiting, ) +internal fun anIncomingSessionVerificationRequest() = VerificationRequest.Incoming.OtherSession( + details = SessionVerificationRequestDetails( + senderProfile = SessionVerificationRequestDetails.SenderProfile( + userId = UserId("@alice:example.com"), + displayName = "Alice", + avatarUrl = null, + ), + flowId = FlowId("1234"), + deviceId = DeviceId("ILAKNDNASDLK"), + firstSeenTimestamp = 1234567890, + ) +) + +internal fun anIncomingUserVerificationRequest() = VerificationRequest.Incoming.User( + details = SessionVerificationRequestDetails( + senderProfile = SessionVerificationRequestDetails.SenderProfile( + userId = UserId("@alice:example.com"), + displayName = "Alice", + avatarUrl = null, + ), + flowId = FlowId("1234"), + deviceId = DeviceId("ILAKNDNASDLK"), + firstSeenTimestamp = 1234567890, + ) +) + internal fun anIncomingVerificationState( step: Step = aStepInitial(), + verificationRequest: VerificationRequest.Incoming = anIncomingSessionVerificationRequest(), eventSink: (IncomingVerificationViewEvents) -> Unit = {}, ) = IncomingVerificationState( step = step, + request = verificationRequest, eventSink = eventSink, ) 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 b31c937f369..4873f0adb6e 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 @@ -10,7 +10,9 @@ package io.element.android.features.verifysession.impl.incoming import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable @@ -27,6 +29,7 @@ import io.element.android.features.verifysession.impl.incoming.IncomingVerificat import io.element.android.features.verifysession.impl.incoming.ui.SessionDetailsView import io.element.android.features.verifysession.impl.ui.VerificationBottomMenu import io.element.android.features.verifysession.impl.ui.VerificationContentVerifying +import io.element.android.features.verifysession.impl.ui.VerificationUserProfileContent import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle @@ -38,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.verification.SessionVerificationData +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.ui.strings.CommonStrings /** @@ -62,7 +66,7 @@ fun IncomingVerificationView( ) }, header = { - IncomingVerificationHeader(step = step) + IncomingVerificationHeader(step = step, request = state.request) }, footer = { IncomingVerificationBottomMenu( @@ -72,37 +76,54 @@ fun IncomingVerificationView( ) { IncomingVerificationContent( step = step, + request = state.request, ) } } @Composable -private fun IncomingVerificationHeader(step: Step) { +private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.Incoming) { val iconStyle = when (step) { Step.Canceled -> BigIcon.Style.AlertSolid - is Step.Initial -> BigIcon.Style.Default(CompoundIcons.LockSolid()) - is Step.Verifying -> BigIcon.Style.Default(CompoundIcons.Reaction()) + is Step.Initial -> if (step.isWaiting) { + BigIcon.Style.Loading + } else { + when (request) { + is VerificationRequest.Incoming.OtherSession -> BigIcon.Style.Default(CompoundIcons.LockSolid()) + is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid()) + } + } + is Step.Verifying -> if (step.isWaiting) { BigIcon.Style.Loading } else { BigIcon.Style.Default(CompoundIcons.LockSolid()) } Step.Completed -> BigIcon.Style.SuccessSolid Step.Failure -> BigIcon.Style.AlertSolid } val titleTextId = when (step) { - Step.Canceled -> R.string.screen_session_verification_request_failure_title + Step.Canceled -> CommonStrings.common_verification_failed is Step.Initial -> R.string.screen_session_verification_request_title is Step.Verifying -> when (step.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_title } - Step.Completed -> R.string.screen_session_verification_request_success_title + Step.Completed -> CommonStrings.common_verification_complete Step.Failure -> R.string.screen_session_verification_request_failure_title } val subtitleTextId = when (step) { Step.Canceled -> R.string.screen_session_verification_request_failure_subtitle - is Step.Initial -> R.string.screen_session_verification_request_subtitle + is Step.Initial -> when (request) { + is VerificationRequest.Incoming.OtherSession -> R.string.screen_session_verification_request_subtitle + is VerificationRequest.Incoming.User -> R.string.screen_session_verification_user_responder_subtitle + } is Step.Verifying -> when (step.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_subtitle - is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_subtitle + is SessionVerificationData.Emojis -> when (request) { + is VerificationRequest.Incoming.OtherSession -> R.string.screen_session_verification_compare_emojis_subtitle + is VerificationRequest.Incoming.User -> R.string.screen_session_verification_compare_emojis_user_subtitle + } + } + Step.Completed -> when (request) { + is VerificationRequest.Incoming.OtherSession -> R.string.screen_session_verification_complete_subtitle + is VerificationRequest.Incoming.User -> R.string.screen_session_verification_complete_user_subtitle } - Step.Completed -> R.string.screen_session_verification_request_success_subtitle Step.Failure -> R.string.screen_session_verification_request_failure_subtitle } PageTitle( @@ -115,9 +136,10 @@ private fun IncomingVerificationHeader(step: Step) { @Composable private fun IncomingVerificationContent( step: Step, + request: VerificationRequest.Incoming, ) { when (step) { - is Step.Initial -> ContentInitial(step) + is Step.Initial -> ContentInitial(step, request) is Step.Verifying -> VerificationContentVerifying(step.data) else -> Unit } @@ -126,24 +148,41 @@ private fun IncomingVerificationContent( @Composable private fun ContentInitial( initialIncoming: Step.Initial, + request: VerificationRequest.Incoming, ) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(24.dp), - ) { - SessionDetailsView( - deviceName = initialIncoming.deviceDisplayName, - deviceId = initialIncoming.deviceId, - signInFormattedTimestamp = initialIncoming.formattedSignInTime, - ) - Text( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 16.dp), - text = stringResource(R.string.screen_session_verification_request_footer), - style = ElementTheme.typography.fontBodyMdMedium, - textAlign = TextAlign.Center, - ) + + when (request) { + is VerificationRequest.Incoming.OtherSession -> { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + SessionDetailsView( + deviceName = initialIncoming.deviceDisplayName, + deviceId = initialIncoming.deviceId, + signInFormattedTimestamp = initialIncoming.formattedSignInTime, + ) + Text( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(bottom = 16.dp), + text = stringResource(R.string.screen_session_verification_request_footer), + style = ElementTheme.typography.fontBodyMdMedium, + textAlign = TextAlign.Center, + ) + } + } + is VerificationRequest.Incoming.User -> { + Column( + modifier = Modifier.fillMaxWidth().padding(top = 24.dp), + ) { + VerificationUserProfileContent( + userId = request.details.senderProfile.userId, + displayName = request.details.senderProfile.displayName, + avatarUrl = request.details.senderProfile.avatarUrl, + ) + } + } } } @@ -157,16 +196,7 @@ private fun IncomingVerificationBottomMenu( when (step) { is Step.Initial -> { if (step.isWaiting) { - VerificationBottomMenu { - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_identity_waiting_on_other_device), - onClick = {}, - enabled = false, - showProgress = true, - ) - InvisibleButton() - } + // Show nothing } else { VerificationBottomMenu { Button( @@ -184,16 +214,7 @@ private fun IncomingVerificationBottomMenu( } is Step.Verifying -> { if (step.isWaiting) { - VerificationBottomMenu { - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_session_verification_positive_button_verifying_ongoing), - onClick = {}, - enabled = false, - showProgress = true, - ) - InvisibleButton() - } + // Show nothing } else { VerificationBottomMenu { Button( diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt index dbd39c4a129..c0815f22338 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt @@ -7,8 +7,6 @@ package io.element.android.features.verifysession.impl.outgoing -import android.app.Activity -import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext @@ -21,7 +19,6 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.appconfig.LearnMoreConfig import io.element.android.compound.theme.ElementTheme import io.element.android.features.verifysession.api.VerifySessionEntryPoint -import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope @@ -33,28 +30,22 @@ class VerifySelfSessionNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { private val callback = plugins().first() + private val inputs = inputs() + private val presenter = presenterFactory.create( - showDeviceVerifiedScreen = inputs().showDeviceVerifiedScreen, + showDeviceVerifiedScreen = inputs.showDeviceVerifiedScreen, + verificationRequest = inputs.verificationRequest, ) - private fun onLearnMoreClick(activity: Activity, dark: Boolean) { - activity.openUrlInChromeCustomTab(null, dark, LearnMoreConfig.ENCRYPTION_URL) - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() - val activity = requireNotNull(LocalActivity.current) - val isDark = ElementTheme.isLightTheme.not() VerifySelfSessionView( state = state, modifier = modifier, - onLearnMoreClick = { - onLearnMoreClick(activity, isDark) - }, - onEnterRecoveryKey = callback::onEnterRecoveryKey, - onResetKey = callback::onResetKey, + onLearnMoreClick = callback::onLearnMoreAboutEncryption, onFinish = callback::onDone, + onBack = callback::onBack, ) } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenter.kt index c0760074ed1..e7a05aa3e1c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenter.kt @@ -11,137 +11,113 @@ package io.element.android.features.verifysession.impl.outgoing import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import com.freeletics.flowredux.compose.rememberStateAndDispatch import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.features.logout.api.LogoutUseCase -import io.element.android.libraries.architecture.AsyncAction 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.meta.BuildMeta import io.element.android.libraries.matrix.api.encryption.EncryptionService -import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationFlowState -import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import timber.log.Timber import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionStateMachine.Event as StateMachineEvent import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionStateMachine.State as StateMachineState class VerifySelfSessionPresenter @AssistedInject constructor( @Assisted private val showDeviceVerifiedScreen: Boolean, + @Assisted private val verificationRequest: VerificationRequest.Outgoing, private val sessionVerificationService: SessionVerificationService, private val encryptionService: EncryptionService, - private val stateMachine: VerifySelfSessionStateMachine, - private val buildMeta: BuildMeta, - private val sessionPreferencesStore: SessionPreferencesStore, - private val logoutUseCase: LogoutUseCase, ) : Presenter { @AssistedFactory interface Factory { - fun create(showDeviceVerifiedScreen: Boolean): VerifySelfSessionPresenter + fun create( + verificationRequest: VerificationRequest.Outgoing, + showDeviceVerifiedScreen: Boolean, + ): VerifySelfSessionPresenter } + private val stateMachine = VerifySelfSessionStateMachine( + sessionVerificationService = sessionVerificationService, + encryptionService = encryptionService, + ) + @Composable override fun present(): VerifySelfSessionState { - val coroutineScope = rememberCoroutineScope() - LaunchedEffect(Unit) { - // Force reset, just in case the service was left in a broken state - sessionVerificationService.reset(true) - } - val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() val stateAndDispatch = stateMachine.rememberStateAndDispatch() - val skipVerification by sessionPreferencesStore.isSessionVerificationSkipped().collectAsState(initial = false) + val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() - val signOutAction = remember { - mutableStateOf>(AsyncAction.Uninitialized) - } val step by remember { derivedStateOf { - if (skipVerification) { - VerifySelfSessionState.Step.Skipped - } else { - when (sessionVerifiedStatus) { - SessionVerifiedStatus.Unknown -> VerifySelfSessionState.Step.Loading - SessionVerifiedStatus.NotVerified -> { - stateAndDispatch.state.value.toVerificationStep( - canEnterRecoveryKey = recoveryState == RecoveryState.INCOMPLETE - ) - } - SessionVerifiedStatus.Verified -> { - if (stateAndDispatch.state.value != StateMachineState.Initial || showDeviceVerifiedScreen) { - // The user has verified the session, we need to show the success screen - VerifySelfSessionState.Step.Completed - } else { - // Automatic verification, which can happen on freshly created account, in this case, skip the screen - VerifySelfSessionState.Step.Skipped + when (verificationRequest) { + is VerificationRequest.Outgoing.CurrentSession -> { + when (sessionVerifiedStatus) { + SessionVerifiedStatus.Unknown -> VerifySelfSessionState.Step.Loading + SessionVerifiedStatus.NotVerified -> { + stateAndDispatch.state.value.toVerificationStep() + } + SessionVerifiedStatus.Verified -> { + if (stateAndDispatch.state.value != StateMachineState.Initial || showDeviceVerifiedScreen) { + // The user has verified the session, we need to show the success screen + VerifySelfSessionState.Step.Completed + } else { + // Automatic verification, which can happen on freshly created account, in this case, skip the screen + VerifySelfSessionState.Step.Exit + } } } } + is VerificationRequest.Outgoing.User -> stateAndDispatch.state.value.toVerificationStep() } } } + // Start this after observing state machine LaunchedEffect(Unit) { + // Force reset, just in case the service was left in a broken state + sessionVerificationService.reset(cancelAnyPendingVerificationAttempt = true) + observeVerificationService() } fun handleEvents(event: VerifySelfSessionViewEvents) { Timber.d("Verification user action: ${event::class.simpleName}") when (event) { - VerifySelfSessionViewEvents.UseAnotherDevice -> stateAndDispatch.dispatchAction(StateMachineEvent.UseAnotherDevice) - VerifySelfSessionViewEvents.RequestVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.RequestVerification) + // Just relay the event to the state machine + VerifySelfSessionViewEvents.RequestVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.RequestVerification(verificationRequest)) VerifySelfSessionViewEvents.StartSasVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.StartSasVerification) VerifySelfSessionViewEvents.ConfirmVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptChallenge) VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge) VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel) VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset) - VerifySelfSessionViewEvents.SignOut -> coroutineScope.signOut(signOutAction) - VerifySelfSessionViewEvents.SkipVerification -> coroutineScope.launch { - sessionPreferencesStore.setSkipSessionVerification(true) - } } } return VerifySelfSessionState( step = step, - signOutAction = signOutAction.value, - displaySkipButton = buildMeta.isDebuggable, + request = verificationRequest, eventSink = ::handleEvents, ) } - private fun StateMachineState?.toVerificationStep( - canEnterRecoveryKey: Boolean - ): VerifySelfSessionState.Step = + private fun StateMachineState?.toVerificationStep(): VerifySelfSessionState.Step = when (val machineState = this) { StateMachineState.Initial, null -> { - VerifySelfSessionState.Step.Initial( - canEnterRecoveryKey = canEnterRecoveryKey, - isLastDevice = encryptionService.isLastDevice.value - ) - } - VerifySelfSessionStateMachine.State.UseAnotherDevice -> { - VerifySelfSessionState.Step.UseAnotherDevice + VerifySelfSessionState.Step.Initial } - StateMachineState.RequestingVerification, - StateMachineState.StartingSasVerification, - StateMachineState.SasVerificationStarted, - StateMachineState.Canceling -> { + is StateMachineState.RequestingVerification, + is StateMachineState.StartingSasVerification, + StateMachineState.SasVerificationStarted -> { VerifySelfSessionState.Step.AwaitingOtherDeviceResponse } @@ -149,7 +125,7 @@ class VerifySelfSessionPresenter @AssistedInject constructor( VerifySelfSessionState.Step.Ready } - StateMachineState.Canceled -> { + is StateMachineState.Canceled -> { VerifySelfSessionState.Step.Canceled } @@ -164,6 +140,10 @@ class VerifySelfSessionPresenter @AssistedInject constructor( StateMachineState.Completed -> { VerifySelfSessionState.Step.Completed } + + StateMachineState.Exit -> { + VerifySelfSessionState.Step.Exit + } } private fun CoroutineScope.observeVerificationService() { @@ -171,33 +151,27 @@ class VerifySelfSessionPresenter @AssistedInject constructor( .onEach { Timber.d("Verification flow state: ${it::class.simpleName}") } .onEach { verificationAttemptState -> when (verificationAttemptState) { - VerificationFlowState.Initial -> stateMachine.dispatch(VerifySelfSessionStateMachine.Event.Reset) + VerificationFlowState.Initial -> stateMachine.dispatch(StateMachineEvent.Reset) VerificationFlowState.DidAcceptVerificationRequest -> { - stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidAcceptVerificationRequest) + stateMachine.dispatch(StateMachineEvent.DidAcceptVerificationRequest) } VerificationFlowState.DidStartSasVerification -> { - stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidStartSasVerification) + stateMachine.dispatch(StateMachineEvent.DidStartSasVerification) } is VerificationFlowState.DidReceiveVerificationData -> { - stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidReceiveChallenge(verificationAttemptState.data)) + stateMachine.dispatch(StateMachineEvent.DidReceiveChallenge(verificationAttemptState.data)) } VerificationFlowState.DidFinish -> { - stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidAcceptChallenge) + stateMachine.dispatch(StateMachineEvent.DidAcceptChallenge) } VerificationFlowState.DidCancel -> { - stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidCancel) + stateMachine.dispatch(StateMachineEvent.DidCancel) } VerificationFlowState.DidFail -> { - stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidFail) + stateMachine.dispatch(StateMachineEvent.DidFail) } } } .launchIn(this) } - - private fun CoroutineScope.signOut(signOutAction: MutableState>) = launch { - suspend { - logoutUseCase.logout(ignoreSdkError = true) - }.runCatchingUpdatingState(signOutAction) - } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionState.kt index 3c998247c73..a8f5a6771b0 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionState.kt @@ -9,29 +9,25 @@ package io.element.android.features.verifysession.impl.outgoing import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.verification.SessionVerificationData +import io.element.android.libraries.matrix.api.verification.VerificationRequest @Immutable data class VerifySelfSessionState( val step: Step, - val signOutAction: AsyncAction, - val displaySkipButton: Boolean, + val request: VerificationRequest.Outgoing, val eventSink: (VerifySelfSessionViewEvents) -> Unit, ) { @Stable sealed interface Step { data object Loading : Step - - // FIXME canEnterRecoveryKey value is never read. - data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean = false) : Step - data object UseAnotherDevice : Step + data object Initial : Step data object Canceled : Step data object AwaitingOtherDeviceResponse : Step data object Ready : Step data class Verifying(val data: SessionVerificationData, val state: AsyncData) : Step data object Completed : Step - data object Skipped : Step + data object Exit : Step } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt index a71b09c7831..96f57b58d9e 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt @@ -19,67 +19,60 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus +import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.timeout -import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import com.freeletics.flowredux.dsl.State as MachineState @OptIn(FlowPreview::class) -class VerifySelfSessionStateMachine @Inject constructor( +class VerifySelfSessionStateMachine( private val sessionVerificationService: SessionVerificationService, private val encryptionService: EncryptionService, ) : FlowReduxStateMachine( - initialState = State.Initial + initialState = State.Initial, ) { init { spec { inState { - on { _: Event.UseAnotherDevice, state -> - state.override { State.UseAnotherDevice.andLogStateChange() } - } - } - inState { - on { _: Event.RequestVerification, state -> - state.override { State.RequestingVerification.andLogStateChange() } + on { event, state -> + when (event.verificationRequest) { + is VerificationRequest.Outgoing.CurrentSession -> sessionVerificationService.requestCurrentSessionVerification() + is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(event.verificationRequest.userId) + } + state.override { State.RequestingVerification(event.verificationRequest).andLogStateChange() } } } inState { - onEnterEffect { - sessionVerificationService.requestVerification() - } - on { _: Event.DidAcceptVerificationRequest, state -> + on { _, state -> state.override { State.VerificationRequestAccepted.andLogStateChange() } } } - inState { - onEnterEffect { - sessionVerificationService.startVerification() - } - } + inState {} inState { - on { _: Event.StartSasVerification, state -> - state.override { State.StartingSasVerification.andLogStateChange() } + onActionEffect { _, state -> + sessionVerificationService.startVerification() } } inState { - on { _: Event.Reset, state -> + on { _, state -> state.override { State.Initial.andLogStateChange() } } } inState { - on { event: Event.DidReceiveChallenge, state -> + on { event, state -> state.override { State.Verifying.ChallengeReceived(event.data).andLogStateChange() } } } inState { - on { _: Event.AcceptChallenge, state -> + on { _, state -> state.override { State.Verifying.Replying(state.snapshot.data, accept = true).andLogStateChange() } } - on { _: Event.DeclineChallenge, state -> + on { _, state -> state.override { State.Verifying.Replying(state.snapshot.data, accept = false).andLogStateChange() } } } @@ -91,7 +84,7 @@ class VerifySelfSessionStateMachine @Inject constructor( sessionVerificationService.declineVerification() } } - on { _: Event.DidAcceptChallenge, state -> + on { _, state -> // If a key backup exists, wait until it's restored or a timeout happens val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse() if (hasBackup) { @@ -104,21 +97,14 @@ class VerifySelfSessionStateMachine @Inject constructor( state.override { State.Completed.andLogStateChange() } } } - inState { - // TODO The 'Canceling' -> 'Canceled' transitions doesn't seem to work anymore, check if something changed in the Rust SDK - onEnterEffect { - sessionVerificationService.cancelVerification() - } - } inState { logReceivedEvents() - on { _: Event.DidStartSasVerification, state: MachineState -> + on { _, state: MachineState -> state.override { State.SasVerificationStarted.andLogStateChange() } } - on { _: Event.Cancel, state: MachineState -> + on { event, state: MachineState -> when (state.snapshot) { - State.Initial, State.Completed, State.Canceled -> state.noChange() - State.UseAnotherDevice -> state.override { State.Initial.andLogStateChange() } + State.Initial, State.Completed, is State.Canceled -> state.override { State.Exit } // For some reason `cancelVerification` is not calling its delegate `didCancel` method so we don't pass from // `Canceling` state to `Canceled` automatically anymore else -> { @@ -127,10 +113,10 @@ class VerifySelfSessionStateMachine @Inject constructor( } } } - on { _: Event.DidCancel, state: MachineState -> + on { event, state: MachineState -> state.override { State.Canceled.andLogStateChange() } } - on { _: Event.DidFail, state: MachineState -> + on { event, state: MachineState -> when (state.snapshot) { is State.RequestingVerification -> state.override { State.Initial.andLogStateChange() } else -> state.override { State.Canceled.andLogStateChange() } @@ -141,14 +127,11 @@ class VerifySelfSessionStateMachine @Inject constructor( } sealed interface State { - /** The initial state, before verification started. */ - data object Initial : State - /** Let the user know that they need to get ready on their other session. */ - data object UseAnotherDevice : State + data object Initial : State /** Waiting for verification acceptance. */ - data object RequestingVerification : State + data class RequestingVerification(val verificationRequest: VerificationRequest.Outgoing) : State /** Verification request accepted. Waiting for start. */ data object VerificationRequestAccepted : State @@ -167,22 +150,18 @@ class VerifySelfSessionStateMachine @Inject constructor( data class Replying(override val data: SessionVerificationData, val accept: Boolean) : Verifying(data) } - /** The verification is being canceled. */ - data object Canceling : State - /** The verification has been canceled, remotely or locally. */ data object Canceled : State /** Verification successful. */ data object Completed : State + + data object Exit : State } sealed interface Event { - /** User wants to use another session. */ - data object UseAnotherDevice : Event - /** Request verification. */ - data object RequestVerification : Event + data class RequestVerification(val verificationRequest: VerificationRequest.Outgoing) : Event /** The current verification request has been accepted. */ data object DidAcceptVerificationRequest : Event @@ -218,3 +197,8 @@ class VerifySelfSessionStateMachine @Inject constructor( data object Reset : Event } } + +sealed interface VerificationType { + data object CurrentSession : VerificationType + data object User : VerificationType +} diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateProvider.kt index 9ebb80b68be..ec3e1c29e2c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateProvider.kt @@ -11,66 +11,76 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionState.Step import io.element.android.features.verifysession.impl.ui.aDecimalsSessionVerificationData import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificationData -import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.verification.VerificationRequest open class VerifySelfSessionStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aVerifySelfSessionState(displaySkipButton = true), aVerifySelfSessionState( - step = Step.AwaitingOtherDeviceResponse + step = Step.Initial, + request = anOutgoingSessionVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized) + step = Step.Initial, + request = anOutgoingUserVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading()) + step = Step.AwaitingOtherDeviceResponse, + request = anOutgoingSessionVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Canceled + step = Step.AwaitingOtherDeviceResponse, + request = anOutgoingUserVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Ready + step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized), + request = anOutgoingSessionVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized) + step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized), + request = anOutgoingUserVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Initial(canEnterRecoveryKey = true) + step = Step.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading()) ), aVerifySelfSessionState( - step = Step.Initial(canEnterRecoveryKey = true, isLastDevice = true) + step = Step.Canceled ), aVerifySelfSessionState( - step = Step.Completed, - displaySkipButton = true, + step = Step.Ready ), aVerifySelfSessionState( - signOutAction = AsyncAction.Loading, - displaySkipButton = true, + step = Step.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized) ), aVerifySelfSessionState( - step = Step.Loading + step = Step.Completed, + request = anOutgoingSessionVerificationRequest(), + ), + aVerifySelfSessionState( + step = Step.Completed, + request = anOutgoingUserVerificationRequest(), ), aVerifySelfSessionState( - step = Step.Skipped + step = Step.Loading ), aVerifySelfSessionState( - step = Step.UseAnotherDevice + step = Step.Exit ), // Add other state here ) } +internal fun anOutgoingUserVerificationRequest() = VerificationRequest.Outgoing.User(userId = UserId("@alice:example.com")) +internal fun anOutgoingSessionVerificationRequest() = VerificationRequest.Outgoing.CurrentSession + internal fun aVerifySelfSessionState( - step: Step = Step.Initial(canEnterRecoveryKey = false), - signOutAction: AsyncAction = AsyncAction.Uninitialized, - displaySkipButton: Boolean = false, + step: Step = Step.Initial, + request: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(), eventSink: (VerifySelfSessionViewEvents) -> Unit = {}, ) = VerifySelfSessionState( step = step, - displaySkipButton = displaySkipButton, + request = request, eventSink = eventSink, - signOutAction = signOutAction, ) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt index 2a73a4b6279..3d09b0a375b 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt @@ -17,12 +17,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -32,22 +28,20 @@ import io.element.android.features.verifysession.impl.R import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionState.Step import io.element.android.features.verifysession.impl.ui.VerificationBottomMenu import io.element.android.features.verifysession.impl.ui.VerificationContentVerifying -import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle -import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.InvisibleButton -import io.element.android.libraries.designsystem.theme.components.OutlinedButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.verification.SessionVerificationData +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.ui.strings.CommonStrings @OptIn(ExperimentalMaterial3Api::class) @@ -55,17 +49,15 @@ import io.element.android.libraries.ui.strings.CommonStrings fun VerifySelfSessionView( state: VerifySelfSessionState, onLearnMoreClick: () -> Unit, - onEnterRecoveryKey: () -> Unit, - onResetKey: () -> Unit, onFinish: () -> Unit, + onBack: () -> Unit, modifier: Modifier = Modifier, ) { val step = state.step fun cancelOrResetFlow() { when (step) { - is Step.Canceled -> state.eventSink(VerifySelfSessionViewEvents.Reset) - is Step.AwaitingOtherDeviceResponse, - Step.UseAnotherDevice, + is Step.AwaitingOtherDeviceResponse, is Step.Canceled -> state.eventSink(VerifySelfSessionViewEvents.Reset) + Step.Initial, Step.Completed -> onBack() Step.Ready -> state.eventSink(VerifySelfSessionViewEvents.Cancel) is Step.Verifying -> { if (!step.state.isLoading()) { @@ -76,18 +68,11 @@ fun VerifySelfSessionView( } } - val latestOnFinish by rememberUpdatedState(newValue = onFinish) - LaunchedEffect(step, latestOnFinish) { - if (step is Step.Skipped) { - latestOnFinish() - } - } BackHandler { cancelOrResetFlow() } - if (step is Step.Loading || - step is Step.Skipped) { + if (step is Step.Loading) { // Just display a loader in this case, to avoid UI glitch. Box( modifier = Modifier.fillMaxSize(), @@ -99,96 +84,89 @@ fun VerifySelfSessionView( HeaderFooterPage( modifier = modifier, topBar = { - TopAppBar( - title = {}, - actions = { - if (step !is Step.Completed && - state.displaySkipButton && - LocalInspectionMode.current.not()) { - TextButton( - text = stringResource(CommonStrings.action_skip), - onClick = { state.eventSink(VerifySelfSessionViewEvents.SkipVerification) } - ) - } - if (step is Step.Initial) { - TextButton( - text = stringResource(CommonStrings.action_signout), - onClick = { state.eventSink(VerifySelfSessionViewEvents.SignOut) } - ) - } - } - ) + TopAppBar(title = {}) }, header = { - VerifySelfSessionHeader(step = step) + VerifySelfSessionHeader(step = step, request = state.request) }, footer = { VerifySelfSessionBottomMenu( screenState = state, onCancelClick = ::cancelOrResetFlow, - onEnterRecoveryKey = onEnterRecoveryKey, onContinueClick = onFinish, - onResetKey = onResetKey, ) } ) { VerifySelfSessionContent( flowState = step, + request = state.request, onLearnMoreClick = onLearnMoreClick, ) } } - - when (state.signOutAction) { - AsyncAction.Loading -> { - ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) - } - is AsyncAction.Success, - is AsyncAction.Confirming, - is AsyncAction.Failure, - AsyncAction.Uninitialized -> Unit - } } @Composable -private fun VerifySelfSessionHeader(step: Step) { +private fun VerifySelfSessionHeader(step: Step, request: VerificationRequest.Outgoing) { val iconStyle = when (step) { Step.Loading -> error("Should not happen") - is Step.Initial -> BigIcon.Style.Default(CompoundIcons.LockSolid()) - Step.UseAnotherDevice -> BigIcon.Style.Default(CompoundIcons.Devices()) - Step.AwaitingOtherDeviceResponse -> BigIcon.Style.Default(CompoundIcons.Devices()) + Step.Initial -> when (request) { + is VerificationRequest.Outgoing.CurrentSession -> BigIcon.Style.Default(CompoundIcons.Devices()) + is VerificationRequest.Outgoing.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid()) + } + Step.AwaitingOtherDeviceResponse -> BigIcon.Style.Loading Step.Canceled -> BigIcon.Style.AlertSolid - Step.Ready, is Step.Verifying -> BigIcon.Style.Default(CompoundIcons.Reaction()) + Step.Ready -> BigIcon.Style.Default(CompoundIcons.Reaction()) Step.Completed -> BigIcon.Style.SuccessSolid - is Step.Skipped -> return + is Step.Verifying -> { + if (step.state is AsyncData.Loading) { + BigIcon.Style.Loading + } else { + BigIcon.Style.Default(CompoundIcons.Reaction()) + } + } + is Step.Exit -> return } val titleTextId = when (step) { Step.Loading -> error("Should not happen") - is Step.Initial -> R.string.screen_identity_confirmation_title - Step.UseAnotherDevice -> R.string.screen_session_verification_use_another_device_title - Step.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_another_device_title + Step.Initial -> when (request) { + is VerificationRequest.Outgoing.CurrentSession -> R.string.screen_session_verification_use_another_device_title + is VerificationRequest.Outgoing.User -> R.string.screen_session_verification_user_initiator_title + } + Step.AwaitingOtherDeviceResponse -> when (request) { + is VerificationRequest.Outgoing.CurrentSession -> R.string.screen_session_verification_waiting_another_device_title + is VerificationRequest.Outgoing.User -> R.string.screen_session_verification_waiting_other_user_title + } Step.Canceled -> CommonStrings.common_verification_failed Step.Ready -> R.string.screen_session_verification_compare_emojis_title - Step.Completed -> R.string.screen_identity_confirmed_title + Step.Completed -> CommonStrings.common_verification_complete is Step.Verifying -> when (step.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_title } - is Step.Skipped -> return + is Step.Exit -> return } val subtitleTextId = when (step) { Step.Loading -> error("Should not happen") - is Step.Initial -> R.string.screen_identity_confirmation_subtitle - Step.UseAnotherDevice -> R.string.screen_session_verification_use_another_device_subtitle - Step.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_another_device_subtitle + Step.Initial -> when (request) { + is VerificationRequest.Outgoing.CurrentSession -> R.string.screen_session_verification_use_another_device_subtitle + is VerificationRequest.Outgoing.User -> R.string.screen_session_verification_user_initiator_subtitle + } + Step.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_subtitle Step.Canceled -> R.string.screen_session_verification_failed_subtitle Step.Ready -> R.string.screen_session_verification_ready_subtitle - Step.Completed -> R.string.screen_identity_confirmed_subtitle + Step.Completed -> when (request) { + is VerificationRequest.Outgoing.CurrentSession -> R.string.screen_identity_confirmed_subtitle + is VerificationRequest.Outgoing.User -> R.string.screen_session_verification_complete_user_subtitle + } is Step.Verifying -> when (step.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_subtitle - is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_subtitle + is SessionVerificationData.Emojis -> when (request) { + is VerificationRequest.Outgoing.CurrentSession -> R.string.screen_session_verification_compare_emojis_subtitle + is VerificationRequest.Outgoing.User -> R.string.screen_session_verification_compare_emojis_user_subtitle + } } - is Step.Skipped -> return + is Step.Exit -> return } PageTitle( @@ -201,11 +179,15 @@ private fun VerifySelfSessionHeader(step: Step) { @Composable private fun VerifySelfSessionContent( flowState: Step, + request: VerificationRequest.Outgoing, onLearnMoreClick: () -> Unit, ) { when (flowState) { is Step.Initial -> { - ContentInitial(onLearnMoreClick) + when (request) { + is VerificationRequest.Outgoing.CurrentSession -> Unit + is VerificationRequest.Outgoing.User -> ContentInitial(onLearnMoreClick) + } } is Step.Verifying -> { VerificationContentVerifying(flowState.data) @@ -235,8 +217,6 @@ private fun ContentInitial( @Composable private fun VerifySelfSessionBottomMenu( screenState: VerifySelfSessionState, - onEnterRecoveryKey: () -> Unit, - onResetKey: () -> Unit, onCancelClick: () -> Unit, onContinueClick: () -> Unit, ) { @@ -248,27 +228,6 @@ private fun VerifySelfSessionBottomMenu( when (verificationViewState) { Step.Loading -> error("Should not happen") is Step.Initial -> { - VerificationBottomMenu { - if (verificationViewState.isLastDevice.not()) { - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_identity_use_another_device), - onClick = { eventSink(VerifySelfSessionViewEvents.UseAnotherDevice) }, - ) - } - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_session_verification_enter_recovery_key), - onClick = onEnterRecoveryKey, - ) - OutlinedButton( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_identity_confirmation_cannot_confirm), - onClick = onResetKey, - ) - } - } - is Step.UseAnotherDevice -> { VerificationBottomMenu { Button( modifier = Modifier.fillMaxWidth(), @@ -302,39 +261,20 @@ private fun VerifySelfSessionBottomMenu( ) } } - is Step.AwaitingOtherDeviceResponse -> { - VerificationBottomMenu { - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_identity_waiting_on_other_device), - onClick = {}, - showProgress = true, - enabled = false, - ) - InvisibleButton() - } - } + is Step.AwaitingOtherDeviceResponse -> Unit is Step.Verifying -> { - val positiveButtonTitle = if (isVerifying) { - stringResource(R.string.screen_session_verification_positive_button_verifying_ongoing) + if (isVerifying) { + // Show nothing } else { - stringResource(R.string.screen_session_verification_they_match) - } - VerificationBottomMenu { - Button( - modifier = Modifier.fillMaxWidth(), - text = positiveButtonTitle, - showProgress = isVerifying, - enabled = !isVerifying, - onClick = { - if (!isVerifying) { + VerificationBottomMenu { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_session_verification_they_match), + onClick = { eventSink(VerifySelfSessionViewEvents.ConfirmVerification) - } - }, - ) - if (isVerifying) { - InvisibleButton() - } else { + }, + ) + TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.screen_session_verification_they_dont_match), @@ -353,7 +293,7 @@ private fun VerifySelfSessionBottomMenu( InvisibleButton() } } - is Step.Skipped -> return + is Step.Exit -> return } } @@ -363,8 +303,7 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta VerifySelfSessionView( state = state, onLearnMoreClick = {}, - onEnterRecoveryKey = {}, - onResetKey = {}, onFinish = {}, + onBack = {}, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewEvents.kt index 752dbc3d7aa..281991cb2d9 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewEvents.kt @@ -8,13 +8,10 @@ package io.element.android.features.verifysession.impl.outgoing sealed interface VerifySelfSessionViewEvents { - data object UseAnotherDevice : VerifySelfSessionViewEvents data object RequestVerification : VerifySelfSessionViewEvents data object StartSasVerification : VerifySelfSessionViewEvents data object ConfirmVerification : VerifySelfSessionViewEvents data object DeclineVerification : VerifySelfSessionViewEvents data object Cancel : VerifySelfSessionViewEvents data object Reset : VerifySelfSessionViewEvents - data object SignOut : VerifySelfSessionViewEvents - data object SkipVerification : VerifySelfSessionViewEvents } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt new file mode 100644 index 00000000000..700df61b92d --- /dev/null +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.verifysession.impl.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.core.UserId + +@Composable +fun VerificationUserProfileContent( + userId: UserId, + displayName: String?, + avatarUrl: String?, +) { + val avatarData = remember(userId, displayName, avatarUrl) { + AvatarData(id = userId.value, name = displayName, url = avatarUrl, size = AvatarSize.UserVerification) + } + + Row( + modifier = Modifier.fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(ElementTheme.colors.bgSubtleSecondary) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Avatar(avatarData) + + Spacer(modifier = Modifier.padding(12.dp)) + + Column { + Text(text = displayName ?: userId.value, style = ElementTheme.typography.fontBodyLgMedium, color = ElementTheme.colors.textPrimary) + + if (displayName != null) { + Text(text = userId.value, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary) + } + } + } +} + +@PreviewsDayNight +@Composable +internal fun VerificationUserProfileContentPreview() = ElementPreview { + VerificationUserProfileContent( + userId = UserId("@alice:example.com"), + displayName = "Alice", + avatarUrl = "https://example.com/avatar.png", + ) +} 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 74b62a5654b..1f41550f3ef 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 @@ -282,7 +282,7 @@ class IncomingVerificationPresenterTest { service: SessionVerificationService = FakeSessionVerificationService(), dateFormatter: DateFormatter = FakeDateFormatter(), ) = IncomingVerificationPresenter( - sessionVerificationRequestDetails = sessionVerificationRequestDetails, + verificationRequest = sessionVerificationRequestDetails, navigator = navigator, sessionVerificationService = service, stateMachine = IncomingVerificationStateMachine(service), diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt index 435e2e688cf..c41b78b7a5d 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt @@ -149,7 +149,7 @@ class VerifySelfSessionPresenterTest { ) val presenter = createVerifySelfSessionPresenter(service) presenter.test { - awaitItem().eventSink(VerifySelfSessionViewEvents.UseAnotherDevice) + awaitItem().eventSink(VerifySelfSessionViewEvents.Initial) awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification) service.emitVerificationFlowState(VerificationFlowState.DidFail) assertThat(awaitItem().step).isInstanceOf(Step.AwaitingOtherDeviceResponse::class.java) @@ -264,7 +264,7 @@ class VerifySelfSessionPresenterTest { presenter.test { val state = requestVerificationAndAwaitVerifyingState(service) state.eventSink(VerifySelfSessionViewEvents.SkipVerification) - assertThat(awaitItem().step).isEqualTo(Step.Skipped) + assertThat(awaitItem().step).isEqualTo(Step.Exit) } } @@ -301,7 +301,7 @@ class VerifySelfSessionPresenterTest { ) presenter.test { skipItems(1) - assertThat(awaitItem().step).isEqualTo(Step.Skipped) + assertThat(awaitItem().step).isEqualTo(Step.Exit) } } @@ -336,9 +336,9 @@ class VerifySelfSessionPresenterTest { ): VerifySelfSessionState { var state = awaitItem() assertThat(state.step).isEqualTo(Step.Initial(false)) - state.eventSink(VerifySelfSessionViewEvents.UseAnotherDevice) + state.eventSink(VerifySelfSessionViewEvents.Initial) state = awaitItem() - assertThat(state.step).isEqualTo(Step.UseAnotherDevice) + assertThat(state.step).isEqualTo(Step.Initial) state.eventSink(VerifySelfSessionViewEvents.RequestVerification) // Await for other device response: fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest) diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt index 9eb8724152a..0dc7483fe8b 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt @@ -213,7 +213,7 @@ class VerifySelfSessionViewTest { ensureCalledOnce { callback -> rule.setVerifySelfSessionView( aVerifySelfSessionState( - step = VerifySelfSessionState.Step.Skipped, + step = VerifySelfSessionState.Step.Exit, displaySkipButton = true, eventSink = EnsureNeverCalledWithParam(), ), diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts index bbb8efdadf0..d95566ebb5e 100644 --- a/libraries/designsystem/build.gradle.kts +++ b/libraries/designsystem/build.gradle.kts @@ -32,6 +32,7 @@ android { implementation(libs.coil.compose) implementation(libs.vanniktech.blurhash) implementation(projects.features.enterprise.api) + implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.core) implementation(projects.libraries.preferences.api) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt index c9a80a50d71..78ea197aad5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt @@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items @@ -33,6 +34,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.ui.strings.CommonStrings @@ -78,6 +80,11 @@ object BigIcon { * A success style with a tinted background. */ data object SuccessSolid : Style + + /** + * A loading style with the default background color. + */ + data object Loading : Style } /** @@ -101,31 +108,7 @@ object BigIcon { Style.Success -> Color.Transparent Style.AlertSolid -> ElementTheme.colors.bgCriticalSubtle Style.SuccessSolid -> ElementTheme.colors.bgSuccessSubtle - } - val icon = when (style) { - is Style.Default -> style.vectorIcon - Style.Alert, - Style.AlertSolid -> CompoundIcons.ErrorSolid() - Style.Success, - Style.SuccessSolid -> CompoundIcons.CheckCircleSolid() - } - val contentDescription = when (style) { - is Style.Default -> style.contentDescription - Style.Alert, - Style.AlertSolid -> stringResource(CommonStrings.common_error) - Style.Success, - Style.SuccessSolid -> stringResource(CommonStrings.common_success) - } - val iconTint = when (style) { - is Style.Default -> if (style.useCriticalTint) { - ElementTheme.colors.iconCriticalPrimary - } else { - ElementTheme.colors.iconSecondary - } - Style.Alert, - Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary - Style.Success, - Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary + Style.Loading -> ElementTheme.colors.bgSubtleSecondary } Box( modifier = modifier @@ -134,12 +117,50 @@ object BigIcon { .background(backgroundColor), contentAlignment = Alignment.Center, ) { - Icon( - modifier = Modifier.size(32.dp), - tint = iconTint, - imageVector = icon, - contentDescription = contentDescription - ) + if (style is Style.Loading) { + CircularProgressIndicator( + modifier = Modifier.size(27.dp), + color = ElementTheme.colors.iconSecondary, + trackColor = Color.Transparent, + strokeWidth = 3.dp, + ) + } else { + val icon = when (style) { + is Style.Default -> style.vectorIcon + Style.Alert, + Style.AlertSolid -> CompoundIcons.ErrorSolid() + Style.Success, + Style.SuccessSolid -> CompoundIcons.CheckCircleSolid() + Style.Loading -> error("This should never be reached") + } + val contentDescription = when (style) { + is Style.Default -> style.contentDescription + Style.Alert, + Style.AlertSolid -> stringResource(CommonStrings.common_error) + Style.Success, + Style.SuccessSolid -> stringResource(CommonStrings.common_success) + Style.Loading -> error("This should never be reached") + } + val iconTint = when (style) { + is Style.Default -> if (style.useCriticalTint) { + ElementTheme.colors.iconCriticalPrimary + } else { + ElementTheme.colors.iconSecondary + } + Style.Alert, + Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary + Style.Success, + Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary + Style.Loading -> error("This should never be reached") + } + + Icon( + modifier = Modifier.size(32.dp), + tint = iconTint, + imageVector = icon, + contentDescription = contentDescription + ) + } } } } @@ -173,6 +194,7 @@ internal class BigIconStyleProvider : PreviewParameterProvider { BigIcon.Style.AlertSolid, BigIcon.Style.Default(Icons.Filled.CatchingPokemon, useCriticalTint = true), BigIcon.Style.Success, - BigIcon.Style.SuccessSolid + BigIcon.Style.SuccessSolid, + BigIcon.Style.Loading, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 437b36235e5..0f84b82fd18 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -61,4 +61,6 @@ enum class AvatarSize(val dp: Dp) { MediaSender(32.dp), DmCreationConfirmation(64.dp), + + UserVerification(52.dp), } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt new file mode 100644 index 00000000000..1d60aac7d35 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.activity.compose.LocalActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab + +@Composable +fun OpenUrlInTabView(url: MutableState) { + val activity = requireNotNull(LocalActivity.current) + val darkTheme = ElementTheme.isLightTheme.not() + + LaunchedEffect(url.value) { + url.value?.let { + activity.openUrlInChromeCustomTab(null, darkTheme, it) + url.value = null + } + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt index aa4a7a4b8af..897e17c6114 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt @@ -15,9 +15,15 @@ import kotlinx.parcelize.Parcelize @Parcelize data class SessionVerificationRequestDetails( - val senderId: UserId, + val senderProfile: SenderProfile, val flowId: FlowId, val deviceId: DeviceId, - val displayName: String?, val firstSeenTimestamp: Long, -) : Parcelable +) : Parcelable { + @Parcelize + data class SenderProfile( + val userId: UserId, + val displayName: String?, + val avatarUrl: String?, + ) : Parcelable +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index cf9468b4938..c1c20e02121 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.api.verification import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.core.UserId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -31,7 +32,12 @@ interface SessionVerificationService { /** * Request verification of the current session. */ - suspend fun requestVerification() + suspend fun requestCurrentSessionVerification() + + /** + * Request verification of the user with the given [userId]. + */ + suspend fun requestUserVerification(userId: UserId) /** * Cancels the current verification attempt. @@ -67,16 +73,18 @@ interface SessionVerificationService { * Set this particular request as the currently active one and register for * events pertaining it. */ - suspend fun acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) + suspend fun acknowledgeVerificationRequest(verificationRequest: VerificationRequest.Incoming) /** * Accept the previously acknowledged verification request. */ suspend fun acceptVerificationRequest() + + fun getCurrentVerificationRequest(): VerificationRequest? } interface SessionVerificationServiceListener { - fun onIncomingSessionRequest(sessionVerificationRequestDetails: SessionVerificationRequestDetails) + fun onIncomingSessionRequest(verificationRequest: VerificationRequest.Incoming) } /** Verification status of the current session. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt new file mode 100644 index 00000000000..f26afe50f61 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.verification + +import android.os.Parcelable +import io.element.android.libraries.matrix.api.core.UserId +import kotlinx.parcelize.Parcelize + +sealed interface VerificationRequest : Parcelable { + sealed interface Outgoing : VerificationRequest { + @Parcelize + data object CurrentSession : Outgoing + + @Parcelize + data class User(val userId: UserId) : Outgoing + } + + sealed class Incoming(open val details: SessionVerificationRequestDetails) : VerificationRequest { + @Parcelize + data class OtherSession(override val details: SessionVerificationRequestDetails) : Incoming(details) + + @Parcelize + data class User(override val details: SessionVerificationRequestDetails) : Incoming(details) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 8cd9a472346..2594dca67c1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -8,13 +8,14 @@ package io.element.android.libraries.matrix.impl.verification import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.verification.SessionVerificationData -import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable @@ -50,6 +51,8 @@ class RustSessionVerificationService( isSyncServiceReady: Flow, private val sessionCoroutineScope: CoroutineScope, ) : SessionVerificationService, SessionVerificationControllerDelegate { + private var currentVerificationRequest: VerificationRequest? = null + private val encryptionService: Encryption = client.encryption() private lateinit var verificationController: SessionVerificationController @@ -88,10 +91,8 @@ class RustSessionVerificationService( verificationStatus == SessionVerifiedStatus.NotVerified } - private var isOwnVerification = true - override fun didReceiveVerificationRequest(details: RustSessionVerificationRequestDetails) { - listener?.onIncomingSessionRequest(details.map()) + listener?.onIncomingSessionRequest(details.toVerificationRequest(UserId(client.userId()))) } private var listener: SessionVerificationServiceListener? = null @@ -111,9 +112,16 @@ class RustSessionVerificationService( this.listener = listener } - override suspend fun requestVerification() = tryOrFail { + override suspend fun requestCurrentSessionVerification() = tryOrFail { initVerificationControllerIfNeeded() verificationController.requestDeviceVerification() + currentVerificationRequest = VerificationRequest.Outgoing.CurrentSession + } + + override suspend fun requestUserVerification(userId: UserId) { + initVerificationControllerIfNeeded() + verificationController.requestUserVerification(userId.value) + currentVerificationRequest = VerificationRequest.Outgoing.User(userId) } override suspend fun cancelVerification() = tryOrFail { @@ -130,12 +138,11 @@ class RustSessionVerificationService( verificationController.startSasVerification() } - override suspend fun acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) = tryOrFail { - isOwnVerification = false + override suspend fun acknowledgeVerificationRequest(verificationRequest: VerificationRequest.Incoming) = tryOrFail { initVerificationControllerIfNeeded() verificationController.acknowledgeVerificationRequest( - senderId = details.senderId.value, - flowId = details.flowId.value, + senderId = verificationRequest.details.senderProfile.userId.value, + flowId = verificationRequest.details.flowId.value, ) } @@ -156,6 +163,10 @@ class RustSessionVerificationService( } } + override fun getCurrentVerificationRequest(): VerificationRequest? { + return currentVerificationRequest + } + // region Delegate implementation // When verification attempt is accepted by the other device @@ -183,7 +194,7 @@ class RustSessionVerificationService( } } .onSuccess { - if (isOwnVerification) { + if (currentVerificationRequest is VerificationRequest.Outgoing.CurrentSession) { // Try waiting for the final recovery state for better UX, but don't block the verification state on it tryOrNull { withTimeout(10.seconds) { @@ -215,7 +226,7 @@ class RustSessionVerificationService( // end-region override suspend fun reset(cancelAnyPendingVerificationAttempt: Boolean) { - isOwnVerification = true + currentVerificationRequest = null if (isReady.value && cancelAnyPendingVerificationAttempt) { // Cancel any pending verification attempt tryOrNull { verificationController.cancelVerification() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt index b617889f40e..d2e15a3fac0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt @@ -11,12 +11,28 @@ import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails +import io.element.android.libraries.matrix.api.verification.VerificationRequest import org.matrix.rustcomponents.sdk.SessionVerificationRequestDetails as RustSessionVerificationRequestDetails +import org.matrix.rustcomponents.sdk.UserProfile as RustUserProfile fun RustSessionVerificationRequestDetails.map() = SessionVerificationRequestDetails( - senderId = UserId(senderProfile.userId), + senderProfile = senderProfile.map(), flowId = FlowId(flowId), deviceId = DeviceId(deviceId), - displayName = senderProfile.displayName, firstSeenTimestamp = firstSeenTimestamp.toLong(), ) + +fun RustUserProfile.map() = SessionVerificationRequestDetails.SenderProfile( + userId = UserId(userId), + displayName = displayName, + avatarUrl = avatarUrl, +) + +fun RustSessionVerificationRequestDetails.toVerificationRequest(currentUserId: UserId): VerificationRequest.Incoming { + val details = map() + return if (currentUserId == details.senderProfile.userId) { + VerificationRequest.Incoming.OtherSession(details) + } else { + VerificationRequest.Incoming.User(details) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 65502a7cd7a..04f09b8b0b9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -37,7 +37,7 @@ class FakeSessionVerificationService( override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus override val needsSessionVerification: Flow = _needsSessionVerification - override suspend fun requestVerification() { + override suspend fun requestCurrentSessionVerification() { requestVerificationLambda() } diff --git a/tools/localazy/config.json b/tools/localazy/config.json index f99291da35c..2495301fb85 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -233,7 +233,9 @@ "name" : ":features:ftue:impl", "includeRegex" : [ "screen_welcome_.*", - "screen_notification_optin_.*" + "screen_notification_optin_.*", + "screen_identity_.*", + "screen_session_verification_enter_recovery_key" ] }, { From 3480d4438576482c23e5ef7eebc5c573ad083d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 21 Feb 2025 12:47:47 +0100 Subject: [PATCH 02/19] Add support for replying to incoming user verification requests --- .../android/appnav/LoggedInFlowNode.kt | 8 +- .../incoming/IncomingVerificationPresenter.kt | 78 ++++++++++++------- .../IncomingVerificationStateMachine.kt | 23 +++--- .../outgoing/VerifySelfSessionStateMachine.kt | 18 +++-- .../impl/outgoing/VerifySelfSessionView.kt | 4 +- .../RustSessionVerificationService.kt | 3 +- 6 files changed, 85 insertions(+), 49 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 3e0eef42631..258378a8ee8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -73,11 +73,11 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData -import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -129,7 +129,11 @@ class LoggedInFlowNode @AssistedInject constructor( private val verificationListener = object : SessionVerificationServiceListener { override fun onIncomingSessionRequest(verificationRequest: VerificationRequest.Incoming) { - backstack.singleTop(NavTarget.IncomingVerificationRequest(verificationRequest)) + // Without this launch the rendering and actual state of this Appyx node's children gets out of sync, resulting in a crash. + // This might be because this method is called back from Rust in a background thread. + MainScope().launch { + backstack.singleTop(NavTarget.IncomingVerificationRequest(verificationRequest)) + } } } 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 21e2910025e..c241cef105a 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 @@ -10,10 +10,12 @@ package io.element.android.features.verifysession.impl.incoming import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import com.freeletics.flowredux.compose.rememberStateAndDispatch import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -22,14 +24,17 @@ import io.element.android.features.verifysession.impl.incoming.IncomingVerificat 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.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import timber.log.Timber import io.element.android.features.verifysession.impl.incoming.IncomingVerificationStateMachine.Event as StateMachineEvent import io.element.android.features.verifysession.impl.incoming.IncomingVerificationStateMachine.State as StateMachineState @@ -37,6 +42,7 @@ import io.element.android.features.verifysession.impl.incoming.IncomingVerificat class IncomingVerificationPresenter @AssistedInject constructor( @Assisted private val verificationRequest: VerificationRequest.Incoming, @Assisted private val navigator: IncomingVerificationNavigator, + @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val sessionVerificationService: SessionVerificationService, private val stateMachine: IncomingVerificationStateMachine, private val dateFormatter: DateFormatter, @@ -51,15 +57,34 @@ class IncomingVerificationPresenter @AssistedInject constructor( @Composable override fun present(): IncomingVerificationState { - LaunchedEffect(Unit) { - // Force reset, just in case the service was left in a broken state - sessionVerificationService.reset( - cancelAnyPendingVerificationAttempt = false - ) - // Acknowledge the request right now - sessionVerificationService.acknowledgeVerificationRequest(verificationRequest) - } + val coroutineScope = rememberCoroutineScope { Dispatchers.IO } + val stateAndDispatch = stateMachine.rememberStateAndDispatch() + + DisposableEffect(Unit) { + coroutineScope.launch { + // Force reset, just in case the service was left in a broken state + sessionVerificationService.reset( + cancelAnyPendingVerificationAttempt = false + ) + + // Start this after observing state machine + observeVerificationService() + + // Acknowledge the request right now + sessionVerificationService.acknowledgeVerificationRequest(verificationRequest) + } + + onDispose { + sessionCoroutineScope.launch { + val currentState = stateAndDispatch.state.value + sessionVerificationService.reset( + cancelAnyPendingVerificationAttempt = currentState?.isPending() == true, + ) + } + } + } + val formattedSignInTime = remember { dateFormatter.format( timestamp = verificationRequest.details.firstSeenTimestamp, @@ -76,17 +101,12 @@ class IncomingVerificationPresenter @AssistedInject constructor( } LaunchedEffect(stateAndDispatch.state.value) { - if ((stateAndDispatch.state.value as? IncomingVerificationStateMachine.State.Initial)?.isCancelled == true) { + if ((stateAndDispatch.state.value as? StateMachineState.Initial)?.isCancelled == true) { // The verification was canceled before it was started, maybe because another session accepted it navigator.onFinish() } } - // Start this after observing state machine - LaunchedEffect(Unit) { - observeVerificationService() - } - fun handleEvents(event: IncomingVerificationViewEvents) { Timber.d("Verification user action: ${event::class.simpleName}") when (event) { @@ -131,36 +151,36 @@ class IncomingVerificationPresenter @AssistedInject constructor( ): Step = when (val machineState = this) { is StateMachineState.Initial, - IncomingVerificationStateMachine.State.AcceptingIncomingVerification, - IncomingVerificationStateMachine.State.RejectingIncomingVerification, + StateMachineState.AcceptingIncomingVerification, + StateMachineState.RejectingIncomingVerification, null -> { Step.Initial( deviceDisplayName = sessionVerificationRequestDetails.senderProfile.displayName ?: sessionVerificationRequestDetails.deviceId.value, deviceId = sessionVerificationRequestDetails.deviceId, formattedSignInTime = formattedSignInTime, - isWaiting = machineState == IncomingVerificationStateMachine.State.AcceptingIncomingVerification || - machineState == IncomingVerificationStateMachine.State.RejectingIncomingVerification, + isWaiting = machineState == StateMachineState.AcceptingIncomingVerification || + machineState == StateMachineState.RejectingIncomingVerification, ) } - is IncomingVerificationStateMachine.State.ChallengeReceived -> + is StateMachineState.ChallengeReceived -> Step.Verifying( data = machineState.data, isWaiting = false, ) - IncomingVerificationStateMachine.State.Completed -> Step.Completed - IncomingVerificationStateMachine.State.Canceling, - IncomingVerificationStateMachine.State.Failure -> Step.Failure - is IncomingVerificationStateMachine.State.AcceptingChallenge -> + StateMachineState.Completed -> Step.Completed + StateMachineState.Canceling, + StateMachineState.Failure -> Step.Failure + is StateMachineState.AcceptingChallenge -> Step.Verifying( data = machineState.data, isWaiting = true, ) - is IncomingVerificationStateMachine.State.RejectingChallenge -> + is StateMachineState.RejectingChallenge -> Step.Verifying( data = machineState.data, isWaiting = true, ) - IncomingVerificationStateMachine.State.Canceled -> Step.Canceled + StateMachineState.Canceled -> Step.Canceled } private fun CoroutineScope.observeVerificationService() { @@ -172,10 +192,10 @@ class IncomingVerificationPresenter @AssistedInject constructor( VerificationFlowState.DidAcceptVerificationRequest, VerificationFlowState.DidStartSasVerification -> Unit is VerificationFlowState.DidReceiveVerificationData -> { - stateMachine.dispatch(IncomingVerificationStateMachine.Event.DidReceiveChallenge(verificationAttemptState.data)) + stateMachine.dispatch(StateMachineEvent.DidReceiveChallenge(verificationAttemptState.data)) } VerificationFlowState.DidFinish -> { - stateMachine.dispatch(IncomingVerificationStateMachine.Event.DidAcceptChallenge) + stateMachine.dispatch(StateMachineEvent.DidAcceptChallenge) } VerificationFlowState.DidCancel -> { // Can happen when: @@ -183,10 +203,10 @@ class IncomingVerificationPresenter @AssistedInject constructor( // - another session has accepted the incoming verification request // - the user reject the challenge from this application (I think this is an error). In this case, the state // machine will ignore this event and change state to Failure. - stateMachine.dispatch(IncomingVerificationStateMachine.Event.DidCancel) + stateMachine.dispatch(StateMachineEvent.DidCancel) } VerificationFlowState.DidFail -> { - stateMachine.dispatch(IncomingVerificationStateMachine.Event.DidFail) + stateMachine.dispatch(StateMachineEvent.DidFail) } } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt index d873c6fd50a..9ff9f50cd80 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt @@ -26,7 +26,7 @@ class IncomingVerificationStateMachine @Inject constructor( init { spec { inState { - on { _: Event.AcceptIncomingRequest, state -> + on { _, state -> state.override { State.AcceptingIncomingVerification.andLogStateChange() } } } @@ -39,23 +39,23 @@ class IncomingVerificationStateMachine @Inject constructor( } } inState { - on { _: Event.AcceptChallenge, state -> + on { _, state -> state.override { State.AcceptingChallenge(state.snapshot.data).andLogStateChange() } } - on { _: Event.DeclineChallenge, state -> + on { _, state -> state.override { State.RejectingChallenge(state.snapshot.data).andLogStateChange() } } } inState { - onEnterEffect { _ -> + onEnterEffect { sessionVerificationService.approveVerification() } - on { _: Event.DidAcceptChallenge, state -> + on { _, state -> state.override { State.Completed.andLogStateChange() } } } inState { - onEnterEffect { _ -> + onEnterEffect { sessionVerificationService.declineVerification() } } @@ -66,7 +66,7 @@ class IncomingVerificationStateMachine @Inject constructor( } inState { logReceivedEvents() - on { _: Event.Cancel, state: MachineState -> + on { _, state: MachineState -> when (state.snapshot) { State.Completed, State.Canceled -> state.noChange() else -> { @@ -75,7 +75,7 @@ class IncomingVerificationStateMachine @Inject constructor( } } } - on { _: Event.DidCancel, state: MachineState -> + on { _, state: MachineState -> when (state.snapshot) { is State.RejectingChallenge -> { state.override { State.Failure.andLogStateChange() } @@ -91,7 +91,7 @@ class IncomingVerificationStateMachine @Inject constructor( State.Failure -> state.noChange() } } - on { _: Event.DidFail, state: MachineState -> + on { _, state: MachineState -> state.override { State.Failure.andLogStateChange() } } } @@ -128,6 +128,11 @@ class IncomingVerificationStateMachine @Inject constructor( /** Verification failure. */ data object Failure : State + + fun isPending(): Boolean = when (this) { + AcceptingIncomingVerification, RejectingIncomingVerification, Failure, is ChallengeReceived, is AcceptingChallenge, is RejectingChallenge -> true + is Initial, Canceling, Canceled, Completed -> false + } } sealed interface Event { diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt index 96f57b58d9e..cab9ebec88c 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt @@ -40,24 +40,30 @@ class VerifySelfSessionStateMachine( spec { inState { on { event, state -> + state.override { State.RequestingVerification(event.verificationRequest).andLogStateChange() } + } + } + inState { + onEnterEffect { event -> when (event.verificationRequest) { is VerificationRequest.Outgoing.CurrentSession -> sessionVerificationService.requestCurrentSessionVerification() is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(event.verificationRequest.userId) } - state.override { State.RequestingVerification(event.verificationRequest).andLogStateChange() } } - } - inState { on { _, state -> state.override { State.VerificationRequestAccepted.andLogStateChange() } } } - inState {} - inState { - onActionEffect { _, state -> + inState { + onEnterEffect { sessionVerificationService.startVerification() } } + inState { + on { _, state -> + state.override { State.StartingSasVerification.andLogStateChange() } + } + } inState { on { _, state -> state.override { State.Initial.andLogStateChange() } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt index 3d09b0a375b..69fb1b1ade9 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt @@ -56,9 +56,9 @@ fun VerifySelfSessionView( val step = state.step fun cancelOrResetFlow() { when (step) { - is Step.AwaitingOtherDeviceResponse, is Step.Canceled -> state.eventSink(VerifySelfSessionViewEvents.Reset) + is Step.Canceled -> state.eventSink(VerifySelfSessionViewEvents.Reset) Step.Initial, Step.Completed -> onBack() - Step.Ready -> state.eventSink(VerifySelfSessionViewEvents.Cancel) + Step.Ready, is Step.AwaitingOtherDeviceResponse -> state.eventSink(VerifySelfSessionViewEvents.Cancel) is Step.Verifying -> { if (!step.state.isLoading()) { state.eventSink(VerifySelfSessionViewEvents.DeclineVerification) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 2594dca67c1..15d84a62840 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -118,7 +118,7 @@ class RustSessionVerificationService( currentVerificationRequest = VerificationRequest.Outgoing.CurrentSession } - override suspend fun requestUserVerification(userId: UserId) { + override suspend fun requestUserVerification(userId: UserId) = tryOrFail { initVerificationControllerIfNeeded() verificationController.requestUserVerification(userId.value) currentVerificationRequest = VerificationRequest.Outgoing.User(userId) @@ -147,6 +147,7 @@ class RustSessionVerificationService( } override suspend fun acceptVerificationRequest() = tryOrFail { + Timber.d("Accepting incoming verification request") verificationController.acceptVerificationRequest() } From 87f32b480193a791058abf71d1dc20d9a65cd753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 21 Feb 2025 13:25:20 +0100 Subject: [PATCH 03/19] Add reset recovery key button and previews to `ChooseSelfVerificationModeView` --- .../ChooseSelfVerificationModeNode.kt | 4 +-- ...ChooseSelfVerificationModeStateProvider.kt | 31 +++++++++++++++++++ .../ChooseSelfVerificationModeView.kt | 30 +++++++++++++++--- .../logout/impl/ui/LogoutActionDialog.kt | 1 - 4 files changed, 58 insertions(+), 8 deletions(-) create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt index 1ad9d5427c0..2d936769ce8 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt @@ -17,6 +17,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.logout.api.direct.DirectLogoutView +import io.element.android.features.logout.api.util.onSuccessLogout import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.SessionScope @@ -52,8 +53,7 @@ class ChooseSelfVerificationModeNode @AssistedInject constructor( directLogoutView.Render( state = state.directLogoutState, - openLogoutUrlIfPresent = true, - customOnSuccessLogout = {}, + onSuccessLogout = ::onSuccessLogout, ) } } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt new file mode 100644 index 00000000000..574aa367c61 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.logout.api.direct.aDirectLogoutState + +class ChooseSelfVerificationModeStateProvider : + PreviewParameterProvider { + override val values = sequenceOf( + aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = true), + aChooseSelfVerificationModeState(isLastDevice = true, canEnterRecoveryKey = false), + aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = true), + aChooseSelfVerificationModeState(isLastDevice = false, canEnterRecoveryKey = false), + ) +} + +fun aChooseSelfVerificationModeState( + isLastDevice: Boolean = false, + canEnterRecoveryKey: Boolean = true, +) = ChooseSelfVerificationModeState( + isLastDevice = isLastDevice, + canEnterRecoveryKey = canEnterRecoveryKey, + directLogoutState = aDirectLogoutState(), + eventSink = {}, +) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt index a1158ab5ee5..4becb566cfa 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons @@ -26,6 +27,8 @@ import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMo import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.OutlinedButton import io.element.android.libraries.designsystem.theme.components.Text @@ -79,11 +82,13 @@ fun ChooseSelfVerificationModeView( onClick = onUseAnotherDevice, ) } - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_session_verification_enter_recovery_key), - onClick = onUseRecoveryKey, - ) + if (state.canEnterRecoveryKey) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_session_verification_enter_recovery_key), + onClick = onUseRecoveryKey, + ) + } OutlinedButton( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.screen_identity_confirmation_cannot_confirm), @@ -106,3 +111,18 @@ fun ChooseSelfVerificationModeView( } } } + +@PreviewsDayNight +@Composable +internal fun ChooseSelfVerificationModeViewPreview( + @PreviewParameter(ChooseSelfVerificationModeStateProvider::class) state: ChooseSelfVerificationModeState +) = ElementPreview { + ChooseSelfVerificationModeView( + state = state, + onUseAnotherDevice = {}, + onUseRecoveryKey = {}, + onResetKey = {}, + onLearnMore = {}, + ) +} + diff --git a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt index 7249871772a..4b8e23ab4ed 100644 --- a/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt +++ b/features/logout/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/LogoutActionDialog.kt @@ -8,7 +8,6 @@ package io.element.android.features.logout.impl.ui import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.res.stringResource import io.element.android.features.logout.impl.R import io.element.android.libraries.architecture.AsyncAction From e2f030280a54c81df58f82103329fb813d33c737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 21 Feb 2025 14:08:01 +0100 Subject: [PATCH 04/19] Add and fix tests, fix lint issues --- features/ftue/impl/build.gradle.kts | 10 ++ .../features/ftue/impl/FtueFlowNode.kt | 1 - .../ChooseSelfVerificationModeNode.kt | 1 - .../ChooseSelfVerificationModeView.kt | 3 +- ...oseSessionVerificationModePresenterTest.kt | 64 +++++++ .../ChooseSessionVerificationModeViewTest.kt | 94 +++++++++++ .../features/logout/impl/LogoutView.kt | 1 + .../roomdetails/impl/RoomDetailsFlowNode.kt | 1 - .../userprofile/UserProfileViewTest.kt | 15 ++ .../api/IncomingVerificationEntryPoint.kt | 1 - .../IncomingVerificationStateProvider.kt | 14 +- .../impl/incoming/IncomingVerificationView.kt | 14 +- .../outgoing/VerifySelfSessionStateMachine.kt | 1 - .../impl/outgoing/VerifySelfSessionView.kt | 10 +- .../impl/ui/VerificationUserProfileContent.kt | 3 +- .../IncomingVerificationPresenterTest.kt | 42 +++-- .../VerifySelfSessionPresenterTest.kt | 159 +++++------------- .../outgoing/VerifySelfSessionViewTest.kt | 84 ++------- .../designsystem/components/BigIcon.kt | 1 - .../designsystem/utils/OpenUrlInTabView.kt | 1 + .../SessionVerificationService.kt | 2 - .../RustSessionVerificationService.kt | 4 - .../FakeSessionVerificationService.kt | 18 +- libraries/ui-strings/build.gradle.kts | 4 + .../tests/testutils/EnsureNeverCalled.kt | 6 + 25 files changed, 319 insertions(+), 235 deletions(-) create mode 100644 features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt create mode 100644 features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index b2f02aaf3df..652f989acb4 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -1,4 +1,5 @@ import extension.setupAnvil +import org.gradle.kotlin.dsl.test /* * Copyright 2023, 2024 New Vector Ltd. @@ -14,6 +15,12 @@ plugins { android { namespace = "io.element.android.features.ftue.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } setupAnvil() @@ -45,6 +52,9 @@ dependencies { testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) + testImplementation(libs.test.robolectric) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.noop) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 2c0ac486f1b..9e44b8ac3f5 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -21,7 +21,6 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot -import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.replace import dagger.assisted.Assisted import dagger.assisted.AssistedInject diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt index 2d936769ce8..6ee5b4d4635 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt @@ -28,7 +28,6 @@ class ChooseSelfVerificationModeNode @AssistedInject constructor( private val presenter: Presenter, private val directLogoutView: DirectLogoutView, ) : Node(buildContext, plugins = plugins) { - interface Callback : Plugin { fun onUseAnotherDevice() fun onUseRecoveryKey() diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt index 4becb566cfa..9839f3bf7c7 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt @@ -73,7 +73,7 @@ fun ChooseSelfVerificationModeView( }, footer = { ButtonColumnMolecule( - modifier = modifier.padding(bottom = 16.dp) + modifier = Modifier.padding(bottom = 16.dp) ) { if (state.isLastDevice.not()) { Button( @@ -125,4 +125,3 @@ internal fun ChooseSelfVerificationModeViewPreview( onLearnMore = {}, ) } - 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 new file mode 100644 index 00000000000..1ac614c81e1 --- /dev/null +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.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.DirectLogoutState +import io.element.android.features.logout.api.direct.aDirectLogoutState +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.encryption.RecoveryState +import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ChooseSessionVerificationModePresenterTest { + @Test + fun `initial state - is relayed from EncryptionService`() = runTest { + val encryptionService = FakeEncryptionService().apply { + // Is last device + emitIsLastDevice(true) + // Can enter recovery key + emitRecoveryState(RecoveryState.INCOMPLETE) + } + val presenter = createPresenter(encryptionService = encryptionService) + presenter.test { + awaitItem().run { + assertThat(isLastDevice).isTrue() + assertThat(canEnterRecoveryKey).isTrue() + assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue() + } + } + } + + @Test + fun `sing out action triggers a direct logout`() = runTest { + val logoutEventRecorder = lambdaRecorder {} + val logoutPresenter = Presenter { + aDirectLogoutState(eventSink = logoutEventRecorder) + } + val presenter = createPresenter(directLogoutPresenter = logoutPresenter) + presenter.test { + val initial = awaitItem() + initial.eventSink(ChooseSelfVerificationModeEvent.SignOut) + + logoutEventRecorder.assertions().isCalledOnce().with(value(DirectLogoutEvents.Logout(ignoreSdkError = false))) + } + } + + fun createPresenter( + encryptionService: FakeEncryptionService = FakeEncryptionService(), + directLogoutPresenter: Presenter = Presenter { aDirectLogoutState() } + ) = ChooseSelfVerificationModePresenter( + encryptionService = encryptionService, + directLogoutPresenter = directLogoutPresenter, + ) +} diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt new file mode 100644 index 00000000000..b89e3e42bd9 --- /dev/null +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.ftue.impl.R +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class ChooseSessionVerificationModeViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on learn more invokes the expected callback`() { + ensureCalledOnce { callback -> + rule.setChooseSelfVerificationModeView( + aChooseSelfVerificationModeState(), + onLearnMoreClick = callback, + ) + rule.clickOn(CommonStrings.action_learn_more) + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on use another device calls the callback`() { + ensureCalledOnce { callback -> + rule.setChooseSelfVerificationModeView( + aChooseSelfVerificationModeState(isLastDevice = false), + onUseAnotherDevice = callback, + ) + rule.clickOn(R.string.screen_identity_use_another_device) + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enter recovery key calls the callback`() { + ensureCalledOnce { callback -> + rule.setChooseSelfVerificationModeView( + aChooseSelfVerificationModeState(canEnterRecoveryKey = true), + onEnterRecoveryKey = callback, + ) + rule.clickOn(R.string.screen_session_verification_enter_recovery_key) + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on cannot confirm calls the reset keys callback`() { + ensureCalledOnce { callback -> + rule.setChooseSelfVerificationModeView( + aChooseSelfVerificationModeState(), + onResetKey = callback, + ) + rule.clickOn(R.string.screen_identity_confirmation_cannot_confirm) + } + } + + private fun AndroidComposeTestRule.setChooseSelfVerificationModeView( + state: ChooseSelfVerificationModeState, + onLearnMoreClick: () -> Unit = EnsureNeverCalled(), + onUseAnotherDevice: () -> Unit = EnsureNeverCalled(), + onResetKey: () -> Unit = EnsureNeverCalled(), + onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + ChooseSelfVerificationModeView( + state = state, + onLearnMore = onLearnMoreClick, + onUseAnotherDevice = onUseAnotherDevice, + onResetKey = onResetKey, + onUseRecoveryKey = onEnterRecoveryKey, + ) + } + } +} 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 f8e5a428c21..ade5a720477 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 @@ -7,6 +7,7 @@ package io.element.android.features.logout.impl +import android.app.Activity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 408a1df1c2e..2cbf2e4ab55 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -38,7 +38,6 @@ import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAnd import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode import io.element.android.features.userprofile.shared.UserProfileNodeHelper import io.element.android.features.verifysession.api.VerifySessionEntryPoint -import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode 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 6c81acd00a2..20ffb88d1cf 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 @@ -20,8 +20,10 @@ import io.element.android.features.userprofile.shared.UserProfileView import io.element.android.features.userprofile.shared.aUserProfileState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings @@ -193,6 +195,17 @@ class UserProfileViewTest { rule.clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog) } + + @Test + fun `on verify user clicked - the right callback is called`() = runTest { + ensureCalledOnceWithParam(A_USER_ID) { callback -> + rule.setUserProfileView( + state = aUserProfileState(userId = A_USER_ID, isVerified = AsyncData.Success(false)), + onVerifyClick = callback, + ) + rule.clickOn(CommonStrings.common_verify_identity) + } + } } private fun AndroidComposeTestRule.setUserProfileView( @@ -202,6 +215,7 @@ private fun AndroidComposeTestRule.setUserP onShareUser: () -> Unit = EnsureNeverCalled(), onDmStarted: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onStartCall: (RoomId) -> Unit = EnsureNeverCalledWithParam(), + onVerifyClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), goBack: () -> Unit = EnsureNeverCalled(), openAvatarPreview: (String, String) -> Unit = EnsureNeverCalledWithTwoParams(), ) { @@ -213,6 +227,7 @@ private fun AndroidComposeTestRule.setUserP onStartCall = onStartCall, goBack = goBack, openAvatarPreview = openAvatarPreview, + onVerifyClick = onVerifyClick, ) } } diff --git a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt index 4a31a521563..9d90f33e8dc 100644 --- a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt +++ b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt @@ -12,7 +12,6 @@ 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 -import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.VerificationRequest interface IncomingVerificationEntryPoint : FeatureEntryPoint { diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt index 95589b64def..cdad2669d69 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt @@ -26,9 +26,15 @@ open class IncomingVerificationStateProvider : PreviewParameterProvider Unit + step is Step.Completed -> Unit + else -> BackButton(onClick = { state.eventSink(IncomingVerificationViewEvents.GoBack) }) + } + } ) }, header = { @@ -93,7 +98,7 @@ private fun IncomingVerificationHeader(step: Step, request: VerificationRequest. is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid()) } } - is Step.Verifying -> if (step.isWaiting) { BigIcon.Style.Loading } else { BigIcon.Style.Default(CompoundIcons.LockSolid()) } + is Step.Verifying -> if (step.isWaiting) BigIcon.Style.Loading else BigIcon.Style.Default(CompoundIcons.LockSolid()) Step.Completed -> BigIcon.Style.SuccessSolid Step.Failure -> BigIcon.Style.AlertSolid } @@ -150,7 +155,6 @@ private fun ContentInitial( initialIncoming: Step.Initial, request: VerificationRequest.Incoming, ) { - when (request) { is VerificationRequest.Incoming.OtherSession -> { Column( diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt index cab9ebec88c..ab109014980 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt @@ -19,7 +19,6 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationRequest import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt index 69fb1b1ade9..d9f494458bd 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt @@ -32,6 +32,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.PageTitle +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.Button @@ -84,7 +85,14 @@ fun VerifySelfSessionView( HeaderFooterPage( modifier = modifier, topBar = { - TopAppBar(title = {}) + TopAppBar( + title = {}, + navigationIcon = if (step != Step.Completed) { + { BackButton(onClick = ::cancelOrResetFlow) } + } else { + {} + } + ) }, header = { VerifySelfSessionHeader(step = step, request = state.request) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt index 700df61b92d..6e54537ea54 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt @@ -34,13 +34,14 @@ fun VerificationUserProfileContent( userId: UserId, displayName: String?, avatarUrl: String?, + modifier: Modifier = Modifier, ) { val avatarData = remember(userId, displayName, avatarUrl) { AvatarData(id = userId.value, name = displayName, url = avatarUrl, size = AvatarSize.UserVerification) } Row( - modifier = Modifier.fillMaxWidth() + modifier = modifier.fillMaxWidth() .clip(RoundedCornerShape(8.dp)) .background(ElementTheme.colors.bgSubtleSecondary) .padding(12.dp), 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 1f41550f3ef..6e866b13af0 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 @@ -15,6 +15,7 @@ import io.element.android.libraries.matrix.api.core.FlowId import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_TIMESTAMP import io.element.android.libraries.matrix.test.A_USER_ID @@ -25,6 +26,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -37,7 +39,7 @@ class IncomingVerificationPresenterTest { @Test fun `present - nominal case - incoming verification successful`() = runTest { - val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } + val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } val acceptVerificationRequestLambda = lambdaRecorder { } val approveVerificationLambda = lambdaRecorder { } val resetLambda = lambdaRecorder { } @@ -60,7 +62,7 @@ class IncomingVerificationPresenterTest { ) ) resetLambda.assertions().isCalledOnce().with(value(false)) - acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(aSessionVerificationRequestDetails)) + acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() // User accept the incoming verification initialState.eventSink(IncomingVerificationViewEvents.StartVerification) @@ -100,7 +102,7 @@ class IncomingVerificationPresenterTest { @Test fun `present - emoji not matching case - incoming verification failure`() = runTest { - val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } + val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } val acceptVerificationRequestLambda = lambdaRecorder { } val declineVerificationLambda = lambdaRecorder { } val resetLambda = lambdaRecorder { } @@ -123,7 +125,7 @@ class IncomingVerificationPresenterTest { ) ) resetLambda.assertions().isCalledOnce().with(value(false)) - acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(aSessionVerificationRequestDetails)) + acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() // User accept the incoming verification initialState.eventSink(IncomingVerificationViewEvents.StartVerification) @@ -157,7 +159,7 @@ class IncomingVerificationPresenterTest { @Test fun `present - incoming verification is remotely canceled`() = runTest { - val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } + val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } val acceptVerificationRequestLambda = lambdaRecorder { } val declineVerificationLambda = lambdaRecorder { } val resetLambda = lambdaRecorder { } @@ -191,7 +193,7 @@ class IncomingVerificationPresenterTest { @Test fun `present - user goes back when comparing emoji - incoming verification failure`() = runTest { - val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } + val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } val acceptVerificationRequestLambda = lambdaRecorder { } val declineVerificationLambda = lambdaRecorder { } val resetLambda = lambdaRecorder { } @@ -214,7 +216,7 @@ class IncomingVerificationPresenterTest { ) ) resetLambda.assertions().isCalledOnce().with(value(false)) - acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(aSessionVerificationRequestDetails)) + acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() // User accept the incoming verification initialState.eventSink(IncomingVerificationViewEvents.StartVerification) @@ -248,7 +250,7 @@ class IncomingVerificationPresenterTest { @Test fun `present - user ignores incoming request`() = runTest { - val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } + val acknowledgeVerificationRequestLambda = lambdaRecorder { _ -> } val acceptVerificationRequestLambda = lambdaRecorder { } val resetLambda = lambdaRecorder { } val fakeSessionVerificationService = FakeSessionVerificationService( @@ -268,24 +270,30 @@ class IncomingVerificationPresenterTest { } } - private val aSessionVerificationRequestDetails = SessionVerificationRequestDetails( - senderId = A_USER_ID, - flowId = FlowId("flowId"), - deviceId = A_DEVICE_ID, - displayName = "a device name", - firstSeenTimestamp = A_TIMESTAMP, + private val anIncomingSessionVerificationRequest = VerificationRequest.Incoming.OtherSession( + details = SessionVerificationRequestDetails( + senderProfile = SessionVerificationRequestDetails.SenderProfile( + userId = A_USER_ID, + displayName = "a device name", + avatarUrl = null, + ), + flowId = FlowId("flowId"), + deviceId = A_DEVICE_ID, + firstSeenTimestamp = A_TIMESTAMP, + ) ) - private fun createPresenter( - sessionVerificationRequestDetails: SessionVerificationRequestDetails = aSessionVerificationRequestDetails, + private fun TestScope.createPresenter( + verificationRequest: VerificationRequest.Incoming = anIncomingSessionVerificationRequest, navigator: IncomingVerificationNavigator = IncomingVerificationNavigator { lambdaError() }, service: SessionVerificationService = FakeSessionVerificationService(), dateFormatter: DateFormatter = FakeDateFormatter(), ) = IncomingVerificationPresenter( - verificationRequest = sessionVerificationRequestDetails, + verificationRequest = verificationRequest, navigator = navigator, sessionVerificationService = service, stateMachine = IncomingVerificationStateMachine(service), dateFormatter = dateFormatter, + sessionCoroutineScope = backgroundScope, ) } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt index c41b78b7a5d..f92a05d348d 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt @@ -9,27 +9,21 @@ package io.element.android.features.verifysession.impl.outgoing import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat -import io.element.android.features.logout.api.LogoutUseCase -import io.element.android.features.logout.test.FakeLogoutUseCase import io.element.android.features.verifysession.impl.outgoing.VerifySelfSessionState.Step import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService -import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationData -import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState -import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService -import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore 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 import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -48,85 +42,70 @@ class VerifySelfSessionPresenterTest { ) presenter.test { awaitItem().run { - assertThat(step).isEqualTo(Step.Initial(false)) - assertThat(displaySkipButton).isTrue() + assertThat(step).isEqualTo(Step.Initial) } } } @Test - fun `present - hides skip verification button on non-debuggable builds`() = runTest { - val buildMeta = aBuildMeta(isDebuggable = false) - val presenter = createVerifySelfSessionPresenter( - service = unverifiedSessionService(), - buildMeta = buildMeta, + fun `present - Handles requestVerification for session verification`() = runTest { + val requestSessionVerificationRecorder = lambdaRecorder {} + val startVerificationRecorder = lambdaRecorder {} + val service = unverifiedSessionService( + requestSessionVerificationLambda = requestSessionVerificationRecorder, + startVerificationLambda = startVerificationRecorder, ) - presenter.test { - assertThat(awaitItem().displaySkipButton).isFalse() - } - } - - @Test - fun `present - Initial state is received, can use recovery key`() = runTest { - val resetLambda = lambdaRecorder { } val presenter = createVerifySelfSessionPresenter( - service = unverifiedSessionService( - resetLambda = resetLambda - ), - encryptionService = FakeEncryptionService().apply { - emitRecoveryState(RecoveryState.INCOMPLETE) - } + service = service, + verificationRequest = anOutgoingSessionVerificationRequest(), ) presenter.test { - assertThat(awaitItem().step).isEqualTo(Step.Initial(true)) - resetLambda.assertions().isCalledOnce().with(value(true)) - } - } + requestVerificationAndAwaitVerifyingState(service) - @Test - fun `present - Initial state is received, can use recovery key and is last device`() = runTest { - val presenter = createVerifySelfSessionPresenter( - service = unverifiedSessionService(), - encryptionService = FakeEncryptionService().apply { - emitIsLastDevice(true) - emitRecoveryState(RecoveryState.INCOMPLETE) - } - ) - presenter.test { - assertThat(awaitItem().step).isEqualTo(Step.Initial(canEnterRecoveryKey = true, isLastDevice = true)) + requestSessionVerificationRecorder.assertions().isCalledOnce() + startVerificationRecorder.assertions().isCalledOnce() } } @Test - fun `present - Handles requestVerification`() = runTest { + fun `present - Handles requestVerification for user verification`() = runTest { + val requestUserVerificationRecorder = lambdaRecorder {} + val startVerificationRecorder = lambdaRecorder {} val service = unverifiedSessionService( - requestVerificationLambda = { }, - startVerificationLambda = { }, + requestUserVerificationLambda = requestUserVerificationRecorder, + startVerificationLambda = startVerificationRecorder, + ) + val presenter = createVerifySelfSessionPresenter( + service = service, + verificationRequest = anOutgoingUserVerificationRequest(), ) - val presenter = createVerifySelfSessionPresenter(service) presenter.test { requestVerificationAndAwaitVerifyingState(service) + + requestUserVerificationRecorder.assertions().isCalledOnce() + startVerificationRecorder.assertions().isCalledOnce() } } @Test - fun `present - Cancellation on initial state does nothing`() = runTest { + fun `present - Cancellation on initial state moves to Exit state`() = runTest { val presenter = createVerifySelfSessionPresenter( service = unverifiedSessionService(), ) presenter.test { val initialState = awaitItem() - assertThat(initialState.step).isEqualTo(Step.Initial(false)) + assertThat(initialState.step).isEqualTo(Step.Initial) val eventSink = initialState.eventSink eventSink(VerifySelfSessionViewEvents.Cancel) - expectNoEvents() + + assertThat(awaitItem().step).isEqualTo(Step.Exit) } } @Test fun `present - A failure when verifying cancels it`() = runTest { val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, startVerificationLambda = { }, approveVerificationLambda = { }, ) @@ -145,22 +124,21 @@ class VerifySelfSessionPresenterTest { @Test fun `present - A fail when requesting verification resets the state to the initial one`() = runTest { val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, ) val presenter = createVerifySelfSessionPresenter(service) presenter.test { - awaitItem().eventSink(VerifySelfSessionViewEvents.Initial) awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification) service.emitVerificationFlowState(VerificationFlowState.DidFail) assertThat(awaitItem().step).isInstanceOf(Step.AwaitingOtherDeviceResponse::class.java) - assertThat(awaitItem().step).isEqualTo(Step.Initial(false)) + assertThat(awaitItem().step).isEqualTo(Step.Initial) } } @Test fun `present - Canceling the flow once it's verifying cancels it`() = runTest { val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, startVerificationLambda = { }, cancelVerificationLambda = { }, ) @@ -175,7 +153,7 @@ class VerifySelfSessionPresenterTest { @Test fun `present - When verifying, if we receive another challenge we ignore it`() = runTest { val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, startVerificationLambda = { }, ) val presenter = createVerifySelfSessionPresenter(service) @@ -189,7 +167,7 @@ class VerifySelfSessionPresenterTest { @Test fun `present - Go back after cancellation returns to initial state`() = runTest { val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, startVerificationLambda = { }, ) val presenter = createVerifySelfSessionPresenter(service) @@ -199,7 +177,7 @@ class VerifySelfSessionPresenterTest { assertThat(awaitItem().step).isEqualTo(Step.Canceled) state.eventSink(VerifySelfSessionViewEvents.Reset) // Went back to initial state - assertThat(awaitItem().step).isEqualTo(Step.Initial(false)) + assertThat(awaitItem().step).isEqualTo(Step.Initial) cancelAndIgnoreRemainingEvents() } } @@ -210,7 +188,7 @@ class VerifySelfSessionPresenterTest { VerificationEmoji(number = 30, emoji = "😀", description = "Smiley") ) val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, startVerificationLambda = { }, approveVerificationLambda = { }, ) @@ -235,7 +213,7 @@ class VerifySelfSessionPresenterTest { @Test fun `present - When verification is declined, the flow is canceled`() = runTest { val service = unverifiedSessionService( - requestVerificationLambda = { }, + requestSessionVerificationLambda = { }, startVerificationLambda = { }, declineVerificationLambda = { }, ) @@ -254,20 +232,6 @@ class VerifySelfSessionPresenterTest { } } - @Test - fun `present - Skip event skips the flow`() = runTest { - val service = unverifiedSessionService( - requestVerificationLambda = { }, - startVerificationLambda = { }, - ) - val presenter = createVerifySelfSessionPresenter(service) - presenter.test { - val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(VerifySelfSessionViewEvents.SkipVerification) - assertThat(awaitItem().step).isEqualTo(Step.Exit) - } - } - @Test fun `present - When verification is done using recovery key, the flow is completed`() = runTest { val service = FakeSessionVerificationService( @@ -305,39 +269,11 @@ class VerifySelfSessionPresenterTest { } } - @Test - fun `present - When user request to sign out, the sign out use case is invoked`() = runTest { - val service = FakeSessionVerificationService( - resetLambda = { }, - ).apply { - emitNeedsSessionVerification(false) - emitVerifiedStatus(SessionVerifiedStatus.Verified) - emitVerificationFlowState(VerificationFlowState.DidFinish) - } - val signOutLambda = lambdaRecorder {} - val presenter = createVerifySelfSessionPresenter( - service, - logoutUseCase = FakeLogoutUseCase(signOutLambda) - ) - presenter.test { - skipItems(1) - val initialItem = awaitItem() - initialItem.eventSink(VerifySelfSessionViewEvents.SignOut) - assertThat(awaitItem().signOutAction.isLoading()).isTrue() - val finalItem = awaitItem() - assertThat(finalItem.signOutAction.isSuccess()).isTrue() - signOutLambda.assertions().isCalledOnce().with(value(true)) - } - } - private suspend fun ReceiveTurbine.requestVerificationAndAwaitVerifyingState( fakeService: FakeSessionVerificationService, sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()), ): VerifySelfSessionState { var state = awaitItem() - assertThat(state.step).isEqualTo(Step.Initial(false)) - state.eventSink(VerifySelfSessionViewEvents.Initial) - state = awaitItem() assertThat(state.step).isEqualTo(Step.Initial) state.eventSink(VerifySelfSessionViewEvents.RequestVerification) // Await for other device response: @@ -360,17 +296,19 @@ class VerifySelfSessionPresenterTest { } private suspend fun unverifiedSessionService( - requestVerificationLambda: () -> Unit = { lambdaError() }, + requestSessionVerificationLambda: () -> Unit = { lambdaError() }, + requestUserVerificationLambda: (UserId) -> Unit = { lambdaError() }, cancelVerificationLambda: () -> Unit = { lambdaError() }, approveVerificationLambda: () -> Unit = { lambdaError() }, declineVerificationLambda: () -> Unit = { lambdaError() }, startVerificationLambda: () -> Unit = { lambdaError() }, resetLambda: (Boolean) -> Unit = { }, - acknowledgeVerificationRequestLambda: (SessionVerificationRequestDetails) -> Unit = { lambdaError() }, + acknowledgeVerificationRequestLambda: (VerificationRequest.Incoming) -> Unit = { lambdaError() }, acceptVerificationRequestLambda: () -> Unit = { lambdaError() }, ): FakeSessionVerificationService { return FakeSessionVerificationService( - requestVerificationLambda = requestVerificationLambda, + requestCurrentSessionVerificationLambda = requestSessionVerificationLambda, + requestUserVerificationLambda = requestUserVerificationLambda, cancelVerificationLambda = cancelVerificationLambda, approveVerificationLambda = approveVerificationLambda, declineVerificationLambda = declineVerificationLambda, @@ -385,20 +323,15 @@ class VerifySelfSessionPresenterTest { private fun createVerifySelfSessionPresenter( service: SessionVerificationService, + verificationRequest: VerificationRequest.Outgoing = anOutgoingSessionVerificationRequest(), encryptionService: EncryptionService = FakeEncryptionService(), - buildMeta: BuildMeta = aBuildMeta(), - sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(), - logoutUseCase: LogoutUseCase = FakeLogoutUseCase(), showDeviceVerifiedScreen: Boolean = false, ): VerifySelfSessionPresenter { return VerifySelfSessionPresenter( showDeviceVerifiedScreen = showDeviceVerifiedScreen, + verificationRequest = verificationRequest, sessionVerificationService = service, encryptionService = encryptionService, - stateMachine = VerifySelfSessionStateMachine(service, encryptionService), - buildMeta = buildMeta, - sessionPreferencesStore = sessionPreferencesStore, - logoutUseCase = logoutUseCase, ) } } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt index 0dc7483fe8b..c24809c31e0 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionViewTest.kt @@ -16,7 +16,6 @@ import io.element.android.features.verifysession.impl.ui.aEmojisSessionVerificat import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled -import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce @@ -25,7 +24,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.junit.runner.RunWith -import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class VerifySelfSessionViewTest { @@ -103,62 +101,30 @@ class VerifySelfSessionViewTest { } @Test - fun `back key pressed - on Completed step does nothing`() { - val eventsRecorder = EventsRecorder() - rule.setVerifySelfSessionView( - aVerifySelfSessionState( - step = VerifySelfSessionState.Step.Completed, - eventSink = eventsRecorder - ), - ) - rule.pressBackKey() - eventsRecorder.assertEmpty() - } - - @Test - fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + fun `back key pressed - on Completed exits the flow`() { ensureCalledOnce { callback -> rule.setVerifySelfSessionView( - aVerifySelfSessionState( + onBack = callback, + state = aVerifySelfSessionState( step = VerifySelfSessionState.Step.Completed, - eventSink = eventsRecorder ), - onFinished = callback, ) - rule.clickOn(CommonStrings.action_continue) + rule.pressBackKey() } } - @Config(qualifiers = "h1024dp") @Test - fun `clicking on enter recovery key calls the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) - ensureCalledOnce { callback -> - rule.setVerifySelfSessionView( - aVerifySelfSessionState( - step = VerifySelfSessionState.Step.Initial(true), - eventSink = eventsRecorder - ), - onEnterRecoveryKey = callback, - ) - rule.clickOn(R.string.screen_session_verification_enter_recovery_key) - } - } - - @Config(qualifiers = "h1024dp") - @Test - fun `clicking on learn more invokes the expected callback`() { + fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> rule.setVerifySelfSessionView( aVerifySelfSessionState( - step = VerifySelfSessionState.Step.Initial(true), + step = VerifySelfSessionState.Step.Completed, eventSink = eventsRecorder ), - onLearnMoreClick = callback, + onFinished = callback, ) - rule.clickOn(CommonStrings.action_learn_more) + rule.clickOn(CommonStrings.action_continue) } } @@ -194,48 +160,18 @@ class VerifySelfSessionViewTest { eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification) } - @Test - fun `clicking on 'Skip' emits the expected event`() { - val eventsRecorder = EventsRecorder() - rule.setVerifySelfSessionView( - aVerifySelfSessionState( - step = VerifySelfSessionState.Step.Initial(canEnterRecoveryKey = true), - displaySkipButton = true, - eventSink = eventsRecorder - ), - ) - rule.clickOn(CommonStrings.action_skip) - eventsRecorder.assertSingle(VerifySelfSessionViewEvents.SkipVerification) - } - - @Test - fun `on Skipped step - onFinished callback is called immediately`() { - ensureCalledOnce { callback -> - rule.setVerifySelfSessionView( - aVerifySelfSessionState( - step = VerifySelfSessionState.Step.Exit, - displaySkipButton = true, - eventSink = EnsureNeverCalledWithParam(), - ), - onFinished = callback, - ) - } - } - private fun AndroidComposeTestRule.setVerifySelfSessionView( state: VerifySelfSessionState, onLearnMoreClick: () -> Unit = EnsureNeverCalled(), - onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(), onFinished: () -> Unit = EnsureNeverCalled(), - onResetKey: () -> Unit = EnsureNeverCalled(), + onBack: () -> Unit = EnsureNeverCalled(), ) { setContent { VerifySelfSessionView( state = state, onLearnMoreClick = onLearnMoreClick, - onEnterRecoveryKey = onEnterRecoveryKey, onFinish = onFinished, - onResetKey = onResetKey, + onBack = onBack, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt index 78ea197aad5..f469555ee45 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt index 1d60aac7d35..9770c6849d3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.MutableState import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab +@Suppress("MutableStateParam") @Composable fun OpenUrlInTabView(url: MutableState) { val activity = requireNotNull(LocalActivity.current) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index c1c20e02121..6e34c3de325 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -79,8 +79,6 @@ interface SessionVerificationService { * Accept the previously acknowledged verification request. */ suspend fun acceptVerificationRequest() - - fun getCurrentVerificationRequest(): VerificationRequest? } interface SessionVerificationServiceListener { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 15d84a62840..4d48543276e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -164,10 +164,6 @@ class RustSessionVerificationService( } } - override fun getCurrentVerificationRequest(): VerificationRequest? { - return currentVerificationRequest - } - // region Delegate implementation // When verification attempt is accepted by the other device diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 04f09b8b0b9..d8788e40264 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -7,11 +7,12 @@ package io.element.android.libraries.matrix.test.verification -import io.element.android.libraries.matrix.api.verification.SessionVerificationRequestDetails +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.flow.Flow @@ -20,13 +21,14 @@ import kotlinx.coroutines.flow.StateFlow class FakeSessionVerificationService( initialSessionVerifiedStatus: SessionVerifiedStatus = SessionVerifiedStatus.Unknown, - private val requestVerificationLambda: () -> Unit = { lambdaError() }, + private val requestCurrentSessionVerificationLambda: () -> Unit = { lambdaError() }, + private val requestUserVerificationLambda: (UserId) -> Unit = { lambdaError() }, private val cancelVerificationLambda: () -> Unit = { lambdaError() }, private val approveVerificationLambda: () -> Unit = { lambdaError() }, private val declineVerificationLambda: () -> Unit = { lambdaError() }, private val startVerificationLambda: () -> Unit = { lambdaError() }, private val resetLambda: (Boolean) -> Unit = { lambdaError() }, - private val acknowledgeVerificationRequestLambda: (SessionVerificationRequestDetails) -> Unit = { lambdaError() }, + private val acknowledgeVerificationRequestLambda: (VerificationRequest.Incoming) -> Unit = { lambdaError() }, private val acceptVerificationRequestLambda: () -> Unit = { lambdaError() }, ) : SessionVerificationService { private val _sessionVerifiedStatus = MutableStateFlow(initialSessionVerifiedStatus) @@ -38,7 +40,11 @@ class FakeSessionVerificationService( override val needsSessionVerification: Flow = _needsSessionVerification override suspend fun requestCurrentSessionVerification() { - requestVerificationLambda() + requestCurrentSessionVerificationLambda() + } + + override suspend fun requestUserVerification(userId: UserId) { + requestUserVerificationLambda(userId) } override suspend fun cancelVerification() { @@ -68,8 +74,8 @@ class FakeSessionVerificationService( this.listener = listener } - override suspend fun acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) { - acknowledgeVerificationRequestLambda(details) + override suspend fun acknowledgeVerificationRequest(verificationRequest: VerificationRequest.Incoming) { + acknowledgeVerificationRequestLambda(verificationRequest) } override suspend fun acceptVerificationRequest() = simulateLongTask { diff --git a/libraries/ui-strings/build.gradle.kts b/libraries/ui-strings/build.gradle.kts index 2f6a51497d6..7e34891c668 100644 --- a/libraries/ui-strings/build.gradle.kts +++ b/libraries/ui-strings/build.gradle.kts @@ -11,4 +11,8 @@ plugins { android { namespace = "io.element.android.libraries.ui.strings" + + lint { + disable += "Typos" + } } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt index 689a52c7925..fe45364f49c 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt @@ -38,3 +38,9 @@ class EnsureNeverCalledWithTwoParamsAndResult : (T, U) -> R { lambdaError("Should not be called and is called with $p1 and $p2") } } + +class EnsureNeverCalledWithThreeParams : (T, U, V) -> Unit { + override fun invoke(p1: T, p2: U, p3: V) { + lambdaError("Should not be called and is called with $p1, $p2 and $p3") + } +} From 13d8f667b4fcad678fd5636bb5702f0d1e326b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 21 Feb 2025 16:12:35 +0100 Subject: [PATCH 05/19] Add 'Profile' item in room details screen --- .../roomdetails/impl/RoomDetailsFlowNode.kt | 4 ++++ .../roomdetails/impl/RoomDetailsNode.kt | 9 ++++++++- .../roomdetails/impl/RoomDetailsView.kt | 19 ++++++++++++++++++ .../roomdetails/impl/RoomDetailsViewTest.kt | 20 +++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 2cbf2e4ab55..b7cc8685e4e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -177,6 +177,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( backstack.push(NavTarget.SecurityAndPrivacy) } + override fun openDmUserProfile(userId: UserId) { + backstack.push(NavTarget.RoomMemberDetails(userId)) + } + override fun onJoinCall() { val inputs = CallType.RoomCall( sessionId = room.sessionId, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index 6ae3c555032..c3a0a3d5195 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -23,6 +23,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.androidutils.system.startSharePlainTextIntent import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope @@ -50,6 +51,7 @@ class RoomDetailsNode @AssistedInject constructor( fun openPinnedMessagesList() fun openKnockRequestsList() fun openSecurityAndPrivacy() + fun openDmUserProfile(userId: UserId) fun onJoinCall() } @@ -126,6 +128,10 @@ class RoomDetailsNode @AssistedInject constructor( callbacks.forEach { it.openSecurityAndPrivacy() } } + private fun onProfileClick(userId: UserId) { + callbacks.forEach { it.openDmUserProfile(userId) } + } + @Composable override fun View(modifier: Modifier) { val context = LocalContext.current @@ -158,7 +164,8 @@ class RoomDetailsNode @AssistedInject constructor( onJoinCallClick = ::onJoinCall, onPinnedMessagesClick = ::openPinnedMessages, onKnockRequestsClick = ::openKnockRequestsLists, - onSecurityAndPrivacyClick = ::openSecurityAndPrivacy + onSecurityAndPrivacyClick = ::openSecurityAndPrivacy, + onProfileClick = ::onProfileClick, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index c1e6a846176..0780376ccb9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -70,6 +70,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.getBestName @@ -101,6 +102,7 @@ fun RoomDetailsView( onPinnedMessagesClick: () -> Unit, onKnockRequestsClick: () -> Unit, onSecurityAndPrivacyClick: () -> Unit, + onProfileClick: (UserId) -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -179,11 +181,16 @@ fun RoomDetailsView( state.eventSink(RoomDetailsEvent.SetFavorite(it)) } ) + if (state.canShowSecurityAndPrivacy) { SecurityAndPrivacyItem( onClick = onSecurityAndPrivacyClick ) } + + state.roomMemberDetailsState?.let { dmMemberDetails -> + ProfileItem(onClick = { onProfileClick(dmMemberDetails.userId) }) + } } if (state.roomType is RoomDetailsType.Room) { @@ -546,6 +553,17 @@ private fun FavoriteItem( ) } +@Composable +private fun ProfileItem( + onClick: () -> Unit, +) { + PreferenceText( + icon = CompoundIcons.UserProfile(), + title = stringResource(id = R.string.screen_room_details_profile_row_title), + onClick = onClick, + ) +} + @Composable private fun MembersItem( memberCount: Long, @@ -655,5 +673,6 @@ private fun ContentToPreview(state: RoomDetailsState) { onPinnedMessagesClick = {}, onKnockRequestsClick = {}, onSecurityAndPrivacyClick = {}, + onProfileClick = {}, ) } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index 8cc1af16385..da7f92aed55 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -15,7 +15,10 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.roomdetails.impl.members.aRoomMember +import io.element.android.features.userprofile.shared.aUserProfileState +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureCalledOnceWithTwoParams @@ -294,6 +297,21 @@ class RoomDetailsViewTest { rule.clickOn(R.string.screen_room_details_requests_to_join_title) } } + + @Config(qualifiers = "h1024dp") + @Test + fun `click on profile invokes the expected callback`() { + ensureCalledOnceWithParam(A_USER_ID) { callback -> + rule.setRoomDetailView( + state = aRoomDetailsState( + eventSink = EventsRecorder(expectEvents = false), + roomMemberDetailsState = aUserProfileState(userId = A_USER_ID), + ), + onProfileClick = callback, + ) + rule.clickOn(R.string.screen_room_details_profile_row_title) + } + } } private fun AndroidComposeTestRule.setRoomDetailView( @@ -314,6 +332,7 @@ private fun AndroidComposeTestRule.setRoomD onPinnedMessagesClick: () -> Unit = EnsureNeverCalled(), onKnockRequestsClick: () -> Unit = EnsureNeverCalled(), onSecurityAndPrivacyClick: () -> Unit = EnsureNeverCalled(), + onProfileClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { RoomDetailsView( @@ -332,6 +351,7 @@ private fun AndroidComposeTestRule.setRoomD onPinnedMessagesClick = onPinnedMessagesClick, onKnockRequestsClick = onKnockRequestsClick, onSecurityAndPrivacyClick = onSecurityAndPrivacyClick, + onProfileClick = onProfileClick, ) } } From 88befcecf887b111c08db3a89b3b769ebab9e4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 21 Feb 2025 17:00:16 +0100 Subject: [PATCH 06/19] Delay displaying the incoming user verification until the UI is ready --- .../kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 258378a8ee8..2e449c2cf95 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -78,6 +78,8 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -85,6 +87,7 @@ import kotlinx.parcelize.Parcelize import timber.log.Timber import java.util.Optional import java.util.UUID +import kotlin.time.Duration.Companion.milliseconds @ContributesNode(SessionScope::class) class LoggedInFlowNode @AssistedInject constructor( @@ -132,6 +135,12 @@ class LoggedInFlowNode @AssistedInject constructor( // Without this launch the rendering and actual state of this Appyx node's children gets out of sync, resulting in a crash. // This might be because this method is called back from Rust in a background thread. MainScope().launch { + // Wait until the app is in foreground to display the incoming verification request + appNavigationStateService.appNavigationState.first { it.isInForeground } + + // Wait for the UI to be ready + delay(500.milliseconds) + backstack.singleTop(NavTarget.IncomingVerificationRequest(verificationRequest)) } } From 320323fddd9618ae34ac1fd60494f163ee24cdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 09:21:04 +0100 Subject: [PATCH 07/19] Fix issues after rebase --- .../choosemode/ChooseSelfVerificationModeNode.kt | 6 +----- .../io/element/android/features/logout/impl/LogoutView.kt | 1 - .../android/features/roomdetails/impl/RoomDetailsView.kt | 6 +++--- .../verifysession/impl/outgoing/VerifySelfSessionNode.kt | 2 -- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt index 6ee5b4d4635..687dc4aefeb 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt @@ -17,7 +17,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.logout.api.direct.DirectLogoutView -import io.element.android.features.logout.api.util.onSuccessLogout import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.SessionScope @@ -50,9 +49,6 @@ class ChooseSelfVerificationModeNode @AssistedInject constructor( modifier = modifier, ) - directLogoutView.Render( - state = state.directLogoutState, - onSuccessLogout = ::onSuccessLogout, - ) + directLogoutView.Render(state = state.directLogoutState) } } 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 ade5a720477..f8e5a428c21 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 @@ -7,7 +7,6 @@ package io.element.android.features.logout.impl -import android.app.Activity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 0780376ccb9..10abb2e86a3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -557,9 +557,9 @@ private fun FavoriteItem( private fun ProfileItem( onClick: () -> Unit, ) { - PreferenceText( - icon = CompoundIcons.UserProfile(), - title = stringResource(id = R.string.screen_room_details_profile_row_title), + ListItem( + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserProfile())), + headlineContent = { Text(stringResource(id = R.string.screen_room_details_profile_row_title)) }, onClick = onClick, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt index c0815f22338..23236b32e09 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionNode.kt @@ -16,8 +16,6 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.appconfig.LearnMoreConfig -import io.element.android.compound.theme.ElementTheme import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope From 53bf1a7efc9d234906eaa6ddd6af00851cabc976 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 6 Mar 2025 08:37:08 +0000 Subject: [PATCH 08/19] Update screenshots --- ...ion.choosemode_ChooseSelfVerificationModeView_Day_0_en.png | 3 +++ ...ion.choosemode_ChooseSelfVerificationModeView_Day_1_en.png | 3 +++ ...ion.choosemode_ChooseSelfVerificationModeView_Day_2_en.png | 3 +++ ...ion.choosemode_ChooseSelfVerificationModeView_Day_3_en.png | 3 +++ ...n.choosemode_ChooseSelfVerificationModeView_Night_0_en.png | 3 +++ ...n.choosemode_ChooseSelfVerificationModeView_Night_1_en.png | 3 +++ ...n.choosemode_ChooseSelfVerificationModeView_Night_2_en.png | 3 +++ ...n.choosemode_ChooseSelfVerificationModeView_Night_3_en.png | 3 +++ .../images/features.roomdetails.impl_RoomDetailsDark_5_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetailsDark_6_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_5_en.png | 4 ++-- .../images/features.roomdetails.impl_RoomDetails_6_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_0_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_1_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_3_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_4_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_6_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_7_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_0_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_1_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_3_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_4_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_6_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_7_en.png | 4 ++-- ...ssion.impl.incoming_IncomingVerificationView_Day_10_en.png | 3 +++ ...ssion.impl.incoming_IncomingVerificationView_Day_11_en.png | 3 +++ ...ssion.impl.incoming_IncomingVerificationView_Day_12_en.png | 3 +++ ...ssion.impl.incoming_IncomingVerificationView_Day_13_en.png | 3 +++ ...ession.impl.incoming_IncomingVerificationView_Day_1_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_2_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_3_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_4_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_5_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_6_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_7_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_8_en.png | 3 +++ ...ession.impl.incoming_IncomingVerificationView_Day_9_en.png | 3 +++ ...ion.impl.incoming_IncomingVerificationView_Night_10_en.png | 3 +++ ...ion.impl.incoming_IncomingVerificationView_Night_11_en.png | 3 +++ ...ion.impl.incoming_IncomingVerificationView_Night_12_en.png | 3 +++ ...ion.impl.incoming_IncomingVerificationView_Night_13_en.png | 3 +++ ...sion.impl.incoming_IncomingVerificationView_Night_1_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_2_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_3_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_4_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_5_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_6_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_7_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_8_en.png | 3 +++ ...sion.impl.incoming_IncomingVerificationView_Night_9_en.png | 3 +++ ...fysession.impl.outgoing_VerifySelfSessionView_Day_0_en.png | 4 ++-- ...ysession.impl.outgoing_VerifySelfSessionView_Day_10_en.png | 4 ++-- ...ysession.impl.outgoing_VerifySelfSessionView_Day_11_en.png | 4 ++-- ...ysession.impl.outgoing_VerifySelfSessionView_Day_13_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_2_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_3_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_4_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_5_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_6_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_7_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_8_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_9_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_0_en.png | 4 ++-- ...ession.impl.outgoing_VerifySelfSessionView_Night_10_en.png | 4 ++-- ...ession.impl.outgoing_VerifySelfSessionView_Night_11_en.png | 4 ++-- ...ession.impl.outgoing_VerifySelfSessionView_Night_13_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_1_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_2_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_3_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_4_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_5_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_6_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_7_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_8_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_9_en.png | 4 ++-- ...ession.impl.ui_VerificationUserProfileContent_Day_0_en.png | 3 +++ ...sion.impl.ui_VerificationUserProfileContent_Night_0_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_90_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_91_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_92_en.png | 3 +++ .../libraries.designsystem.components_BigIcon_Day_0_en.png | 4 ++-- .../libraries.designsystem.components_BigIcon_Night_0_en.png | 4 ++-- ...designsystem.components_PageTitleWithIconFull_Day_6_en.png | 3 +++ ...signsystem.components_PageTitleWithIconFull_Night_6_en.png | 3 +++ 85 files changed, 197 insertions(+), 116 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_90_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_91_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_92_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_6_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en.png new file mode 100644 index 00000000000..2dd4cdac47c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:470cb7c851af9d91cc44d52d502b50764136b079b600ca58ae2512e7ff12e34b +size 29534 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en.png new file mode 100644 index 00000000000..08cde67f549 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49db4684bd8f16361848130650ce1ee0fbc49fff5452fab177e5920ab857c1a9 +size 24663 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en.png new file mode 100644 index 00000000000..7e53aff6619 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1504a5137b90bf6c03ae8d3a90946ae1a9b528013f9c3898a0e91e4d073d159 +size 34347 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en.png new file mode 100644 index 00000000000..6736d8bf0a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd973e0f9c5940e3f216f27af682fffd156535b6d759e4cec01c3979eb35f1c9 +size 29624 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en.png new file mode 100644 index 00000000000..c6692886f47 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dba1ef09211361216ee035c64ee50d62ab1df5191d365ef26f5a4f741477867 +size 28635 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en.png new file mode 100644 index 00000000000..cdb70c0358b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba7502251d19d1fe54fa631b7c71afcdfc3c864cbab04d741a0c4a8d691b2e7f +size 24048 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en.png new file mode 100644 index 00000000000..7a116903690 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14770fdc3e68b250b5f7dad6bb6fb1bf387c66774287276ab4b505c144007b33 +size 33299 diff --git a/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en.png new file mode 100644 index 00000000000..86c1142a7a0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0466b1f78eda1575072f9be443fcb5741ff9952fd215b9aa9b2281de7109da70 +size 28783 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png index 479c21d6808..726667ed5b0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7479bd11493b82e7027599a938979fd50fb75c31cf7c505cf8669db9387fb124 -size 38805 +oid sha256:f1a69af9c184578a355345037ec34016c4b4ab26a2719bfaffafa1027365a678 +size 38453 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png index e3715da6da1..62f03c88e1a 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56418af183e3287631cead30c1077d5734ff59f014c03e2ea0df8d91d38ade01 -size 42255 +oid sha256:c41fdaa2341788494026277683590e0fe58fcc6b4c93919c77dcf29fd1e95c15 +size 41752 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png index 8953624294c..a51874b64b8 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56c21416493a55bb9f105a9908f2b9e07441ec5f5ae3afb68f59fb8329cdb242 -size 39527 +oid sha256:e99e06f6281be006fb12e0a56e7523c8144256562464474cb2aa918f6576d636 +size 39091 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png index 7396ea70f95..06d2ff4d324 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetails_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:128bc1d07c659dfdb25a5cac2513ef3dfcad6a156fd3a38457eec40a735e74c9 -size 43265 +oid sha256:d59deaee5d608c279670104c0f3bfcb5bbda90ac9779111441053ea1b9d40d4c +size 42658 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png index 1296c86341a..2884844ad31 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f41090fceca5845f88dbbc6b983e6330ecd3b86d2056ba4584f1fca26305bb0a -size 28943 +oid sha256:0b560dacbf56801956dea441405e2f0694289944fb8aecd11cd7f697fb31b3d7 +size 24715 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png index bceae72d32f..e45afadfc2f 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0516c0355081b7b2d745ec46fd160fd566be224ef7d350849c9990a8c6320563 -size 27018 +oid sha256:1e84d3ab362641d96a97606427a60cf5d20660fe81e53b621b947cc367d010a2 +size 22585 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png index 630ea68a0b0..c07abc7b584 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f20c32a85bf93f25c51c18b79adc0f8237173dc9fe0705570634adbcc94b1350 -size 36484 +oid sha256:ba80787576dd893f90fcdc83021e47abbde4ae2bcaaca2c167178ba7c2be3886 +size 36481 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png index 623baa9c2fb..c85a2d454c8 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cc7bdca7fe74fe0b16ab23eb4866b3af5245ed762c74a1edba087070cfe32b8 -size 30448 +oid sha256:3a2278fd516dff3a5c86702f09d5621e8b3bb3de1df702b5036fa114975e4d85 +size 27844 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png index ab96c446237..ffebf78252a 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfc7c5d99c0d1e5e0194a5961aac43efea94ae100fca0249f6b00e20b47cbb09 -size 26565 +oid sha256:014bb710c822ba5420b9d8d8a973c6734a98f854787c7d0d88efc979c731380b +size 23296 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png index 7ed764b1986..b26b0accd80 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2f9d02116554413601c8b11fb75581f30ebb6ce5b8ade2a5a62a0aa4ce04705 -size 29914 +oid sha256:c35c7c31daa5a498b7698ea818b0eeaa8a81173161d40b5ddca2154176ed9a5e +size 25696 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png index 96061cad5ee..3192cac4490 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f86dd19d8056f16ae1a5337c49f35d420f2b83baafb29360c8c07c3347d5a93f -size 28074 +oid sha256:cdb1bad54179154d28a8b41ad072b4c9d7d9061e06c1a3e1f46bcf30e5155248 +size 24043 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png index 50419932b11..3e569070f00 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46d3c77fff43081041fb952fe9c8ea733c5ec164ca9aca548924d3cbefb52369 -size 26131 +oid sha256:c9b53eec1ca771ba3d0e0929b8da5b3ec99f576b8b8ae0f059c4256ce19f5d56 +size 22043 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png index db4d01fbfb4..4d0032fc29b 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fdbd16b4e78f890f60668fccfbe7af6c6a4f5ba22af26e9f3735dafb13e9d8f -size 33700 +oid sha256:ce136a49edadeaadc229ec485b27bf2d4ae20601e948bca46c632a4035b90130 +size 33697 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png index 28ea8e7526b..22aeb8dc7a5 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79f2c2bb7772dd91b37351e4d1e658155f341d83cb6d496eded65b0b543fa2b1 -size 27898 +oid sha256:580fc1fe77975e83cf83fe582c2f3f4aa7003cf302e7dadcd6bb97c04cbbf148 +size 25517 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png index 2e9d6040cbf..7fe0d89bf62 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4acb962472308c94998b3bc67d71a6a69b9bad0294461df374f7e74dcf703328 -size 24379 +oid sha256:315c9ff214cd0c728d082afe34fe811e58d2a07e28c808b7d84a790c79f22734 +size 21492 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png index 14bbcdb78f8..118fdb916aa 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a409716052c9c12c963668098943b8f286e41d6a10f5639d11b7b464cb9d0c3d -size 28909 +oid sha256:650918f76a64db0b2308e99a6670e5e10c0713426c5c95b0f8b3ac7984a971ee +size 24887 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en.png new file mode 100644 index 00000000000..53b828b23e8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8634747fdeca8b754396dd32e02c36956017860118dbc33098aaa10fee358ec +size 28876 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en.png new file mode 100644 index 00000000000..88d6b6c4358 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66af8c7679f62fede7b09d6661f0b78d7264a2a415cdf5faf0928aa115adfc2b +size 24514 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en.png new file mode 100644 index 00000000000..03a91ddf873 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c89798843bd3bc2a02094e200ddfc4fcdad8e71554d64c3f0cf2bfc75e894dc +size 24514 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en.png new file mode 100644 index 00000000000..03a91ddf873 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c89798843bd3bc2a02094e200ddfc4fcdad8e71554d64c3f0cf2bfc75e894dc +size 24514 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en.png index 47148f43012..6e3fdb2bd3e 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2473606524b856b4afd81e4a32b29c7456788746078387fff280f3c8eb577396 -size 41622 +oid sha256:1da0edef645c5abc391f0425e3a1f8719c1f061f66517b131a81cdd59d2bd04a +size 40105 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png index c12e74814b8..fe394f3b14b 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a8c359687b3ec5f038602b9022ef3967af12c6632a59c56bd10fecf7d92233c -size 46457 +oid sha256:5bd5eb4e86f00c2807e244856f3645444062b3404b37add53c02d9b8555012b6 +size 36286 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en.png index fbae9acd3c2..d7df699feb1 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6576c0b77984df1930f0115e1ea90e6720f0fb5f500e186c591f0fd2eb771b6 -size 44729 +oid sha256:682472baf2a1bc5f99cfadc3177615167f1f7531923ec34374f59e263b50d79f +size 37049 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png index 683805307f1..ef0f96e1c71 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90c8e131e639b6638a75468d41a3ff0107822e3450c6edbcc4d9eccdd180f6ae -size 31537 +oid sha256:6006a987dce3f67d2a4b600a26c7251af4584e3fcb498e2bbf490dc285083ae7 +size 32261 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png index 28a80f269d4..cd7e23e55f6 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fc2ecdf5b8880b67e9ee9eacba9636819396236325f6ce28f0bf5755fa5ae5e -size 21892 +oid sha256:991dd800b2031ae915a166c83111df9c4ecb6dd6fb9014208a97e2836dfdfa77 +size 45796 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png index 376d2f7449b..9e17f544550 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8371da870ec795fbced08edd199c6e6c53b1557b648686fe89c278def41e425b -size 24134 +oid sha256:69f56c1e99d79223134d0aa7ee30582463e323d3c1c10471fe8709a90438c3ce +size 46138 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en.png index 376d2f7449b..0d8f547d670 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8371da870ec795fbced08edd199c6e6c53b1557b648686fe89c278def41e425b -size 24134 +oid sha256:baa21d8011741ac2bca82ac26fa289d016a3ddcc16d408f92812adc662e27869 +size 40088 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en.png new file mode 100644 index 00000000000..c377b2577bd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c93ea8f1be90fc1bc43bc94c86eb7b7605872fbd96ac142ba1d633e20978d7a1 +size 40427 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png new file mode 100644 index 00000000000..6a3d8f51ccc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bb234aea96d3dbd8eacb6b85c7fb50c23d7fb9e9add31f0c894167e66492e03 +size 30939 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en.png new file mode 100644 index 00000000000..8a110274ca7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:227c4049433b734d591eeea0cb5cca65ce27b3f4fe385eb224ec322148865f6b +size 28321 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en.png new file mode 100644 index 00000000000..f86af6507de --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0393e0549647a0fe6ebef2372d9e6885db7225d137e44d4057e3fe0a15b1aa8 +size 23895 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en.png new file mode 100644 index 00000000000..8ecf2388f58 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb51fe5a51969f768609a5e5eaf5baa95005b094bf1995c3b304f637bf58fbc7 +size 24251 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en.png new file mode 100644 index 00000000000..8ecf2388f58 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb51fe5a51969f768609a5e5eaf5baa95005b094bf1995c3b304f637bf58fbc7 +size 24251 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en.png index d9bfdf19913..848a72bbd51 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6948418922ae7fc3fe1ec3475635cb8c53b15fa49128897e24b39f9b2dfc02f0 -size 40413 +oid sha256:4087f776842690767225ab62c5c1d7717cf039f4ec53494f834cbed9d1cc21c5 +size 38971 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png index 924aa9fc28d..e6626477eba 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f385489dab05d85ad04be5324e2b6d0ca4333f46671aee69ac11a9feb04f966b -size 45526 +oid sha256:c61ae62b686709a1a7c69c4cf3415fb59cbeae5e7fd1359b5d77e120e32abadb +size 35520 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en.png index fc0ad477da5..142cf5e2fc2 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7792bff8e012d42e7dea995152f5c0674f1c4a24e333cbfcb52d4ad4fc58ec2 -size 43783 +oid sha256:67f80eb2f896018c000419a2d57f3eb198951dafb60fa5e6e57dadd70170e711 +size 36091 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png index c34fdec6573..03f5de7dbd1 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:744f66b5f395a854dce1f9abc8dbb43fbbee5de1d18b6c18d82a614eb2d32096 -size 30762 +oid sha256:eee91f2b7f360cfecdf99f0f072f1158f7646a62d75d7617358241ccfa7042a1 +size 31611 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png index e359daa2237..4e03197c02c 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3a1ef5cbd6ec289fe3b9a48811b77b37f71e53fa2be4e34d16a27fae8189fba -size 21226 +oid sha256:733eca4911eab1b300fc6e104e39d60d6f80371175c53f5cc03c15f4268afb92 +size 44855 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png index a5853744391..2827a25e77e 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23dff654bad158f522929fdb6fb6911f4c511eb688bb28995f2d2a56bd747758 -size 23930 +oid sha256:c6d3505e15743b807b91e37ccf844faecb7d8b32dbee500e47d8a84061245027 +size 45223 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en.png index a5853744391..a967a634d1e 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23dff654bad158f522929fdb6fb6911f4c511eb688bb28995f2d2a56bd747758 -size 23930 +oid sha256:827bb9533946b491d3cc28d7026866f699aa6287a23daa0aa8869cad91e20a6d +size 39537 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en.png new file mode 100644 index 00000000000..4bb302114b1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f75852d849d47db25fd64cbd71e6fcb98141a52fa35c329fb6a9ba35dd2b5eee +size 39902 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png new file mode 100644 index 00000000000..81544bb05d5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f519ff15216d23809e5c4e607eca52078c90c1a69d2e5f0aeaf443d6ab49c2ef +size 30148 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en.png index 7e53aff6619..66b44846eb9 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1504a5137b90bf6c03ae8d3a90946ae1a9b528013f9c3898a0e91e4d073d159 -size 34347 +oid sha256:bf4643bb5ac46e4e13725c6f333b23a9c5a8d3c54d8f2eecfae9811b7237d87c +size 31303 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en.png index 6d93a90b6cb..7f95a4dd263 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0aba8429b1c853012d17fab62350edab5c91721560978b9024ed436d99f7ebe -size 33694 +oid sha256:c6f9f5dceb1e65cc382ef9a2f52f771635d390779f45304b7a7caeeb9e30d51c +size 27879 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en.png index 1b3302c89cc..6e507eb825e 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:615107c5e6d654779b1a8fb0ac3e5511f03ac0eac2a5c3ae0424972b92401ca2 -size 5244 +oid sha256:bb1651647db82174f84e0e166f234e9e397df90fff77dd5f254fe1a18ed23d4e +size 25226 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en.png index 55f09c6d9d8..3212a9557bf 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54904239d5e4789b82299ec5fe2d68386a7a77aea9045bde2956c648e68bcb8a -size 30965 +oid sha256:87343c744ceb706cc5ab7d1391f914b0020a21e5e12cb3d8d6af536c1f0f71f4 +size 4017 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png index cd629687fb7..b3615472766 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67f61b340f5ebfbec1147f7ef37f31054cd37b32abf88058bdb18970bbf7d3fa -size 30082 +oid sha256:6b4ee533e46b9ef21d7edd1c81b4fc7e95deb0de1a5cc9ab37fb38d43b8d4c8f +size 34085 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en.png index c12e74814b8..bf8a3e39279 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a8c359687b3ec5f038602b9022ef3967af12c6632a59c56bd10fecf7d92233c -size 46457 +oid sha256:1d9f27a4db5f7ddc46d1564b16c7efba385ba1c4015473639b34d65875e7f37d +size 23186 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en.png index fbae9acd3c2..16a8ed358fb 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6576c0b77984df1930f0115e1ea90e6720f0fb5f500e186c591f0fd2eb771b6 -size 44729 +oid sha256:266f19ba9cc406a67db3ce12b7cdac15452ac8f0945ceccf8e7b9b247a50d227 +size 20367 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en.png index af703a33cf5..fc4d46ae297 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4bed9db1d0c920e48dd564d76494e09876c948b2fbdadccb8c4d23b49d28d3d9 -size 24255 +oid sha256:68bd384868363a724f8ae69ac8fc978677e8391724fdc9b5e2d11cd9b6cb93d1 +size 46825 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en.png index 9afd661315f..1e08933aa01 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5b1f18748888c5f20d663f3c6f21fe577915f0b55e7e963be3ccf7e88cda07a -size 19250 +oid sha256:8e203ae7deccba3ba29f4f659d867f14aad8e515976fe2379dc20972e5f42ae5 +size 47164 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en.png index 683805307f1..0d8f547d670 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90c8e131e639b6638a75468d41a3ff0107822e3450c6edbcc4d9eccdd180f6ae -size 31537 +oid sha256:baa21d8011741ac2bca82ac26fa289d016a3ddcc16d408f92812adc662e27869 +size 40088 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en.png index 7e53aff6619..8cb2aed49e7 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1504a5137b90bf6c03ae8d3a90946ae1a9b528013f9c3898a0e91e4d073d159 -size 34347 +oid sha256:c7adcf41fe721cf1bdff61850881590bdcb32cff258343c24eb4e44df7dab734 +size 24621 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en.png index 2dd4cdac47c..bc8a1643bd3 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:470cb7c851af9d91cc44d52d502b50764136b079b600ca58ae2512e7ff12e34b -size 29534 +oid sha256:ad5caabfdf57933e5d0c0a9071464bb92729b4aaa6150b2117ce46542dea7fc6 +size 19655 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en.png index 878c05c7f4c..085b7d20e54 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b07e8c136c56d3e23fe3180e791b414c249865e0d7fa5132393bbf7aa09a72bf -size 26631 +oid sha256:37a07b591a038d78626986edfeb142b0581f028ece4742885d4c09a288497b22 +size 31904 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en.png index 7a116903690..a01d276dd7d 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14770fdc3e68b250b5f7dad6bb6fb1bf387c66774287276ab4b505c144007b33 -size 33299 +oid sha256:4fe86ca7a863bb66a6abe6febcc1ee4eec188d57a18f76dd973f3dad00911691 +size 30570 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en.png index 71b604907ca..7674d030026 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_10_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c428e8289390624b3e4f606404ac54acc19cbe0a6df666661f309b7cc0a88031 -size 32082 +oid sha256:68fffe7c273430425bcf00ed18174c47023e114d9df8108f0681c61cb88ee6fc +size 27229 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en.png index cba2bf39b20..4e0f525fec7 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_11_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a360e21538876df4d8aa1b4a3e95e4982df6307a69df4d887416cbbd76b8cd99 -size 5250 +oid sha256:fd887339bef515c0403052338579d5885d6f0f2c31aa3ce511521a97841f3dc4 +size 24531 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en.png index 73601439425..850faebafaa 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_13_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1011caa6960fb64acf48f5d6c2933c9826f66afcdb80280d8509123fb507ab53 -size 30283 +oid sha256:c6ec71c6d10cfffbffb89364967f721762040da16b129da701eccb5568aa414e +size 3999 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png index 43a7fe5af41..676aaf77164 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce120b819be555167e5c6200e068bca8c1f3a1245b5a6c7939fb012c793a4a57 -size 29220 +oid sha256:3e7d989da1a0a8bfd82a404a92c575e6a964dffa9e8fe4148eaa5c822df14f99 +size 33157 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en.png index 924aa9fc28d..5e7f4fa0b38 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f385489dab05d85ad04be5324e2b6d0ca4333f46671aee69ac11a9feb04f966b -size 45526 +oid sha256:90a6b06c59eb121a265f33299a97360e3a81c29daafc850c65f658cd4ffde99a +size 22695 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en.png index fc0ad477da5..a8397081112 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_3_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7792bff8e012d42e7dea995152f5c0674f1c4a24e333cbfcb52d4ad4fc58ec2 -size 43783 +oid sha256:84f577862d06ccb4f5a4c3ae9d95574dfc884f52ba08e121708c6833b417d130 +size 20033 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en.png index 5883152c3e1..de964a62be0 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2542e7ad670ba57f203c3c07d9f6dcd654100fd7c7681846fdf3657436f261f4 -size 23951 +oid sha256:08c61fe78ffd95d42b9210613814ef5d975c97be277a025a26ddb903b4d10a8d +size 45829 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en.png index f3ea2ba2cc9..d96167c1b5c 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41fa05234a46873d525a7414191e110e157d8226dc5e034bf9ddbbbbd33cde37 -size 18696 +oid sha256:1954fc7c779cf4e7c15238a79a0f937965dfd074394be7642ea469bf5fd9482c +size 46196 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en.png index c34fdec6573..a967a634d1e 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:744f66b5f395a854dce1f9abc8dbb43fbbee5de1d18b6c18d82a614eb2d32096 -size 30762 +oid sha256:827bb9533946b491d3cc28d7026866f699aa6287a23daa0aa8869cad91e20a6d +size 39537 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en.png index 7a116903690..25f198a9e57 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14770fdc3e68b250b5f7dad6bb6fb1bf387c66774287276ab4b505c144007b33 -size 33299 +oid sha256:b41f5b0620537d0e31e5bad7a6e763fc3efeb0cd75bf64ef3d283b14b5d5ba49 +size 24271 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en.png index c6692886f47..950ce671d12 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_8_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9dba1ef09211361216ee035c64ee50d62ab1df5191d365ef26f5a4f741477867 -size 28635 +oid sha256:ee7867cdd6be342a0a2e48672c5b4d7d6273bdfe75d92ec5b5d110c31b930113 +size 19010 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en.png index 4c3a6af3384..1b47eec015e 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0601e1c9d553113eb16d89e888e23320585ea139a6653d2b56e830834ae4fe68 -size 26011 +oid sha256:ddc9b2c7654db142caff95bcdfb5b3fa7d0ad16ec7025989711118fc29a25567 +size 31047 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png new file mode 100644 index 00000000000..20f100c6479 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c64b781b818af8855f0672a06bb5b8be6921a4b4a70d2dacd470c0f13c82792 +size 12554 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png new file mode 100644 index 00000000000..5540a7b2fe0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35c908b67f328ce90667760b7d7f2fe4f0e120fc872416801ca6086860a9a1e3 +size 12110 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_90_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_90_en.png new file mode 100644 index 00000000000..79872e52442 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_90_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3667c472d9832e0d25996889ec0887685dfac049c043a6c8a448789a5f7c6909 +size 17200 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_91_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_91_en.png new file mode 100644 index 00000000000..b630ea26fc3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_91_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f96f824d47ed90dd4fee4cd64b807bc49dd273510d6cd2072b2bf5cdaf740dd +size 15943 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_92_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_92_en.png new file mode 100644 index 00000000000..a4fd665ea1a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_92_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42488ef04ffbbe625b7da963a2b1c3c9cabb0d80bb77a2d4f8b4c4afdcd9bf95 +size 20337 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png index af2614df6bd..96355854791 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ec29adc030f96aae3952c785b71b2e712d9f4a6cc6cced5f4ac139f5ba4f3a7 -size 12393 +oid sha256:1fed22f221c1087ffb066b950918924cfc4af1618bb7724c57fa27d4cfe50340 +size 14089 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png index 82c063087ba..08a56eb13c3 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_BigIcon_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aa2203ef22231bf8368849fcf1b17feb08292e116e5e33ed67c3f29ff138d25 -size 12535 +oid sha256:b028b36b64fecede982569046613903a8df32e7f95243a0eb4c25e89205c141f +size 14199 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_6_en.png new file mode 100644 index 00000000000..0684f2bcaf4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d81aade2945be977896038addacdb9246d17bb1a57fb3bd0d43169e382ce9f03 +size 13126 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_6_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_6_en.png new file mode 100644 index 00000000000..7e7a23ed38b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components_PageTitleWithIconFull_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:610e68016011b14db56217ebcd27a86ab597c1668a3a90b09c3e39ac308f8764 +size 13054 From 34fcd7921d51f5e7975bd1fe022d16b3b704576b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:05:55 +0100 Subject: [PATCH 09/19] Use right copy for 'verify user' action --- .../android/features/userprofile/shared/UserProfileView.kt | 2 +- .../element/android/features/userprofile/UserProfileViewTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 76681a050cd..68ef2071841 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 @@ -124,7 +124,7 @@ private fun VerifyUserSection( ) { if (state.isVerified.dataOrNull() == false) { ListItem( - headlineContent = { Text(stringResource(CommonStrings.common_verify_identity)) }, + headlineContent = { Text(stringResource(CommonStrings.common_verify_user)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), onClick = onVerifyClick, ) 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 20ffb88d1cf..9a14a20221b 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 @@ -203,7 +203,7 @@ class UserProfileViewTest { state = aUserProfileState(userId = A_USER_ID, isVerified = AsyncData.Success(false)), onVerifyClick = callback, ) - rule.clickOn(CommonStrings.common_verify_identity) + rule.clickOn(CommonStrings.common_verify_user) } } } From a653a54ec2c5dd841616d9e106b089f8747a4f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:06:18 +0100 Subject: [PATCH 10/19] Add padding between text items in the incoming user verification request screen --- .../verifysession/impl/ui/VerificationUserProfileContent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt index 6e54537ea54..3b10499b393 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt @@ -8,6 +8,7 @@ package io.element.android.features.verifysession.impl.ui import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -51,7 +52,7 @@ fun VerificationUserProfileContent( Spacer(modifier = Modifier.padding(12.dp)) - Column { + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { Text(text = displayName ?: userId.value, style = ElementTheme.typography.fontBodyLgMedium, color = ElementTheme.colors.textPrimary) if (displayName != null) { From f95748ddb344409c5b8007dadc8657f034286437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:06:43 +0100 Subject: [PATCH 11/19] Fix icons in the outgoing user verification --- .../verifysession/impl/outgoing/VerifySelfSessionView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt index d9f494458bd..05914ba73d4 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionView.kt @@ -120,7 +120,7 @@ private fun VerifySelfSessionHeader(step: Step, request: VerificationRequest.Out Step.Loading -> error("Should not happen") Step.Initial -> when (request) { is VerificationRequest.Outgoing.CurrentSession -> BigIcon.Style.Default(CompoundIcons.Devices()) - is VerificationRequest.Outgoing.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid()) + is VerificationRequest.Outgoing.User -> BigIcon.Style.Default(CompoundIcons.LockSolid()) } Step.AwaitingOtherDeviceResponse -> BigIcon.Style.Loading Step.Canceled -> BigIcon.Style.AlertSolid From a15ece90fabe8d00452f1b22649ecf3b59c5dd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:06:59 +0100 Subject: [PATCH 12/19] Fix icons in the incoming user verification screens --- .../verifysession/impl/incoming/IncomingVerificationView.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 96b5a72655a..0bafdbbb24d 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 @@ -98,7 +98,11 @@ private fun IncomingVerificationHeader(step: Step, request: VerificationRequest. is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid()) } } - is Step.Verifying -> if (step.isWaiting) BigIcon.Style.Loading else BigIcon.Style.Default(CompoundIcons.LockSolid()) + is Step.Verifying -> if (step.isWaiting) { + BigIcon.Style.Loading + } else { + BigIcon.Style.Default(CompoundIcons.ReactionSolid()) + } Step.Completed -> BigIcon.Style.SuccessSolid Step.Failure -> BigIcon.Style.AlertSolid } From a973dd9774db3e62017675f6a56c56f868026928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:07:26 +0100 Subject: [PATCH 13/19] Remove `showDeviceVerifiedScreen` parameter from `NavTarget.UseAnotherDevice` --- .../FtueSessionVerificationFlowNode.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt index 43373f3a9a5..eeffbdfe50e 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt @@ -55,7 +55,7 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( data object Root : NavTarget @Parcelize - data class UseAnotherDevice(val showDeviceVerifiedScreen: Boolean) : NavTarget + data object UseAnotherDevice : NavTarget @Parcelize data object EnterRecoveryKey : NavTarget @@ -72,7 +72,7 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( override fun onDone() { lifecycleScope.launch { // Move to the completed state view in the verification flow - backstack.newRoot(NavTarget.UseAnotherDevice(showDeviceVerifiedScreen = true)) + backstack.newRoot(NavTarget.UseAnotherDevice) } } } @@ -82,7 +82,7 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( is NavTarget.Root -> { val callback = object : ChooseSelfVerificationModeNode.Callback { override fun onUseAnotherDevice() { - backstack.push(NavTarget.UseAnotherDevice(showDeviceVerifiedScreen = true)) + backstack.push(NavTarget.UseAnotherDevice) } override fun onUseRecoveryKey() { @@ -103,7 +103,7 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( is NavTarget.UseAnotherDevice -> { verifySessionEntryPoint.nodeBuilder(this, buildContext) .params(VerifySessionEntryPoint.Params( - showDeviceVerifiedScreen = navTarget.showDeviceVerifiedScreen, + showDeviceVerifiedScreen = true, verificationRequest = VerificationRequest.Outgoing.CurrentSession, )) .callback(object : VerifySessionEntryPoint.Callback { From 2b6a3e83d2125bf4d8cc95380ca472bb1e6bb341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:08:10 +0100 Subject: [PATCH 14/19] Fix minor naming/visibility issues --- .../io/element/android/features/ftue/impl/di/FtueModule.kt | 2 +- .../choosemode/ChooseSessionVerificationModePresenterTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt index 24e118b0afd..4387b3dc00f 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt @@ -19,5 +19,5 @@ import io.element.android.libraries.di.SessionScope @Module interface FtueModule { @Binds - fun bindChooseVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter + fun bindChooseSelfVerificationMethodPresenter(presenter: ChooseSelfVerificationModePresenter): Presenter } 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 1ac614c81e1..3801001ed41 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 @@ -54,7 +54,7 @@ class ChooseSessionVerificationModePresenterTest { } } - fun createPresenter( + private fun createPresenter( encryptionService: FakeEncryptionService = FakeEncryptionService(), directLogoutPresenter: Presenter = Presenter { aDirectLogoutState() } ) = ChooseSelfVerificationModePresenter( From 3f8759194454429aa2e67fe3cb65e75433c15a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:08:29 +0100 Subject: [PATCH 15/19] Split event and state for `ChooseSelfVerificationModePresenter` into their own files --- .../ChooseSelfVerificationModeEvent.kt | 12 ++++++++++++ .../ChooseSelfVerificationModePresenter.kt | 11 ----------- .../ChooseSelfVerificationModeState.kt | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt new file mode 100644 index 00000000000..99e59506937 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +sealed interface ChooseSelfVerificationModeEvent { + data object SignOut : ChooseSelfVerificationModeEvent +} 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 ae7bf36c639..0aec3b4e1d1 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 @@ -45,14 +45,3 @@ class ChooseSelfVerificationModePresenter @Inject constructor( ) } } - -data class ChooseSelfVerificationModeState( - val isLastDevice: Boolean, - val canEnterRecoveryKey: Boolean, - val directLogoutState: DirectLogoutState, - val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, -) - -sealed interface ChooseSelfVerificationModeEvent { - object SignOut : ChooseSelfVerificationModeEvent -} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt new file mode 100644 index 00000000000..21c37a4ae29 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.ftue.impl.sessionverification.choosemode + +import io.element.android.features.logout.api.direct.DirectLogoutState + +data class ChooseSelfVerificationModeState( + val isLastDevice: Boolean, + val canEnterRecoveryKey: Boolean, + val directLogoutState: DirectLogoutState, + val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, +) From 18b8a3648064ca84357d01d6a2de857275c0edce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 6 Mar 2025 16:09:02 +0100 Subject: [PATCH 16/19] Allow exiting the FTUE flow, which will close the app. The previous state will be restored when the app is reopened. --- .../android/features/ftue/impl/FtueFlowNode.kt | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 9e44b8ac3f5..034701fec48 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.navigation.backpresshandlerstrategies.BaseBackPressHandlerStrategy import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack @@ -38,8 +37,6 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SessionScope import io.element.android.services.analytics.api.AnalyticsService -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn @@ -59,7 +56,6 @@ class FtueFlowNode @AssistedInject constructor( backstack = BackStack( initialElement = NavTarget.Placeholder, savedStateMap = buildContext.savedStateMap, - backPressHandler = NoOpBackstackHandlerStrategy(), ), buildContext = buildContext, plugins = plugins, @@ -175,11 +171,3 @@ class FtueFlowNode @AssistedInject constructor( } } } - -private class NoOpBackstackHandlerStrategy : BaseBackPressHandlerStrategy() { - override val canHandleBackPressFlow: StateFlow = MutableStateFlow(true) - - override fun onBackPressed() { - // No-op - } -} From 54babecdf386f28f917dcede623f65ae604c18f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 7 Mar 2025 11:34:14 +0100 Subject: [PATCH 17/19] When outgoing verification fails, move to the `Canceled` state. Then, when resetting the state machine state also reset the verification service. --- .../impl/outgoing/VerifySelfSessionStateMachine.kt | 6 ++---- .../impl/outgoing/VerifySelfSessionPresenterTest.kt | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt index ab109014980..8d5be7551c2 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt @@ -65,6 +65,7 @@ class VerifySelfSessionStateMachine( } inState { on { _, state -> + sessionVerificationService.reset(cancelAnyPendingVerificationAttempt = false) state.override { State.Initial.andLogStateChange() } } } @@ -122,10 +123,7 @@ class VerifySelfSessionStateMachine( state.override { State.Canceled.andLogStateChange() } } on { event, state: MachineState -> - when (state.snapshot) { - is State.RequestingVerification -> state.override { State.Initial.andLogStateChange() } - else -> state.override { State.Canceled.andLogStateChange() } - } + state.override { State.Canceled.andLogStateChange() } } } } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt index f92a05d348d..2328fc56cdd 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionPresenterTest.kt @@ -122,7 +122,7 @@ class VerifySelfSessionPresenterTest { } @Test - fun `present - A fail when requesting verification resets the state to the initial one`() = runTest { + fun `present - A fail when requesting verification resets the state to the canceled one`() = runTest { val service = unverifiedSessionService( requestSessionVerificationLambda = { }, ) @@ -131,7 +131,7 @@ class VerifySelfSessionPresenterTest { awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification) service.emitVerificationFlowState(VerificationFlowState.DidFail) assertThat(awaitItem().step).isInstanceOf(Step.AwaitingOtherDeviceResponse::class.java) - assertThat(awaitItem().step).isEqualTo(Step.Initial) + assertThat(awaitItem().step).isEqualTo(Step.Canceled) } } From 995a1223d05e280b37caefb779190dcd20600723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 7 Mar 2025 11:36:27 +0100 Subject: [PATCH 18/19] Remove some dead code --- .../impl/outgoing/VerifySelfSessionStateMachine.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt index 8d5be7551c2..746cbe8aa45 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/VerifySelfSessionStateMachine.kt @@ -200,8 +200,3 @@ class VerifySelfSessionStateMachine( data object Reset : Event } } - -sealed interface VerificationType { - data object CurrentSession : VerificationType - data object User : VerificationType -} From e1b03bc197c0bfa741148547f9d6b90cdcaa03c6 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 7 Mar 2025 11:41:49 +0000 Subject: [PATCH 19/19] Update screenshots --- .../features.userprofile.shared_UserProfileView_Day_0_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_1_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_6_en.png | 4 ++-- .../features.userprofile.shared_UserProfileView_Day_7_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_0_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_1_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_6_en.png | 4 ++-- ...features.userprofile.shared_UserProfileView_Night_7_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_2_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_4_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_5_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_6_en.png | 4 ++-- ...ession.impl.incoming_IncomingVerificationView_Day_9_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_2_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_4_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_5_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_6_en.png | 4 ++-- ...sion.impl.incoming_IncomingVerificationView_Night_9_en.png | 4 ++-- ...fysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png | 4 ++-- ...session.impl.outgoing_VerifySelfSessionView_Night_1_en.png | 4 ++-- ...ession.impl.ui_VerificationUserProfileContent_Day_0_en.png | 4 ++-- ...sion.impl.ui_VerificationUserProfileContent_Night_0_en.png | 4 ++-- 22 files changed, 44 insertions(+), 44 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png index 2884844ad31..7653b907031 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b560dacbf56801956dea441405e2f0694289944fb8aecd11cd7f697fb31b3d7 -size 24715 +oid sha256:4e395d9ca4fc6b2eb1ea9a8fd6e2ad34e7397d9f64cf39e05eed50a1bfffabd7 +size 24186 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png index e45afadfc2f..9f82a024d9f 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e84d3ab362641d96a97606427a60cf5d20660fe81e53b621b947cc367d010a2 -size 22585 +oid sha256:87cb4255c8346796a226cdfc2afb67641a3b27c2c1a15629a07c3293fcad5131 +size 22078 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png index ffebf78252a..ca9fcec3777 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:014bb710c822ba5420b9d8d8a973c6734a98f854787c7d0d88efc979c731380b -size 23296 +oid sha256:a465e6254ae595d62a335914414ad2f33b76736f529d1e188f3d0e0d57268f65 +size 23015 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png index b26b0accd80..3c27d5ab57b 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c35c7c31daa5a498b7698ea818b0eeaa8a81173161d40b5ddca2154176ed9a5e -size 25696 +oid sha256:ef88b209eb93fb6b4e5aa411c4cdcb9ff1aa49bd44f11c2271a4d758482b145a +size 25181 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png index 3192cac4490..572f9e52e57 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdb1bad54179154d28a8b41ad072b4c9d7d9061e06c1a3e1f46bcf30e5155248 -size 24043 +oid sha256:6894ddb5662e7da82e5ee42fb0c9eb30afd87e15e415ba42be218ebab434d53a +size 23531 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png index 3e569070f00..1acafc94c6b 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9b53eec1ca771ba3d0e0929b8da5b3ec99f576b8b8ae0f059c4256ce19f5d56 -size 22043 +oid sha256:0f393f620cc11b7aec7bf52f7efc5a5872ed07a14e7fbf2149b16171dbb2b03e +size 21463 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png index 7fe0d89bf62..43510395578 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:315c9ff214cd0c728d082afe34fe811e58d2a07e28c808b7d84a790c79f22734 -size 21492 +oid sha256:4b48c3e2c5e06a612473e252ba52eead6efd741b7ea21fdb778ec9ed5952a2a2 +size 21181 diff --git a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png index 118fdb916aa..2c7ce358ee7 100644 --- a/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.userprofile.shared_UserProfileView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:650918f76a64db0b2308e99a6670e5e10c0713426c5c95b0f8b3ac7984a971ee -size 24887 +oid sha256:201c3f2385059321f7500ca8d559bb973a51aeb56fd417e943be496ae55d0869 +size 24370 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png index fe394f3b14b..6cd8aaacec8 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bd5eb4e86f00c2807e244856f3645444062b3404b37add53c02d9b8555012b6 -size 36286 +oid sha256:8ebb02ae3454f3cbb28e3aac886d088888251d25ba2c5e8fb7adde700f10383e +size 36323 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png index ef0f96e1c71..7c87a4d5146 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6006a987dce3f67d2a4b600a26c7251af4584e3fcb498e2bbf490dc285083ae7 -size 32261 +oid sha256:f8aa70b0d5eace900fc8c58674d2f3cdcf409335a33e8cf3eea5f60fb495d009 +size 32299 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png index cd7e23e55f6..93ebfdce9da 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:991dd800b2031ae915a166c83111df9c4ecb6dd6fb9014208a97e2836dfdfa77 -size 45796 +oid sha256:c28294b67c1d8f448300b35a4ec6ebecd105334375af61b61612b82b5509d417 +size 46402 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png index 9e17f544550..231e55a9892 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69f56c1e99d79223134d0aa7ee30582463e323d3c1c10471fe8709a90438c3ce -size 46138 +oid sha256:becc11517522d030f4539c67d7b4728661135a43b05e9c902481b79539155912 +size 46738 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png index 6a3d8f51ccc..c4fff6eb048 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bb234aea96d3dbd8eacb6b85c7fb50c23d7fb9e9add31f0c894167e66492e03 -size 30939 +oid sha256:acd3515c98dc74f265fbd4c8b82c60a65ae7c764994016ab39bf25062d70140f +size 31496 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png index e6626477eba..8d074c872f4 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c61ae62b686709a1a7c69c4cf3415fb59cbeae5e7fd1359b5d77e120e32abadb -size 35520 +oid sha256:31d4e4cbf57e53a5bf5e63daf0b1926d39dc67d2859a436fe1eb548830897ee3 +size 35532 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png index 03f5de7dbd1..f2049210adb 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eee91f2b7f360cfecdf99f0f072f1158f7646a62d75d7617358241ccfa7042a1 -size 31611 +oid sha256:6b753111cd6ca0f030222d97ebbbeb5deb9f3b9d64d82dd8970116a3913be06f +size 31615 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png index 4e03197c02c..d1de4446837 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:733eca4911eab1b300fc6e104e39d60d6f80371175c53f5cc03c15f4268afb92 -size 44855 +oid sha256:aabe3ce5f01f2be5a7a8ae7907a3d7b76b3b11a3012df4e69149084cf0fd0341 +size 45434 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png index 2827a25e77e..4d68f63f9e6 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6d3505e15743b807b91e37ccf844faecb7d8b32dbee500e47d8a84061245027 -size 45223 +oid sha256:3470688050248c93b696e95561bdf4be61dd57046d66eec297086c7cc449c97d +size 45801 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png index 81544bb05d5..bb9b28459c9 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f519ff15216d23809e5c4e607eca52078c90c1a69d2e5f0aeaf443d6ab49c2ef -size 30148 +oid sha256:4d6df8d6e08ff4c4822fb75063dc9c04aa09334050ee3dc0801bcd10b7b95e7b +size 30690 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png index b3615472766..98a0ff76e4a 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b4ee533e46b9ef21d7edd1c81b4fc7e95deb0de1a5cc9ab37fb38d43b8d4c8f -size 34085 +oid sha256:ebc36a702cc711e490f33912735a90f5cd85b1702c352931cd2f91d6598252bb +size 33166 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png index 676aaf77164..122bfaee2f1 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.outgoing_VerifySelfSessionView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e7d989da1a0a8bfd82a404a92c575e6a964dffa9e8fe4148eaa5c822df14f99 -size 33157 +oid sha256:3129b8b649e5fb26cf559cce754a84788c186acee1e201989a3f405a8a4c71ac +size 32354 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png index 20f100c6479..38e1253d41d 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c64b781b818af8855f0672a06bb5b8be6921a4b4a70d2dacd470c0f13c82792 -size 12554 +oid sha256:e5d78f00d33cad5e00719eec49f6b55959b5c23b3842a0f2abc9280961ebb29d +size 12660 diff --git a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png index 5540a7b2fe0..486dae34af3 100644 --- a/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35c908b67f328ce90667760b7d7f2fe4f0e120fc872416801ca6086860a9a1e3 -size 12110 +oid sha256:4612c8404b671a01129d2334a7aa296218bb4b00820265ef5d00fd9603e7f850 +size 12272