Show warning dialog when attempting to save optimized media.

This commit is contained in:
Cody Henthorne
2026-03-09 14:00:37 -04:00
committed by jeffrey-signal
parent c554c0d456
commit 6a3d1634b9
4 changed files with 87 additions and 5 deletions

View File

@@ -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<MediaTable.MediaRecord> 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<MediaTable.MediaRecord> 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)
);

View File

@@ -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))
}
}

View File

@@ -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()
}
}

View File

@@ -3395,6 +3395,17 @@
<!-- Message explaining that it is not possible to save media to the device storage after the user denied the system save to storage permission. -->
<string name="AttachmentSaver__unable_to_write_to_external_storage_without_permission">Unable to save to external storage without permissions</string>
<!-- Dialog title shown when attempting to save media that has been offloaded from the device by the storage optimization feature. -->
<string name="AttachmentSaver__cant_save_media">Can\'t save media</string>
<!-- Dialog message explaining that media cannot be saved because it has been offloaded from the device by the storage optimization feature. -->
<string name="AttachmentSaver__media_offloaded_message">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.</string>
<!-- Dialog title shown when attempting to save multiple media items, some of which have been offloaded from the device by the storage optimization feature. -->
<string name="AttachmentSaver__cant_save_all_items">Can\'t save all items</string>
<!-- Dialog message explaining that some selected media cannot be saved because it has been offloaded from the device by the storage optimization feature. -->
<string name="AttachmentSaver__some_media_offloaded_message">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.</string>
<!-- Button label in the offloaded media dialog that navigates to the storage settings screen. -->
<string name="AttachmentSaver__view_storage_settings">View storage settings</string>
<!-- SearchToolbar -->
<string name="SearchToolbar_search">Search</string>
<!-- Hint when searching filtered chat content -->