mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 11:08:33 +00:00
Updated the feel of swipe-to-reply.
This commit is contained in:
parent
4e2afa7362
commit
1d8e85fcad
@ -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());
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user