Improve session recovery screens (#2657)

* Improve enter recovery key screen UI

* Add instructions to reset the encryption of the logged in account.

* Update screenshots

* Fix maestro flow

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-04-09 19:01:06 +02:00 committed by GitHub
parent d0f26777da
commit cf072fa1e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
164 changed files with 765 additions and 358 deletions

View file

@ -34,17 +34,7 @@ class VerifySelfSessionNode @AssistedInject constructor(
@Assisted plugins: List<Plugin>,
private val presenter: VerifySelfSessionPresenter,
) : Node(buildContext, plugins = plugins) {
private fun onEnterRecoveryKey() {
plugins<VerifySessionEntryPoint.Callback>().forEach {
it.onEnterRecoveryKey()
}
}
private fun onDone() {
plugins<VerifySessionEntryPoint.Callback>().forEach {
it.onDone()
}
}
private val callback = plugins<VerifySessionEntryPoint.Callback>().first()
@Composable
override fun View(modifier: Modifier) {
@ -52,8 +42,9 @@ class VerifySelfSessionNode @AssistedInject constructor(
VerifySelfSessionView(
state = state,
modifier = modifier,
onEnterRecoveryKey = ::onEnterRecoveryKey,
onFinished = ::onDone,
onEnterRecoveryKey = callback::onEnterRecoveryKey,
onCreateNewRecoveryKey = callback::onCreateNewRecoveryKey,
onFinished = callback::onDone,
)
}
}

View file

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

View file

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

View file

@ -17,6 +17,7 @@
package io.element.android.features.verifysession.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
@ -26,28 +27,31 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider<VerifySelfS
get() = sequenceOf(
aVerifySelfSessionState(displaySkipButton = true),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse
verificationFlowStep = VerificationStep.AwaitingOtherDeviceResponse
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized)
verificationFlowStep = VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Uninitialized)
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading())
verificationFlowStep = VerificationStep.Verifying(aEmojisSessionVerificationData(), AsyncData.Loading())
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled
verificationFlowStep = VerificationStep.Canceled
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready
verificationFlowStep = VerificationStep.Ready
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
verificationFlowStep = VerificationStep.Verifying(aDecimalsSessionVerificationData(), AsyncData.Uninitialized)
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true)
verificationFlowStep = VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = false)
),
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed,
verificationFlowStep = VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = true)
),
aVerifySelfSessionState(
verificationFlowStep = VerificationStep.Completed,
displaySkipButton = true,
),
// Add other state here
@ -67,7 +71,7 @@ private fun aDecimalsSessionVerificationData(
}
internal fun aVerifySelfSessionState(
verificationFlowStep: VerifySelfSessionState.VerificationStep = VerifySelfSessionState.VerificationStep.Initial(false),
verificationFlowStep: VerificationStep = VerificationStep.Initial(canEnterRecoveryKey = false, isLastDevice = false),
displaySkipButton: Boolean = false,
eventSink: (VerifySelfSessionViewEvents) -> Unit = {},
) = VerifySelfSessionState(

View file

@ -66,6 +66,7 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver
fun VerifySelfSessionView(
state: VerifySelfSessionState,
onEnterRecoveryKey: () -> Unit,
onCreateNewRecoveryKey: () -> Unit,
onFinished: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -114,6 +115,7 @@ fun VerifySelfSessionView(
screenState = state,
goBack = ::resetFlow,
onEnterRecoveryKey = onEnterRecoveryKey,
onCreateNewRecoveryKey = onCreateNewRecoveryKey,
onFinished = onFinished,
)
}
@ -226,6 +228,7 @@ private fun EmojiItemView(emoji: VerificationEmoji, modifier: Modifier = Modifie
private fun BottomMenu(
screenState: VerifySelfSessionState,
onEnterRecoveryKey: () -> Unit,
onCreateNewRecoveryKey: () -> Unit,
goBack: () -> Unit,
onFinished: () -> Unit,
) {
@ -236,12 +239,21 @@ private fun BottomMenu(
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,
)
if (verificationViewState.isLastDevice) {
BottomMenu(
positiveButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key),
onPositiveButtonClicked = onEnterRecoveryKey,
negativeButtonTitle = stringResource(R.string.screen_identity_confirmation_create_new_recovery_key),
onNegativeButtonClicked = onCreateNewRecoveryKey,
)
} else {
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(
@ -334,6 +346,7 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta
VerifySelfSessionView(
state = state,
onEnterRecoveryKey = {},
onCreateNewRecoveryKey = {},
onFinished = {},
)
}

View file

@ -53,7 +53,7 @@ class VerifySelfSessionPresenterTests {
presenter.present()
}.test {
awaitItem().run {
assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false))
assertThat(displaySkipButton).isTrue()
}
}
@ -80,7 +80,22 @@ class VerifySelfSessionPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true))
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true, false))
}
}
@Test
fun `present - Initial state is received, can use recovery key and is last device`() = runTest {
val presenter = createVerifySelfSessionPresenter(
encryptionService = FakeEncryptionService().apply {
emitIsLastDevice(true)
emitRecoveryState(RecoveryState.INCOMPLETE)
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true, true))
}
}
@ -103,7 +118,7 @@ class VerifySelfSessionPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false))
val eventSink = initialState.eventSink
eventSink(VerifySelfSessionViewEvents.StartSasVerification)
// Await for other device response:
@ -122,7 +137,7 @@ class VerifySelfSessionPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false))
val eventSink = initialState.eventSink
eventSink(VerifySelfSessionViewEvents.Cancel)
expectNoEvents()
@ -157,7 +172,7 @@ class VerifySelfSessionPresenterTests {
awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification)
service.shouldFail = false
assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.AwaitingOtherDeviceResponse::class.java)
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false))
}
}
@ -216,7 +231,7 @@ class VerifySelfSessionPresenterTests {
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
state.eventSink(VerifySelfSessionViewEvents.Reset)
// Went back to initial state
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false))
cancelAndIgnoreRemainingEvents()
}
}
@ -297,7 +312,7 @@ class VerifySelfSessionPresenterTests {
sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()),
): VerifySelfSessionState {
var state = awaitItem()
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false))
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false))
state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
// Await for other device response:
state = awaitItem()

View file

@ -17,6 +17,7 @@
package io.element.android.features.verifysession.impl
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.libraries.architecture.AsyncData
@ -29,6 +30,7 @@ import io.element.android.tests.testutils.ensureCalledOnce
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
import org.robolectric.annotation.Config
@ -39,16 +41,12 @@ class VerifySelfSessionViewTest {
@Test
fun `back key pressed - when canceled resets the flow`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled,
eventSink = eventsRecorder
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled,
eventSink = eventsRecorder
),
)
rule.pressBackKey()
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Reset)
}
@ -56,16 +54,12 @@ class VerifySelfSessionViewTest {
@Test
fun `back key pressed - when awaiting response cancels the verification`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse,
eventSink = eventsRecorder
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse,
eventSink = eventsRecorder
),
)
rule.pressBackKey()
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel)
}
@ -73,16 +67,12 @@ class VerifySelfSessionViewTest {
@Test
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.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready,
eventSink = eventsRecorder
),
)
rule.pressBackKey()
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel)
}
@ -90,19 +80,15 @@ class VerifySelfSessionViewTest {
@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
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
data = aEmojisSessionVerificationData(),
state = AsyncData.Uninitialized,
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
eventSink = eventsRecorder
),
)
rule.pressBackKey()
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification)
}
@ -110,19 +96,15 @@ class VerifySelfSessionViewTest {
@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
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
data = aEmojisSessionVerificationData(),
state = AsyncData.Loading(),
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
eventSink = eventsRecorder
),
)
rule.pressBackKey()
eventsRecorder.assertEmpty()
}
@ -130,16 +112,12 @@ class VerifySelfSessionViewTest {
@Test
fun `back key pressed - on Completed step does nothing`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed,
eventSink = eventsRecorder
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed,
eventSink = eventsRecorder
),
)
rule.pressBackKey()
eventsRecorder.assertEmpty()
}
@ -148,16 +126,13 @@ class VerifySelfSessionViewTest {
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 {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed,
eventSink = eventsRecorder
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = callback,
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed,
eventSink = eventsRecorder
),
onFinished = callback,
)
rule.clickOn(CommonStrings.action_continue)
}
}
@ -167,36 +142,45 @@ class VerifySelfSessionViewTest {
fun `clicking on enter recovery key calls the expected callback`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false)
ensureCalledOnce { callback ->
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true),
eventSink = eventsRecorder
),
onEnterRecoveryKey = callback,
onFinished = EnsureNeverCalled(),
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true, false),
eventSink = eventsRecorder
),
onEnterRecoveryKey = callback,
)
rule.clickOn(R.string.screen_session_verification_enter_recovery_key)
}
}
@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
fun `clicking on they match emits the expected event`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
data = aEmojisSessionVerificationData(),
state = AsyncData.Uninitialized,
),
eventSink = eventsRecorder
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
data = aEmojisSessionVerificationData(),
state = AsyncData.Uninitialized,
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
eventSink = eventsRecorder
),
)
rule.clickOn(R.string.screen_session_verification_they_match)
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.ConfirmVerification)
}
@ -204,19 +188,15 @@ class VerifySelfSessionViewTest {
@Test
fun `clicking on they do not match emits the expected event`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
data = aEmojisSessionVerificationData(),
state = AsyncData.Uninitialized,
),
eventSink = eventsRecorder
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying(
data = aEmojisSessionVerificationData(),
state = AsyncData.Uninitialized,
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
eventSink = eventsRecorder
),
)
rule.clickOn(R.string.screen_session_verification_they_dont_match)
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification)
}
@ -224,17 +204,13 @@ class VerifySelfSessionViewTest {
@Test
fun `clicking on 'Skip' emits the expected event`() {
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>()
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true),
displaySkipButton = true,
eventSink = eventsRecorder
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = EnsureNeverCalled(),
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = false),
displaySkipButton = true,
eventSink = eventsRecorder
),
)
rule.clickOn(CommonStrings.action_skip)
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.SkipVerification)
}
@ -242,17 +218,30 @@ class VerifySelfSessionViewTest {
@Test
fun `on Skipped step - onFinished callback is called immediately`() {
ensureCalledOnce { callback ->
rule.setContent {
VerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Skipped,
displaySkipButton = true,
eventSink = EnsureNeverCalledWithParam(),
),
onEnterRecoveryKey = EnsureNeverCalled(),
onFinished = callback,
)
}
rule.setVerifySelfSessionView(
aVerifySelfSessionState(
verificationFlowStep = VerifySelfSessionState.VerificationStep.Skipped,
displaySkipButton = true,
eventSink = EnsureNeverCalledWithParam(),
),
onFinished = callback,
)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setVerifySelfSessionView(
state: VerifySelfSessionState,
onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(),
onCreateNewRecoveryKey: () -> Unit = EnsureNeverCalled(),
onFinished: () -> Unit = EnsureNeverCalled(),
) {
rule.setContent {
VerifySelfSessionView(
state = state,
onEnterRecoveryKey = onEnterRecoveryKey,
onCreateNewRecoveryKey = onCreateNewRecoveryKey,
onFinished = onFinished,
)
}
}
}