From 6a3d1634b92fd3d684ab9f2379db25b47d1d34f0 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 9 Mar 2026 14:00:37 -0400 Subject: [PATCH] Show warning dialog when attempting to save optimized media. --- .../MediaOverviewPageFragment.java | 28 +++++++++++++- .../mediapreview/MediaPreviewV2Fragment.kt | 16 ++++++-- .../util/OffloadedMediaDialogUtil.kt | 37 +++++++++++++++++++ app/src/main/res/values/strings.xml | 11 ++++++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/OffloadedMediaDialogUtil.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java index 15639e4559..96daac72c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -50,11 +50,14 @@ import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity; import org.thoughtcrime.securesms.mms.PartAuthority; import org.signal.core.ui.permissions.Permissions; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.BottomOffsetDecoration; +import org.thoughtcrime.securesms.util.OffloadedMediaDialogUtil; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.ViewUtil; import java.util.Arrays; +import java.util.Collection; import java.util.Objects; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -368,9 +371,32 @@ public final class MediaOverviewPageFragment extends LoggingFragment bottomActionBar.setItems(Arrays.asList( new ActionItem(R.drawable.symbol_save_android_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_save_plural, selectionCount), () -> { + Collection selected = getListAdapter().getSelectedMedia(); + + if (SignalStore.backup().getOptimizeStorage()) { + boolean allOffloaded = selected.stream().allMatch(r -> r.getAttachment() != null && !r.getAttachment().hasData); + boolean someOffloaded = !allOffloaded && selected.stream().anyMatch(r -> r.getAttachment() != null && !r.getAttachment().hasData); + + if (allOffloaded) { + OffloadedMediaDialogUtil.showAllOffloaded(requireContext()); + return; + } else if (someOffloaded) { + OffloadedMediaDialogUtil.showPartiallyOffloaded(requireContext(), () -> { + Collection saveable = selected.stream().filter(r -> r.getAttachment() == null || r.getAttachment().hasData).collect(java.util.stream.Collectors.toList()); + lifecycleDisposable.add( + MediaActions + .handleSaveMedia(MediaOverviewPageFragment.this, saveable) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::exitMultiSelect) + ); + }); + return; + } + } + lifecycleDisposable.add( MediaActions - .handleSaveMedia(MediaOverviewPageFragment.this, getListAdapter().getSelectedMedia()) + .handleSaveMedia(MediaOverviewPageFragment.this, selected) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::exitMultiSelect) ); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index 9e9f5e867b..b35b61b3c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.databinding.FragmentMediaPreviewV2Binding import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mediapreview.caption.ExpandingCaptionView import org.thoughtcrime.securesms.mediapreview.mediarail.CenterDecoration import org.thoughtcrime.securesms.mediapreview.mediarail.MediaRailAdapter @@ -72,6 +73,7 @@ import org.thoughtcrime.securesms.util.Debouncer import org.thoughtcrime.securesms.util.FullscreenHelper import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MessageConstraintsUtil +import org.thoughtcrime.securesms.util.OffloadedMediaDialogUtil import org.thoughtcrime.securesms.util.SaveAttachmentUtil import org.thoughtcrime.securesms.util.SpanUtil import org.thoughtcrime.securesms.util.ViewUtil @@ -584,14 +586,20 @@ class MediaPreviewV2Fragment : } private fun saveToDisk(mediaItem: MediaTable.MediaRecord) { - val uri = mediaItem.attachment?.uri - val contentType = mediaItem.attachment?.contentType + val attachment = mediaItem.attachment + if (attachment != null && !attachment.hasData && SignalStore.backup.optimizeStorage) { + OffloadedMediaDialogUtil.showAllOffloaded(requireContext()) + return + } + + val uri = attachment?.uri + val contentType = attachment?.contentType if (uri == null || contentType == null) { Log.w(TAG, "Unable to save attachment with null URI or contentType.") return } - val attachment = SaveAttachmentUtil.SaveAttachment( + val saveAttachment = SaveAttachmentUtil.SaveAttachment( uri = uri, contentType = contentType, date = if (mediaItem.date > 0) mediaItem.date else System.currentTimeMillis(), @@ -599,7 +607,7 @@ class MediaPreviewV2Fragment : ) lifecycleScope.launch { - AttachmentSaver(this@MediaPreviewV2Fragment).saveAttachments(setOf(attachment)) + AttachmentSaver(this@MediaPreviewV2Fragment).saveAttachments(setOf(saveAttachment)) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/OffloadedMediaDialogUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/OffloadedMediaDialogUtil.kt new file mode 100644 index 0000000000..34fb82f597 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/OffloadedMediaDialogUtil.kt @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.util + +import android.content.Context +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity + +object OffloadedMediaDialogUtil { + + /** + * Show dialog when all selected media has been offloaded. No save is attempted. + */ + @JvmStatic + fun showAllOffloaded(context: Context) { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.AttachmentSaver__cant_save_media) + .setMessage(R.string.AttachmentSaver__media_offloaded_message) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(R.string.AttachmentSaver__view_storage_settings) { _, _ -> context.startActivity(AppSettingsActivity.manageStorage(context)) } + .show() + } + + /** + * Show dialog when some selected media has been offloaded. OK proceeds with saving the non-offloaded items. + */ + @JvmStatic + fun showPartiallyOffloaded(context: Context, onProceed: Runnable) { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.AttachmentSaver__cant_save_all_items) + .setMessage(R.string.AttachmentSaver__some_media_offloaded_message) + .setCancelable(true) + .setPositiveButton(android.R.string.ok) { _, _ -> onProceed.run() } + .setNegativeButton(R.string.AttachmentSaver__view_storage_settings) { _, _ -> context.startActivity(AppSettingsActivity.manageStorage(context)) } + .show() + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6d08f6659..0bc053b2f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3395,6 +3395,17 @@ Unable to save to external storage without permissions + + Can\'t save media + + This media has been offloaded from your device because \"Optimize on-device storage\" is turned on. You can download items one-by-one, or turn off \"Optimize on-device storage\" to download all media to your device. + + Can\'t save all items + + Some media you\'ve selected has been offloaded from your device because \"Optimize on-device storage\" is turned on. You can save items one-by-one, or turn off \"Optimize on-device storage\" to download all media to your device. + + View storage settings + Search