Add warning dialogs when mixing local backups and optimize media.

This commit is contained in:
Cody Henthorne
2026-07-01 15:20:48 -04:00
committed by Alex Hart
parent 5a6b0cbfef
commit 9be6f488c4
8 changed files with 76 additions and 12 deletions
@@ -33,6 +33,7 @@ sealed interface LocalBackupsSettingsCallback {
fun onLearnMoreClick()
fun onLaunchBackupLocationPickerClick()
fun onTurnOffAndDeleteConfirmed()
fun onViewOptimizeStorageSettingClick()
object Empty : LocalBackupsSettingsCallback {
override fun onNavigationClick() = Unit
@@ -43,6 +44,7 @@ sealed interface LocalBackupsSettingsCallback {
override fun onLearnMoreClick() = Unit
override fun onLaunchBackupLocationPickerClick() = Unit
override fun onTurnOffAndDeleteConfirmed() = Unit
override fun onViewOptimizeStorageSettingClick() = Unit
}
}
@@ -144,4 +146,8 @@ class DefaultLocalBackupsSettingsCallback(
override fun onTurnOffAndDeleteConfirmed() {
viewModel.turnOffAndDelete(fragment.requireContext())
}
override fun onViewOptimizeStorageSettingClick() {
fragment.findNavController().safeNavigate(R.id.action_direct_to_storagePreferenceFragment)
}
}
@@ -53,6 +53,7 @@ internal fun LocalBackupsSettingsScreen(
val context = LocalContext.current
var showChooseLocationDialog by rememberSaveable { mutableStateOf(false) }
var showTurnOffAndDeleteDialog by rememberSaveable { mutableStateOf(false) }
var showOptimizeStorageWarningDialog by rememberSaveable { mutableStateOf(false) }
val learnMore = stringResource(id = R.string.BackupsPreferenceFragment__learn_more)
val restoreText = stringResource(id = R.string.OnDeviceBackupsScreen__to_restore_a_backup, learnMore).trim()
@@ -80,6 +81,14 @@ internal fun LocalBackupsSettingsScreen(
educationSheetMessage = stringResource(R.string.RemoteBackupsSettingsFragment__to_view_your_key)
)
val proceedWithTurnOn = {
if (BackupUtil.isUserSelectionRequired(context)) {
showChooseLocationDialog = true
} else {
callback.onTurnOnClick()
}
}
Scaffolds.Settings(
title = stringResource(id = R.string.RemoteBackupsSettingsFragment__on_device_backups),
navigationIcon = ImageVector.vectorResource(CoreUiR.drawable.symbol_arrow_start_24),
@@ -104,11 +113,10 @@ internal fun LocalBackupsSettingsScreen(
Buttons.MediumTonal(
onClick = {
// For the SAF-based flow, present an in-screen dialog before launching the picker.
if (BackupUtil.isUserSelectionRequired(context)) {
showChooseLocationDialog = true
if (state.optimizeStorageEnabled) {
showOptimizeStorageWarningDialog = true
} else {
callback.onTurnOnClick()
proceedWithTurnOn()
}
},
enabled = state.canTurnOn,
@@ -220,6 +228,24 @@ internal fun LocalBackupsSettingsScreen(
)
}
if (showOptimizeStorageWarningDialog) {
Dialogs.AdvancedAlertDialog(
body = stringResource(id = R.string.OnDeviceBackupsScreen__you_have_optimize_signal_storage_on),
positive = stringResource(id = R.string.OnDeviceBackupsScreen__view_setting),
neutral = stringResource(id = R.string.OnDeviceBackupsScreen__continue_without_turning_off),
negative = stringResource(id = android.R.string.cancel),
onPositive = {
showOptimizeStorageWarningDialog = false
callback.onViewOptimizeStorageSettingClick()
},
onNeutral = {
showOptimizeStorageWarningDialog = false
proceedWithTurnOn()
},
onNegative = { showOptimizeStorageWarningDialog = false }
)
}
if (showTurnOffAndDeleteDialog) {
Dialogs.SimpleAlertDialog(
title = stringResource(id = R.string.BackupDialog_delete_backups),
@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.keyvalue.protos.LocalBackupCreationProgress
data class LocalBackupsSettingsState(
val backupsEnabled: Boolean = false,
val canTurnOn: Boolean = true,
val optimizeStorageEnabled: Boolean = false,
val lastBackupLabel: String? = null,
val folderDisplayName: String? = null,
val scheduleTimeLabel: String? = null,
@@ -103,7 +103,8 @@ class LocalBackupsViewModel : ViewModel(), BackupKeyCredentialManagerHandler {
internalSettingsState.update {
it.copy(
canTurnOn = canTurnOn,
scheduleTimeLabel = backupTime
scheduleTimeLabel = backupTime,
optimizeStorageEnabled = SignalStore.backup.optimizeStorage
)
}
}
@@ -118,6 +118,8 @@ class ManageStorageSettingsFragment : ComposeFragment() {
navController.navigate("paid-tier-pending")
} else if (state.onDeviceStorageOptimizationState == ManageStorageSettingsViewModel.OnDeviceStorageOptimizationState.REQUIRES_PAID_TIER) {
UpgradeToEnableOptimizedStorageSheet().show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
} else if (enabled && state.localBackupsEnabled) {
navController.navigate("confirm-optimize-with-local-backup")
} else {
viewModel.setOptimizeStorage(enabled)
}
@@ -238,6 +240,17 @@ class ManageStorageSettingsFragment : ComposeFragment() {
)
}
dialog("confirm-optimize-with-local-backup") {
Dialogs.SimpleAlertDialog(
title = stringResource(id = R.string.ManageStorageSettingsFragment__media_will_be_removed_from_your_on_device_backup),
body = stringResource(id = R.string.ManageStorageSettingsFragment__turning_on_optimize_signal_storage_will_offload),
confirm = stringResource(id = R.string.ManageStorageSettingsFragment__turn_on),
dismiss = stringResource(id = android.R.string.cancel),
onConfirm = { viewModel.setOptimizeStorage(true) },
onDismiss = { navController.popBackStack() }
)
}
dialog(
route = "paid-tier-pending"
) {
@@ -35,7 +35,8 @@ class ManageStorageSettingsViewModel : ViewModel() {
ManageStorageState(
keepMessagesDuration = SignalStore.settings.keepMessagesDuration,
lengthLimit = if (SignalStore.settings.isTrimByLengthEnabled) SignalStore.settings.threadTrimLength else ManageStorageState.NO_LIMIT,
syncTrimDeletes = SignalStore.settings.shouldSyncThreadTrimDeletes()
syncTrimDeletes = SignalStore.settings.shouldSyncThreadTrimDeletes(),
localBackupsEnabled = SignalStore.backup.newLocalBackupsEnabled
)
)
val state = store.asStateFlow()
@@ -179,7 +180,8 @@ class ManageStorageSettingsViewModel : ViewModel() {
val breakdown: MediaTable.StorageBreakdown? = null,
val onDeviceStorageOptimizationState: OnDeviceStorageOptimizationState = OnDeviceStorageOptimizationState.FEATURE_NOT_AVAILABLE,
val storageOptimizationStateChanged: Boolean = false,
val isPaidTierPending: Boolean = false
val isPaidTierPending: Boolean = false,
val localBackupsEnabled: Boolean = false
) {
companion object {
const val NO_LIMIT = 0
+12
View File
@@ -8242,6 +8242,12 @@
<!-- Text describing how to restore a backup displayed on the on-device backups screen -->
<string name="OnDeviceBackupsScreen__to_restore_a_backup">To restore a backup, install a new copy of Signal. Open the app and tap "Restore backup", then locate a backup file.</string>
<!-- Body of a dialog shown when turning on on-device backups while "Optimize Signal storage" is enabled, warning that optimized media won't be included -->
<string name="OnDeviceBackupsScreen__you_have_optimize_signal_storage_on">You have \"Optimize Signal storage\" on. If you want to include all media in your on-device backup, turn the setting off.</string>
<!-- Dialog action that takes the user to the "Optimize Signal storage" setting -->
<string name="OnDeviceBackupsScreen__view_setting">View setting</string>
<!-- Dialog action to proceed with turning on on-device backups without turning off "Optimize Signal storage" -->
<string name="OnDeviceBackupsScreen__continue_without_turning_off">Continue without turning off</string>
<!-- Title of a megaphone shown to prompt the user to verify their recovery key -->
<string name="VerifyBackupKey__title">Verify your recovery key</string>
@@ -8625,6 +8631,12 @@
<string name="ManageStorageSettingsFragment__unused_media_will_be_offloaded">Unused media will be offloaded, but can be downloaded from your backup anytime.</string>
<!-- Dialog message for paid tier pending dialog -->
<string name="ManageStorageSettingsFragment__storage_optimization_can_only_be_used">Storage optimization can only be used with the paid tier of Signal Secure Backups. Your backups subscription is still processing and not yet active. Please try again later.</string>
<!-- Title of a dialog shown when turning on "Optimize Signal storage" while on-device backups are enabled -->
<string name="ManageStorageSettingsFragment__media_will_be_removed_from_your_on_device_backup">Media will be removed from your on-device backup</string>
<!-- Body of a dialog shown when turning on "Optimize Signal storage" while on-device backups are enabled -->
<string name="ManageStorageSettingsFragment__turning_on_optimize_signal_storage_will_offload">Turning on \"Optimize Signal storage\" will offload old media from your device. You can download it again any time. It will also remove old media from your on-device backup.</string>
<!-- Dialog action to confirm turning on "Optimize Signal storage" -->
<string name="ManageStorageSettingsFragment__turn_on">Turn on</string>
<!-- UpgradeToEnableOptimizedStorageSheet -->
<!-- Title on a bottom sheet, detailing that the user must upgrade to enable storage optimization -->
@@ -772,16 +772,19 @@ object Dialogs {
.clip(AlertDialogDefaults.shape)
) {
Column(modifier = Modifier.padding(24.dp)) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge
)
if (title.isNotEmpty()) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
}
Text(
text = body,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(vertical = 16.dp)
modifier = Modifier.padding(bottom = 16.dp)
)
Column(