Make sure we display errors when we create a recovery key and it fails (#5079)
* Make sure we display errors when we create a recovery key and it fails * Add another preview for the error state * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
7958bb4692
commit
2750835a36
@@ -60,6 +60,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.UserSavedKey)
|
||||
SecureBackupSetupEvents.DismissDialog -> {
|
||||
showSaveConfirmationDialog = false
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.ClearError)
|
||||
}
|
||||
SecureBackupSetupEvents.Done -> {
|
||||
showSaveConfirmationDialog = true
|
||||
@@ -89,6 +90,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
||||
SecureBackupSetupStateMachine.State.CreatingKey -> SetupState.Creating
|
||||
is SecureBackupSetupStateMachine.State.KeyCreated -> SetupState.Created(formattedRecoveryKey = key)
|
||||
is SecureBackupSetupStateMachine.State.KeyCreatedAndSaved -> SetupState.CreatedAndSaved(formattedRecoveryKey = key)
|
||||
is SecureBackupSetupStateMachine.State.Error -> SetupState.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +105,20 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkHasCreatedKey(it))
|
||||
},
|
||||
onFailure = {
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||
if (it is Exception) {
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
observeEncryptionService(stateAndDispatch)
|
||||
Timber.tag(loggerTagSetup.value).d("Calling encryptionService.enableRecovery()")
|
||||
encryptionService.enableRecovery(waitForBackupsToUpload = false)
|
||||
encryptionService.enableRecovery(waitForBackupsToUpload = false).onFailure {
|
||||
Timber.tag(loggerTagSetup.value).e(it, "Failed to enable recovery")
|
||||
if (it is Exception) {
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ sealed interface SetupState {
|
||||
data object Creating : SetupState
|
||||
data class Created(val formattedRecoveryKey: String) : SetupState
|
||||
data class CreatedAndSaved(val formattedRecoveryKey: String) : SetupState
|
||||
data class Error(val exception: Exception) : SetupState
|
||||
}
|
||||
|
||||
fun SetupState.recoveryKey(): String? = when (this) {
|
||||
|
||||
@@ -26,8 +26,8 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
||||
}
|
||||
}
|
||||
inState<State.CreatingKey> {
|
||||
on { _: Event.SdkError, state: MachineState<State.CreatingKey> ->
|
||||
state.override { State.Initial }
|
||||
on { event: Event.SdkError, state: MachineState<State.CreatingKey> ->
|
||||
state.override { State.Error(event.exception) }
|
||||
}
|
||||
on { event: Event.SdkHasCreatedKey, state: MachineState<State.CreatingKey> ->
|
||||
state.override { State.KeyCreated(event.key) }
|
||||
@@ -38,6 +38,11 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
||||
state.override { State.KeyCreatedAndSaved(state.snapshot.key) }
|
||||
}
|
||||
}
|
||||
inState<State.Error> {
|
||||
on { _: Event.ClearError, state: MachineState<State.Error> ->
|
||||
state.override { State.Initial }
|
||||
}
|
||||
}
|
||||
inState<State.KeyCreatedAndSaved> {
|
||||
}
|
||||
}
|
||||
@@ -48,12 +53,14 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
||||
data object CreatingKey : State
|
||||
data class KeyCreated(val key: String) : State
|
||||
data class KeyCreatedAndSaved(val key: String) : State
|
||||
data class Error(val exception: Exception) : State
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
data object UserCreatesKey : Event
|
||||
data class SdkHasCreatedKey(val key: String) : Event
|
||||
data class SdkError(val throwable: Throwable) : Event
|
||||
data class SdkError(val exception: Exception) : Event
|
||||
data object UserSavedKey : Event
|
||||
data object ClearError : Event
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ open class SecureBackupSetupStateProvider : PreviewParameterProvider<SecureBacku
|
||||
setupState = SetupState.CreatedAndSaved(aFormattedRecoveryKey()),
|
||||
showSaveConfirmationDialog = true,
|
||||
),
|
||||
aSecureBackupSetupState(setupState = SetupState.Error(Exception("Test error"))),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten
|
||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
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
|
||||
@@ -49,6 +50,16 @@ fun SecureBackupSetupView(
|
||||
Content(state = state)
|
||||
}
|
||||
|
||||
if (state.setupState is SetupState.Error) {
|
||||
ErrorDialog(
|
||||
title = stringResource(id = CommonStrings.common_something_went_wrong),
|
||||
content = stringResource(id = CommonStrings.common_something_went_wrong_message),
|
||||
onSubmit = {
|
||||
state.eventSink.invoke(SecureBackupSetupEvents.DismissDialog)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (state.showSaveConfirmationDialog) {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(id = R.string.screen_recovery_key_setup_confirmation_title),
|
||||
@@ -70,7 +81,8 @@ private fun SecureBackupSetupState.canGoBack(): Boolean {
|
||||
private fun title(state: SecureBackupSetupState): String {
|
||||
return when (state.setupState) {
|
||||
SetupState.Init,
|
||||
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) {
|
||||
SetupState.Creating,
|
||||
is SetupState.Error -> if (state.isChangeRecoveryKeyUserStory) {
|
||||
stringResource(id = R.string.screen_recovery_key_change_title)
|
||||
} else {
|
||||
stringResource(id = R.string.screen_recovery_key_setup_title)
|
||||
@@ -85,7 +97,8 @@ private fun title(state: SecureBackupSetupState): String {
|
||||
private fun subtitle(state: SecureBackupSetupState): String {
|
||||
return when (state.setupState) {
|
||||
SetupState.Init,
|
||||
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) {
|
||||
SetupState.Creating,
|
||||
is SetupState.Error -> if (state.isChangeRecoveryKeyUserStory) {
|
||||
stringResource(id = R.string.screen_recovery_key_change_description)
|
||||
} else {
|
||||
stringResource(id = R.string.screen_recovery_key_setup_description)
|
||||
@@ -137,7 +150,8 @@ private fun ColumnScope.Buttons(
|
||||
val chooserTitle = stringResource(id = R.string.screen_recovery_key_save_action)
|
||||
when (state.setupState) {
|
||||
SetupState.Init,
|
||||
SetupState.Creating -> {
|
||||
SetupState.Creating,
|
||||
is SetupState.Error -> {
|
||||
Button(
|
||||
text = stringResource(id = CommonStrings.action_done),
|
||||
enabled = false,
|
||||
|
||||
@@ -109,6 +109,34 @@ class SecureBackupSetupPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle errors`() = runTest {
|
||||
val encryptionService = FakeEncryptionService(
|
||||
enableRecoveryLambda = { Result.failure(IllegalStateException("Test error")) }
|
||||
)
|
||||
val presenter = createSecureBackupSetupPresenter(
|
||||
isChangeRecoveryKeyUserStory = false,
|
||||
encryptionService = encryptionService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isChangeRecoveryKeyUserStory).isFalse()
|
||||
assertThat(initialState.setupState).isEqualTo(SetupState.Init)
|
||||
|
||||
initialState.eventSink(SecureBackupSetupEvents.CreateRecoveryKey)
|
||||
val creatingState = awaitItem()
|
||||
assertThat(creatingState.setupState).isEqualTo(SetupState.Creating)
|
||||
val failedState = awaitItem()
|
||||
assertThat(failedState.setupState).isInstanceOf(SetupState.Error::class.java)
|
||||
failedState.eventSink(SecureBackupSetupEvents.DismissDialog)
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.setupState).isEqualTo(SetupState.Init)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - change recovery key and save it`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
@@ -153,7 +181,9 @@ class SecureBackupSetupPresenterTest {
|
||||
|
||||
private fun createSecureBackupSetupPresenter(
|
||||
isChangeRecoveryKeyUserStory: Boolean = false,
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(
|
||||
enableRecoveryLambda = { Result.success(Unit) },
|
||||
),
|
||||
): SecureBackupSetupPresenter {
|
||||
return SecureBackupSetupPresenter(
|
||||
isChangeRecoveryKeyUserStory = isChangeRecoveryKeyUserStory,
|
||||
|
||||
@@ -26,7 +26,8 @@ class FakeEncryptionService(
|
||||
private val pinUserIdentityResult: (UserId) -> Result<Unit> = { lambdaError() },
|
||||
private val isUserVerifiedResult: (UserId) -> Result<Boolean> = { lambdaError() },
|
||||
private val withdrawVerificationResult: (UserId) -> Result<Unit> = { lambdaError() },
|
||||
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() }
|
||||
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() },
|
||||
private val enableRecoveryLambda: (Boolean) -> Result<Unit> = { lambdaError() },
|
||||
) : EncryptionService {
|
||||
private var disableRecoveryFailure: Exception? = null
|
||||
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
|
||||
@@ -87,7 +88,7 @@ class FakeEncryptionService(
|
||||
}
|
||||
|
||||
override suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit> = simulateLongTask {
|
||||
return Result.success(Unit)
|
||||
return enableRecoveryLambda(waitForBackupsToUpload)
|
||||
}
|
||||
|
||||
fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user