Move session verification to FTUE flow, make it mandatory (#2594)
* Move session verification to the FTUE * Allow session verification flow to be restarted * Use `EncryptionService` to display session verification faster * Remove session verification item from settings * Remove session verification banner from room list * Remove 'verification needed' variant from the `TimelineEncryptedHistoryBanner` * Improve verification flow UI and UX * Remove 'verification successful' snackbar message * Only register push provider after the session has been verified * Hide room list while the session hasn't been verified * Prevent deep links from changing the navigation if the session isn't verified * Update screenshots * Renamed `FtueState` to `FtueService`, created an actual `FtueState`. --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
06ba5eaafc
commit
8b335a9125
198 changed files with 822 additions and 761 deletions
|
|
@ -53,7 +53,7 @@ class VerifySelfSessionNode @AssistedInject constructor(
|
|||
state = state,
|
||||
modifier = modifier,
|
||||
onEnterRecoveryKey = ::onEnterRecoveryKey,
|
||||
goBack = ::onDone,
|
||||
onFinished = ::onDone,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
when (event) {
|
||||
VerifySelfSessionViewEvents.RequestVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.RequestVerification)
|
||||
VerifySelfSessionViewEvents.StartSasVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.StartSasVerification)
|
||||
VerifySelfSessionViewEvents.Restart -> stateAndDispatch.dispatchAction(StateMachineEvent.Restart)
|
||||
VerifySelfSessionViewEvents.ConfirmVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptChallenge)
|
||||
VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
||||
VerifySelfSessionViewEvents.CancelAndClose -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
||||
VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
||||
VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset)
|
||||
}
|
||||
}
|
||||
return VerifySelfSessionState(
|
||||
|
|
@ -118,7 +118,7 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
private fun CoroutineScope.observeVerificationService() {
|
||||
sessionVerificationService.verificationFlowState.onEach { verificationAttemptState ->
|
||||
when (verificationAttemptState) {
|
||||
VerificationFlowState.Initial -> stateMachine.dispatch(VerifySelfSessionStateMachine.Event.Restart)
|
||||
VerificationFlowState.Initial -> stateMachine.dispatch(VerifySelfSessionStateMachine.Event.Reset)
|
||||
VerificationFlowState.AcceptedVerificationRequest -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidAcceptVerificationRequest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,24 +20,35 @@
|
|||
package io.element.android.features.verifysession.impl
|
||||
|
||||
import com.freeletics.flowredux.dsl.FlowReduxStateMachine
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
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 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(
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val encryptionService: EncryptionService,
|
||||
) : FlowReduxStateMachine<VerifySelfSessionStateMachine.State, VerifySelfSessionStateMachine.Event>(
|
||||
initialState = State.Initial
|
||||
) {
|
||||
init {
|
||||
spec {
|
||||
inState<State.Initial> {
|
||||
on { _: Event.RequestVerification, state: MachineState<State.Initial> ->
|
||||
on { _: Event.RequestVerification, state ->
|
||||
state.override { State.RequestingVerification }
|
||||
}
|
||||
on { _: Event.StartSasVerification, state: MachineState<State.Initial> ->
|
||||
on { _: Event.StartSasVerification, state ->
|
||||
state.override { State.StartingSasVerification }
|
||||
}
|
||||
}
|
||||
|
|
@ -45,12 +56,9 @@ class VerifySelfSessionStateMachine @Inject constructor(
|
|||
onEnterEffect {
|
||||
sessionVerificationService.requestVerification()
|
||||
}
|
||||
on { _: Event.DidAcceptVerificationRequest, state: MachineState<State.RequestingVerification> ->
|
||||
on { _: Event.DidAcceptVerificationRequest, state ->
|
||||
state.override { State.VerificationRequestAccepted }
|
||||
}
|
||||
on { _: Event.DidFail, state: MachineState<State.RequestingVerification> ->
|
||||
state.override { State.Initial }
|
||||
}
|
||||
}
|
||||
inState<State.StartingSasVerification> {
|
||||
onEnterEffect {
|
||||
|
|
@ -58,25 +66,28 @@ class VerifySelfSessionStateMachine @Inject constructor(
|
|||
}
|
||||
}
|
||||
inState<State.VerificationRequestAccepted> {
|
||||
on { _: Event.StartSasVerification, state: MachineState<State.VerificationRequestAccepted> ->
|
||||
on { _: Event.StartSasVerification, state ->
|
||||
state.override { State.StartingSasVerification }
|
||||
}
|
||||
}
|
||||
inState<State.Canceled> {
|
||||
on { _: Event.Restart, state: MachineState<State.Canceled> ->
|
||||
on { _: Event.RequestVerification, state ->
|
||||
state.override { State.RequestingVerification }
|
||||
}
|
||||
on { _: Event.Reset, state ->
|
||||
state.override { State.Initial }
|
||||
}
|
||||
}
|
||||
inState<State.SasVerificationStarted> {
|
||||
on { event: Event.DidReceiveChallenge, state: MachineState<State.SasVerificationStarted> ->
|
||||
on { event: Event.DidReceiveChallenge, state ->
|
||||
state.override { State.Verifying.ChallengeReceived(event.data) }
|
||||
}
|
||||
}
|
||||
inState<State.Verifying.ChallengeReceived> {
|
||||
on { _: Event.AcceptChallenge, state: MachineState<State.Verifying.ChallengeReceived> ->
|
||||
on { _: Event.AcceptChallenge, state ->
|
||||
state.override { State.Verifying.Replying(state.snapshot.data, accept = true) }
|
||||
}
|
||||
on { _: Event.DeclineChallenge, state: MachineState<State.Verifying.ChallengeReceived> ->
|
||||
on { _: Event.DeclineChallenge, state ->
|
||||
state.override { State.Verifying.Replying(state.snapshot.data, accept = false) }
|
||||
}
|
||||
}
|
||||
|
|
@ -88,11 +99,21 @@ class VerifySelfSessionStateMachine @Inject constructor(
|
|||
sessionVerificationService.declineVerification()
|
||||
}
|
||||
}
|
||||
on { _: Event.DidAcceptChallenge, state: MachineState<State.Verifying.Replying> ->
|
||||
on { _: Event.DidAcceptChallenge, state ->
|
||||
// If a key backup exists, wait until it's restored or a timeout happens
|
||||
val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse()
|
||||
if (hasBackup) {
|
||||
tryOrNull {
|
||||
encryptionService.recoveryStateStateFlow.filter { it == RecoveryState.ENABLED }
|
||||
.timeout(10.seconds)
|
||||
.first()
|
||||
}
|
||||
}
|
||||
state.override { State.Completed }
|
||||
}
|
||||
}
|
||||
inState<State.Canceling> {
|
||||
// TODO The 'Canceling' -> 'Canceled' transitions doesn't seem to work anymore, check if something changed in the Rust SDK
|
||||
onEnterEffect {
|
||||
sessionVerificationService.cancelVerification()
|
||||
}
|
||||
|
|
@ -102,21 +123,24 @@ class VerifySelfSessionStateMachine @Inject constructor(
|
|||
state.override { State.SasVerificationStarted }
|
||||
}
|
||||
on { _: Event.Cancel, state: MachineState<State> ->
|
||||
if (state.snapshot in sequenceOf(
|
||||
State.Initial,
|
||||
State.Completed,
|
||||
State.Canceled
|
||||
)) {
|
||||
state.noChange()
|
||||
} else {
|
||||
state.override { State.Canceling }
|
||||
when (state.snapshot) {
|
||||
State.Initial, State.Completed, State.Canceled -> state.noChange()
|
||||
// For some reason `cancelVerification` is not calling its delegate `didCancel` method so we don't pass from
|
||||
// `Canceling` state to `Canceled` automatically anymore
|
||||
else -> {
|
||||
sessionVerificationService.cancelVerification()
|
||||
state.override { State.Canceled }
|
||||
}
|
||||
}
|
||||
}
|
||||
on { _: Event.DidCancel, state: MachineState<State> ->
|
||||
state.override { State.Canceled }
|
||||
}
|
||||
on { _: Event.DidFail, state: MachineState<State> ->
|
||||
state.override { State.Canceled }
|
||||
when (state.snapshot) {
|
||||
is State.RequestingVerification -> state.override { State.Initial }
|
||||
else -> state.override { State.Canceled }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -190,7 +214,7 @@ class VerifySelfSessionStateMachine @Inject constructor(
|
|||
/** Request failed. */
|
||||
data object DidFail : Event
|
||||
|
||||
/** Restart the verification flow. */
|
||||
data object Restart : Event
|
||||
/** Reset the verification flow to the initial state. */
|
||||
data object Reset : Event
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -42,15 +39,16 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.verifysession.impl.emoji.toEmojiResource
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
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.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||
|
|
@ -62,36 +60,37 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver
|
|||
fun VerifySelfSessionView(
|
||||
state: VerifySelfSessionState,
|
||||
onEnterRecoveryKey: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
onFinished: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun goBackAndCancelIfNeeded() {
|
||||
state.eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
goBack()
|
||||
}
|
||||
if (state.verificationFlowStep is FlowStep.Completed) {
|
||||
goBack()
|
||||
fun resetFlow() {
|
||||
state.eventSink(VerifySelfSessionViewEvents.Reset)
|
||||
}
|
||||
BackHandler {
|
||||
goBackAndCancelIfNeeded()
|
||||
when (state.verificationFlowStep) {
|
||||
is FlowStep.Canceled -> resetFlow()
|
||||
is FlowStep.AwaitingOtherDeviceResponse, FlowStep.Ready -> state.eventSink(VerifySelfSessionViewEvents.Cancel)
|
||||
is FlowStep.Verifying -> {
|
||||
if (!state.verificationFlowStep.state.isLoading()) {
|
||||
state.eventSink(VerifySelfSessionViewEvents.DeclineVerification)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
val verificationFlowStep = state.verificationFlowStep
|
||||
val buttonsVisible by remember(verificationFlowStep) {
|
||||
derivedStateOf { verificationFlowStep != FlowStep.AwaitingOtherDeviceResponse && verificationFlowStep != FlowStep.Completed }
|
||||
}
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
header = {
|
||||
HeaderContent(verificationFlowStep = verificationFlowStep)
|
||||
},
|
||||
footer = {
|
||||
if (buttonsVisible) {
|
||||
BottomMenu(
|
||||
screenState = state,
|
||||
goBack = ::goBackAndCancelIfNeeded,
|
||||
onEnterRecoveryKey = onEnterRecoveryKey
|
||||
)
|
||||
}
|
||||
BottomMenu(
|
||||
screenState = state,
|
||||
goBack = ::resetFlow,
|
||||
onEnterRecoveryKey = onEnterRecoveryKey,
|
||||
onFinished = onFinished,
|
||||
)
|
||||
}
|
||||
) {
|
||||
Content(flowState = verificationFlowStep)
|
||||
|
|
@ -100,40 +99,38 @@ fun VerifySelfSessionView(
|
|||
|
||||
@Composable
|
||||
private fun HeaderContent(verificationFlowStep: FlowStep) {
|
||||
val iconResourceId = when (verificationFlowStep) {
|
||||
is FlowStep.Initial -> R.drawable.ic_verification_devices
|
||||
FlowStep.Canceled -> R.drawable.ic_verification_warning
|
||||
FlowStep.AwaitingOtherDeviceResponse -> R.drawable.ic_verification_waiting
|
||||
FlowStep.Ready, is FlowStep.Verifying, FlowStep.Completed -> R.drawable.ic_verification_emoji
|
||||
val iconStyle = when (verificationFlowStep) {
|
||||
is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> BigIcon.Style.Default(CompoundIcons.LockSolid())
|
||||
FlowStep.Canceled -> BigIcon.Style.AlertSolid
|
||||
FlowStep.Ready, is FlowStep.Verifying -> BigIcon.Style.Default(CompoundIcons.Reaction())
|
||||
FlowStep.Completed -> BigIcon.Style.SuccessSolid
|
||||
}
|
||||
val titleTextId = when (verificationFlowStep) {
|
||||
is FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_title
|
||||
is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_identity_confirmation_title
|
||||
FlowStep.Canceled -> CommonStrings.common_verification_cancelled
|
||||
FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_title
|
||||
FlowStep.Ready,
|
||||
FlowStep.Completed -> R.string.screen_session_verification_compare_emojis_title
|
||||
FlowStep.Ready -> R.string.screen_session_verification_compare_emojis_title
|
||||
FlowStep.Completed -> R.string.screen_identity_confirmed_title
|
||||
is FlowStep.Verifying -> when (verificationFlowStep.data) {
|
||||
is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title
|
||||
is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_title
|
||||
}
|
||||
}
|
||||
val subtitleTextId = when (verificationFlowStep) {
|
||||
is FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_subtitle
|
||||
is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_identity_confirmation_subtitle
|
||||
FlowStep.Canceled -> R.string.screen_session_verification_cancelled_subtitle
|
||||
FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_subtitle
|
||||
FlowStep.Ready -> R.string.screen_session_verification_ready_subtitle
|
||||
FlowStep.Completed -> R.string.screen_session_verification_compare_emojis_subtitle
|
||||
FlowStep.Completed -> R.string.screen_identity_confirmation_subtitle
|
||||
is FlowStep.Verifying -> when (verificationFlowStep.data) {
|
||||
is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_subtitle
|
||||
is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_subtitle
|
||||
}
|
||||
}
|
||||
|
||||
IconTitleSubtitleMolecule(
|
||||
PageTitle(
|
||||
modifier = Modifier.padding(top = 60.dp),
|
||||
iconResourceId = iconResourceId,
|
||||
iconStyle = iconStyle,
|
||||
title = stringResource(id = titleTextId),
|
||||
subTitle = stringResource(id = subtitleTextId)
|
||||
subtitle = stringResource(id = subtitleTextId)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,20 +138,12 @@ private fun HeaderContent(verificationFlowStep: FlowStep) {
|
|||
private fun Content(flowState: FlowStep) {
|
||||
Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) {
|
||||
when (flowState) {
|
||||
is FlowStep.Initial, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit
|
||||
FlowStep.AwaitingOtherDeviceResponse -> ContentWaiting()
|
||||
is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit
|
||||
is FlowStep.Verifying -> ContentVerifying(flowState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentWaiting() {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentVerifying(verificationFlowStep: FlowStep.Verifying) {
|
||||
when (verificationFlowStep.data) {
|
||||
|
|
@ -212,76 +201,102 @@ private fun BottomMenu(
|
|||
screenState: VerifySelfSessionState,
|
||||
onEnterRecoveryKey: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
onFinished: () -> Unit,
|
||||
) {
|
||||
val verificationViewState = screenState.verificationFlowStep
|
||||
val eventSink = screenState.eventSink
|
||||
|
||||
val isVerifying = (verificationViewState as? FlowStep.Verifying)?.state is AsyncData.Loading<Unit>
|
||||
val positiveButtonTitle = when (verificationViewState) {
|
||||
is FlowStep.Initial -> R.string.screen_session_verification_positive_button_initial
|
||||
FlowStep.Canceled -> R.string.screen_session_verification_positive_button_canceled
|
||||
|
||||
when (verificationViewState) {
|
||||
is FlowStep.Initial -> {
|
||||
BottomMenu(
|
||||
positiveButtonTitle = stringResource(R.string.screen_identity_use_another_device),
|
||||
onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.RequestVerification) },
|
||||
negativeButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key),
|
||||
onNegativeButtonClicked = onEnterRecoveryKey,
|
||||
)
|
||||
}
|
||||
is FlowStep.Canceled -> {
|
||||
BottomMenu(
|
||||
positiveButtonTitle = stringResource(R.string.screen_session_verification_positive_button_canceled),
|
||||
onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.RequestVerification) },
|
||||
negativeButtonTitle = stringResource(CommonStrings.action_cancel),
|
||||
onNegativeButtonClicked = goBack,
|
||||
)
|
||||
}
|
||||
is FlowStep.Ready -> {
|
||||
BottomMenu(
|
||||
positiveButtonTitle = stringResource(CommonStrings.action_start),
|
||||
onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.StartSasVerification) },
|
||||
negativeButtonTitle = stringResource(CommonStrings.action_cancel),
|
||||
onNegativeButtonClicked = goBack,
|
||||
)
|
||||
}
|
||||
is FlowStep.AwaitingOtherDeviceResponse -> {
|
||||
BottomMenu(
|
||||
positiveButtonTitle = stringResource(R.string.screen_identity_waiting_on_other_device),
|
||||
onPositiveButtonClicked = {},
|
||||
isLoading = true,
|
||||
)
|
||||
}
|
||||
is FlowStep.Verifying -> {
|
||||
if (isVerifying) {
|
||||
R.string.screen_session_verification_positive_button_verifying_ongoing
|
||||
val positiveButtonTitle = if (isVerifying) {
|
||||
stringResource(R.string.screen_session_verification_positive_button_verifying_ongoing)
|
||||
} else {
|
||||
R.string.screen_session_verification_they_match
|
||||
stringResource(R.string.screen_session_verification_they_match)
|
||||
}
|
||||
BottomMenu(
|
||||
positiveButtonTitle = positiveButtonTitle,
|
||||
onPositiveButtonClicked = {
|
||||
if (!isVerifying) {
|
||||
eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
||||
}
|
||||
},
|
||||
negativeButtonTitle = stringResource(R.string.screen_session_verification_they_dont_match),
|
||||
onNegativeButtonClicked = { eventSink(VerifySelfSessionViewEvents.DeclineVerification) },
|
||||
isLoading = isVerifying,
|
||||
)
|
||||
}
|
||||
FlowStep.Ready -> CommonStrings.action_start
|
||||
else -> null
|
||||
}
|
||||
val negativeButtonTitle = when (verificationViewState) {
|
||||
is FlowStep.Initial -> CommonStrings.action_cancel
|
||||
FlowStep.Canceled -> CommonStrings.action_cancel
|
||||
is FlowStep.Verifying -> R.string.screen_session_verification_they_dont_match
|
||||
else -> null
|
||||
}
|
||||
val negativeButtonEnabled = !isVerifying
|
||||
|
||||
val positiveButtonEvent = when (verificationViewState) {
|
||||
is FlowStep.Initial -> VerifySelfSessionViewEvents.RequestVerification
|
||||
FlowStep.Ready -> VerifySelfSessionViewEvents.StartSasVerification
|
||||
is FlowStep.Verifying -> if (!isVerifying) VerifySelfSessionViewEvents.ConfirmVerification else null
|
||||
FlowStep.Canceled -> VerifySelfSessionViewEvents.Restart
|
||||
else -> null
|
||||
}
|
||||
|
||||
val negativeButtonCallback: () -> Unit = when (verificationViewState) {
|
||||
is FlowStep.Verifying -> {
|
||||
{ eventSink(VerifySelfSessionViewEvents.DeclineVerification) }
|
||||
is FlowStep.Completed -> {
|
||||
BottomMenu(
|
||||
positiveButtonTitle = stringResource(CommonStrings.action_continue),
|
||||
onPositiveButtonClicked = onFinished,
|
||||
)
|
||||
}
|
||||
else -> goBack
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BottomMenu(
|
||||
positiveButtonTitle: String?,
|
||||
onPositiveButtonClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
negativeButtonTitle: String? = null,
|
||||
negativeButtonEnabled: Boolean = negativeButtonTitle != null,
|
||||
onNegativeButtonClicked: () -> Unit = {},
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
modifier = modifier.padding(bottom = 16.dp)
|
||||
) {
|
||||
if (positiveButtonTitle != null) {
|
||||
Button(
|
||||
text = stringResource(positiveButtonTitle),
|
||||
showProgress = isVerifying,
|
||||
text = positiveButtonTitle,
|
||||
showProgress = isLoading,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { positiveButtonEvent?.let { eventSink(it) } }
|
||||
onClick = onPositiveButtonClicked,
|
||||
)
|
||||
}
|
||||
if (negativeButtonTitle != null) {
|
||||
TextButton(
|
||||
text = stringResource(negativeButtonTitle),
|
||||
text = negativeButtonTitle,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = negativeButtonCallback,
|
||||
onClick = onNegativeButtonClicked,
|
||||
enabled = negativeButtonEnabled,
|
||||
)
|
||||
}
|
||||
if (verificationViewState is FlowStep.Initial && verificationViewState.canEnterRecoveryKey) {
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.common_or),
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(R.string.screen_session_verification_enter_recovery_key),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onEnterRecoveryKey,
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -292,6 +307,6 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta
|
|||
VerifySelfSessionView(
|
||||
state = state,
|
||||
onEnterRecoveryKey = {},
|
||||
goBack = {},
|
||||
onFinished = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ package io.element.android.features.verifysession.impl
|
|||
sealed interface VerifySelfSessionViewEvents {
|
||||
data object RequestVerification : VerifySelfSessionViewEvents
|
||||
data object StartSasVerification : VerifySelfSessionViewEvents
|
||||
data object Restart : VerifySelfSessionViewEvents
|
||||
data object ConfirmVerification : VerifySelfSessionViewEvents
|
||||
data object DeclineVerification : VerifySelfSessionViewEvents
|
||||
data object CancelAndClose : VerifySelfSessionViewEvents
|
||||
data object Cancel : VerifySelfSessionViewEvents
|
||||
data object Reset : VerifySelfSessionViewEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.3,35.5V11Q8.3,9.8 9.2,8.9Q10.1,8 11.3,8H40.8Q41.45,8 41.875,8.425Q42.3,8.85 42.3,9.5Q42.3,10.15 41.875,10.575Q41.45,11 40.8,11H11.3Q11.3,11 11.3,11Q11.3,11 11.3,11V35.5H20.75Q21.7,35.5 22.35,36.15Q23,36.8 23,37.75Q23,38.7 22.35,39.35Q21.7,40 20.75,40H6.25Q5.3,40 4.65,39.35Q4,38.7 4,37.75Q4,36.8 4.65,36.15Q5.3,35.5 6.25,35.5ZM27.95,40Q27.15,40 26.575,39.4Q26,38.8 26,37.8V15.95Q26,15.15 26.575,14.575Q27.15,14 27.95,14H41.55Q42.55,14 43.275,14.575Q44,15.15 44,15.95V37.8Q44,38.8 43.275,39.4Q42.55,40 41.55,40ZM29,35.5H41V17H29Z"/>
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M31.3,21.35Q32.45,21.35 33.225,20.575Q34,19.8 34,18.65Q34,17.5 33.225,16.725Q32.45,15.95 31.3,15.95Q30.15,15.95 29.375,16.725Q28.6,17.5 28.6,18.65Q28.6,19.8 29.375,20.575Q30.15,21.35 31.3,21.35ZM16.7,21.35Q17.85,21.35 18.625,20.575Q19.4,19.8 19.4,18.65Q19.4,17.5 18.625,16.725Q17.85,15.95 16.7,15.95Q15.55,15.95 14.775,16.725Q14,17.5 14,18.65Q14,19.8 14.775,20.575Q15.55,21.35 16.7,21.35ZM24,34.95Q26.85,34.95 29.375,33.6Q31.9,32.25 33.35,29.85Q33.75,29.25 33.425,28.8Q33.1,28.35 32.4,28.35H15.6Q14.9,28.35 14.6,28.8Q14.3,29.25 14.7,29.85Q16.15,32.25 18.65,33.6Q21.15,34.95 24,34.95ZM24,44Q19.9,44 16.25,42.425Q12.6,40.85 9.875,38.125Q7.15,35.4 5.575,31.75Q4,28.1 4,23.95Q4,19.85 5.575,16.2Q7.15,12.55 9.875,9.85Q12.6,7.15 16.25,5.575Q19.9,4 24.05,4Q28.15,4 31.8,5.575Q35.45,7.15 38.15,9.85Q40.85,12.55 42.425,16.2Q44,19.85 44,24Q44,28.1 42.425,31.75Q40.85,35.4 38.15,38.125Q35.45,40.85 31.8,42.425Q28.15,44 24,44ZM24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24ZM24,41Q31.1,41 36.05,36.025Q41,31.05 41,24Q41,16.9 36.05,11.95Q31.1,7 24,7Q16.95,7 11.975,11.95Q7,16.9 7,24Q7,31.05 11.975,36.025Q16.95,41 24,41Z"/>
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.8,41H32.2V34.65Q32.2,31.15 29.825,28.625Q27.45,26.1 24,26.1Q20.55,26.1 18.175,28.625Q15.8,31.15 15.8,34.65ZM38.5,44H9.5Q8.85,44 8.425,43.575Q8,43.15 8,42.5Q8,41.85 8.425,41.425Q8.85,41 9.5,41H12.8V34.65Q12.8,31.15 14.625,28.225Q16.45,25.3 19.7,24Q16.45,22.7 14.625,19.75Q12.8,16.8 12.8,13.3V7H9.5Q8.85,7 8.425,6.575Q8,6.15 8,5.5Q8,4.85 8.425,4.425Q8.85,4 9.5,4H38.5Q39.15,4 39.575,4.425Q40,4.85 40,5.5Q40,6.15 39.575,6.575Q39.15,7 38.5,7H35.2V13.3Q35.2,16.8 33.35,19.75Q31.5,22.7 28.3,24Q31.55,25.3 33.375,28.225Q35.2,31.15 35.2,34.65V41H38.5Q39.15,41 39.575,41.425Q40,41.85 40,42.5Q40,43.15 39.575,43.575Q39.15,44 38.5,44Z"/>
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M24,24.6Q24.65,24.6 25.075,24.175Q25.5,23.75 25.5,23.1V15.75Q25.5,15.1 25.075,14.675Q24.65,14.25 24,14.25Q23.35,14.25 22.925,14.675Q22.5,15.1 22.5,15.75V23.1Q22.5,23.75 22.925,24.175Q23.35,24.6 24,24.6ZM24,31.3Q24.7,31.3 25.2,30.8Q25.7,30.3 25.7,29.6Q25.7,28.9 25.2,28.4Q24.7,27.9 24,27.9Q23.3,27.9 22.8,28.4Q22.3,28.9 22.3,29.6Q22.3,30.3 22.8,30.8Q23.3,31.3 24,31.3ZM24,43.85Q23.8,43.85 23.625,43.825Q23.45,43.8 23.3,43.75Q16.6,41.75 12.3,35.525Q8,29.3 8,21.85V12.05Q8,11.1 8.55,10.325Q9.1,9.55 9.95,9.2L22.95,4.35Q23.5,4.15 24,4.15Q24.5,4.15 25.05,4.35L38.05,9.2Q38.9,9.55 39.45,10.325Q40,11.1 40,12.05V21.85Q40,29.3 35.7,35.525Q31.4,41.75 24.7,43.75Q24.7,43.75 24,43.85ZM24,40.85Q29.75,38.95 33.375,33.675Q37,28.4 37,21.85V12.05Q37,12.05 37,12.05Q37,12.05 37,12.05L24,7.15Q24,7.15 24,7.15Q24,7.15 24,7.15L11,12.05Q11,12.05 11,12.05Q11,12.05 11,12.05V21.85Q11,28.4 14.625,33.675Q18.25,38.95 24,40.85ZM24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Z"/>
|
||||
</vector>
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_subtitle">"Verify this device to set up secure messaging."</string>
|
||||
<string name="screen_identity_confirmation_title">"Confirm that it\'s you"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Now you can read or send messages securely, and anyone you chat with can also trust this device."</string>
|
||||
<string name="screen_identity_confirmed_title">"Device verified"</string>
|
||||
<string name="screen_identity_use_another_device">"Use another device"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Waiting on other device…"</string>
|
||||
<string name="screen_session_verification_cancelled_subtitle">"Something doesn’t seem right. Either the request timed out or the request was denied."</string>
|
||||
<string name="screen_session_verification_compare_emojis_subtitle">"Confirm that the emojis below match those shown on your other session."</string>
|
||||
<string name="screen_session_verification_compare_emojis_title">"Compare emojis"</string>
|
||||
|
|
|
|||
|
|
@ -106,13 +106,13 @@ class VerifySelfSessionPresenterTests {
|
|||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||
val eventSink = initialState.eventSink
|
||||
eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
eventSink(VerifySelfSessionViewEvents.Cancel)
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - A fail in the flow cancels it`() = runTest {
|
||||
fun `present - A failure when verifying cancels it`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = createVerifySelfSessionPresenter(service)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -128,6 +128,21 @@ class VerifySelfSessionPresenterTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - A fail when requesting verification resets the state to the initial one`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = createVerifySelfSessionPresenter(service)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
service.shouldFail = true
|
||||
awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
service.shouldFail = false
|
||||
assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.AwaitingOtherDeviceResponse::class.java)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Canceling the flow once it's verifying cancels it`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
|
|
@ -136,8 +151,7 @@ class VerifySelfSessionPresenterTests {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
state.eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
state.eventSink(VerifySelfSessionViewEvents.Cancel)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
}
|
||||
}
|
||||
|
|
@ -165,13 +179,30 @@ class VerifySelfSessionPresenterTests {
|
|||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
service.givenVerificationFlowState(VerificationFlowState.Canceled)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
state.eventSink(VerifySelfSessionViewEvents.Restart)
|
||||
state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
// Went back to requesting verification
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Go back after cancelation returns to initial state`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = createVerifySelfSessionPresenter(service)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
service.givenVerificationFlowState(VerificationFlowState.Canceled)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
state.eventSink(VerifySelfSessionViewEvents.Reset)
|
||||
// Went back to initial state
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - When verification is approved, the flow completes if there is no error`() = runTest {
|
||||
val emojis = listOf(
|
||||
|
|
@ -247,7 +278,7 @@ class VerifySelfSessionPresenterTests {
|
|||
return VerifySelfSessionPresenter(
|
||||
sessionVerificationService = service,
|
||||
encryptionService = encryptionService,
|
||||
stateMachine = VerifySelfSessionStateMachine(service),
|
||||
stateMachine = VerifySelfSessionStateMachine(service, encryptionService),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,45 +36,98 @@ class VerifySelfSessionViewTest {
|
|||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on cancel calls the expected callback and emits the expected Event`() {
|
||||
fun `back key pressed - when canceled resets the flow`() {
|
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
goBack = callback,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
rule.pressBackKey()
|
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Reset)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on back key calls the expected callback and emits the expected Event`() {
|
||||
fun `back key pressed - when awaiting response cancels the verification`() {
|
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
goBack = callback,
|
||||
)
|
||||
}
|
||||
rule.pressBackKey()
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
rule.pressBackKey()
|
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when flow is completed, the expected callback is invoked`() {
|
||||
fun `back key pressed - when ready to verify cancels the verification`() {
|
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
rule.pressBackKey()
|
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `back key pressed - when verifying and not loading declines the verification`() {
|
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
|
||||
data = aEmojisSessionVerificationData(),
|
||||
state = AsyncData.Uninitialized,
|
||||
),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
rule.pressBackKey()
|
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `back key pressed - when verifying and loading does nothing`() {
|
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
|
||||
rule.setContent {
|
||||
VerifySelfSessionView(
|
||||
aVerifySelfSessionState(
|
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
|
||||
data = aEmojisSessionVerificationData(),
|
||||
state = AsyncData.Loading(),
|
||||
),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
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<VerifySelfSessionViewEvents>(expectEvents = false)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setContent {
|
||||
|
|
@ -84,9 +137,10 @@ class VerifySelfSessionViewTest {
|
|||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
goBack = callback,
|
||||
onFinished = callback,
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +156,7 @@ class VerifySelfSessionViewTest {
|
|||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = callback,
|
||||
goBack = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_session_verification_enter_recovery_key)
|
||||
|
|
@ -122,7 +176,7 @@ class VerifySelfSessionViewTest {
|
|||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
goBack = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_session_verification_they_match)
|
||||
|
|
@ -142,7 +196,7 @@ class VerifySelfSessionViewTest {
|
|||
eventSink = eventsRecorder
|
||||
),
|
||||
onEnterRecoveryKey = EnsureNeverCalled(),
|
||||
goBack = EnsureNeverCalled(),
|
||||
onFinished = EnsureNeverCalled(),
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_session_verification_they_dont_match)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue