Signal-Android/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemSwipeCallba...

202 lines
7.0 KiB
Java

package org.thoughtcrime.securesms.conversation;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Vibrator;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.util.AccessibilityUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback {
private static float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX;
private static long SWIPE_SUCCESS_VIBE_TIME_MS = 10;
private boolean swipeBack;
private boolean shouldTriggerSwipeFeedback;
private boolean canTriggerSwipe;
private float latestDownX;
private float latestDownY;
private final SwipeAvailabilityProvider swipeAvailabilityProvider;
private final ConversationItemTouchListener itemTouchListener;
private final OnSwipeListener onSwipeListener;
ConversationItemSwipeCallback(@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider,
@NonNull OnSwipeListener onSwipeListener)
{
super(0, ItemTouchHelper.END);
this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate);
this.swipeAvailabilityProvider = swipeAvailabilityProvider;
this.onSwipeListener = onSwipeListener;
this.shouldTriggerSwipeFeedback = true;
this.canTriggerSwipe = true;
}
void attachToRecyclerView(@NonNull RecyclerView recyclerView) {
recyclerView.addOnItemTouchListener(itemTouchListener);
new ItemTouchHelper(this).attachToRecyclerView(recyclerView);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target)
{
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
@Override
public int getSwipeDirs(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder)
{
if (cannotSwipeViewHolder(viewHolder)) return 0;
return super.getSwipeDirs(recyclerView, viewHolder);
}
@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
if (swipeBack) {
swipeBack = false;
return 0;
}
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
@Override
public void onChildDraw(
@NonNull Canvas c,
@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx, float dy, int actionState, boolean isCurrentlyActive)
{
if (cannotSwipeViewHolder(viewHolder)) return;
float sign = getSignFromDirection(viewHolder.itemView);
boolean isCorrectSwipeDir = sameSign(dx, sign);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, Math.abs(dx), sign);
handleSwipeFeedback((ConversationItem) viewHolder.itemView, Math.abs(dx));
if (canTriggerSwipe) {
setTouchListener(recyclerView, viewHolder, Math.abs(dx));
}
} else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE || dx == 0) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView, 0, 1);
}
if (dx == 0) {
shouldTriggerSwipeFeedback = true;
canTriggerSwipe = true;
}
}
private void handleSwipeFeedback(@NonNull ConversationItem item, float dx) {
if (dx > SWIPE_SUCCESS_DX && shouldTriggerSwipeFeedback) {
vibrate(item.getContext());
ConversationSwipeAnimationHelper.trigger(item);
shouldTriggerSwipeFeedback = false;
}
}
private void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder) {
if (cannotSwipeViewHolder(viewHolder)) return;
ConversationItem item = ((ConversationItem) viewHolder.itemView);
ConversationMessage messageRecord = item.getConversationMessage();
onSwipeListener.onSwipe(messageRecord);
}
@SuppressLint("ClickableViewAccessibility")
private void setTouchListener(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx)
{
recyclerView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
shouldTriggerSwipeFeedback = true;
break;
case MotionEvent.ACTION_UP:
handleTouchActionUp(recyclerView, viewHolder, dx);
case MotionEvent.ACTION_CANCEL:
swipeBack = true;
shouldTriggerSwipeFeedback = false;
resetProgressIfAnimationsDisabled(viewHolder);
break;
}
return false;
});
}
private void handleTouchActionUp(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dx)
{
if (dx > SWIPE_SUCCESS_DX) {
canTriggerSwipe = false;
onSwiped(viewHolder);
if (shouldTriggerSwipeFeedback) {
vibrate(viewHolder.itemView.getContext());
}
recyclerView.setOnTouchListener(null);
}
recyclerView.cancelPendingInputEvents();
}
private static void resetProgressIfAnimationsDisabled(RecyclerView.ViewHolder viewHolder) {
if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) {
ConversationSwipeAnimationHelper.update((ConversationItem) viewHolder.itemView,
0f,
getSignFromDirection(viewHolder.itemView));
}
}
private boolean cannotSwipeViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
if (!(viewHolder.itemView instanceof ConversationItem)) return true;
ConversationItem item = ((ConversationItem) viewHolder.itemView);
return !swipeAvailabilityProvider.isSwipeAvailable(item.getConversationMessage()) ||
item.disallowSwipe(latestDownX, latestDownY);
}
private void updateLatestDownCoordinate(float x, float y) {
latestDownX = x;
latestDownY = y;
}
private static float getSignFromDirection(@NonNull View view) {
return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -1f : 1f;
}
private static boolean sameSign(float dX, float sign) {
return dX * sign > 0;
}
private static void vibrate(@NonNull Context context) {
Vibrator vibrator = ServiceUtil.getVibrator(context);
if (vibrator != null) vibrator.vibrate(SWIPE_SUCCESS_VIBE_TIME_MS);
}
interface SwipeAvailabilityProvider {
boolean isSwipeAvailable(ConversationMessage conversationMessage);
}
interface OnSwipeListener {
void onSwipe(ConversationMessage conversationMessage);
}
}