mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 00:29:11 +01:00
Prompt users to backup during quick restore.
This commit is contained in:
committed by
Alex Hart
parent
4921198cd8
commit
9012a2afc0
@@ -68,7 +68,7 @@ class AppSettingsActivity : DSLSettingsActivity(), GooglePayComponent {
|
||||
AppSettingsRoute.LinkDeviceRoute.LinkDevice -> AppSettingsFragmentDirections.actionDirectToDevices()
|
||||
AppSettingsRoute.UsernameLinkRoute.UsernameLink -> AppSettingsFragmentDirections.actionDirectToUsernameLinkSettings()
|
||||
is AppSettingsRoute.AccountRoute.Username -> AppSettingsFragmentDirections.actionDirectToUsernameRecovery()
|
||||
is AppSettingsRoute.BackupsRoute.Remote -> AppSettingsFragmentDirections.actionDirectToRemoteBackupsSettingsFragment()
|
||||
is AppSettingsRoute.BackupsRoute.Remote -> AppSettingsFragmentDirections.actionDirectToRemoteBackupsSettingsFragment().setForQuickRestore(appSettingsRoute.forQuickRestore)
|
||||
AppSettingsRoute.ChatFoldersRoute.ChatFolders -> AppSettingsFragmentDirections.actionDirectToChatFoldersFragment()
|
||||
is AppSettingsRoute.ChatFoldersRoute.CreateChatFolders -> AppSettingsFragmentDirections.actionDirectToCreateFoldersFragment(
|
||||
appSettingsRoute.folderId,
|
||||
@@ -204,7 +204,8 @@ class AppSettingsActivity : DSLSettingsActivity(), GooglePayComponent {
|
||||
fun usernameRecovery(context: Context): Intent = getIntentForStartLocation(context, AppSettingsRoute.AccountRoute.Username(mode = UsernameEditMode.RECOVERY))
|
||||
|
||||
@JvmStatic
|
||||
fun remoteBackups(context: Context): Intent = getIntentForStartLocation(context, AppSettingsRoute.BackupsRoute.Remote())
|
||||
@JvmOverloads
|
||||
fun remoteBackups(context: Context, forQuickRestore: Boolean = false): Intent = getIntentForStartLocation(context, AppSettingsRoute.BackupsRoute.Remote(forQuickRestore = forQuickRestore))
|
||||
|
||||
@JvmStatic
|
||||
fun chatFolders(context: Context): Intent = getIntentForStartLocation(context, AppSettingsRoute.ChatFoldersRoute.ChatFolders)
|
||||
|
||||
@@ -37,11 +37,13 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
@@ -49,6 +51,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -71,6 +74,7 @@ import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.signal.core.ui.compose.BottomSheets
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.DayNightPreviews
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
@@ -113,6 +117,7 @@ import org.thoughtcrime.securesms.compose.StatusBarColorNestedScrollConnection
|
||||
import org.thoughtcrime.securesms.help.HelpFragment
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
@@ -186,7 +191,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
override fun onBackupNowClick() {
|
||||
viewModel.onBackupNowClick()
|
||||
viewModel.onBackupNowClick(args.forQuickRestore)
|
||||
}
|
||||
|
||||
override fun onTurnOffAndDeleteBackupsClick() {
|
||||
@@ -295,6 +300,10 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
override fun onFreeTierBackupSizeLearnMore() {
|
||||
CommunicationActions.openBrowserLink(requireContext(), "https://support.signal.org/hc/articles/9708267671322")
|
||||
}
|
||||
|
||||
override fun onTransferScanQrCodeClick() {
|
||||
startActivity(MediaSelectionActivity.camera(context!!))
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayBackupKey() {
|
||||
@@ -400,6 +409,7 @@ private interface ContentCallbacks {
|
||||
fun onIncludeDebuglogClick(newState: Boolean) = Unit
|
||||
fun onMediaBackupSizeClick() = Unit
|
||||
fun onFreeTierBackupSizeLearnMore() = Unit
|
||||
fun onTransferScanQrCodeClick() = Unit
|
||||
|
||||
object Empty : ContentCallbacks
|
||||
}
|
||||
@@ -668,6 +678,13 @@ private fun RemoteBackupsSettingsContent(
|
||||
onDismiss = contentCallbacks::onDialogDismissed
|
||||
)
|
||||
}
|
||||
|
||||
RemoteBackupsSettingsState.Dialog.READY_TO_TRANSFER -> {
|
||||
ReadyToTransferBottomSheet(
|
||||
onScanQrCodeClick = contentCallbacks::onTransferScanQrCodeClick,
|
||||
onDismiss = contentCallbacks::onDialogDismissed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val snackbarMessageId = remember(state.snackbar) {
|
||||
@@ -1699,6 +1716,90 @@ private fun ResumeRestoreOverCellularDialog(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bottom sheet displayed when the device is ready to transfer data to a new device.
|
||||
* Shows a QR code illustration and prompts the user to scan the QR code on the target device.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ReadyToTransferBottomSheet(
|
||||
onScanQrCodeClick: () -> Unit = {},
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismiss,
|
||||
dragHandle = { BottomSheets.Handle() }
|
||||
) {
|
||||
ReadyToTransferContent(
|
||||
onScanQrCodeClick = onScanQrCodeClick,
|
||||
onCancelClick = onDismiss
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReadyToTransferContent(
|
||||
onScanQrCodeClick: () -> Unit = {},
|
||||
onCancelClick: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 40.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.illustration_scan_qr_transfer),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(192.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(24.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.RemoteBackupsSettingsFragment__ready_to_transfer),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.horizontalGutters()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.RemoteBackupsSettingsFragment__use_this_device_to_scan_qr),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.horizontalGutters()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(36.dp))
|
||||
|
||||
Buttons.LargeTonal(
|
||||
onClick = onScanQrCodeClick,
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minWidth = 220.dp)
|
||||
) {
|
||||
Text(text = stringResource(R.string.RemoteBackupsSettingsFragment__scan_qr_code))
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = onCancelClick,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(android.R.string.cancel),
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackupReadyToDownloadRow(
|
||||
ready: BackupRestoreState.Ready,
|
||||
@@ -2156,6 +2257,14 @@ private fun SkipDownloadDialogPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun ReadyToTransferContentPreview() {
|
||||
Previews.BottomSheetPreview {
|
||||
ReadyToTransferContent()
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun BackupDeletionCardPreview() {
|
||||
|
||||
@@ -55,7 +55,8 @@ data class RemoteBackupsSettingsState(
|
||||
CANCEL_MEDIA_RESTORE_PROTECTION,
|
||||
RESTORE_OVER_CELLULAR_PROTECTION,
|
||||
FREE_TIER_MEDIA_EXPLAINER,
|
||||
KEY_ROTATION_LIMIT_REACHED
|
||||
KEY_ROTATION_LIMIT_REACHED,
|
||||
READY_TO_TRANSFER
|
||||
}
|
||||
|
||||
enum class Snackbar {
|
||||
|
||||
@@ -88,6 +88,8 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
val state: StateFlow<RemoteBackupsSettingsState> = _state
|
||||
val restoreState: StateFlow<BackupRestoreState> = _restoreState
|
||||
|
||||
private var forQuickRestore = false
|
||||
|
||||
init {
|
||||
ArchiveUploadProgress.triggerUpdate()
|
||||
|
||||
@@ -172,6 +174,10 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
.collect { current ->
|
||||
if (previous != null && previous != current.state && current.state == ArchiveUploadProgressState.State.None) {
|
||||
Log.d(TAG, "Refreshing state after archive upload.")
|
||||
if (forQuickRestore) {
|
||||
Log.d(TAG, "Backup completed with the forQuickRestore flag on. Refreshing state.")
|
||||
_state.value = _state.value.copy(dialog = RemoteBackupsSettingsState.Dialog.READY_TO_TRANSFER)
|
||||
}
|
||||
refreshState(null)
|
||||
}
|
||||
previous = current.state
|
||||
@@ -275,8 +281,9 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun onBackupNowClick() {
|
||||
fun onBackupNowClick(forQuickRestore: Boolean) {
|
||||
BackupMessagesJob.enqueue()
|
||||
this.forQuickRestore = forQuickRestore
|
||||
}
|
||||
|
||||
fun cancelUpload() {
|
||||
|
||||
@@ -65,7 +65,7 @@ sealed interface AppSettingsRoute : Parcelable {
|
||||
sealed interface BackupsRoute : AppSettingsRoute {
|
||||
data object Backups : BackupsRoute
|
||||
data object Local : BackupsRoute
|
||||
data class Remote(val backupLaterSelected: Boolean = false) : BackupsRoute
|
||||
data class Remote(val backupLaterSelected: Boolean = false, val forQuickRestore: Boolean = false) : BackupsRoute
|
||||
data object DisplayKey : BackupsRoute
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user