mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Implement stop/resume media restore and update restore over cellular.
This commit is contained in:
committed by
Michelle Tang
parent
9867fa3f50
commit
93403a0d2c
@@ -217,7 +217,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
override fun onCancelMediaRestore() {
|
||||
viewModel.cancelMediaRestore()
|
||||
viewModel.requestDialog(RemoteBackupsSettingsState.Dialog.CANCEL_MEDIA_RESTORE_PROTECTION)
|
||||
}
|
||||
|
||||
override fun onDisplaySkipMediaRestoreProtectionDialog() {
|
||||
@@ -245,8 +245,12 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
BackupAlertBottomSheet.create(BackupAlert.BackupFailed).show(parentFragmentManager, null)
|
||||
}
|
||||
|
||||
override fun onRestoreUsingCellularClick(canUseCellular: Boolean) {
|
||||
viewModel.setCanRestoreUsingCellular(canUseCellular)
|
||||
override fun onRestoreUsingCellularConfirm() {
|
||||
viewModel.requestDialog(RemoteBackupsSettingsState.Dialog.RESTORE_OVER_CELLULAR_PROTECTION)
|
||||
}
|
||||
|
||||
override fun onRestoreUsingCellularClick() {
|
||||
viewModel.setCanRestoreUsingCellular()
|
||||
}
|
||||
|
||||
override fun onRedemptionErrorDetailsClick() {
|
||||
@@ -334,7 +338,8 @@ private interface ContentCallbacks {
|
||||
fun onLearnMoreAboutLostSubscription() = Unit
|
||||
fun onContactSupport() = Unit
|
||||
fun onLearnMoreAboutBackupFailure() = Unit
|
||||
fun onRestoreUsingCellularClick(canUseCellular: Boolean) = Unit
|
||||
fun onRestoreUsingCellularConfirm() = Unit
|
||||
fun onRestoreUsingCellularClick() = Unit
|
||||
fun onRedemptionErrorDetailsClick() = Unit
|
||||
}
|
||||
|
||||
@@ -425,18 +430,20 @@ private fun RemoteBackupsSettingsContent(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Rows.ToggleRow(
|
||||
checked = canRestoreUsingCellular,
|
||||
text = stringResource(id = R.string.RemoteBackupsSettingsFragment__restore_using_cellular),
|
||||
onCheckChanged = contentCallbacks::onRestoreUsingCellularClick
|
||||
)
|
||||
if (!canRestoreUsingCellular) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = stringResource(R.string.RemoteBackupsSettingsFragment__resume_download),
|
||||
icon = painterResource(R.drawable.symbol_arrow_circle_down_24),
|
||||
onClick = contentCallbacks::onRestoreUsingCellularConfirm
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (backupRestoreState is BackupRestoreState.Ready && backupState is RemoteBackupsSettingsState.BackupState.Canceled) {
|
||||
} else if (backupRestoreState is BackupRestoreState.Ready) {
|
||||
item {
|
||||
BackupReadyToDownloadRow(
|
||||
ready = backupRestoreState,
|
||||
endOfSubscription = backupState.renewalTime,
|
||||
backupState = backupState,
|
||||
onDownloadClick = contentCallbacks::onStartMediaRestore
|
||||
)
|
||||
}
|
||||
@@ -536,6 +543,20 @@ private fun RemoteBackupsSettingsContent(
|
||||
onSkipClick = contentCallbacks::onSkipMediaRestore
|
||||
)
|
||||
}
|
||||
|
||||
RemoteBackupsSettingsState.Dialog.CANCEL_MEDIA_RESTORE_PROTECTION -> {
|
||||
CancelInitialRestoreDialog(
|
||||
onDismiss = contentCallbacks::onDialogDismissed,
|
||||
onSkipClick = contentCallbacks::onSkipMediaRestore
|
||||
)
|
||||
}
|
||||
|
||||
RemoteBackupsSettingsState.Dialog.RESTORE_OVER_CELLULAR_PROTECTION -> {
|
||||
ResumeRestoreOverCellularDialog(
|
||||
onDismiss = contentCallbacks::onDialogDismissed,
|
||||
onResumeOverCellularClick = contentCallbacks::onRestoreUsingCellularClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val snackbarMessageId = remember(requestedSnackbar) {
|
||||
@@ -1164,6 +1185,37 @@ private fun SkipDownloadDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CancelInitialRestoreDialog(
|
||||
onSkipClick: () -> Unit = {},
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(R.string.RemoteBackupsSettingsFragment__skip_restore_question),
|
||||
body = stringResource(R.string.RemoteBackupsSettingsFragment__skip_restore_message),
|
||||
confirm = stringResource(R.string.RemoteBackupsSettingsFragment__skip),
|
||||
dismiss = stringResource(android.R.string.cancel),
|
||||
confirmColor = MaterialTheme.colorScheme.error,
|
||||
onConfirm = onSkipClick,
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResumeRestoreOverCellularDialog(
|
||||
onResumeOverCellularClick: () -> Unit = {},
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(R.string.ResumeRestoreCellular_resume_using_cellular_title),
|
||||
body = stringResource(R.string.ResumeRestoreCellular_resume_using_cellular_message),
|
||||
confirm = stringResource(R.string.BackupStatus__resume),
|
||||
dismiss = stringResource(android.R.string.cancel),
|
||||
onConfirm = onResumeOverCellularClick,
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CircularProgressDialog(
|
||||
@@ -1254,11 +1306,16 @@ private fun BackupFrequencyDialog(
|
||||
@Composable
|
||||
private fun BackupReadyToDownloadRow(
|
||||
ready: BackupRestoreState.Ready,
|
||||
endOfSubscription: Duration,
|
||||
backupState: RemoteBackupsSettingsState.BackupState,
|
||||
onDownloadClick: () -> Unit = {}
|
||||
) {
|
||||
val days = (endOfSubscription - System.currentTimeMillis().milliseconds).inWholeDays.toInt()
|
||||
val string = pluralStringResource(R.plurals.RemoteBackupsSettingsFragment__you_have_s_of_backup_data, days, ready.bytes, days)
|
||||
val string = if (backupState is RemoteBackupsSettingsState.BackupState.Canceled) {
|
||||
val days = (backupState.renewalTime - System.currentTimeMillis().milliseconds).inWholeDays.toInt()
|
||||
pluralStringResource(R.plurals.RemoteBackupsSettingsFragment__you_have_s_of_backup_data, days, ready.bytes, days)
|
||||
} else {
|
||||
stringResource(R.string.RemoteBackupsSettingsFragment__you_have_s_of_backup_data_not_on_device, ready.bytes)
|
||||
}
|
||||
|
||||
val annotated = buildAnnotatedString {
|
||||
append(string)
|
||||
val startIndex = string.indexOf(ready.bytes)
|
||||
@@ -1436,7 +1493,7 @@ private fun BackupReadyToDownloadPreview() {
|
||||
Previews.Preview {
|
||||
BackupReadyToDownloadRow(
|
||||
ready = BackupRestoreState.Ready("12GB"),
|
||||
endOfSubscription = System.currentTimeMillis().milliseconds + 30.days
|
||||
backupState = RemoteBackupsSettingsState.BackupState.None
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,9 @@ data class RemoteBackupsSettingsState(
|
||||
DOWNLOADING_YOUR_BACKUP,
|
||||
TURN_OFF_FAILED,
|
||||
SUBSCRIPTION_NOT_FOUND,
|
||||
SKIP_MEDIA_RESTORE_PROTECTION
|
||||
SKIP_MEDIA_RESTORE_PROTECTION,
|
||||
CANCEL_MEDIA_RESTORE_PROTECTION,
|
||||
RESTORE_OVER_CELLULAR_PROTECTION
|
||||
}
|
||||
|
||||
enum class Snackbar {
|
||||
|
||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -83,15 +84,25 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val restoreProgress = MediaRestoreProgressBanner()
|
||||
|
||||
var optimizedRemainingBytes = 0L
|
||||
while (isActive) {
|
||||
if (restoreProgress.enabled) {
|
||||
Log.d(TAG, "Backup is being restored. Collecting updates.")
|
||||
restoreProgress.dataFlow.collectLatest { latest ->
|
||||
_restoreState.update { BackupRestoreState.FromBackupStatusData(latest) }
|
||||
}
|
||||
restoreProgress
|
||||
.dataFlow
|
||||
.takeWhile { it !is BackupStatusData.RestoringMedia || it.restoreStatus != BackupStatusData.RestoreStatus.FINISHED }
|
||||
.collectLatest { latest ->
|
||||
_restoreState.update { BackupRestoreState.FromBackupStatusData(latest) }
|
||||
}
|
||||
} else if (
|
||||
!SignalStore.backup.optimizeStorage &&
|
||||
SignalStore.backup.userManuallySkippedMediaRestore &&
|
||||
SignalDatabase.attachments.getOptimizedMediaAttachmentSize().also { optimizedRemainingBytes = it } > 0
|
||||
) {
|
||||
_restoreState.update { BackupRestoreState.Ready(optimizedRemainingBytes.bytes.toUnitString()) }
|
||||
} else if (SignalStore.backup.totalRestorableAttachmentSize > 0L) {
|
||||
_restoreState.update { BackupRestoreState.Ready(SignalStore.backup.totalRestorableAttachmentSize.bytes.toUnitString()) }
|
||||
} else if (BackupRepository.shouldDisplayBackupFailedSettingsRow()) {
|
||||
@@ -126,9 +137,9 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
_state.update { it.copy(canBackUpUsingCellular = canBackUpUsingCellular) }
|
||||
}
|
||||
|
||||
fun setCanRestoreUsingCellular(canRestoreUsingCellular: Boolean) {
|
||||
SignalStore.backup.restoreWithCellular = canRestoreUsingCellular
|
||||
_state.update { it.copy(canRestoreUsingCellular = canRestoreUsingCellular) }
|
||||
fun setCanRestoreUsingCellular() {
|
||||
SignalStore.backup.restoreWithCellular = true
|
||||
_state.update { it.copy(canRestoreUsingCellular = true) }
|
||||
}
|
||||
|
||||
fun setBackupsFrequency(backupsFrequency: BackupFrequency) {
|
||||
@@ -139,17 +150,13 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun beginMediaRestore() {
|
||||
// TODO - [backups] Begin media restore.
|
||||
BackupRepository.resumeMediaRestore()
|
||||
}
|
||||
|
||||
fun skipMediaRestore() {
|
||||
BackupRepository.skipMediaRestore()
|
||||
}
|
||||
|
||||
fun cancelMediaRestore() {
|
||||
// TODO - [backups] Cancel in-progress media restoration
|
||||
}
|
||||
|
||||
fun requestDialog(dialog: RemoteBackupsSettingsState.Dialog) {
|
||||
_state.update { it.copy(dialog = dialog) }
|
||||
}
|
||||
@@ -269,14 +276,17 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
hasActiveSignalSubscription && hasActiveGooglePlayBillingSubscription -> {
|
||||
Log.d(TAG, "Found active signal subscription and active google play subscription. Clearing mismatch.")
|
||||
SignalStore.backup.subscriptionStateMismatchDetected = false
|
||||
}
|
||||
|
||||
!hasActiveSignalSubscription && !hasActiveGooglePlayBillingSubscription -> {
|
||||
Log.d(TAG, "Found inactive signal subscription and inactive google play subscription. Clearing mismatch.")
|
||||
SignalStore.backup.subscriptionStateMismatchDetected = false
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Hit unexpected subscription mismatch state: signal:false, google:true")
|
||||
return
|
||||
|
||||
@@ -294,6 +294,7 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
||||
|
||||
fun wipeAllDataAndRestoreFromRemote() {
|
||||
SignalExecutors.BOUNDED_IO.execute {
|
||||
SignalStore.backup.restoreWithCellular = false
|
||||
restoreFromRemote()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user