Support deletion and guides when manipulating objects.

* Fix issue with avatar selection
* Remove save button on video editor screen (we never supported this)
* Fix mentions
fork-5.53.8
Alex Hart 2021-09-07 09:32:20 -03:00 zatwierdzone przez Greyson Parrelli
rodzic 0dfa6aab09
commit 1514f91687
18 zmienionych plików z 582 dodań i 190 usunięć

Wyświetl plik

@ -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 {

Wyświetl plik

@ -0,0 +1,8 @@
package org.thoughtcrime.securesms.imageeditor
/**
* Renderer that can maintain a "selected" state
*/
interface SelectableRenderer : Renderer {
fun onSelected(selected: Boolean)
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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();

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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
)
}

Wyświetl plik

@ -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)
)
}

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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;
}
}
}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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" />

Wyświetl plik

@ -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>

Wyświetl plik

@ -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"

Wyświetl plik

@ -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 -->