mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 10:58:34 +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 {
|
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 static long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
|
||||||
|
|
||||||
private boolean swipeBack;
|
private boolean swipeBack;
|
||||||
@ -76,25 +76,26 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
|||||||
@NonNull Canvas c,
|
@NonNull Canvas c,
|
||||||
@NonNull RecyclerView recyclerView,
|
@NonNull RecyclerView recyclerView,
|
||||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||||
float dX, float dY, int actionState, boolean isCurrentlyActive)
|
float dx, float dy, int actionState, boolean isCurrentlyActive)
|
||||||
{
|
{
|
||||||
if (cannotSwipeViewHolder(viewHolder)) return;
|
if (cannotSwipeViewHolder(viewHolder)) return;
|
||||||
|
|
||||||
float sign = getSignFromDirection(viewHolder.itemView);
|
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) {
|
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) {
|
||||||
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, progress, sign);
|
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
|
||||||
handleSwipeFeedback((ConversationItem) viewHolder.itemView, progress);
|
handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx));
|
||||||
setTouchListener(recyclerView, viewHolder, progress);
|
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) {
|
private void handleSwipeFeedback(@NonNull ConversationItem item, float dx) {
|
||||||
if (progress > SWIPE_SUCCESS_PROGRESS && shouldTriggerSwipeFeedback) {
|
if (dx > SWIPE_SUCCESS_DX && shouldTriggerSwipeFeedback) {
|
||||||
vibrate(item.getContext());
|
vibrate(item.getContext());
|
||||||
ConversationSwipeAnimationHelper.trigger(item);
|
ConversationSwipeAnimationHelper.trigger(item);
|
||||||
shouldTriggerSwipeFeedback = false;
|
shouldTriggerSwipeFeedback = false;
|
||||||
@ -112,7 +113,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
|||||||
|
|
||||||
private void setTouchListener(@NonNull RecyclerView recyclerView,
|
private void setTouchListener(@NonNull RecyclerView recyclerView,
|
||||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||||
float progress)
|
float dx)
|
||||||
{
|
{
|
||||||
recyclerView.setOnTouchListener((v, event) -> {
|
recyclerView.setOnTouchListener((v, event) -> {
|
||||||
switch (event.getAction()) {
|
switch (event.getAction()) {
|
||||||
@ -120,7 +121,7 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
|||||||
shouldTriggerSwipeFeedback = true;
|
shouldTriggerSwipeFeedback = true;
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
handleTouchActionUp(recyclerView, viewHolder, progress);
|
handleTouchActionUp(recyclerView, viewHolder, dx);
|
||||||
case MotionEvent.ACTION_CANCEL:
|
case MotionEvent.ACTION_CANCEL:
|
||||||
swipeBack = true;
|
swipeBack = true;
|
||||||
shouldTriggerSwipeFeedback = false;
|
shouldTriggerSwipeFeedback = false;
|
||||||
@ -133,9 +134,9 @@ class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
|
|||||||
|
|
||||||
private void handleTouchActionUp(@NonNull RecyclerView recyclerView,
|
private void handleTouchActionUp(@NonNull RecyclerView recyclerView,
|
||||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||||
float progress)
|
float dx)
|
||||||
{
|
{
|
||||||
if (progress > SWIPE_SUCCESS_PROGRESS) {
|
if (dx > SWIPE_SUCCESS_DX) {
|
||||||
onSwiped(viewHolder);
|
onSwiped(viewHolder);
|
||||||
if (shouldTriggerSwipeFeedback) {
|
if (shouldTriggerSwipeFeedback) {
|
||||||
vibrate(viewHolder.itemView.getContext());
|
vibrate(viewHolder.itemView.getContext());
|
||||||
|
@ -12,17 +12,16 @@ import org.thoughtcrime.securesms.util.Util;
|
|||||||
|
|
||||||
final class ConversationSwipeAnimationHelper {
|
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_OVERSHOOT = 1.8f;
|
||||||
private static final float REPLY_SCALE_MAX = 1.2f;
|
private static final float REPLY_SCALE_MAX = 1.2f;
|
||||||
private static final float REPLY_SCALE_MIN = 1f;
|
private static final float REPLY_SCALE_MIN = 1f;
|
||||||
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
|
private static final long REPLY_SCALE_OVERSHOOT_DURATION = 200;
|
||||||
|
|
||||||
private static final Interpolator BUBBLE_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(48));
|
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 / SCALED_PROGRESS_TRIGGER_POINT);
|
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 REPLY_TRANSITION_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(10));
|
||||||
private static final Interpolator AVATAR_INTERPOLATOR = new ClampingLinearInterpolator(0f, dpToPx(8));
|
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);
|
private static final Interpolator REPLY_SCALE_INTERPOLATOR = new ClampingLinearInterpolator(REPLY_SCALE_MIN, REPLY_SCALE_MAX);
|
||||||
@ -30,29 +29,30 @@ final class ConversationSwipeAnimationHelper {
|
|||||||
private ConversationSwipeAnimationHelper() {
|
private ConversationSwipeAnimationHelper() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void update(@NonNull ConversationItem conversationItem, float progress, float sign) {
|
public static void update(@NonNull ConversationItem conversationItem, float dx, float sign) {
|
||||||
float scaledProgress = Math.min(1f, progress * PROGRESS_SCALE_FACTOR);
|
float progress = dx / TRIGGER_DX;
|
||||||
updateBodyBubbleTransition(conversationItem.bodyBubble, scaledProgress, sign);
|
|
||||||
updateReplyIconTransition(conversationItem.reply, scaledProgress, sign);
|
updateBodyBubbleTransition(conversationItem.bodyBubble, dx, sign);
|
||||||
updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, scaledProgress, sign);
|
updateReplyIconTransition(conversationItem.reply, dx, progress, sign);
|
||||||
|
updateContactPhotoHolderTransition(conversationItem.contactPhotoHolder, progress, sign);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void trigger(@NonNull ConversationItem conversationItem) {
|
public static void trigger(@NonNull ConversationItem conversationItem) {
|
||||||
triggerReplyIcon(conversationItem.reply);
|
triggerReplyIcon(conversationItem.reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float progress, float sign) {
|
private static void updateBodyBubbleTransition(@NonNull View bodyBubble, float dx, float sign) {
|
||||||
bodyBubble.setTranslationX(BUBBLE_INTERPOLATOR.getInterpolation(progress) * 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) {
|
if (progress > 0.05f) {
|
||||||
replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress));
|
replyIcon.setAlpha(REPLY_ALPHA_INTERPOLATOR.getInterpolation(progress));
|
||||||
} else replyIcon.setAlpha(0f);
|
} else replyIcon.setAlpha(0f);
|
||||||
|
|
||||||
replyIcon.setTranslationX(REPLY_TRANSITION_INTERPOLATOR.getInterpolation(progress) * sign);
|
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);
|
float scale = REPLY_SCALE_INTERPOLATOR.getInterpolation(progress);
|
||||||
replyIcon.setScaleX(scale);
|
replyIcon.setScaleX(scale);
|
||||||
replyIcon.setScaleY(scale);
|
replyIcon.setScaleY(scale);
|
||||||
@ -81,6 +81,36 @@ final class ConversationSwipeAnimationHelper {
|
|||||||
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
|
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 static final class ClampingLinearInterpolator implements Interpolator {
|
||||||
|
|
||||||
private final float slope;
|
private final float slope;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user