From 9be6f488c4182280489f7350c73c03e7cf29e5af Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 1 Jul 2026 15:20:48 -0400 Subject: [PATCH] Add warning dialogs when mixing local backups and optimize media. --- .../local/LocalBackupsSettingsCallback.kt | 6 ++++ .../local/LocalBackupsSettingsScreen.kt | 34 ++++++++++++++++--- .../local/LocalBackupsSettingsState.kt | 1 + .../backups/local/LocalBackupsViewModel.kt | 3 +- .../storage/ManageStorageSettingsFragment.kt | 13 +++++++ .../storage/ManageStorageSettingsViewModel.kt | 6 ++-- app/src/main/res/values/strings.xml | 12 +++++++ .../org/signal/core/ui/compose/Dialogs.kt | 13 ++++--- 8 files changed, 76 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsCallback.kt index fbc594f4f8..9ed2cd9169 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsCallback.kt @@ -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) + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsScreen.kt index 3faef41950..047a265b74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsScreen.kt @@ -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), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsState.kt index dce43499a8..44c1939d8c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsSettingsState.kt @@ -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, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsViewModel.kt index d94903073b..96941e1488 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/backups/local/LocalBackupsViewModel.kt @@ -103,7 +103,8 @@ class LocalBackupsViewModel : ViewModel(), BackupKeyCredentialManagerHandler { internalSettingsState.update { it.copy( canTurnOn = canTurnOn, - scheduleTimeLabel = backupTime + scheduleTimeLabel = backupTime, + optimizeStorageEnabled = SignalStore.backup.optimizeStorage ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt index eb9d492c3c..991a9a6b62 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsFragment.kt @@ -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" ) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsViewModel.kt index 86bee3ae12..11b21dd02a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/storage/ManageStorageSettingsViewModel.kt @@ -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 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 863f33e5a4..ce4ad9d090 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8242,6 +8242,12 @@ To restore a backup, install a new copy of Signal. Open the app and tap "Restore backup", then locate a backup file. + + You have \"Optimize Signal storage\" on. If you want to include all media in your on-device backup, turn the setting off. + + View setting + + Continue without turning off Verify your recovery key @@ -8625,6 +8631,12 @@ Unused media will be offloaded, but can be downloaded from your backup anytime. 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. + + Media will be removed from your on-device backup + + 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. + + Turn on diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/Dialogs.kt b/core/ui/src/main/java/org/signal/core/ui/compose/Dialogs.kt index 33ffa700bd..8a84dd7375 100644 --- a/core/ui/src/main/java/org/signal/core/ui/compose/Dialogs.kt +++ b/core/ui/src/main/java/org/signal/core/ui/compose/Dialogs.kt @@ -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(