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