Add option to hide save storage warning.

This commit is contained in:
Michelle Tang
2025-01-23 14:17:58 -05:00
committed by GitHub
parent 83af313305
commit f128df7d95
9 changed files with 84 additions and 36 deletions

View File

@@ -2381,7 +2381,7 @@ class ConversationFragment :
val attachments = SaveAttachmentUtil.getAttachmentsForRecord(record) val attachments = SaveAttachmentUtil.getAttachmentsForRecord(record)
SaveAttachmentUtil.showWarningDialog(requireContext(), attachments.size) { _, _ -> SaveAttachmentUtil.showWarningDialogIfNecessary(requireContext()) {
if (StorageUtil.canWriteToMediaStore()) { if (StorageUtil.canWriteToMediaStore()) {
performAttachmentSave(attachments) performAttachmentSave(attachments)
} else { } else {

View File

@@ -29,8 +29,9 @@ public class UiHintValues extends SignalStoreValues {
private static final String LAST_SUPPORT_VERSION_SEEN = "uihints.last_support_version_seen"; private static final String LAST_SUPPORT_VERSION_SEEN = "uihints.last_support_version_seen";
private static final String HAS_EVER_ENABLED_REMOTE_BACKUPS = "uihints.has_ever_enabled_remote_backups"; private static final String HAS_EVER_ENABLED_REMOTE_BACKUPS = "uihints.has_ever_enabled_remote_backups";
private static final String HAS_SEEN_CHAT_FOLDERS_EDUCATION_SHEET = "uihints.has_seen_chat_folders_education_sheet"; private static final String HAS_SEEN_CHAT_FOLDERS_EDUCATION_SHEET = "uihints.has_seen_chat_folders_education_sheet";
private static final String HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET = "uihints.has_seen_link_device_qr_education_sheet"; private static final String HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET = "uihints.has_seen_link_device_qr_education_sheet";
private static final String HAS_SEEN_LINK_DEVICE_AUTH_SHEET = "uihints.has_seen_link_device_auth_sheet"; private static final String HAS_SEEN_LINK_DEVICE_AUTH_SHEET = "uihints.has_seen_link_device_auth_sheet";
private static final String HAS_DISMISSED_SAVE_STORAGE_WARNING = "uihints.has_dismissed_save_storage_warning";
UiHintValues(@NonNull KeyValueStore store) { UiHintValues(@NonNull KeyValueStore store) {
super(store); super(store);
@@ -236,4 +237,12 @@ public class UiHintValues extends SignalStoreValues {
public boolean hasSeenLinkDeviceAuthSheet() { public boolean hasSeenLinkDeviceAuthSheet() {
return getBoolean(HAS_SEEN_LINK_DEVICE_AUTH_SHEET, false); return getBoolean(HAS_SEEN_LINK_DEVICE_AUTH_SHEET, false);
} }
public boolean hasDismissedSaveStorageWarning() {
return getBoolean(HAS_DISMISSED_SAVE_STORAGE_WARNING, false);
}
public void markDismissedSaveStorageWarning() {
putBoolean(HAS_DISMISSED_SAVE_STORAGE_WARNING, true);
}
} }

View File

@@ -16,11 +16,9 @@ import org.thoughtcrime.securesms.database.MediaTable;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob; import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StorageUtil; import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
@@ -46,13 +44,13 @@ final class MediaActions {
return; return;
} }
SaveAttachmentTask.showWarningDialog(context, (dialogInterface, which) -> Permissions.with(fragment) SaveAttachmentTask.showWarningDialogIfNecessary(context, () -> Permissions.with(fragment)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary() .ifNecessary()
.withPermanentDenialDialog(fragment.getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) .withPermanentDenialDialog(fragment.getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
.onAnyDenied(() -> Toast.makeText(context, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) .onAnyDenied(() -> Toast.makeText(context, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
.onAllGranted(() -> performSaveToDisk(context, mediaRecords, postExecute)) .onAllGranted(() -> performSaveToDisk(context, mediaRecords, postExecute))
.execute(), mediaRecords.size()); .execute());
} }
static void handleDeleteMedia(@NonNull Context context, static void handleDeleteMedia(@NonNull Context context,

View File

@@ -4,7 +4,6 @@ import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
@@ -558,10 +557,10 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
} }
private fun saveToDisk(mediaItem: MediaTable.MediaRecord) { private fun saveToDisk(mediaItem: MediaTable.MediaRecord) {
SaveAttachmentTask.showWarningDialog(requireContext()) { _: DialogInterface?, _: Int -> SaveAttachmentTask.showWarningDialogIfNecessary(requireContext()) {
if (StorageUtil.canWriteToMediaStore()) { if (StorageUtil.canWriteToMediaStore()) {
performSaveToDisk(mediaItem) performSaveToDisk(mediaItem)
return@showWarningDialog return@showWarningDialogIfNecessary
} }
Permissions.with(this) Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE) .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)

View File

@@ -653,7 +653,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override @Override
public void onSave() { public void onSave() {
SaveAttachmentTask.showWarningDialog(requireContext(), (dialogInterface, i) -> { SaveAttachmentTask.showWarningDialogIfNecessary(requireContext(), () -> {
if (StorageUtil.canWriteToMediaStore()) { if (StorageUtil.canWriteToMediaStore()) {
performSaveToDisk(); performSaveToDisk();
return; return;

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.util;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.database.Cursor; import android.database.Cursor;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.net.Uri; import android.net.Uri;
@@ -11,6 +10,7 @@ import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.CheckBox;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -24,6 +24,7 @@ import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
@@ -434,20 +435,25 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
} }
} }
public static void showWarningDialog(Context context, OnClickListener onAcceptListener) { public static void showWarningDialogIfNecessary(Context context, Runnable onSave) {
showWarningDialog(context, onAcceptListener, 1); if (SignalStore.uiHints().hasDismissedSaveStorageWarning()) {
} onSave.run();
} else {
public static void showWarningDialog(Context context, OnClickListener onAcceptListener, int count) { new MaterialAlertDialogBuilder(context)
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context); .setView(R.layout.dialog_save_attachment)
builder.setTitle(R.string.ConversationFragment_save_to_sd_card); .setTitle(R.string.ConversationFragment__save_to_phone)
builder.setIcon(R.drawable.symbol_error_triangle_fill_24); .setCancelable(true)
builder.setCancelable(true); .setMessage(R.string.ConversationFragment__this_media_will_be_saved)
builder.setMessage(context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_media_to_storage_warning, .setPositiveButton(R.string.save, ((dialog, i) -> {
count, count)); CheckBox checkbox = ((AlertDialog) dialog).findViewById(R.id.checkbox);
builder.setPositiveButton(R.string.yes, onAcceptListener); if (checkbox.isChecked()) {
builder.setNegativeButton(R.string.no, null); SignalStore.uiHints().markDismissedSaveStorageWarning();
builder.show(); }
onSave.run();
}))
.setNegativeButton(android.R.string.cancel, null)
.show();
}
} }
} }

View File

@@ -9,7 +9,6 @@ import android.annotation.SuppressLint
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.DialogInterface.OnClickListener
import android.database.Cursor import android.database.Cursor
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri import android.net.Uri
@@ -17,8 +16,10 @@ import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.CheckBox
import android.widget.Toast import android.widget.Toast
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.appcompat.app.AlertDialog
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
@@ -28,6 +29,7 @@ import org.signal.core.util.orNull
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -52,15 +54,25 @@ object SaveAttachmentUtil {
private val TAG = Log.tag(SaveAttachmentUtil::class.java) private val TAG = Log.tag(SaveAttachmentUtil::class.java)
fun showWarningDialog(context: Context, count: Int, onAcceptListener: OnClickListener) { fun showWarningDialogIfNecessary(context: Context, onSave: () -> Unit) {
MaterialAlertDialogBuilder(context) if (SignalStore.uiHints.hasDismissedSaveStorageWarning()) {
.setTitle(R.string.ConversationFragment_save_to_sd_card) onSave()
.setIcon(R.drawable.symbol_error_triangle_fill_24) } else {
.setCancelable(true) MaterialAlertDialogBuilder(context)
.setMessage(context.resources.getQuantityString(R.plurals.ConversationFragment_saving_n_media_to_storage_warning, count, count)) .setView(R.layout.dialog_save_attachment)
.setPositiveButton(R.string.yes, onAcceptListener) .setTitle(R.string.ConversationFragment__save_to_phone)
.setNegativeButton(R.string.no, null) .setCancelable(true)
.show() .setMessage(R.string.ConversationFragment__this_media_will_be_saved)
.setPositiveButton(R.string.save) { dialog, _ ->
val checkbox = (dialog as AlertDialog).findViewById<CheckBox>(R.id.checkbox)!!
if (checkbox.isChecked) {
SignalStore.uiHints.markDismissedSaveStorageWarning()
}
onSave()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
} }
fun getAttachmentsForRecord(record: MmsMessageRecord): Set<SaveAttachment> { fun getAttachmentsForRecord(record: MmsMessageRecord): Set<SaveAttachment> {

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ConversationFragment_dont_show_again"
style="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp" />
</FrameLayout>

View File

@@ -586,6 +586,12 @@
<item quantity="one">Saving attachment to storage…</item> <item quantity="one">Saving attachment to storage…</item>
<item quantity="other">Saving %1$d attachments to storage…</item> <item quantity="other">Saving %1$d attachments to storage…</item>
</plurals> </plurals>
<!-- Dialog title asking to save media to your phone's storage -->
<string name="ConversationFragment__save_to_phone">Save to phone?</string>
<!-- Dialog message explaining that media will be saved to your phone and can potentially be accessed by other phones. -->
<string name="ConversationFragment__this_media_will_be_saved">This media will be saved to your phone\'s storage. Other apps may be able to access it depending on your phone\'s permissions.</string>
<!-- Checkbox shown in dialog to not show the dialog again in future cases -->
<string name="ConversationFragment_dont_show_again">Don\'t show again</string>
<string name="ConversationFragment_pending">Pending…</string> <string name="ConversationFragment_pending">Pending…</string>
<!-- Describes how the message was sent when looking at a message detail. Previously, messages could be through other means like SMS --> <!-- Describes how the message was sent when looking at a message detail. Previously, messages could be through other means like SMS -->
<string name="ConversationFragment_push">Data (Signal)</string> <string name="ConversationFragment_push">Data (Signal)</string>