Use AttachmentSaver to save image editor files to device storage.

This commit is contained in:
Jeffrey Starke
2025-03-24 10:13:39 -04:00
committed by Cody Henthorne
parent b1ff5dc5ef
commit f0bb74a187
2 changed files with 35 additions and 35 deletions

View File

@@ -11,6 +11,8 @@ import android.widget.CheckBox
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.core.Completable
import kotlinx.coroutines.rx3.rxCompletable
import kotlinx.coroutines.withContext
import org.signal.core.ui.view.AlertDialogResult
import org.signal.core.ui.view.awaitResult
@@ -52,6 +54,8 @@ class AttachmentSaver(private val host: Host) {
saveAttachments(attachments)
}
fun saveAttachmentsRx(attachments: Set<SaveAttachment>): Completable = rxCompletable { saveAttachments(attachments) }
suspend fun saveAttachments(attachments: Set<SaveAttachment>) {
if (checkIsSaveWarningAccepted(attachmentCount = attachments.size) == SaveToStorageWarningResult.ACCEPTED) {
if (checkCanWriteToMediaStore() == RequestPermissionResult.GRANTED) {

View File

@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.scribbles;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
@@ -16,7 +15,6 @@ import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.ColorInt;
@@ -34,7 +32,7 @@ import com.bumptech.glide.request.target.Target;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.FontUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.signal.imageeditor.core.Bounds;
@@ -50,6 +48,7 @@ import org.signal.imageeditor.core.renderers.MultiLineTextRenderer;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.ResizeAnimation;
import org.thoughtcrime.securesms.attachments.AttachmentSaver;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.fonts.FontTypefaceProvider;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@@ -66,8 +65,7 @@ import org.thoughtcrime.securesms.scribbles.stickers.FeatureSticker;
import org.thoughtcrime.securesms.scribbles.stickers.TappableRenderer;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThrottledDebouncer;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -78,6 +76,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static android.app.Activity.RESULT_OK;
public final class ImageEditorFragment extends Fragment implements ImageEditorHudV2.EventListener,
@@ -89,7 +90,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
public static final boolean CAN_RENDER_EMOJI = FontUtil.canRenderEmojiAtFontSize(1024);
private static final float PORTRAIT_ASPECT_RATIO = 9 / 16f;
private static final float PORTRAIT_ASPECT_RATIO = 9 / 16f;
private static final String KEY_IMAGE_URI = "image_uri";
private static final String KEY_MODE = "mode";
@@ -99,6 +100,9 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private static final int DRAW_HUD_PROTECTION = ViewUtil.dpToPx(72);
private static final int CROP_HUD_PROTECTION = ViewUtil.dpToPx(144);
private static final int CONTROLS_PROTECTION = ViewUtil.dpToPx(74);
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
private EditorModel restoredModel;
private Pair<Uri, FaceDetectionResult> cachedFaceDetection;
@@ -224,6 +228,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
lifecycleDisposable.bindTo(getViewLifecycleOwner());
controller.restoreState();
Mode mode = Mode.getByCode(requireArguments().getString(KEY_MODE));
@@ -245,7 +250,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorView.addTextInputFilter(new RemoveEmojiTextFilter());
}
int width = getResources().getDisplayMetrics().widthPixels;
int width = getResources().getDisplayMetrics().widthPixels;
int height = (int) ((16 / 9f) * width) - CONTROLS_PROTECTION;
imageEditorView.setMinimumHeight(height);
imageEditorView.requestLayout();
@@ -654,20 +659,13 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
@Override
public void onSave() {
SaveAttachmentTask.showWarningDialogIfNecessary(requireContext(), 1, () -> {
if (StorageUtil.canWriteToMediaStore()) {
performSaveToDisk();
return;
}
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.withPermanentDenialDialog(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(requireContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
.onAllGranted(this::performSaveToDisk)
.execute();
});
lifecycleDisposable.add(
Single.fromCallable(this::renderToSingleUseBlob)
.subscribeOn(Schedulers.computation())
.map(uri -> new SaveAttachmentUtil.SaveAttachment(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), null))
.flatMapCompletable(attachment -> new AttachmentSaver(this).saveAttachmentsRx(Collections.singleton(attachment)))
.subscribe()
);
}
@Override
@@ -754,10 +752,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
resizeAnimation.cancel();
}
int maxHeight = getHeightForOrientation(orientation);
float aspectRatio = getAspectRatioForOrientation(orientation);
int targetWidth = getWidthForOrientation(orientation);
int targetHeight = (int) ((1 / aspectRatio) * targetWidth) - CONTROLS_PROTECTION;
int maxHeight = getHeightForOrientation(orientation);
float aspectRatio = getAspectRatioForOrientation(orientation);
int targetWidth = getWidthForOrientation(orientation);
int targetHeight = (int) ((1 / aspectRatio) * targetWidth) - CONTROLS_PROTECTION;
if (targetHeight > maxHeight) {
targetHeight = maxHeight;
@@ -798,14 +796,6 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
return mode != ImageEditorHudV2.Mode.NONE;
}
private void performSaveToDisk() {
SimpleTask.run(this::renderToSingleUseBlob, uri -> {
SaveAttachmentTask saveTask = new SaveAttachmentTask(requireContext());
SaveAttachmentTask.Attachment attachment = new SaveAttachmentTask.Attachment(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), null);
saveTask.executeOnExecutor(SignalExecutors.BOUNDED, attachment);
});
}
@SuppressLint("WrongThread")
@WorkerThread
public @NonNull Uri renderToSingleUseBlob() {
@@ -967,7 +957,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorHud.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
}
}
editorElement.animatePartialFadeOut(imageEditorView::invalidate);
});
} else {
@@ -998,7 +988,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
if (imageEditorHud.getMode() != ImageEditorHudV2.Mode.TEXT) {
imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_TEXT);
}
} else if (editorElement != null && editorElement.getRenderer() instanceof UriGlideRenderer){
} else if (editorElement != null && editorElement.getRenderer() instanceof UriGlideRenderer) {
editorElement.animatePartialFadeIn(imageEditorView::invalidate);
imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_STICKER);
}
@@ -1076,6 +1066,12 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
};
@SuppressWarnings("deprecation")
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
public interface Controller {
void onTouchEventsNeeded(boolean needed);