kopia lustrzana https://github.com/ryukoposting/Signal-Android
Support deletion and guides when manipulating objects.
* Fix issue with avatar selection * Remove save button on video editor screen (we never supported this) * Fix mentionsfork-5.53.8
rodzic
0dfa6aab09
commit
1514f91687
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package org.thoughtcrime.securesms.imageeditor
|
||||
|
||||
/**
|
||||
* Renderer that can maintain a "selected" state
|
||||
*/
|
||||
interface SelectableRenderer : Renderer {
|
||||
fun onSelected(selected: Boolean)
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
|||
* <p>
|
||||
* 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<Line> 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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<Animator> {
|
||||
private fun computeSendButtonAnimators(state: MediaSelectionState): List<Animator> {
|
||||
|
||||
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<Animator> {
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<Uri, FaceDetectionResult> 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() {
|
||||
|
|
|
@ -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<View> = setOf(drawButton, textButton, stickerButton, blurButton)
|
||||
|
||||
|
@ -73,14 +77,24 @@ class ImageEditorHudV2 @JvmOverloads constructor(
|
|||
private val drawButtonRow: Set<View> = setOf(cancelButton, doneButton, drawButton, textButton, stickerButton, blurButton)
|
||||
private val cropButtonRow: Set<View> = setOf(cancelButton, doneButton, cropRotateButton, cropFlipButton, cropAspectLockButton)
|
||||
|
||||
private val viewsToSlide: Set<View> = drawButtonRow + cropButtonRow
|
||||
private val allModeTools: Set<View> = drawTools + blurTools + drawButtonRow + cropButtonRow + delete
|
||||
|
||||
private val modeChangeAnimationThrottler = ThrottledDebouncer(ANIMATION_DURATION)
|
||||
private val undoToolsAnimationThrottler = ThrottledDebouncer(ANIMATION_DURATION)
|
||||
private val viewsToSlide: Set<View> = 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<View> = setOf(),
|
||||
outSet: Set<View> = setOf(),
|
||||
throttledDebouncer: ThrottledDebouncer = modeChangeAnimationThrottler
|
||||
outSet: Set<View> = 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<View>) {
|
||||
viewSet.forEach { view ->
|
||||
if (!view.isVisible) {
|
||||
view.animation = getInAnimation(view)
|
||||
view.animation.duration = ANIMATION_DURATION
|
||||
view.visibility = VISIBLE
|
||||
private fun animateUndoChange(
|
||||
inSet: Set<View> = setOf(),
|
||||
outSet: Set<View> = 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<View>): List<Animator> {
|
||||
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<View>): List<Animator> {
|
||||
val fades = viewSet.map { child ->
|
||||
ObjectAnimator.ofFloat(child, "alpha", 0f).apply {
|
||||
doOnEnd { child.visible = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateOutViewSet(viewSet: Set<View>) {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Bitmap> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M8.515,13.5131L7.454,12.4521L10.325,9.5821L11.45,8.7791L3,8.7791L3,7.2791L11.45,7.2791L10.409,6.5351L7.451,3.5411L8.518,2.4871L13.997,8.0321L8.515,13.5131Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M8.515,13.5131L7.454,12.4521L10.325,9.5821L11.45,8.7791L3,8.7791L3,7.2791L11.45,7.2791L10.409,6.5351L7.451,3.5411L8.518,2.4871L13.997,8.0321L8.515,13.5131Z" />
|
||||
</vector>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="oval" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/transparent_black_40" />
|
||||
<stroke android:color="@color/core_white" android:width="1.5dp" />
|
||||
</shape>
|
|
@ -5,6 +5,7 @@
|
|||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<size android:width="44dp" android:height="44dp" />
|
||||
<solid android:color="@color/transparent_black_40" />
|
||||
<stroke android:color="@color/core_ultramarine" android:width="2dp"/>
|
||||
<corners android:radius="10dp" />
|
||||
<padding android:right="12dp" android:bottom="12dp" android:top="12dp" android:left="12dp" />
|
||||
|
@ -25,7 +26,6 @@
|
|||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<size android:width="44dp" android:height="44dp" />
|
||||
<solid android:color="@color/transparent_black_40" />
|
||||
<stroke android:color="@color/core_white" android:width="1.5dp"/>
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<ImageView
|
||||
|
@ -79,8 +83,10 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__draw"
|
||||
android:padding="6dp"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_text_button"
|
||||
|
@ -88,6 +94,8 @@
|
|||
app:layout_goneMarginEnd="10dp"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/image_editor_hud_draw"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -95,8 +103,10 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__write_text"
|
||||
android:padding="6dp"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_sticker_button"
|
||||
|
@ -104,6 +114,8 @@
|
|||
app:layout_goneMarginEnd="10dp"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/image_editor_hud_text"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -111,8 +123,10 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__add_a_sticker"
|
||||
android:padding="6dp"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_blur_button"
|
||||
|
@ -120,6 +134,8 @@
|
|||
app:layout_goneMarginEnd="10dp"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/image_editor_hud_sticker"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -127,8 +143,10 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__blur"
|
||||
android:padding="6dp"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_done_button"
|
||||
|
@ -136,6 +154,8 @@
|
|||
app:layout_goneMarginEnd="10dp"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/image_editor_hud_blur"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -144,14 +164,18 @@
|
|||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__done_editing"
|
||||
android:padding="6dp"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/ic_confirm_36"
|
||||
app:tint="@color/core_white"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -221,9 +245,11 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__draw"
|
||||
android:padding="6dp"
|
||||
android:scaleType="centerInside"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_flip_button"
|
||||
|
@ -231,6 +257,8 @@
|
|||
app:layout_goneMarginEnd="10dp"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/ic_rotate_outline_24"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -238,9 +266,11 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__draw"
|
||||
android:padding="6dp"
|
||||
android:scaleType="centerInside"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_aspect_lock_button"
|
||||
|
@ -248,6 +278,8 @@
|
|||
app:layout_goneMarginEnd="10dp"
|
||||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/ic_flip_outline_24"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
|
@ -255,8 +287,10 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__draw"
|
||||
android:scaleType="centerInside"
|
||||
android:translationY="56dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_done_button"
|
||||
|
@ -265,6 +299,8 @@
|
|||
app:layout_goneMarginStart="10dp"
|
||||
app:srcCompat="@drawable/ic_crop_unlock_24"
|
||||
app:tint="@color/core_white"
|
||||
tools:alpha="1"
|
||||
tools:translationY="0dp"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- endregion -->
|
||||
|
@ -330,7 +366,42 @@
|
|||
android:saveEnabled="false" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- -->
|
||||
<!-- Delete -->
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/image_editor_bottom_guide"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_end="72dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/image_editor_hud_delete"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:alpha="0"
|
||||
android:contentDescription="@string/ImageEditorHud__delete"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/image_editor_bottom_guide"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_editor_hud_delete_bg"
|
||||
android:layout_width="41dp"
|
||||
android:layout_height="41dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/image_editor_hud_delete_background"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="centerInside"
|
||||
app:srcCompat="@drawable/ic_trash_white_24" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -3812,6 +3812,7 @@
|
|||
<string name="MediaReviewImagePageFragment__discard_changes">Discard changes?</string>
|
||||
<string name="MediaReviewFragment__view_once_message">View once message</string>
|
||||
<string name="MediaReviewImagePageFragment__youll_lose_any_changes">You\'ll lose any changes you\'ve made to this photo.</string>
|
||||
<string name="ImageEditorHud__delete">Delete</string>
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue