Always display 'lost recovery key?' option (#2745)

* Always display 'lost recovery key?' option

* Use `isLastDevice` it to display only 'enter recovery key' option for verification.

* Update strings. This should fix the wrong term 'passcode' being used in the recovery key screen title.

* Disable 'lost your recovery key?' button while the screen is in a loading state

* Update screenshots

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-04-25 15:28:24 +02:00 committed by GitHub
parent 5579119f2e
commit fe9b3f7cdb
30 changed files with 181 additions and 83 deletions

1
changelog.d/2740.bugfix Normal file
View file

@ -0,0 +1 @@
Instead of displaying 'create new recovery key' on the session verification screen when there is no other session active, display it always under the 'enter recovery key' screen.

View file

@ -58,9 +58,6 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
@Parcelize @Parcelize
data object EnterRecoveryKey : NavTarget data object EnterRecoveryKey : NavTarget
@Parcelize
data object CreateNewRecoveryKey : NavTarget
} }
interface Callback : Plugin { interface Callback : Plugin {
@ -68,10 +65,6 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
} }
private val secureBackupEntryPointCallback = object : SecureBackupEntryPoint.Callback { private val secureBackupEntryPointCallback = object : SecureBackupEntryPoint.Callback {
override fun onCreateNewRecoveryKey() {
backstack.push(NavTarget.CreateNewRecoveryKey)
}
override fun onDone() { override fun onDone() {
lifecycleScope.launch { lifecycleScope.launch {
// Move to the completed state view in the verification flow // Move to the completed state view in the verification flow
@ -89,10 +82,6 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
backstack.push(NavTarget.EnterRecoveryKey) backstack.push(NavTarget.EnterRecoveryKey)
} }
override fun onCreateNewRecoveryKey() {
backstack.push(NavTarget.CreateNewRecoveryKey)
}
override fun onDone() { override fun onDone() {
plugins<Callback>().forEach { it.onDone() } plugins<Callback>().forEach { it.onDone() }
} }
@ -105,12 +94,6 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor(
.callback(secureBackupEntryPointCallback) .callback(secureBackupEntryPointCallback)
.build() .build()
} }
is NavTarget.CreateNewRecoveryKey -> {
secureBackupEntryPoint.nodeBuilder(this, buildContext)
.params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.CreateNewRecoveryKey))
.callback(secureBackupEntryPointCallback)
.build()
}
} }
} }

View file

@ -9,8 +9,8 @@
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi"</string> <string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"If that doesnt work, sign in manually"</string> <string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"If that doesnt work, sign in manually"</string>
<string name="screen_qr_code_login_connection_note_secure_state_title">"Connection not secure"</string> <string name="screen_qr_code_login_connection_note_secure_state_title">"Connection not secure"</string>
<string name="screen_qr_code_login_device_code_subtitle">"Youll be asked to enter the two digits shown below."</string> <string name="screen_qr_code_login_device_code_subtitle">"Youll be asked to enter the two digits shown on this device."</string>
<string name="screen_qr_code_login_device_code_title">"Enter number on your device"</string> <string name="screen_qr_code_login_device_code_title">"Enter the number below on your other device"</string>
<string name="screen_qr_code_login_initial_state_item_1">"Open %1$s on a desktop device"</string> <string name="screen_qr_code_login_initial_state_item_1">"Open %1$s on a desktop device"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Click on your avatar"</string> <string name="screen_qr_code_login_initial_state_item_2">"Click on your avatar"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string> <string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>

View file

@ -41,7 +41,6 @@ interface SecureBackupEntryPoint : FeatureEntryPoint {
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
interface Callback : Plugin { interface Callback : Plugin {
fun onCreateNewRecoveryKey()
fun onDone() fun onDone()
} }

View file

@ -23,6 +23,12 @@ plugins {
android { android {
namespace = "io.element.android.features.securebackup.impl" namespace = "io.element.android.features.securebackup.impl"
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
} }
anvil { anvil {
@ -51,8 +57,11 @@ dependencies {
testImplementation(libs.molecule.runtime) testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)
testImplementation(libs.test.turbine) testImplementation(libs.test.turbine)
testImplementation(libs.test.robolectric)
testImplementation(libs.androidx.compose.ui.test.junit)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)
} }

View file

@ -136,6 +136,10 @@ class SecureBackupFlowNode @AssistedInject constructor(
backstack.pop() backstack.pop()
} }
} }
override fun onCreateNewRecoveryKey() {
backstack.push(NavTarget.CreateNewRecoveryKey)
}
} }
createNode<SecureBackupEnterRecoveryKeyNode>(buildContext, plugins = listOf(callback)) createNode<SecureBackupEnterRecoveryKeyNode>(buildContext, plugins = listOf(callback))
} }

View file

@ -35,6 +35,7 @@ class SecureBackupEnterRecoveryKeyNode @AssistedInject constructor(
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin { interface Callback : Plugin {
fun onEnterRecoveryKeySuccess() fun onEnterRecoveryKeySuccess()
fun onCreateNewRecoveryKey()
} }
private val callback = plugins<Callback>().first() private val callback = plugins<Callback>().first()
@ -47,6 +48,7 @@ class SecureBackupEnterRecoveryKeyNode @AssistedInject constructor(
modifier = modifier, modifier = modifier,
onDone = callback::onEnterRecoveryKeySuccess, onDone = callback::onEnterRecoveryKeySuccess,
onBackClicked = ::navigateUp, onBackClicked = ::navigateUp,
onCreateNewRecoveryKey = callback::onCreateNewRecoveryKey
) )
} }
} }

View file

@ -36,6 +36,7 @@ fun aSecureBackupEnterRecoveryKeyState(
recoveryKey: String = aFormattedRecoveryKey(), recoveryKey: String = aFormattedRecoveryKey(),
isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(), isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(),
submitAction: AsyncAction<Unit> = AsyncAction.Uninitialized, submitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit = {},
) = SecureBackupEnterRecoveryKeyState( ) = SecureBackupEnterRecoveryKeyState(
recoveryKeyViewState = RecoveryKeyViewState( recoveryKeyViewState = RecoveryKeyViewState(
recoveryKeyUserStory = RecoveryKeyUserStory.Enter, recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
@ -44,5 +45,5 @@ fun aSecureBackupEnterRecoveryKeyState(
), ),
isSubmitEnabled = isSubmitEnabled, isSubmitEnabled = isSubmitEnabled,
submitAction = submitAction, submitAction = submitAction,
eventSink = {} eventSink = eventSink,
) )

View file

@ -32,6 +32,7 @@ import io.element.android.libraries.designsystem.components.async.AsyncActionVie
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight 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.Button
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
@ -39,6 +40,7 @@ fun SecureBackupEnterRecoveryKeyView(
state: SecureBackupEnterRecoveryKeyState, state: SecureBackupEnterRecoveryKeyState,
onDone: () -> Unit, onDone: () -> Unit,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onCreateNewRecoveryKey: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
AsyncActionView( AsyncActionView(
@ -57,7 +59,7 @@ fun SecureBackupEnterRecoveryKeyView(
title = stringResource(id = R.string.screen_recovery_key_confirm_title), title = stringResource(id = R.string.screen_recovery_key_confirm_title),
subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description), subTitle = stringResource(id = R.string.screen_recovery_key_confirm_description),
content = { Content(state = state) }, content = { Content(state = state) },
buttons = { Buttons(state = state) } buttons = { Buttons(state = state, onCreateRecoveryKey = onCreateNewRecoveryKey) }
) )
} }
@ -81,6 +83,7 @@ private fun Content(
@Composable @Composable
private fun ColumnScope.Buttons( private fun ColumnScope.Buttons(
state: SecureBackupEnterRecoveryKeyState, state: SecureBackupEnterRecoveryKeyState,
onCreateRecoveryKey: () -> Unit,
) { ) {
Button( Button(
text = stringResource(id = CommonStrings.action_continue), text = stringResource(id = CommonStrings.action_continue),
@ -91,6 +94,12 @@ private fun ColumnScope.Buttons(
state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit) state.eventSink.invoke(SecureBackupEnterRecoveryKeyEvents.Submit)
} }
) )
TextButton(
text = stringResource(id = R.string.screen_recovery_key_confirm_lost_recovery_key),
enabled = !state.submitAction.isLoading(),
modifier = Modifier.fillMaxWidth(),
onClick = onCreateRecoveryKey,
)
} }
@PreviewsDayNight @PreviewsDayNight
@ -102,5 +111,6 @@ internal fun SecureBackupEnterRecoveryKeyViewPreview(
state = state, state = state,
onDone = {}, onDone = {},
onBackClicked = {}, onBackClicked = {},
onCreateNewRecoveryKey = {},
) )
} }

View file

@ -35,8 +35,9 @@
<string name="screen_recovery_key_confirm_key_description">"If you have a security key or security phrase, this will work too."</string> <string name="screen_recovery_key_confirm_key_description">"If you have a security key or security phrase, this will work too."</string>
<string name="screen_recovery_key_confirm_key_label">"Recovery key or passcode"</string> <string name="screen_recovery_key_confirm_key_label">"Recovery key or passcode"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Enter…"</string> <string name="screen_recovery_key_confirm_key_placeholder">"Enter…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Lost your recovery key?"</string>
<string name="screen_recovery_key_confirm_success">"Recovery key confirmed"</string> <string name="screen_recovery_key_confirm_success">"Recovery key confirmed"</string>
<string name="screen_recovery_key_confirm_title">"Enter your recovery key or passcode"</string> <string name="screen_recovery_key_confirm_title">"Enter your recovery key"</string>
<string name="screen_recovery_key_copied_to_clipboard">"Copied recovery key"</string> <string name="screen_recovery_key_copied_to_clipboard">"Copied recovery key"</string>
<string name="screen_recovery_key_generating_key">"Generating…"</string> <string name="screen_recovery_key_generating_key">"Generating…"</string>
<string name="screen_recovery_key_save_action">"Save recovery key"</string> <string name="screen_recovery_key_save_action">"Save recovery key"</string>

View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.securebackup.impl.enter
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.securebackup.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import io.element.android.tests.testutils.pressBackKey
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SecureBackupEnterRecoveryKeyViewTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `back key pressed - calls onBackClicked`() {
ensureCalledOnce { callback ->
rule.setSecureBackupEnterRecoveryKeyView(
aSecureBackupEnterRecoveryKeyState(),
onBackClicked = callback,
)
rule.pressBackKey()
}
}
@Test
fun `back button clicked - calls onBackClicked`() {
ensureCalledOnce { callback ->
rule.setSecureBackupEnterRecoveryKeyView(
aSecureBackupEnterRecoveryKeyState(),
onBackClicked = callback,
)
rule.pressBack()
}
}
@Test
fun `tapping on Continue when key is valid - calls expected action`() {
val recorder = EventsRecorder<SecureBackupEnterRecoveryKeyEvents>()
rule.setSecureBackupEnterRecoveryKeyView(
aSecureBackupEnterRecoveryKeyState(isSubmitEnabled = true, eventSink = recorder),
)
rule.clickOn(CommonStrings.action_continue)
recorder.assertSingle(SecureBackupEnterRecoveryKeyEvents.Submit)
}
@Test
fun `tapping on Lost your recovery key - calls onCreateNewRecoveryKey`() {
ensureCalledOnce { callback ->
rule.setSecureBackupEnterRecoveryKeyView(
aSecureBackupEnterRecoveryKeyState(),
onCreateNewRecoveryKey = callback,
)
rule.clickOn(R.string.screen_recovery_key_confirm_lost_recovery_key)
}
}
@Test
fun `when submit action succeeds - calls onDone`() {
ensureCalledOnce { callback ->
rule.setSecureBackupEnterRecoveryKeyView(
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Success(Unit)),
onDone = callback,
)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSecureBackupEnterRecoveryKeyView(
state: SecureBackupEnterRecoveryKeyState,
onDone: () -> Unit = EnsureNeverCalled(),
onBackClicked: () -> Unit = EnsureNeverCalled(),
onCreateNewRecoveryKey: () -> Unit = EnsureNeverCalled(),
) {
rule.setContent {
SecureBackupEnterRecoveryKeyView(
state = state,
onDone = onDone,
onBackClicked = onBackClicked,
onCreateNewRecoveryKey = onCreateNewRecoveryKey
)
}
}
}

View file

@ -31,7 +31,6 @@ interface VerifySessionEntryPoint : FeatureEntryPoint {
interface Callback : Plugin { interface Callback : Plugin {
fun onEnterRecoveryKey() fun onEnterRecoveryKey()
fun onCreateNewRecoveryKey()
fun onDone() fun onDone()
} }
} }

View file

@ -43,7 +43,6 @@ class VerifySelfSessionNode @AssistedInject constructor(
state = state, state = state,
modifier = modifier, modifier = modifier,
onEnterRecoveryKey = callback::onEnterRecoveryKey, onEnterRecoveryKey = callback::onEnterRecoveryKey,
onCreateNewRecoveryKey = callback::onCreateNewRecoveryKey,
onFinished = callback::onDone, onFinished = callback::onDone,
) )
} }

View file

@ -103,7 +103,10 @@ class VerifySelfSessionPresenter @Inject constructor(
): VerifySelfSessionState.VerificationStep = ): VerifySelfSessionState.VerificationStep =
when (val machineState = this) { when (val machineState = this) {
StateMachineState.Initial, null -> { StateMachineState.Initial, null -> {
VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = canEnterRecoveryKey, isLastDevice = encryptionService.isLastDevice.value) VerifySelfSessionState.VerificationStep.Initial(
canEnterRecoveryKey = canEnterRecoveryKey,
isLastDevice = encryptionService.isLastDevice.value
)
} }
StateMachineState.RequestingVerification, StateMachineState.RequestingVerification,
StateMachineState.StartingSasVerification, StateMachineState.StartingSasVerification,

View file

@ -29,7 +29,7 @@ data class VerifySelfSessionState(
) { ) {
@Stable @Stable
sealed interface VerificationStep { sealed interface VerificationStep {
data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean) : VerificationStep data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean = false) : VerificationStep
data object Canceled : VerificationStep data object Canceled : VerificationStep
data object AwaitingOtherDeviceResponse : VerificationStep data object AwaitingOtherDeviceResponse : VerificationStep
data object Ready : VerificationStep data object Ready : VerificationStep

View file

@ -45,7 +45,7 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
verificationFlowStep = VerificationStep.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized) verificationFlowStep = VerificationStep.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
), ),
aVerifySelfSessionState( aVerifySelfSessionState(
verificationFlowStep = VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = false) verificationFlowStep = VerificationStep.Initial(canEnterRecoveryKey = true)
), ),
aVerifySelfSessionState( aVerifySelfSessionState(
verificationFlowStep = VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = true) verificationFlowStep = VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = true)
@ -71,7 +71,7 @@ private fun aDecimalsSessionVerificationData(
} }
internal fun aVerifySelfSessionState( internal fun aVerifySelfSessionState(
verificationFlowStep: VerificationStep = VerificationStep.Initial(canEnterRecoveryKey = false, isLastDevice = false), verificationFlowStep: VerificationStep = VerificationStep.Initial(canEnterRecoveryKey = false),
displaySkipButton: Boolean = false, displaySkipButton: Boolean = false,
eventSink: (VerifySelfSessionViewEvents) -> Unit = {}, eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
) = VerifySelfSessionState( ) = VerifySelfSessionState(

View file

@ -66,7 +66,6 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver
fun VerifySelfSessionView( fun VerifySelfSessionView(
state: VerifySelfSessionState, state: VerifySelfSessionState,
onEnterRecoveryKey: () -> Unit, onEnterRecoveryKey: () -> Unit,
onCreateNewRecoveryKey: () -> Unit,
onFinished: () -> Unit, onFinished: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -115,7 +114,6 @@ fun VerifySelfSessionView(
screenState = state, screenState = state,
goBack = ::resetFlow, goBack = ::resetFlow,
onEnterRecoveryKey = onEnterRecoveryKey, onEnterRecoveryKey = onEnterRecoveryKey,
onCreateNewRecoveryKey = onCreateNewRecoveryKey,
onFinished = onFinished, onFinished = onFinished,
) )
} }
@ -228,7 +226,6 @@ private fun EmojiItemView(emoji: VerificationEmoji, modifier: Modifier = Modifie
private fun BottomMenu( private fun BottomMenu(
screenState: VerifySelfSessionState, screenState: VerifySelfSessionState,
onEnterRecoveryKey: () -> Unit, onEnterRecoveryKey: () -> Unit,
onCreateNewRecoveryKey: () -> Unit,
goBack: () -> Unit, goBack: () -> Unit,
onFinished: () -> Unit, onFinished: () -> Unit,
) { ) {
@ -243,8 +240,6 @@ private fun BottomMenu(
BottomMenu( BottomMenu(
positiveButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key), positiveButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key),
onPositiveButtonClicked = onEnterRecoveryKey, onPositiveButtonClicked = onEnterRecoveryKey,
negativeButtonTitle = stringResource(R.string.screen_identity_confirmation_create_new_recovery_key),
onNegativeButtonClicked = onCreateNewRecoveryKey,
) )
} else { } else {
BottomMenu( BottomMenu(
@ -346,7 +341,6 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta
VerifySelfSessionView( VerifySelfSessionView(
state = state, state = state,
onEnterRecoveryKey = {}, onEnterRecoveryKey = {},
onCreateNewRecoveryKey = {},
onFinished = {}, onFinished = {},
) )
} }

View file

@ -53,7 +53,7 @@ class VerifySelfSessionPresenterTests {
presenter.present() presenter.present()
}.test { }.test {
awaitItem().run { awaitItem().run {
assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(displaySkipButton).isTrue() assertThat(displaySkipButton).isTrue()
} }
} }
@ -80,7 +80,7 @@ class VerifySelfSessionPresenterTests {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true, false)) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true))
} }
} }
@ -95,7 +95,7 @@ class VerifySelfSessionPresenterTests {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true, true)) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = true))
} }
} }
@ -118,7 +118,7 @@ class VerifySelfSessionPresenterTests {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
val eventSink = initialState.eventSink val eventSink = initialState.eventSink
eventSink(VerifySelfSessionViewEvents.StartSasVerification) eventSink(VerifySelfSessionViewEvents.StartSasVerification)
// Await for other device response: // Await for other device response:
@ -137,7 +137,7 @@ class VerifySelfSessionPresenterTests {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
val eventSink = initialState.eventSink val eventSink = initialState.eventSink
eventSink(VerifySelfSessionViewEvents.Cancel) eventSink(VerifySelfSessionViewEvents.Cancel)
expectNoEvents() expectNoEvents()
@ -172,7 +172,7 @@ class VerifySelfSessionPresenterTests {
awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification) awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification)
service.shouldFail = false service.shouldFail = false
assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.AwaitingOtherDeviceResponse::class.java) assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.AwaitingOtherDeviceResponse::class.java)
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
} }
} }
@ -231,7 +231,7 @@ class VerifySelfSessionPresenterTests {
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
state.eventSink(VerifySelfSessionViewEvents.Reset) state.eventSink(VerifySelfSessionViewEvents.Reset)
// Went back to initial state // Went back to initial state
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
} }
@ -315,7 +315,7 @@ class VerifySelfSessionPresenterTests {
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()), sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
): VerifySelfSessionState { ): VerifySelfSessionState {
var state = awaitItem() var state = awaitItem()
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
state.eventSink(VerifySelfSessionViewEvents.RequestVerification) state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
// Await for other device response: // Await for other device response:
state = awaitItem() state = awaitItem()

View file

@ -144,7 +144,7 @@ class VerifySelfSessionViewTest {
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setVerifySelfSessionView( rule.setVerifySelfSessionView(
aVerifySelfSessionState( aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true, false), verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true),
eventSink = eventsRecorder eventSink = eventsRecorder
), ),
onEnterRecoveryKey = callback, onEnterRecoveryKey = callback,
@ -153,22 +153,6 @@ class VerifySelfSessionViewTest {
} }
} }
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on create new recovery key calls the expected callback`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false)
ensureCalledOnce { callback ->
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true, true),
eventSink = eventsRecorder
),
onCreateNewRecoveryKey = callback,
)
rule.clickOn(R.string.screen_identity_confirmation_create_new_recovery_key)
}
}
@Test @Test
fun `clicking on they match emits the expected event`() { fun `clicking on they match emits the expected event`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
@ -206,7 +190,7 @@ class VerifySelfSessionViewTest {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setVerifySelfSessionView( rule.setVerifySelfSessionView(
aVerifySelfSessionState( aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = false), verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true),
displaySkipButton = true, displaySkipButton = true,
eventSink = eventsRecorder eventSink = eventsRecorder
), ),
@ -232,14 +216,12 @@ class VerifySelfSessionViewTest {
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
state: VerifySelfSessionState, state: VerifySelfSessionState,
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(), onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
onCreateNewRecoveryKey: () -> Unit = EnsureNeverCalled(),
onFinished: () -> Unit = EnsureNeverCalled(), onFinished: () -> Unit = EnsureNeverCalled(),
) { ) {
rule.setContent { rule.setContent {
VerifySelfSessionView( VerifySelfSessionView(
state = state, state = state,
onEnterRecoveryKey = onEnterRecoveryKey, onEnterRecoveryKey = onEnterRecoveryKey,
onCreateNewRecoveryKey = onCreateNewRecoveryKey,
onFinished = onFinished, onFinished = onFinished,
) )
} }

View file

@ -215,6 +215,7 @@
<string name="common_topic">"Topic"</string> <string name="common_topic">"Topic"</string>
<string name="common_topic_placeholder">"What is this room about?"</string> <string name="common_topic_placeholder">"What is this room about?"</string>
<string name="common_unable_to_decrypt">"Unable to decrypt"</string> <string name="common_unable_to_decrypt">"Unable to decrypt"</string>
<string name="common_unable_to_decrypt_no_access">"You don\'t have access to this message"</string>
<string name="common_unable_to_invite_message">"Invites couldn\'t be sent to one or more users."</string> <string name="common_unable_to_invite_message">"Invites couldn\'t be sent to one or more users."</string>
<string name="common_unable_to_invite_title">"Unable to send invite(s)"</string> <string name="common_unable_to_invite_title">"Unable to send invite(s)"</string>
<string name="common_unlock">"Unlock"</string> <string name="common_unlock">"Unlock"</string>

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6df37aa3a9d8411d3b3f9cd4dc67e48c271030350fee5cc464da8b564bea14d0 oid sha256:5584b334e0ca80d3619d00d1168b2a15ced299e529adb8bd53bfc9bc9e495ecf
size 40410 size 41339

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:923a6792ecb57eaefb86042327495ffa48294187eb97fdb2f36ca16b8e2d2859 oid sha256:3ffbd8973d7370f0a992dc148b73bcc243b77b4e80d9a7ce8a49aec1312ec451
size 52163 size 52942

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9296a3f7d3c3ee8aa4d8ee3fc310cab8c539c7adfd4b14156da65608a5e6fd10 oid sha256:8154d4dfb395001e30dfb0807e796996abb812aa40523a3ec2c7a996345a1cb0
size 50565 size 50651

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0fee5009acc5128d48791066fb45555d0fa261cf09c9b372cca8570561c3055b oid sha256:ed4451219724c9b5dfb151a3d138dd54dfba6c78b961881bf56b602813dae13b
size 44297 size 44523

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b11e7ce69453d49bb916bec23ce0974e33ae788911ab12cea777ba238141bc77 oid sha256:73b5a7598ca2255e88ef2098168e326aa1d23952ca8a43360380a6bfc2b36cce
size 37965 size 38770

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:aefbf69085a8279090018dd35a465614b2c876932746e650e7faf6d84f062381 oid sha256:92ec6f85b19f217c9b5242e183e868d7288c0aca3578e6e563907910197113fe
size 48364 size 49105

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9b6c6e849d68d516000644352c347587910f99ffd0d441feb8cf72a981aa30e3 oid sha256:857fe8d13c9801edffb6ae9e36317a279addee2ea1729fc4b0be84d45e06d023
size 46849 size 47449

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:65c1faa6875a6c2b9ebe282f83e8a82b6e358f724082b53525d31a6c1bff4b15 oid sha256:2731b003df8b067a1d8f04ccd8209ce8f6a3de0c6469c29ea3cc1025390e378a
size 39312 size 39419

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:409858802c14fd755380aa67fedb52f04a5668cf0999104f4fff4ffbc3711743 oid sha256:8421c783940ce8e9033d313b4c10b084fa7334b8c72d7e236c3619710b0c2658
size 28968 size 23676

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a7eb330ff5a506ee6198a0ec5acc3ef1b9db58161aa8eb9dedf468719f3e55a6 oid sha256:b4d63523589ee13ac766f135efe070e297e0673287bd970d3c805f57ac567fec
size 26778 size 22138