Skip to content

Commit 2a5df54

Browse files
committed
Fix crash: show an error message with a Retry button when there is no network when displaying the BootstrapBottomSheet.
1 parent 0573915 commit 2a5df54

File tree

6 files changed

+141
-25
lines changed

6 files changed

+141
-25
lines changed

vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapActions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import im.vector.app.core.platform.VectorViewModelAction
2020
import java.io.OutputStream
2121

2222
sealed class BootstrapActions : VectorViewModelAction {
23+
object Retry : BootstrapActions()
2324

2425
// Navigation
2526

vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetBoot
212212
views.bootstrapTitleText.text = getString(R.string.upgrade_security)
213213
showFragment(BootstrapMigrateBackupFragment::class)
214214
}
215+
is BootstrapStep.Error -> {
216+
views.bootstrapIcon.isVisible = true
217+
views.bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title)
218+
showFragment(BootstrapErrorFragment::class)
219+
}
215220
}
216221
super.invalidate()
217222
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.crypto.recover
18+
19+
import android.view.LayoutInflater
20+
import android.view.ViewGroup
21+
import com.airbnb.mvrx.parentFragmentViewModel
22+
import com.airbnb.mvrx.withState
23+
import dagger.hilt.android.AndroidEntryPoint
24+
import im.vector.app.R
25+
import im.vector.app.core.epoxy.onClick
26+
import im.vector.app.core.extensions.setTextOrHide
27+
import im.vector.app.core.platform.VectorBaseFragment
28+
import im.vector.app.databinding.FragmentBootstrapErrorBinding
29+
30+
@AndroidEntryPoint
31+
class BootstrapErrorFragment :
32+
VectorBaseFragment<FragmentBootstrapErrorBinding>() {
33+
34+
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBootstrapErrorBinding {
35+
return FragmentBootstrapErrorBinding.inflate(inflater, container, false)
36+
}
37+
38+
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
39+
40+
override fun invalidate() = withState(sharedViewModel) { state ->
41+
when (state.step) {
42+
is BootstrapStep.Error -> {
43+
views.bootstrapDescriptionText.setTextOrHide(errorFormatter.toHumanReadable(state.step.error))
44+
}
45+
else -> {
46+
// Should not happen, show a generic error
47+
views.bootstrapDescriptionText.setTextOrHide(getString(R.string.unknown_error))
48+
}
49+
}
50+
views.bootstrapRetryButton.onClick {
51+
sharedViewModel.handle(BootstrapActions.Retry)
52+
}
53+
}
54+
}

vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromR
5454
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
5555
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
5656
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
57+
import timber.log.Timber
5758
import java.io.OutputStream
5859
import kotlin.coroutines.Continuation
5960
import kotlin.coroutines.resumeWithException
@@ -118,37 +119,48 @@ class BootstrapSharedViewModel @AssistedInject constructor(
118119
}
119120
}
120121
SetupMode.NORMAL -> {
121-
// need to check if user have an existing keybackup
122-
setState {
123-
copy(step = BootstrapStep.CheckingMigration)
124-
}
122+
checkMigration()
123+
}
124+
}
125+
}
126+
127+
private fun checkMigration() {
128+
// need to check if user have an existing keybackup
129+
setState {
130+
copy(step = BootstrapStep.CheckingMigration)
131+
}
125132

126-
// We need to check if there is an existing backup
127-
viewModelScope.launch(Dispatchers.IO) {
128-
val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }?.toKeysVersionResult()
129-
if (version == null) {
130-
// we just resume plain bootstrap
131-
doesKeyBackupExist = false
133+
// We need to check if there is an existing backup
134+
viewModelScope.launch(Dispatchers.IO) {
135+
try {
136+
val version = tryOrNull { session.cryptoService().keysBackupService().getCurrentVersion() }?.toKeysVersionResult()
137+
if (version == null) {
138+
// we just resume plain bootstrap
139+
doesKeyBackupExist = false
140+
setState {
141+
copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist, methods = this.secureBackupMethod))
142+
}
143+
} else {
144+
// we need to get existing backup passphrase/key and convert to SSSS
145+
val keyVersion = tryOrNull {
146+
session.cryptoService().keysBackupService().getVersion(version.version)
147+
}
148+
if (keyVersion == null) {
149+
// strange case... just finish?
150+
_viewEvents.post(BootstrapViewEvents.Dismiss(false))
151+
} else {
152+
doesKeyBackupExist = true
153+
isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
132154
setState {
133155
copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist, methods = this.secureBackupMethod))
134156
}
135-
} else {
136-
// we need to get existing backup passphrase/key and convert to SSSS
137-
val keyVersion = tryOrNull {
138-
session.cryptoService().keysBackupService().getVersion(version.version)
139-
}
140-
if (keyVersion == null) {
141-
// strange case... just finish?
142-
_viewEvents.post(BootstrapViewEvents.Dismiss(false))
143-
} else {
144-
doesKeyBackupExist = true
145-
isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null
146-
setState {
147-
copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist, methods = this.secureBackupMethod))
148-
}
149-
}
150157
}
151158
}
159+
} catch (failure: Throwable) {
160+
Timber.e(failure, "Error while checking key backup")
161+
setState {
162+
copy(step = BootstrapStep.Error(failure))
163+
}
152164
}
153165
}
154166
}
@@ -268,6 +280,9 @@ class BootstrapSharedViewModel @AssistedInject constructor(
268280
copy(step = BootstrapStep.AccountReAuth(stringProvider.getString(R.string.authentication_error)))
269281
}
270282
}
283+
BootstrapActions.Retry -> {
284+
checkMigration()
285+
}
271286
}
272287
}
273288

@@ -568,6 +583,12 @@ class BootstrapSharedViewModel @AssistedInject constructor(
568583
)
569584
}
570585
}
586+
is BootstrapStep.Error -> {
587+
// do we let you cancel from here?
588+
if (state.canLeave) {
589+
_viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null))
590+
}
591+
}
571592
}
572593
}
573594

vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapStep.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ sealed class BootstrapStep {
105105
object Initializing : BootstrapStep()
106106
data class SaveRecoveryKey(val isSaved: Boolean) : BootstrapStep()
107107
object DoneSuccess : BootstrapStep()
108+
109+
data class Error(val error: Throwable) : BootstrapStep()
108110
}
109111

110112
fun BootstrapStep.GetBackupSecretForMigration.useKey(): Boolean {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="wrap_content"
7+
android:minHeight="200dp"
8+
android:padding="16dp">
9+
10+
<TextView
11+
android:id="@+id/bootstrapDescriptionText"
12+
style="@style/Widget.Vector.TextView.Body"
13+
android:layout_width="match_parent"
14+
android:layout_height="wrap_content"
15+
android:layout_marginTop="8dp"
16+
android:text="@string/error_check_network"
17+
android:textColor="?vctr_content_primary"
18+
app:layout_constraintTop_toTopOf="parent" />
19+
20+
<Button
21+
android:id="@+id/bootstrapRetryButton"
22+
style="@style/Widget.Vector.Button"
23+
android:layout_width="wrap_content"
24+
android:layout_height="wrap_content"
25+
android:layout_marginTop="@dimen/layout_vertical_margin"
26+
android:text="@string/global_retry"
27+
app:layout_constraintBottom_toBottomOf="parent"
28+
app:layout_constraintEnd_toEndOf="parent"
29+
app:layout_constraintStart_toStartOf="parent"
30+
app:layout_constraintTop_toBottomOf="@id/bootstrapDescriptionText"
31+
tools:ignore="MissingConstraints" />
32+
33+
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)