Skip to content

Commit afa01e9

Browse files
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 <[email protected]>
1 parent 34848aa commit afa01e9

File tree

11 files changed

+86
-11
lines changed

11 files changed

+86
-11
lines changed

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
6060
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.UserSavedKey)
6161
SecureBackupSetupEvents.DismissDialog -> {
6262
showSaveConfirmationDialog = false
63+
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.ClearError)
6364
}
6465
SecureBackupSetupEvents.Done -> {
6566
showSaveConfirmationDialog = true
@@ -89,6 +90,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
8990
SecureBackupSetupStateMachine.State.CreatingKey -> SetupState.Creating
9091
is SecureBackupSetupStateMachine.State.KeyCreated -> SetupState.Created(formattedRecoveryKey = key)
9192
is SecureBackupSetupStateMachine.State.KeyCreatedAndSaved -> SetupState.CreatedAndSaved(formattedRecoveryKey = key)
93+
is SecureBackupSetupStateMachine.State.Error -> SetupState.Error(exception)
9294
}
9395
}
9496

@@ -103,13 +105,20 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
103105
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkHasCreatedKey(it))
104106
},
105107
onFailure = {
106-
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
108+
if (it is Exception) {
109+
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
110+
}
107111
}
108112
)
109113
} else {
110114
observeEncryptionService(stateAndDispatch)
111115
Timber.tag(loggerTagSetup.value).d("Calling encryptionService.enableRecovery()")
112-
encryptionService.enableRecovery(waitForBackupsToUpload = false)
116+
encryptionService.enableRecovery(waitForBackupsToUpload = false).onFailure {
117+
Timber.tag(loggerTagSetup.value).e(it, "Failed to enable recovery")
118+
if (it is Exception) {
119+
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
120+
}
121+
}
113122
}
114123
}
115124

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ sealed interface SetupState {
2323
data object Creating : SetupState
2424
data class Created(val formattedRecoveryKey: String) : SetupState
2525
data class CreatedAndSaved(val formattedRecoveryKey: String) : SetupState
26+
data class Error(val exception: Exception) : SetupState
2627
}
2728

2829
fun SetupState.recoveryKey(): String? = when (this) {

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
2626
}
2727
}
2828
inState<State.CreatingKey> {
29-
on { _: Event.SdkError, state: MachineState<State.CreatingKey> ->
30-
state.override { State.Initial }
29+
on { event: Event.SdkError, state: MachineState<State.CreatingKey> ->
30+
state.override { State.Error(event.exception) }
3131
}
3232
on { event: Event.SdkHasCreatedKey, state: MachineState<State.CreatingKey> ->
3333
state.override { State.KeyCreated(event.key) }
@@ -38,6 +38,11 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
3838
state.override { State.KeyCreatedAndSaved(state.snapshot.key) }
3939
}
4040
}
41+
inState<State.Error> {
42+
on { _: Event.ClearError, state: MachineState<State.Error> ->
43+
state.override { State.Initial }
44+
}
45+
}
4146
inState<State.KeyCreatedAndSaved> {
4247
}
4348
}
@@ -48,12 +53,14 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
4853
data object CreatingKey : State
4954
data class KeyCreated(val key: String) : State
5055
data class KeyCreatedAndSaved(val key: String) : State
56+
data class Error(val exception: Exception) : State
5157
}
5258

5359
sealed interface Event {
5460
data object UserCreatesKey : Event
5561
data class SdkHasCreatedKey(val key: String) : Event
56-
data class SdkError(val throwable: Throwable) : Event
62+
data class SdkError(val exception: Exception) : Event
5763
data object UserSavedKey : Event
64+
data object ClearError : Event
5865
}
5966
}

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateProvider.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ open class SecureBackupSetupStateProvider : PreviewParameterProvider<SecureBacku
2323
setupState = SetupState.CreatedAndSaved(aFormattedRecoveryKey()),
2424
showSaveConfirmationDialog = true,
2525
),
26+
aSecureBackupSetupState(setupState = SetupState.Error(Exception("Test error"))),
2627
// Add other states here
2728
)
2829
}

features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten
2424
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
2525
import io.element.android.libraries.designsystem.components.BigIcon
2626
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
27+
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
2728
import io.element.android.libraries.designsystem.preview.ElementPreview
2829
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
2930
import io.element.android.libraries.designsystem.theme.components.Button
@@ -49,6 +50,16 @@ fun SecureBackupSetupView(
4950
Content(state = state)
5051
}
5152

53+
if (state.setupState is SetupState.Error) {
54+
ErrorDialog(
55+
title = stringResource(id = CommonStrings.common_something_went_wrong),
56+
content = stringResource(id = CommonStrings.common_something_went_wrong_message),
57+
onSubmit = {
58+
state.eventSink.invoke(SecureBackupSetupEvents.DismissDialog)
59+
},
60+
)
61+
}
62+
5263
if (state.showSaveConfirmationDialog) {
5364
ConfirmationDialog(
5465
title = stringResource(id = R.string.screen_recovery_key_setup_confirmation_title),
@@ -70,7 +81,8 @@ private fun SecureBackupSetupState.canGoBack(): Boolean {
7081
private fun title(state: SecureBackupSetupState): String {
7182
return when (state.setupState) {
7283
SetupState.Init,
73-
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) {
84+
SetupState.Creating,
85+
is SetupState.Error -> if (state.isChangeRecoveryKeyUserStory) {
7486
stringResource(id = R.string.screen_recovery_key_change_title)
7587
} else {
7688
stringResource(id = R.string.screen_recovery_key_setup_title)
@@ -85,7 +97,8 @@ private fun title(state: SecureBackupSetupState): String {
8597
private fun subtitle(state: SecureBackupSetupState): String {
8698
return when (state.setupState) {
8799
SetupState.Init,
88-
SetupState.Creating -> if (state.isChangeRecoveryKeyUserStory) {
100+
SetupState.Creating,
101+
is SetupState.Error -> if (state.isChangeRecoveryKeyUserStory) {
89102
stringResource(id = R.string.screen_recovery_key_change_description)
90103
} else {
91104
stringResource(id = R.string.screen_recovery_key_setup_description)
@@ -137,7 +150,8 @@ private fun ColumnScope.Buttons(
137150
val chooserTitle = stringResource(id = R.string.screen_recovery_key_save_action)
138151
when (state.setupState) {
139152
SetupState.Init,
140-
SetupState.Creating -> {
153+
SetupState.Creating,
154+
is SetupState.Error -> {
141155
Button(
142156
text = stringResource(id = CommonStrings.action_done),
143157
enabled = false,

features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,34 @@ class SecureBackupSetupPresenterTest {
109109
}
110110
}
111111

112+
@Test
113+
fun `present - handle errors`() = runTest {
114+
val encryptionService = FakeEncryptionService(
115+
enableRecoveryLambda = { Result.failure(IllegalStateException("Test error")) }
116+
)
117+
val presenter = createSecureBackupSetupPresenter(
118+
isChangeRecoveryKeyUserStory = false,
119+
encryptionService = encryptionService
120+
)
121+
moleculeFlow(RecompositionMode.Immediate) {
122+
presenter.present()
123+
}.test {
124+
val initialState = awaitItem()
125+
assertThat(initialState.isChangeRecoveryKeyUserStory).isFalse()
126+
assertThat(initialState.setupState).isEqualTo(SetupState.Init)
127+
128+
initialState.eventSink(SecureBackupSetupEvents.CreateRecoveryKey)
129+
val creatingState = awaitItem()
130+
assertThat(creatingState.setupState).isEqualTo(SetupState.Creating)
131+
val failedState = awaitItem()
132+
assertThat(failedState.setupState).isInstanceOf(SetupState.Error::class.java)
133+
failedState.eventSink(SecureBackupSetupEvents.DismissDialog)
134+
135+
val finalState = awaitItem()
136+
assertThat(finalState.setupState).isEqualTo(SetupState.Init)
137+
}
138+
}
139+
112140
@Test
113141
fun `present - change recovery key and save it`() = runTest {
114142
val encryptionService = FakeEncryptionService()
@@ -153,7 +181,9 @@ class SecureBackupSetupPresenterTest {
153181

154182
private fun createSecureBackupSetupPresenter(
155183
isChangeRecoveryKeyUserStory: Boolean = false,
156-
encryptionService: EncryptionService = FakeEncryptionService(),
184+
encryptionService: EncryptionService = FakeEncryptionService(
185+
enableRecoveryLambda = { Result.success(Unit) },
186+
),
157187
): SecureBackupSetupPresenter {
158188
return SecureBackupSetupPresenter(
159189
isChangeRecoveryKeyUserStory = isChangeRecoveryKeyUserStory,

libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class FakeEncryptionService(
2626
private val pinUserIdentityResult: (UserId) -> Result<Unit> = { lambdaError() },
2727
private val isUserVerifiedResult: (UserId) -> Result<Boolean> = { lambdaError() },
2828
private val withdrawVerificationResult: (UserId) -> Result<Unit> = { lambdaError() },
29-
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() }
29+
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() },
30+
private val enableRecoveryLambda: (Boolean) -> Result<Unit> = { lambdaError() },
3031
) : EncryptionService {
3132
private var disableRecoveryFailure: Exception? = null
3233
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
@@ -87,7 +88,7 @@ class FakeEncryptionService(
8788
}
8889

8990
override suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit> = simulateLongTask {
90-
return Result.success(Unit)
91+
return enableRecoveryLambda(waitForBackupsToUpload)
9192
}
9293

9394
fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)