From 1d8e85fcada6472db5b0a08a7d4ce2200df7a2e7 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 26 Sep 2019 13:43:38 -0400 Subject: [PATCH] Updated the feel of swipe-to-reply. --- .../ConversationItemSwipeCallback.java | 29 +++++----- .../ConversationSwipeAnimationHelper.java | 58 ++++++++++++++----- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationItemSwipeCallback.java b/src/org/thoughtcrime/securesms/conversation/ConversationItemSwipeCallback.java index d95cefca3e..26f166b16b 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationItemSwipeCallback.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationItemSwipeCallback.java @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil; class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { - private static float SWIPE_SUCCESS_PROGRESS = ConversationSwipeAnimationHelper.PROGRESS_TRIGGER_POINT; + private static float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX; private static long SWIPE_SUCCESS_VIBE_TIME_MS = 10; private boolean swipeBack; @@ -76,25 +76,26 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { @NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, - float dX, float dY, int actionState, boolean isCurrentlyActive) + float dx, float dy, int actionState, boolean isCurrentlyActive) { if (cannotSwipeViewHolder(viewHolder)) return; float sign = getSignFromDirection(viewHolder.itemView); - boolean isCorrectSwipeDir = sameSign(dX, sign); + boolean isCorrectSwipeDir = sameSign(dx, sign); - float progress = Math.abs(dX) / (float) viewHolder.itemView.getWidth(); if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) { - ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, progress, sign); - handleSwipeFeedback((ConversationItem) viewHolder.itemView, progress); - setTouchListener(recyclerView, viewHolder, progress); + ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign); + handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx)); + setTouchListener(recyclerView, viewHolder, Math.abs(dx)); + } else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE || dx == 0) { + ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, 0, 1); } - if (progress == 0) shouldTriggerSwipeFeedback = true; + if (dx == 0) shouldTriggerSwipeFeedback = true; } - private void handleSwipeFeedback(@NonNull ConversationItem item, float progress) { - if (progress > SWIPE_SUCCESS_PROGRESS && shouldTriggerSwipeFeedback) { + private void handleSwipeFeedback(@NonNull ConversationItem item, float dx) { + if (dx > SWIPE_SUCCESS_DX && shouldTriggerSwipeFeedback) { vibrate(item.getContext()); ConversationSwipeAnimationHelper.trigger(item); shouldTriggerSwipeFeedback = false; @@ -112,7 +113,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { private void setTouchListener(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, - float progress) + float dx) { recyclerView.setOnTouchListener((v, event) -> { switch (event.getAction()) { @@ -120,7 +121,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { shouldTriggerSwipeFeedback = true; break; case MotionEvent.ACTION_UP: - handleTouchActionUp(recyclerView, viewHolder, progress); + handleTouchActionUp(recyclerView, viewHolder, dx); case MotionEvent.ACTION_CANCEL: swipeBack = true; shouldTriggerSwipeFeedback = false; @@ -133,9 +134,9 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { private void handleTouchActionUp(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, - float progress) + float dx) { - if (progress > SWIPE_SUCCESS_PROGRESS) { + if (dx > SWIPE_SUCCESS_DX) { onSwiped(viewHolder); if (shouldTriggerSwipeFeedback) { vibrate(viewHolder.itemView.getContext()); diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationSwipeAnimationHelper.java b/src/org/thoughtcrime/securesms/conversation/ConversationSwipeAnimationHelper.java index 7fa56be156..793331ba00 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationSwipeAnimationHelper.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationSwipeAnimationHelper.java @@ -12,17 +12,16 @@ import org.thoughtcrime.securesms.util.Util; final class ConversationSwipeAnimationHelper { - public static final float PROGRESS_TRIGGER_POINT = 0.375f; + static final float TRIGGER_DX = dpToPx(64); + static final float MAX_DX = dpToPx(96); - private static final float PROGRESS_SCALE_FACTOR = 2.0f; - private static final float SCALED_PROGRESS_TRIGGER_POINT = PROGRESS_TRIGGER_POINT * PROGRESS_SCALE_FACTOR; private static final float REPLY_SCALE_OVERSHOOT = 1.8f; private static final float REPLY_SCALE_MAX = 1.2f; private static final float REPLY_SCALE_MIN = 1f; private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200; - private static final Interpolator BUBBLE_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(48)); - private static final Interpolator REPLY_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(0f, 1f, 1f / SCALED_PROGRESS_TRIGGER_POINT); + private static final Interpolator BUBBLE_INTERPOLATOR = new BubblePositionInterpolator(0f, TRIGGER_DX, MAX_DX); + private static final Interpolator REPLY_ALPHA_INTERPOLATOR = new ClampingLinearInterpolator(0f, 1f, 1f); private static final Interpolator REPLY_TRANSITION_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(10)); private static final Interpolator AVATAR_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(8)); private static final Interpolator REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX); @@ -30,29 +29,30 @@ final class ConversationSwipeAnimationHelper { private ConversationSwipeAnimationHelper() { } - public static void update(@NonNull ConversationItem conversationItem, float progress, float sign) { - float scaledProgress = Math.min(1f, progress * PROGRESS_SCALE_FACTOR); - updateBodyBubbleTransition(conversationItem.bodyBubble, scaledProgress, sign); - updateReplyIconTransition(conversationItem.reply, scaledProgress, sign); - updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, scaledProgress, sign); + public static void update(@NonNull ConversationItem conversationItem, float dx, float sign) { + float progress = dx / TRIGGER_DX; + + updateBodyBubbleTransition(conversationItem.bodyBubble, dx, sign); + updateReplyIconTransition(conversationItem.reply, dx, progress, sign); + updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, progress, sign); } public static void trigger(@NonNull ConversationItem conversationItem) { triggerReplyIcon(conversationItem.reply); } - private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float progress, float sign) { - bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(progress) * sign); + private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float dx, float sign) { + bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(dx) * sign); } - private static void updateReplyIconTransition(@NonNull View replyIcon, float progress, float sign) { + private static void updateReplyIconTransition(@NonNull View replyIcon, float dx, float progress, float sign) { if (progress > 0.05f) { replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress)); } else replyIcon.setAlpha(0f); replyIcon.setTranslationX(REPLY_TRANSITION_INTERPOLATOR.getInterpolation(progress) * sign); - if (progress < SCALED_PROGRESS_TRIGGER_POINT) { + if (dx < TRIGGER_DX) { float scale = REPLY_SCALE_INTERPOLATOR.getInterpolation(progress); replyIcon.setScaleX(scale); replyIcon.setScaleY(scale); @@ -81,6 +81,36 @@ final class ConversationSwipeAnimationHelper { return (int) (dp * Resources.getSystem().getDisplayMetrics().density); } + private static final class BubblePositionInterpolator implements Interpolator { + + private final float start; + private final float middle; + private final float end; + + private BubblePositionInterpolator(float start, float middle, float end) { + this.start = start; + this.middle = middle; + this.end = end; + } + + @Override + public float getInterpolation(float input) { + if (input < start) { + return start; + } else if (input < middle) { + return input; + } else { + float segmentLength = end - middle; + float segmentTraveled = input - middle; + float segmentCompletion = segmentTraveled / segmentLength; + float scaleDownFactor = middle / (input * 2); + float output = middle + (segmentLength * segmentCompletion * scaleDownFactor); + + return Math.min(output, end); + } + } + } + private static final class ClampingLinearInterpolator implements Interpolator { private final float slope;