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
This commit is contained in:
Alex Hart
2021-09-07 09:32:20 -03:00
committed by Greyson Parrelli
parent 0dfa6aab09
commit 1514f91687
18 changed files with 582 additions and 190 deletions

View File

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

View File

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

View File

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