mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Implement skip restore on the iOS to android failure screen.
This commit is contained in:
@@ -30,6 +30,7 @@ import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||
import org.signal.registration.proto.RegistrationProvisionMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.RestoreTimestampResult
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LinkedDeviceInfo
|
||||
@@ -150,6 +151,8 @@ class RegistrationViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
var registrationProvisioningMessage: RegistrationProvisionMessage? = null
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun maybePrefillE164(context: Context) {
|
||||
Log.v(TAG, "maybePrefillE164()")
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package org.thoughtcrime.securesms.registration.ui.restore
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -17,40 +19,97 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.registration.proto.RegistrationProvisionMessage
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
||||
import org.thoughtcrime.securesms.registration.ui.RegistrationViewModel
|
||||
import org.thoughtcrime.securesms.registration.ui.shared.RegistrationScreen
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import kotlin.getValue
|
||||
|
||||
/**
|
||||
* Shown when the old device is iOS and they are trying to transfer/restore on Android without a Signal Backup.
|
||||
*/
|
||||
class NoBackupToRestoreFragment : ComposeFragment() {
|
||||
|
||||
private val sharedViewModel by activityViewModels<RegistrationViewModel>()
|
||||
private val viewModel by viewModel {
|
||||
NoBackupToRestoreViewModel(sharedViewModel.registrationProvisioningMessage!!)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
sharedViewModel
|
||||
.state
|
||||
.map { it.registerAccountError }
|
||||
.filterNotNull()
|
||||
.collect {
|
||||
sharedViewModel.registerAccountErrorShown()
|
||||
viewModel.handleRegistrationFailure(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
NoBackupToRestoreContent(
|
||||
onSkipRestore = {},
|
||||
state = state,
|
||||
onSkipRestore = {
|
||||
viewModel.skipRestoreAndRegister()
|
||||
|
||||
val message = viewModel.state.value.provisioningMessage
|
||||
sharedViewModel.registerWithBackupKey(
|
||||
context = requireContext(),
|
||||
backupKey = message.accountEntropyPool,
|
||||
e164 = message.e164,
|
||||
pin = message.pin,
|
||||
aciIdentityKeyPair = message.aciIdentityKeyPair,
|
||||
pniIdentityKeyPair = message.pniIdentityKeyPair
|
||||
)
|
||||
},
|
||||
onCancel = {
|
||||
sharedViewModel.registrationProvisioningMessage = null
|
||||
findNavController().safeNavigate(NoBackupToRestoreFragmentDirections.restartRegistrationFlow())
|
||||
}
|
||||
},
|
||||
onRegistrationErrorDismiss = viewModel::clearRegistrationError
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NoBackupToRestoreContent(
|
||||
state: NoBackupToRestoreViewModel.NoBackupToRestoreState,
|
||||
onSkipRestore: () -> Unit = {},
|
||||
onCancel: () -> Unit = {}
|
||||
onCancel: () -> Unit = {},
|
||||
onRegistrationErrorDismiss: () -> Unit = {}
|
||||
) {
|
||||
RegistrationScreen(
|
||||
title = stringResource(id = R.string.NoBackupToRestore_title),
|
||||
@@ -83,6 +142,22 @@ private fun NoBackupToRestoreContent(
|
||||
|
||||
StepRow(icon = painterResource(R.drawable.symbol_check_circle_24), text = stringResource(id = R.string.NoBackupToRestore_step3))
|
||||
}
|
||||
|
||||
if (state.isRegistering) {
|
||||
Dialogs.IndeterminateProgressDialog()
|
||||
} else if (state.showRegistrationError) {
|
||||
val message = when (state.registerAccountResult) {
|
||||
is RegisterAccountResult.IncorrectRecoveryPassword -> stringResource(R.string.RestoreViaQr_registration_error)
|
||||
is RegisterAccountResult.RateLimited -> stringResource(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
|
||||
else -> stringResource(R.string.RegistrationActivity_error_connecting_to_service)
|
||||
}
|
||||
|
||||
Dialogs.SimpleMessageDialog(
|
||||
message = message,
|
||||
onDismiss = onRegistrationErrorDismiss,
|
||||
dismiss = stringResource(android.R.string.ok)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +188,6 @@ private fun StepRow(
|
||||
@Composable
|
||||
private fun NoBackupToRestoreContentPreview() {
|
||||
Previews.Preview {
|
||||
NoBackupToRestoreContent()
|
||||
NoBackupToRestoreContent(state = NoBackupToRestoreViewModel.NoBackupToRestoreState(provisioningMessage = RegistrationProvisionMessage()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registration.ui.restore
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.registration.proto.RegistrationProvisionMessage
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.Skipped
|
||||
import org.thoughtcrime.securesms.registration.data.QuickRegistrationRepository
|
||||
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
||||
import org.whispersystems.signalservice.api.provisioning.RestoreMethod
|
||||
|
||||
class NoBackupToRestoreViewModel(decode: RegistrationProvisionMessage) : ViewModel() {
|
||||
companion object {
|
||||
private val TAG = Log.tag(NoBackupToRestoreViewModel::class)
|
||||
}
|
||||
|
||||
private val store: MutableStateFlow<NoBackupToRestoreState> = MutableStateFlow(NoBackupToRestoreState(provisioningMessage = decode))
|
||||
|
||||
val state: StateFlow<NoBackupToRestoreState> = store
|
||||
|
||||
fun skipRestoreAndRegister() {
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.Skipped
|
||||
store.update { it.copy(isRegistering = true) }
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
QuickRegistrationRepository.setRestoreMethodForOldDevice(RestoreMethod.DECLINE)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleRegistrationFailure(registerAccountResult: RegisterAccountResult) {
|
||||
store.update {
|
||||
if (it.isRegistering) {
|
||||
Log.w(TAG, "Unable to register [${registerAccountResult::class.simpleName}]", registerAccountResult.getCause(), true)
|
||||
it.copy(
|
||||
isRegistering = false,
|
||||
showRegistrationError = true,
|
||||
registerAccountResult = registerAccountResult
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearRegistrationError() {
|
||||
store.update {
|
||||
it.copy(
|
||||
showRegistrationError = false,
|
||||
registerAccountResult = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class NoBackupToRestoreState(
|
||||
val isRegistering: Boolean = false,
|
||||
val provisioningMessage: RegistrationProvisionMessage,
|
||||
val showRegistrationError: Boolean = false,
|
||||
val registerAccountResult: RegisterAccountResult? = null
|
||||
)
|
||||
}
|
||||
@@ -110,6 +110,7 @@ class RestoreViaQrFragment : ComposeFragment() {
|
||||
if (message.platform == RegistrationProvisionMessage.Platform.ANDROID || message.tier != null) {
|
||||
sharedViewModel.registerWithBackupKey(requireContext(), message.accountEntropyPool, message.e164, message.pin, message.aciIdentityKeyPair, message.pniIdentityKeyPair)
|
||||
} else {
|
||||
sharedViewModel.registrationProvisioningMessage = message
|
||||
findNavController().safeNavigate(RestoreViaQrFragmentDirections.goToNoBackupToRestore())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user