diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java
index d8f523c57f..95108d9092 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/ImageEditorView.java
@@ -4,19 +4,13 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
-import android.graphics.Rect;
import android.graphics.RectF;
-import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
-import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.inputmethod.EditorInfo;
import android.widget.FrameLayout;
-import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
@@ -28,10 +22,6 @@ import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.thoughtcrime.securesms.imageeditor.model.ThumbRenderer;
import org.thoughtcrime.securesms.imageeditor.renderers.BezierDrawingRenderer;
import org.thoughtcrime.securesms.imageeditor.renderers.MultiLineTextRenderer;
-import org.thoughtcrime.securesms.util.ViewUtil;
-
-import java.util.Arrays;
-import java.util.Collections;
/**
* ImageEditorView
@@ -78,7 +68,7 @@ public final class ImageEditorView extends FrameLayout {
private UndoRedoStackListener undoRedoStackListener;
@Nullable
- private DrawListener drawListener;
+ private DragListener dragListener;
private final Matrix viewMatrix = new Matrix();
private final RectF viewPort = Bounds.newFullBounds();
@@ -237,7 +227,10 @@ public final class ImageEditorView extends FrameLayout {
moreThanOnePointerUsedInSession = false;
model.pushUndoPoint();
editSession = startEdit(inverse, point, selected);
- notifyStartIfInDraw();
+
+ if (editSession != null) {
+ notifyDragStart(editSession.getSelected());
+ }
if (tapListener != null && allowTaps()) {
if (editSession != null) {
@@ -265,6 +258,7 @@ public final class ImageEditorView extends FrameLayout {
}
model.moving(editSession.getSelected());
invalidate();
+ notifyDragMove(editSession.getSelected(), event);
return true;
}
break;
@@ -308,7 +302,7 @@ public final class ImageEditorView extends FrameLayout {
if (editSession != null) {
editSession.commit();
dragDropRelease(false);
- notifyEndIfInDraw();
+ notifyDragEnd(editSession.getSelected());
editSession = null;
model.postEdit(moreThanOnePointerUsedInSession);
@@ -324,27 +318,31 @@ public final class ImageEditorView extends FrameLayout {
return super.onTouchEvent(event);
}
- private void notifyStartIfInDraw() {
- if (mode == Mode.Draw || mode == Mode.Blur) {
- if (drawListener != null) {
- drawListener.onDrawStarted();
- }
+ private void notifyDragStart(@Nullable EditorElement editorElement) {
+ if (dragListener != null) {
+ dragListener.onDragStarted(editorElement);
}
}
- private void notifyEndIfInDraw() {
- if (mode == Mode.Draw || mode == Mode.Blur) {
- if (drawListener != null) {
- drawListener.onDrawEnded();
- }
+ private void notifyDragMove(@Nullable EditorElement editorElement, @NonNull MotionEvent event) {
+ if (dragListener != null) {
+ dragListener.onDragMoved(editorElement, event);
+ }
+ }
+
+ private void notifyDragEnd(@Nullable EditorElement editorElement) {
+ if (dragListener != null) {
+ dragListener.onDragEnded(editorElement);
}
}
private @Nullable EditSession startEdit(@NonNull Matrix inverse, @NonNull PointF point, @Nullable EditorElement selected) {
- if (mode == Mode.Draw || mode == Mode.Blur) {
+ EditSession editSession = startAMoveAndResizeSession(inverse, point, selected);
+ if (editSession == null && (mode == Mode.Draw || mode == Mode.Blur)) {
return startADrawingSession(point);
} else {
- return startAMoveAndResizeSession(inverse, point, selected);
+ setMode(Mode.MoveAndResize);
+ return editSession;
}
}
@@ -380,6 +378,11 @@ public final class ImageEditorView extends FrameLayout {
return ElementDragEditSession.startDrag(selected, inverse, point);
}
+ @NonNull
+ public Mode getMode() {
+ return mode;
+ }
+
public void setMode(@NonNull Mode mode) {
this.mode = mode;
}
@@ -430,8 +433,8 @@ public final class ImageEditorView extends FrameLayout {
this.undoRedoStackListener = undoRedoStackListener;
}
- public void setDrawListener(@Nullable DrawListener drawListener) {
- this.drawListener = drawListener;
+ public void setDragListener(@Nullable DragListener dragListener) {
+ this.dragListener = dragListener;
}
public void setTapListener(TapListener tapListener) {
@@ -504,9 +507,10 @@ public final class ImageEditorView extends FrameLayout {
void onSizeChanged(int newWidth, int newHeight);
}
- public interface DrawListener {
- void onDrawStarted();
- void onDrawEnded();
+ public interface DragListener {
+ void onDragStarted(@Nullable EditorElement editorElement);
+ void onDragMoved(@Nullable EditorElement editorElement, @NonNull MotionEvent event);
+ void onDragEnded(@Nullable EditorElement editorElement);
}
public interface TapListener {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/SelectableRenderer.kt b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/SelectableRenderer.kt
new file mode 100644
index 0000000000..c12047cf46
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/SelectableRenderer.kt
@@ -0,0 +1,8 @@
+package org.thoughtcrime.securesms.imageeditor
+
+/**
+ * Renderer that can maintain a "selected" state
+ */
+interface SelectableRenderer : Renderer {
+ fun onSelected(selected: Boolean)
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
index 26d10d8556..c620243ba2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/model/EditorElement.java
@@ -235,6 +235,14 @@ public final class EditorElement implements Parcelable {
alphaAnimation = AlphaAnimation.animate(0, 1, invalidate);
}
+ public void animatePartialFadeOut(@Nullable Runnable invalidate) {
+ alphaAnimation = AlphaAnimation.animate(alphaAnimation.getValue(), 0.5f, invalidate);
+ }
+
+ public void animatePartialFadeIn(@Nullable Runnable invalidate) {
+ alphaAnimation = AlphaAnimation.animate(alphaAnimation.getValue(), 1f, invalidate);
+ }
+
@Nullable EditorElement parentOf(@NonNull EditorElement element) {
if (children.contains(element)) {
return this;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java
index 0afbece99e..b8b913bcc8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/InverseFillRenderer.java
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.imageeditor.renderers;
import android.graphics.Path;
+import android.graphics.RectF;
import android.os.Parcel;
import androidx.annotation.ColorInt;
@@ -20,14 +21,19 @@ public final class InverseFillRenderer implements Renderer {
private final int color;
- private final Path path = new Path();
+ private final RectF dst = new RectF();
+ private final Path path = new Path();
@Override
public void render(@NonNull RendererContext rendererContext) {
rendererContext.canvas.save();
+ rendererContext.mapRect(dst, Bounds.FULL_BOUNDS);
+ rendererContext.canvasMatrix.setToIdentity();
+
path.reset();
- path.addRoundRect(Bounds.FULL_BOUNDS, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18), Path.Direction.CW);
+ path.addRoundRect(dst, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18), Path.Direction.CW);
+
rendererContext.canvas.clipPath(path);
rendererContext.canvas.drawColor(color);
rendererContext.canvas.restore();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/MultiLineTextRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/MultiLineTextRenderer.java
index 91d878bfda..efcd8a2114 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/MultiLineTextRenderer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/MultiLineTextRenderer.java
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.imageeditor.renderers;
import android.animation.ValueAnimator;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
@@ -16,6 +17,8 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.imageeditor.Bounds;
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
import org.thoughtcrime.securesms.imageeditor.RendererContext;
+import org.thoughtcrime.securesms.imageeditor.SelectableRenderer;
+import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.ArrayList;
import java.util.List;
@@ -27,7 +30,7 @@ import static java.util.Collections.emptyList;
*
* Scales down the text size of long lines to fit inside the {@link Bounds} width.
*/
-public final class MultiLineTextRenderer extends InvalidateableRenderer implements ColorableRenderer {
+public final class MultiLineTextRenderer extends InvalidateableRenderer implements ColorableRenderer, SelectableRenderer {
@NonNull
private String text = "";
@@ -43,6 +46,7 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
private int selStart;
private int selEnd;
private boolean hasFocus;
+ private boolean selected;
private List lines = emptyList();
@@ -51,6 +55,9 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
private final Matrix recommendedEditorMatrix = new Matrix();
+ private final SelectedElementGuideRenderer selectedElementGuideRenderer = new SelectedElementGuideRenderer();
+ private final RectF textBounds = new RectF();
+
public MultiLineTextRenderer(@Nullable String text, @ColorInt int color) {
setColor(color);
float regularTextSize = paint.getTextSize();
@@ -67,8 +74,17 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
public void render(@NonNull RendererContext rendererContext) {
super.render(rendererContext);
+ float height = 0;
+ float width = 0;
for (Line line : lines) {
line.render(rendererContext);
+ height += line.heightInBounds - line.ascentInBounds + line.descentInBounds;
+ width = Math.max(line.textBounds.width(), width);
+ }
+
+ if (selected && rendererContext.isEditing()) {
+ textBounds.set(-width, -height / 2f, width, 0f);
+ selectedElementGuideRenderer.render(rendererContext, textBounds);
}
}
@@ -314,6 +330,13 @@ public final class MultiLineTextRenderer extends InvalidateableRenderer implemen
}
}
+ @Override
+ public void onSelected(boolean selected) {
+ if (this.selected != selected) {
+ this.selected = selected;
+ }
+ }
+
@Override
public boolean hitTest(float x, float y) {
for (Line line : lines) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/SelectedElementGuideRenderer.kt b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/SelectedElementGuideRenderer.kt
new file mode 100644
index 0000000000..60c600e762
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/imageeditor/renderers/SelectedElementGuideRenderer.kt
@@ -0,0 +1,99 @@
+package org.thoughtcrime.securesms.imageeditor.renderers
+
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.RectF
+import org.thoughtcrime.securesms.imageeditor.Bounds
+import org.thoughtcrime.securesms.imageeditor.RendererContext
+import org.thoughtcrime.securesms.util.ViewUtil
+
+class SelectedElementGuideRenderer {
+
+ companion object {
+ private const val PADDING: Int = 10
+ }
+
+ private val allPointsOnScreen = FloatArray(8)
+ private val allPointsInLocalCords = floatArrayOf(
+ Bounds.LEFT, Bounds.TOP,
+ Bounds.RIGHT, Bounds.TOP,
+ Bounds.RIGHT, Bounds.BOTTOM,
+ Bounds.LEFT, Bounds.BOTTOM
+ )
+
+ private val circleRadius = ViewUtil.dpToPx(5).toFloat()
+
+ private val guidePaint = Paint().apply {
+ isAntiAlias = true
+ strokeWidth = ViewUtil.dpToPx(15).toFloat() / 10f
+ color = Color.WHITE
+ style = Paint.Style.STROKE
+ }
+
+ private val circlePaint = Paint().apply {
+ isAntiAlias = true
+ color = Color.WHITE
+ style = Paint.Style.FILL
+ }
+
+ private val path = Path()
+
+ /**
+ * Draw self to the context.
+ *
+ * @param rendererContext The context to draw to.
+ */
+ fun render(rendererContext: RendererContext) {
+ rendererContext.canvasMatrix.mapPoints(allPointsOnScreen, allPointsInLocalCords)
+ performRender(rendererContext)
+ }
+
+ fun render(rendererContext: RendererContext, contentBounds: RectF) {
+ rendererContext.canvasMatrix.mapPoints(
+ allPointsOnScreen,
+ floatArrayOf(
+ contentBounds.left - PADDING,
+ contentBounds.top - PADDING,
+ contentBounds.right + PADDING,
+ contentBounds.top - PADDING,
+ contentBounds.right + PADDING,
+ contentBounds.bottom + PADDING,
+ contentBounds.left - PADDING,
+ contentBounds.bottom + PADDING
+ )
+ )
+
+ performRender(rendererContext)
+ }
+
+ private fun performRender(rendererContext: RendererContext) {
+ rendererContext.save()
+
+ rendererContext.canvasMatrix.setToIdentity()
+
+ path.reset()
+ path.moveTo(allPointsOnScreen[0], allPointsOnScreen[1])
+ path.lineTo(allPointsOnScreen[2], allPointsOnScreen[3])
+ path.lineTo(allPointsOnScreen[4], allPointsOnScreen[5])
+ path.lineTo(allPointsOnScreen[6], allPointsOnScreen[7])
+ path.close()
+
+ rendererContext.canvas.drawPath(path, guidePaint)
+ // TODO: Implement scaling
+// rendererContext.canvas.drawCircle(
+// (allPointsOnScreen[6] + allPointsOnScreen[0]) / 2f,
+// (allPointsOnScreen[7] + allPointsOnScreen[1]) / 2f,
+// circleRadius,
+// circlePaint
+// )
+// rendererContext.canvas.drawCircle(
+// (allPointsOnScreen[4] + allPointsOnScreen[2]) / 2f,
+// (allPointsOnScreen[5] + allPointsOnScreen[3]) / 2f,
+// circleRadius,
+// circlePaint
+// )
+
+ rendererContext.restore()
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt
index 5f01cc93fc..cdaad5669f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt
@@ -11,6 +11,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.subjects.PublishSubject
import org.thoughtcrime.securesms.TransportOption
+import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
import org.thoughtcrime.securesms.mediasend.VideoEditorFragment
@@ -229,7 +230,7 @@ class MediaSelectionViewModel(
isViewOnceEnabled(),
destination.getRecipientId(),
if (selectedRecipientIds.isNotEmpty()) selectedRecipientIds else destination.getRecipientIdList(),
- emptyList(), // TODO [alex] -- mentions
+ MentionAnnotation.getMentionsFromAnnotations(store.state.message),
store.state.transportOption
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt
index d11de9c743..26c77331ab 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt
@@ -263,7 +263,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
animators.addAll(computeAddMessageAnimators(state))
animators.addAll(computeViewOnceButtonAnimators(state))
animators.addAll(computeAddMediaButtonsAnimators(state))
- animators.addAll(computeSendAndSaveButtonAnimators(state))
+ animators.addAll(computeSendButtonAnimators(state))
+ animators.addAll(computeSaveButtonAnimators(state))
animators.addAll(computeQualityButtonAnimators(state))
animators.addAll(computeCropAndRotateButtonAnimators(state))
animators.addAll(computeDrawToolButtonAnimators(state))
@@ -349,21 +350,35 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
}
}
- private fun computeSendAndSaveButtonAnimators(state: MediaSelectionState): List {
+ private fun computeSendButtonAnimators(state: MediaSelectionState): List {
val slideIn = listOf(
MediaReviewAnimatorController.getSlideInAnimator(sendButton),
- MediaReviewAnimatorController.getSlideInAnimator(saveButton)
)
return slideIn + if (state.isTouchEnabled) {
listOf(
MediaReviewAnimatorController.getFadeInAnimator(sendButton),
- MediaReviewAnimatorController.getFadeInAnimator(saveButton)
)
} else {
listOf(
MediaReviewAnimatorController.getFadeOutAnimator(sendButton),
+ )
+ }
+ }
+
+ private fun computeSaveButtonAnimators(state: MediaSelectionState): List {
+
+ val slideIn = listOf(
+ MediaReviewAnimatorController.getSlideInAnimator(saveButton)
+ )
+
+ return slideIn + if (state.isTouchEnabled && !MediaUtil.isVideo(state.focusedMedia?.mimeType)) {
+ listOf(
+ MediaReviewAnimatorController.getFadeInAnimator(saveButton)
+ )
+ } else {
+ listOf(
MediaReviewAnimatorController.getFadeOutAnimator(saveButton)
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java
index b37242038c..8df3bae2e5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java
@@ -12,6 +12,7 @@ import android.graphics.RectF;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
@@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.imageeditor.Bounds;
import org.thoughtcrime.securesms.imageeditor.ColorableRenderer;
import org.thoughtcrime.securesms.imageeditor.ImageEditorView;
import org.thoughtcrime.securesms.imageeditor.Renderer;
+import org.thoughtcrime.securesms.imageeditor.SelectableRenderer;
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
import org.thoughtcrime.securesms.imageeditor.renderers.FaceBlurRenderer;
@@ -54,6 +56,7 @@ import org.thoughtcrime.securesms.util.ParcelUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.thoughtcrime.securesms.util.ThrottledDebouncer;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
@@ -76,6 +79,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private static final int SELECT_STICKER_REQUEST_CODE = 124;
+ private static final int HUD_PROTECTION = ViewUtil.dpToPx(72);
+
private EditorModel restoredModel;
private Pair cachedFaceDetection;
@@ -84,6 +89,8 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
private int imageMaxHeight;
private int imageMaxWidth;
+ private final ThrottledDebouncer deleteFadeDebouncer = new ThrottledDebouncer(500);
+
public static class Data {
private final Bundle bundle;
@@ -196,12 +203,14 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorView = view.findViewById(R.id.image_editor_view);
int width = getResources().getDisplayMetrics().widthPixels;
- imageEditorView.setMinimumHeight((int) ((16 / 9f) * width));
+ int height = (int) ((16 / 9f) * width);
+ imageEditorView.setMinimumHeight(height);
imageEditorView.requestLayout();
+ imageEditorHud.setBottomOfImageEditorView(getResources().getDisplayMetrics().heightPixels - height);
imageEditorHud.setEventListener(this);
- imageEditorView.setDrawListener(drawListener);
+ imageEditorView.setDragListener(dragListener);
imageEditorView.setTapListener(selectionListener);
imageEditorView.setDrawingChangedListener(stillTouching -> onDrawingChanged(stillTouching, true));
imageEditorView.setUndoRedoStackListener(this::onUndoRedoAvailabilityChanged);
@@ -332,7 +341,9 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
public void onTextEntryDialogDismissed(boolean hasText) {
imageEditorView.doneTextEditing();
- if (!hasText) {
+ if (hasText) {
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_TEXT);
+ } else {
onUndo();
imageEditorHud.setMode(ImageEditorHudV2.Mode.DRAW);
}
@@ -347,7 +358,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
imageEditorView.getModel().addElementCentered(element, 1);
imageEditorView.invalidate();
- currentSelection = element;
+ setCurrentSelection(element);
startTextEntityEditing(element, true);
}
@@ -360,9 +371,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
if (uri != null) {
UriGlideRenderer renderer = new UriGlideRenderer(uri, true, imageMaxWidth, imageMaxHeight);
EditorElement element = new EditorElement(renderer, EditorModel.Z_STICKERS);
- imageEditorView.getModel().addElementCentered(element, 0.2f);
- currentSelection = element;
+ imageEditorView.getModel().addElementCentered(element, 0.4f);
+ setCurrentSelection(element);
hasMadeAnEditThisSession = true;
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_STICKER);
}
} else {
imageEditorHud.setMode(ImageEditorHudV2.Mode.DRAW);
@@ -389,6 +401,10 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
}
+ if (mode != ImageEditorHudV2.Mode.CROP) {
+ imageEditorView.getModel().doneCrop();
+ }
+
switch (mode) {
case CROP: {
imageEditorView.getModel().startCrop();
@@ -418,12 +434,14 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
break;
}
- case MOVE_DELETE:
+ case MOVE_STICKER:
+ break;
+
+ case MOVE_TEXT:
break;
case NONE: {
- imageEditorView.getModel().doneCrop();
- currentSelection = null;
+ setCurrentSelection(null);
hasMadeAnEditThisSession = false;
break;
}
@@ -604,6 +622,12 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
int targetWidth = requireView().getMeasuredWidth() - ViewUtil.dpToPx(32);
int targetHeight = (int) ((1 / aspectRatio) * targetWidth);
+ int maxHeight = getResources().getDisplayMetrics().heightPixels - HUD_PROTECTION;
+ if (targetHeight > maxHeight) {
+ targetHeight = maxHeight;
+ targetWidth = Math.round(targetHeight * aspectRatio);
+ }
+
if (targetWidth < requireView().getMeasuredWidth()) {
resizeAnimation = new ResizeAnimation(imageEditorView, targetWidth, targetHeight);
resizeAnimation.setDuration(250);
@@ -626,7 +650,7 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
private static boolean shouldScaleViewPort(@NonNull ImageEditorHudV2.Mode mode) {
- return mode != ImageEditorHudV2.Mode.NONE;
+ return mode != ImageEditorHudV2.Mode.NONE && mode != ImageEditorHudV2.Mode.CROP;
}
private void performSaveToDisk() {
@@ -655,10 +679,6 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
if (isUserEdit) {
hasMadeAnEditThisSession = true;
}
-
- if (!stillTouching && shouldExitModeOnChange(imageEditorHud.getMode())) {
- onPopEditorMode();
- }
}
private void onUndoRedoAvailabilityChanged(boolean undoAvailable, boolean redoAvailable) {
@@ -700,39 +720,43 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
private boolean shouldHandleOnBackPressed(ImageEditorHudV2.Mode mode) {
- return mode == ImageEditorHudV2.Mode.CROP ||
- mode == ImageEditorHudV2.Mode.DRAW ||
- mode == ImageEditorHudV2.Mode.HIGHLIGHT ||
- mode == ImageEditorHudV2.Mode.BLUR ||
- mode == ImageEditorHudV2.Mode.TEXT ||
- mode == ImageEditorHudV2.Mode.MOVE_DELETE ||
+ return mode == ImageEditorHudV2.Mode.CROP ||
+ mode == ImageEditorHudV2.Mode.DRAW ||
+ mode == ImageEditorHudV2.Mode.HIGHLIGHT ||
+ mode == ImageEditorHudV2.Mode.BLUR ||
+ mode == ImageEditorHudV2.Mode.TEXT ||
+ mode == ImageEditorHudV2.Mode.MOVE_STICKER ||
+ mode == ImageEditorHudV2.Mode.MOVE_TEXT ||
mode == ImageEditorHudV2.Mode.INSERT_STICKER;
}
- private boolean shouldExitModeOnChange(ImageEditorHudV2.Mode mode) {
- return mode == ImageEditorHudV2.Mode.MOVE_DELETE || mode == ImageEditorHudV2.Mode.INSERT_STICKER;
- }
-
private void onPopEditorMode() {
- currentSelection = null;
+ setCurrentSelection(null);
switch (imageEditorHud.getMode()) {
case NONE:
return;
case CROP:
+ onCancel();
+ break;
case DRAW:
case HIGHLIGHT:
case BLUR:
- onCancel();
+ if (Mode.getByCode(requireArguments().getString(KEY_MODE)) == Mode.NORMAL) {
+ onCancel();
+ } else {
+ controller.onTouchEventsNeeded(true);
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.CROP);
+ }
break;
case INSERT_STICKER:
+ case MOVE_STICKER:
+ case MOVE_TEXT:
+ case DELETE:
case TEXT:
controller.onTouchEventsNeeded(true);
imageEditorHud.setMode(ImageEditorHudV2.Mode.DRAW);
break;
- case MOVE_DELETE:
- onDone();
- break;
}
}
@@ -748,15 +772,54 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
};
- private final ImageEditorView.DrawListener drawListener = new ImageEditorView.DrawListener() {
+ private final ImageEditorView.DragListener dragListener = new ImageEditorView.DragListener() {
@Override
- public void onDrawStarted() {
- imageEditorHud.animate().alpha(0f);
+ public void onDragStarted(@Nullable EditorElement editorElement) {
+ if (imageEditorHud.getMode() == ImageEditorHudV2.Mode.CROP) {
+ return;
+ }
+
+ setCurrentSelection(editorElement);
+ if (imageEditorView.getMode() == ImageEditorView.Mode.MoveAndResize) {
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.DELETE);
+ } else {
+ imageEditorHud.animate().alpha(0f);
+ }
}
@Override
- public void onDrawEnded() {
+ public void onDragMoved(@Nullable EditorElement editorElement, @NonNull MotionEvent event) {
+ if (imageEditorHud.getMode() == ImageEditorHudV2.Mode.CROP || editorElement == null) {
+ return;
+ }
+
+ imageEditorHud.onMoved(event);
+ if (imageEditorHud.isInDeleteRect()) {
+ deleteFadeDebouncer.publish(() -> editorElement.animatePartialFadeOut(imageEditorView::invalidate));
+ } else {
+ deleteFadeDebouncer.publish(() -> editorElement.animatePartialFadeIn(imageEditorView::invalidate));
+ }
+ }
+
+ @Override
+ public void onDragEnded(@Nullable EditorElement editorElement) {
imageEditorHud.animate().alpha(1f);
+ if (imageEditorHud.getMode() == ImageEditorHudV2.Mode.CROP) {
+ return;
+ }
+
+ if (imageEditorHud.isInDeleteRect()) {
+ deleteFadeDebouncer.clear();
+ onDelete();
+ setCurrentSelection(null);
+ onPopEditorMode();
+ } else if (editorElement != null && editorElement.getRenderer() instanceof MultiLineTextRenderer){
+ editorElement.animatePartialFadeIn(imageEditorView::invalidate);
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_TEXT);
+ } else if (editorElement != null && editorElement.getRenderer() instanceof UriGlideRenderer){
+ editorElement.animatePartialFadeIn(imageEditorView::invalidate);
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_STICKER);
+ }
}
};
@@ -766,35 +829,26 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
public void onEntityDown(@Nullable EditorElement editorElement) {
if (editorElement != null) {
controller.onTouchEventsNeeded(true);
+ }
+ }
- boolean isMoveableElement = editorElement.getZOrder() == EditorModel.Z_STICKERS ||
- editorElement.getZOrder() == EditorModel.Z_TEXT;
-
- boolean notInsertSticker = imageEditorHud.getMode() != ImageEditorHudV2.Mode.INSERT_STICKER;
-
- if (isMoveableElement && notInsertSticker) {
- imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_DELETE);
+ @Override
+ public void onEntitySingleTap(@Nullable EditorElement editorElement) {
+ setCurrentSelection(editorElement);
+ if (currentSelection != null) {
+ if (editorElement.getRenderer() instanceof MultiLineTextRenderer) {
+ setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), imageEditorView.isTextEditing());
+ } else {
+ imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_STICKER);
}
} else {
onPopEditorMode();
}
}
- @Override
- public void onEntitySingleTap(@Nullable EditorElement editorElement) {
- currentSelection = editorElement;
- if (currentSelection != null) {
- if (editorElement.getRenderer() instanceof MultiLineTextRenderer) {
- setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), imageEditorView.isTextEditing());
- } else {
- imageEditorHud.setMode(ImageEditorHudV2.Mode.MOVE_DELETE);
- }
- }
- }
-
@Override
public void onEntityDoubleTap(@NonNull EditorElement editorElement) {
- currentSelection = editorElement;
+ setCurrentSelection(editorElement);
if (editorElement.getRenderer() instanceof MultiLineTextRenderer) {
setTextElement(editorElement, (ColorableRenderer) editorElement.getRenderer(), true);
}
@@ -813,6 +867,22 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
}
};
+ private void setCurrentSelection(@Nullable EditorElement currentSelection) {
+ setSelectionState(this.currentSelection, false);
+
+ this.currentSelection = currentSelection;
+
+ setSelectionState(this.currentSelection, true);
+
+ imageEditorView.invalidate();
+ }
+
+ private void setSelectionState(@Nullable EditorElement editorElement, boolean selected) {
+ if (editorElement != null && editorElement.getRenderer() instanceof SelectableRenderer) {
+ ((SelectableRenderer) editorElement.getRenderer()).onSelected(selected);
+ }
+ }
+
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt
index 94818a8992..ed3f2d626f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt
@@ -1,23 +1,25 @@
package org.thoughtcrime.securesms.scribbles
import android.animation.Animator
+import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
+import android.graphics.Rect
import android.util.AttributeSet
+import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
-import android.view.animation.Animation
-import android.view.animation.AnimationUtils
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.SeekBar
import androidx.annotation.IntRange
import androidx.appcompat.widget.AppCompatSeekBar
+import androidx.constraintlayout.widget.Guideline
+import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
-import androidx.core.view.isVisible
import com.airbnb.lottie.SimpleColorFilter
import com.google.android.material.switchmaterial.SwitchMaterial
import org.thoughtcrime.securesms.R
@@ -28,7 +30,6 @@ import org.thoughtcrime.securesms.scribbles.HSVColorSlider.setUpForColor
import org.thoughtcrime.securesms.util.Debouncer
import org.thoughtcrime.securesms.util.ThrottledDebouncer
import org.thoughtcrime.securesms.util.ViewUtil
-import org.thoughtcrime.securesms.util.setListeners
import org.thoughtcrime.securesms.util.visible
class ImageEditorHudV2 @JvmOverloads constructor(
@@ -64,6 +65,9 @@ class ImageEditorHudV2 @JvmOverloads constructor(
private val blurToast: View = findViewById(R.id.image_editor_hud_blur_toast)
private val blurHelpText: View = findViewById(R.id.image_editor_hud_blur_help_text)
private val colorIndicator: ImageView = findViewById(R.id.image_editor_hud_color_indicator)
+ private val delete: FrameLayout = findViewById(R.id.image_editor_hud_delete)
+ private val deleteBackground: View = findViewById(R.id.image_editor_hud_delete_bg)
+ private val bottomGuideline: Guideline = findViewById(R.id.image_editor_bottom_guide)
private val selectableSet: Set = setOf(drawButton, textButton, stickerButton, blurButton)
@@ -73,14 +77,24 @@ class ImageEditorHudV2 @JvmOverloads constructor(
private val drawButtonRow: Set = setOf(cancelButton, doneButton, drawButton, textButton, stickerButton, blurButton)
private val cropButtonRow: Set = setOf(cancelButton, doneButton, cropRotateButton, cropFlipButton, cropAspectLockButton)
- private val viewsToSlide: Set = drawButtonRow + cropButtonRow
+ private val allModeTools: Set = drawTools + blurTools + drawButtonRow + cropButtonRow + delete
- private val modeChangeAnimationThrottler = ThrottledDebouncer(ANIMATION_DURATION)
- private val undoToolsAnimationThrottler = ThrottledDebouncer(ANIMATION_DURATION)
+ private val viewsToSlide: Set = drawButtonRow + cropButtonRow
private val toastDebouncer = Debouncer(3000)
private var colorIndicatorAlphaAnimator: Animator? = null
+ private val deleteDebouncer = ThrottledDebouncer(500)
+
+ private val rect = Rect()
+
+ private var modeAnimatorSet: AnimatorSet? = null
+ private var undoAnimatorSet: AnimatorSet? = null
+ private var deleteSizeAnimatorSet: AnimatorSet? = null
+
+ var isInDeleteRect: Boolean = false
+ private set
+
init {
initializeViews()
setMode(currentMode)
@@ -105,7 +119,7 @@ class ImageEditorHudV2 @JvmOverloads constructor(
doneButton.setOnClickListener {
if (isAvatarEdit && currentMode == Mode.CROP) {
- setMode(Mode.NONE)
+ setMode(Mode.DRAW)
} else {
listener?.onDone()
}
@@ -148,6 +162,10 @@ class ImageEditorHudV2 @JvmOverloads constructor(
setupWidthSeekBar()
}
+ fun setBottomOfImageEditorView(bottom: Int) {
+ bottomGuideline.setGuidelineEnd(bottom)
+ }
+
@SuppressLint("ClickableViewAccessibility")
private fun setupWidthSeekBar() {
widthSeekBar.thumb = HSVColorSlider.createThumbDrawable(Color.WHITE)
@@ -251,10 +269,49 @@ class ImageEditorHudV2 @JvmOverloads constructor(
fun getMode(): Mode = currentMode
+ fun onMoved(motionEvent: MotionEvent) {
+ delete.getHitRect(rect)
+ if (rect.contains(motionEvent.x.toInt(), motionEvent.y.toInt())) {
+ isInDeleteRect = true
+ deleteDebouncer.publish { scaleDeleteUp() }
+ } else {
+ isInDeleteRect = false
+ deleteDebouncer.publish { scaleDeleteDown() }
+ }
+ }
+
+ private fun scaleDeleteUp() {
+ if (delete.isHapticFeedbackEnabled && deleteBackground.scaleX < 1.365f) {
+ delete.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
+ }
+
+ deleteSizeAnimatorSet?.cancel()
+ deleteSizeAnimatorSet = AnimatorSet().apply {
+ playTogether(
+ ObjectAnimator.ofFloat(deleteBackground, "scaleX", deleteBackground.scaleX, 1.365f),
+ ObjectAnimator.ofFloat(deleteBackground, "scaleY", deleteBackground.scaleY, 1.365f),
+ )
+ duration = ANIMATION_DURATION
+ start()
+ }
+ }
+
+ private fun scaleDeleteDown() {
+ deleteSizeAnimatorSet?.cancel()
+ deleteSizeAnimatorSet = AnimatorSet().apply {
+ playTogether(
+ ObjectAnimator.ofFloat(deleteBackground, "scaleX", deleteBackground.scaleX, 1f),
+ ObjectAnimator.ofFloat(deleteBackground, "scaleY", deleteBackground.scaleY, 1f),
+ )
+ duration = ANIMATION_DURATION
+ start()
+ }
+ }
+
fun setUndoAvailability(undoAvailability: Boolean) {
this.undoAvailability = undoAvailability
- if (currentMode != Mode.NONE) {
+ if (currentMode != Mode.NONE && currentMode != Mode.DELETE) {
if (undoAvailability) {
animateInUndoTools()
} else {
@@ -268,6 +325,8 @@ class ImageEditorHudV2 @JvmOverloads constructor(
currentMode = mode
// updateVisibilities
clearSelection()
+ modeAnimatorSet?.cancel()
+ undoAnimatorSet?.cancel()
when (mode) {
Mode.NONE -> presentModeNone()
@@ -276,8 +335,10 @@ class ImageEditorHudV2 @JvmOverloads constructor(
Mode.DRAW -> presentModeDraw()
Mode.BLUR -> presentModeBlur()
Mode.HIGHLIGHT -> presentModeHighlight()
- Mode.INSERT_STICKER -> presentModeMoveDelete()
- Mode.MOVE_DELETE -> presentModeMoveDelete()
+ Mode.INSERT_STICKER -> presentModeMoveSticker()
+ Mode.MOVE_STICKER -> presentModeMoveSticker()
+ Mode.DELETE -> presentModeDelete()
+ Mode.MOVE_TEXT -> presentModeText()
}
if (notify) {
@@ -288,25 +349,17 @@ class ImageEditorHudV2 @JvmOverloads constructor(
}
private fun presentModeNone() {
- if (isAvatarEdit) {
- animateViewSetChange(
- inSet = drawButtonRow,
- outSet = cropButtonRow + blurTools + drawTools
- )
- animateInUndoTools()
- } else {
- animateViewSetChange(
- inSet = setOf(),
- outSet = drawButtonRow + cropButtonRow + blurTools + drawTools
- )
- animateOutUndoTools()
- }
+ animateModeChange(
+ inSet = setOf(),
+ outSet = allModeTools
+ )
+ animateOutUndoTools()
}
private fun presentModeCrop() {
- animateViewSetChange(
+ animateModeChange(
inSet = cropButtonRow - if (isAvatarEdit) setOf(cropAspectLockButton) else setOf(),
- outSet = drawButtonRow + blurTools + drawTools
+ outSet = allModeTools
)
animateInUndoTools()
}
@@ -316,9 +369,9 @@ class ImageEditorHudV2 @JvmOverloads constructor(
brushToggle.setImageResource(R.drawable.ic_draw_white_24)
listener?.onColorChange(getActiveColor())
updateColorIndicator()
- animateViewSetChange(
+ animateModeChange(
inSet = drawButtonRow + drawTools,
- outSet = cropButtonRow + blurTools
+ outSet = allModeTools
)
animateInUndoTools()
}
@@ -328,35 +381,44 @@ class ImageEditorHudV2 @JvmOverloads constructor(
brushToggle.setImageResource(R.drawable.ic_marker_24)
listener?.onColorChange(getActiveColor())
updateColorIndicator()
- animateViewSetChange(
+ animateModeChange(
inSet = drawButtonRow + drawTools,
- outSet = cropButtonRow + blurTools
+ outSet = allModeTools
)
animateInUndoTools()
}
private fun presentModeBlur() {
blurButton.isSelected = true
- animateViewSetChange(
+ animateModeChange(
inSet = drawButtonRow + blurTools,
- outSet = drawTools
+ outSet = allModeTools
)
animateInUndoTools()
}
private fun presentModeText() {
- textButton.isSelected = true
- animateViewSetChange(
- inSet = setOf(drawSeekBar),
- outSet = drawTools + blurTools + drawButtonRow + cropButtonRow
+ animateModeChange(
+ inSet = drawButtonRow + setOf(drawSeekBar),
+ outSet = allModeTools
)
- animateOutUndoTools()
+ animateInUndoTools()
}
- private fun presentModeMoveDelete() {
- animateViewSetChange(
- outSet = drawTools + blurTools + drawButtonRow + cropButtonRow
+ private fun presentModeMoveSticker() {
+ animateModeChange(
+ inSet = drawButtonRow,
+ outSet = allModeTools
)
+ animateInUndoTools()
+ }
+
+ private fun presentModeDelete() {
+ animateModeChange(
+ inSet = setOf(delete),
+ outSet = allModeTools
+ )
+ animateOutUndoTools()
}
private fun clearSelection() {
@@ -368,72 +430,71 @@ class ImageEditorHudV2 @JvmOverloads constructor(
colorIndicator.translationX = (drawSeekBar.thumb.bounds.left.toFloat() + ViewUtil.dpToPx(16))
}
- private fun animateViewSetChange(
+ private fun animateModeChange(
inSet: Set = setOf(),
- outSet: Set = setOf(),
- throttledDebouncer: ThrottledDebouncer = modeChangeAnimationThrottler
+ outSet: Set = setOf()
) {
val actualOutSet = outSet - inSet
+ val animations = animateInViewSet(inSet) + animateOutViewSet(actualOutSet)
- throttledDebouncer.publish {
- animateInViewSet(inSet)
- animateOutViewSet(actualOutSet)
+ modeAnimatorSet = AnimatorSet().apply {
+ playTogether(animations)
+ duration = ANIMATION_DURATION
+ start()
}
}
- private fun animateInViewSet(viewSet: Set) {
- viewSet.forEach { view ->
- if (!view.isVisible) {
- view.animation = getInAnimation(view)
- view.animation.duration = ANIMATION_DURATION
- view.visibility = VISIBLE
+ private fun animateUndoChange(
+ inSet: Set = setOf(),
+ outSet: Set = setOf()
+ ) {
+ val actualOutSet = outSet - inSet
+ val animations = animateInViewSet(inSet) + animateOutViewSet(actualOutSet)
+
+ undoAnimatorSet = AnimatorSet().apply {
+ playTogether(animations)
+ duration = ANIMATION_DURATION
+ start()
+ }
+ }
+
+ private fun animateInViewSet(viewSet: Set): List {
+ val fades = viewSet
+ .map { child ->
+ child.visible = true
+ ObjectAnimator.ofFloat(child, "alpha", 1f)
+ }
+
+ val slides = viewSet.filter { it in viewsToSlide }.map { child ->
+ ObjectAnimator.ofFloat(child, "translationY", 0f)
+ }
+
+ return fades + slides
+ }
+
+ private fun animateOutViewSet(viewSet: Set): List {
+ val fades = viewSet.map { child ->
+ ObjectAnimator.ofFloat(child, "alpha", 0f).apply {
+ doOnEnd { child.visible = false }
}
}
- }
- private fun animateOutViewSet(viewSet: Set) {
- viewSet.forEach { view ->
- if (view.isVisible) {
- val animation: Animation = getOutAnimation(view)
- animation.duration = ANIMATION_DURATION
- animation.setListeners(
- onAnimationEnd = {
- view.visibility = GONE
- }
- )
-
- view.startAnimation(animation)
- }
+ val slides = viewSet.filter { it in viewsToSlide }.map { child ->
+ ObjectAnimator.ofFloat(child, "translationY", ViewUtil.dpToPx(56).toFloat())
}
- }
- private fun getInAnimation(view: View): Animation {
- return if (viewsToSlide.contains(view)) {
- AnimationUtils.loadAnimation(context, R.anim.slide_from_bottom)
- } else {
- AnimationUtils.loadAnimation(context, R.anim.fade_in)
- }
- }
-
- private fun getOutAnimation(view: View): Animation {
- return if (viewsToSlide.contains(view)) {
- AnimationUtils.loadAnimation(context, R.anim.slide_to_bottom)
- } else {
- AnimationUtils.loadAnimation(context, R.anim.fade_out)
- }
+ return fades + slides
}
private fun animateInUndoTools() {
- animateViewSetChange(
- inSet = undoToolsIfAvailable(),
- throttledDebouncer = undoToolsAnimationThrottler
+ animateUndoChange(
+ inSet = undoToolsIfAvailable()
)
}
private fun animateOutUndoTools() {
- animateViewSetChange(
- outSet = undoTools,
- throttledDebouncer = undoToolsAnimationThrottler
+ animateUndoChange(
+ outSet = undoTools
)
}
@@ -452,7 +513,9 @@ class ImageEditorHudV2 @JvmOverloads constructor(
DRAW,
HIGHLIGHT,
BLUR,
- MOVE_DELETE,
+ MOVE_STICKER,
+ MOVE_TEXT,
+ DELETE,
INSERT_STICKER
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java
index 5be7fd610b..ce2ef29aa3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/UriGlideRenderer.java
@@ -28,8 +28,10 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.imageeditor.Bounds;
import org.thoughtcrime.securesms.imageeditor.Renderer;
import org.thoughtcrime.securesms.imageeditor.RendererContext;
+import org.thoughtcrime.securesms.imageeditor.SelectableRenderer;
import org.thoughtcrime.securesms.imageeditor.model.EditorElement;
import org.thoughtcrime.securesms.imageeditor.model.EditorModel;
+import org.thoughtcrime.securesms.imageeditor.renderers.SelectedElementGuideRenderer;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest;
@@ -42,7 +44,7 @@ import java.util.concurrent.ExecutionException;
*
* The image can be encrypted.
*/
-public final class UriGlideRenderer implements Renderer {
+public final class UriGlideRenderer implements SelectableRenderer {
private static final String TAG = Log.tag(UriGlideRenderer.class);
@@ -63,6 +65,10 @@ public final class UriGlideRenderer implements Renderer {
private final float blurRadius;
private final RequestListener bitmapRequestListener;
+ private boolean selected;
+
+ private final SelectedElementGuideRenderer selectedElementGuideRenderer = new SelectedElementGuideRenderer();
+
@Nullable private Bitmap bitmap;
@Nullable private Bitmap blurredBitmap;
@Nullable private Paint blurPaint;
@@ -136,6 +142,10 @@ public final class UriGlideRenderer implements Renderer {
// If failed to load, we draw a black out, in case image was sticker positioned to cover private info.
rendererContext.canvas.drawRect(Bounds.FULL_BOUNDS, paint);
}
+
+ if (selected && rendererContext.isEditing()) {
+ selectedElementGuideRenderer.render(rendererContext);
+ }
}
private void renderBlurOverlay(RendererContext rendererContext) {
@@ -324,4 +334,11 @@ public final class UriGlideRenderer implements Renderer {
dest.writeInt(maxHeight);
dest.writeFloat(blurRadius);
}
+
+ @Override
+ public void onSelected(boolean selected) {
+ if (this.selected != selected) {
+ this.selected = selected;
+ }
+ }
}
diff --git a/app/src/main/res/drawable/ic_arrow_right_16.xml b/app/src/main/res/drawable/ic_arrow_right_16.xml
index b17333c299..294bff12a8 100644
--- a/app/src/main/res/drawable/ic_arrow_right_16.xml
+++ b/app/src/main/res/drawable/ic_arrow_right_16.xml
@@ -1,9 +1,10 @@
-
+
diff --git a/app/src/main/res/drawable/image_editor_hud_delete_background.xml b/app/src/main/res/drawable/image_editor_hud_delete_background.xml
new file mode 100644
index 0000000000..a8c8222939
--- /dev/null
+++ b/app/src/main/res/drawable/image_editor_hud_delete_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/v2_media_review_selected_item_overlay.xml b/app/src/main/res/drawable/v2_media_review_selected_item_overlay.xml
index 7afb0f7876..cde7209a20 100644
--- a/app/src/main/res/drawable/v2_media_review_selected_item_overlay.xml
+++ b/app/src/main/res/drawable/v2_media_review_selected_item_overlay.xml
@@ -5,6 +5,7 @@
-
+
@@ -25,7 +26,6 @@
-
-
diff --git a/app/src/main/res/layout/v2_media_add_message_dialog_fragment.xml b/app/src/main/res/layout/v2_media_add_message_dialog_fragment.xml
index ea6feb50a8..20b1f3ca81 100644
--- a/app/src/main/res/layout/v2_media_add_message_dialog_fragment.xml
+++ b/app/src/main/res/layout/v2_media_add_message_dialog_fragment.xml
@@ -57,8 +57,8 @@
android:background="@null"
android:hint="@string/MediaReviewFragment__add_a_message"
android:inputType="textCapSentences"
- android:minHeight="32dp"
- android:paddingEnd="12dp"
+ android:minHeight="36dp"
+ android:paddingEnd="10dp"
android:textAppearance="@style/TextAppearance.Signal.Body2"
app:layout_constraintBottom_toTopOf="@id/emoji_drawer_stub"
app:layout_constraintEnd_toStartOf="@id/confirm_button"
@@ -71,7 +71,7 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
- android:padding="8dp"
+ android:padding="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/input_barrier"
app:srcCompat="@drawable/v2_media_add_a_message_check" />
diff --git a/app/src/main/res/layout/v2_media_image_editor_hud.xml b/app/src/main/res/layout/v2_media_image_editor_hud.xml
index c4bfb6cc5d..203ef5b9f4 100644
--- a/app/src/main/res/layout/v2_media_image_editor_hud.xml
+++ b/app/src/main/res/layout/v2_media_image_editor_hud.xml
@@ -64,14 +64,18 @@
android:layout_height="48dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
+ android:alpha="0"
android:contentDescription="@string/ImageEditorHud__cancel"
android:padding="6dp"
+ android:translationY="56dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_goneMarginEnd="10dp"
app:srcCompat="@drawable/ic_cancel_36"
app:tint="@color/core_white"
+ tools:alpha="1"
+ tools:translationY="0dp"
tools:visibility="visible" />
@@ -330,7 +366,42 @@
android:saveEnabled="false" />
-
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/v2_media_review_fragment.xml b/app/src/main/res/layout/v2_media_review_fragment.xml
index 62eb363303..50f382e208 100644
--- a/app/src/main/res/layout/v2_media_review_fragment.xml
+++ b/app/src/main/res/layout/v2_media_review_fragment.xml
@@ -160,10 +160,10 @@
android:layout_marginBottom="20dp"
android:background="@drawable/rounded_rectangle_secondary_dark"
android:gravity="start|center_vertical"
- android:minHeight="32dp"
+ android:minHeight="36dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
- android:textAppearance="@style/TextAppearance.Signal.Body2"
+ android:textAppearance="@style/Signal.Text.Body"
android:textColor="@color/core_white"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/button_barrier"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3a252cfaad..9c16242fb1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3812,6 +3812,7 @@
Discard changes?
View once message
You\'ll lose any changes you\'ve made to this photo.
+ Delete