That fuck shit the fascists are using
at master 222 lines 8.2 kB view raw
1package org.tm.archive.conversation; 2 3import android.annotation.SuppressLint; 4import android.content.Context; 5import android.graphics.Canvas; 6import android.os.Vibrator; 7import android.view.MotionEvent; 8import android.view.View; 9 10import androidx.annotation.NonNull; 11import androidx.annotation.Nullable; 12import androidx.recyclerview.widget.ItemTouchHelper; 13import androidx.recyclerview.widget.RecyclerView; 14 15import org.tm.archive.conversation.v2.items.InteractiveConversationElement; 16import org.tm.archive.util.AccessibilityUtil; 17import org.tm.archive.util.ServiceUtil; 18 19import java.util.Objects; 20 21public class ConversationItemSwipeCallback extends ItemTouchHelper.SimpleCallback { 22 23 private static float SWIPE_SUCCESS_DX = ConversationSwipeAnimationHelper.TRIGGER_DX; 24 private static long SWIPE_SUCCESS_VIBE_TIME_MS = 10; 25 26 private boolean swipeBack; 27 private boolean shouldTriggerSwipeFeedback; 28 private boolean canTriggerSwipe; 29 private float latestDownX; 30 private float latestDownY; 31 32 private final SwipeAvailabilityProvider swipeAvailabilityProvider; 33 private final ConversationItemTouchListener itemTouchListener; 34 private final OnSwipeListener onSwipeListener; 35 36 public ConversationItemSwipeCallback(@NonNull SwipeAvailabilityProvider swipeAvailabilityProvider, 37 @NonNull OnSwipeListener onSwipeListener) 38 { 39 super(0, ItemTouchHelper.END); 40 this.itemTouchListener = new ConversationItemTouchListener(this::updateLatestDownCoordinate); 41 this.swipeAvailabilityProvider = swipeAvailabilityProvider; 42 this.onSwipeListener = onSwipeListener; 43 this.shouldTriggerSwipeFeedback = true; 44 this.canTriggerSwipe = true; 45 } 46 47 public void attachToRecyclerView(@NonNull RecyclerView recyclerView) { 48 recyclerView.addOnItemTouchListener(itemTouchListener); 49 new ItemTouchHelper(this).attachToRecyclerView(recyclerView); 50 } 51 52 @Override 53 public boolean onMove(@NonNull RecyclerView recyclerView, 54 @NonNull RecyclerView.ViewHolder viewHolder, 55 @NonNull RecyclerView.ViewHolder target) 56 { 57 return false; 58 } 59 60 @Override 61 public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { 62 } 63 64 @Override 65 public int getSwipeDirs(@NonNull RecyclerView recyclerView, 66 @NonNull RecyclerView.ViewHolder viewHolder) 67 { 68 if (cannotSwipeViewHolder(viewHolder)) return 0; 69 return super.getSwipeDirs(recyclerView, viewHolder); 70 } 71 72 @Override 73 public int convertToAbsoluteDirection(int flags, int layoutDirection) { 74 if (swipeBack) { 75 swipeBack = false; 76 return 0; 77 } 78 return super.convertToAbsoluteDirection(flags, layoutDirection); 79 } 80 81 @Override 82 public void onChildDraw( 83 @NonNull Canvas c, 84 @NonNull RecyclerView recyclerView, 85 @NonNull RecyclerView.ViewHolder viewHolder, 86 float dx, float dy, int actionState, boolean isCurrentlyActive) 87 { 88 if (cannotSwipeViewHolder(viewHolder)) return; 89 90 float sign = getSignFromDirection(viewHolder.itemView); 91 boolean isCorrectSwipeDir = sameSign(dx, sign); 92 93 if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && isCorrectSwipeDir) { 94 ConversationSwipeAnimationHelper.update(requireInteractiveConversationElement(viewHolder), Math.abs(dx), sign); 95 recyclerView.invalidate(); 96 handleSwipeFeedback(recyclerView.getContext(), requireInteractiveConversationElement(viewHolder), Math.abs(dx)); 97 if (canTriggerSwipe) { 98 setTouchListener(recyclerView, viewHolder, Math.abs(dx)); 99 } 100 } else if (actionState == ItemTouchHelper.ACTION_STATE_IDLE || dx == 0) { 101 ConversationSwipeAnimationHelper.update(requireInteractiveConversationElement(viewHolder), 0, 1); 102 recyclerView.invalidate(); 103 } 104 105 if (dx == 0) { 106 shouldTriggerSwipeFeedback = true; 107 canTriggerSwipe = true; 108 } 109 } 110 111 private void handleSwipeFeedback(@NonNull Context context, @NonNull InteractiveConversationElement interactiveConversationElement, float dx) { 112 if (dx > SWIPE_SUCCESS_DX && shouldTriggerSwipeFeedback) { 113 vibrate(context); 114 ConversationSwipeAnimationHelper.trigger(interactiveConversationElement); 115 shouldTriggerSwipeFeedback = false; 116 } 117 } 118 119 private void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder) { 120 if (cannotSwipeViewHolder(viewHolder)) return; 121 122 InteractiveConversationElement element = requireInteractiveConversationElement(viewHolder); 123 124 onSwipeListener.onSwipe(element.getConversationMessage()); 125 } 126 127 @SuppressLint("ClickableViewAccessibility") 128 private void setTouchListener(@NonNull RecyclerView recyclerView, 129 @NonNull RecyclerView.ViewHolder viewHolder, 130 float dx) 131 { 132 recyclerView.setOnTouchListener((v, event) -> { 133 switch (event.getAction()) { 134 case MotionEvent.ACTION_DOWN: 135 shouldTriggerSwipeFeedback = true; 136 break; 137 case MotionEvent.ACTION_UP: 138 handleTouchActionUp(recyclerView, viewHolder, dx); 139 case MotionEvent.ACTION_CANCEL: 140 swipeBack = true; 141 shouldTriggerSwipeFeedback = false; 142 resetProgressIfAnimationsDisabled(recyclerView, viewHolder); 143 break; 144 } 145 return false; 146 }); 147 } 148 149 private void handleTouchActionUp(@NonNull RecyclerView recyclerView, 150 @NonNull RecyclerView.ViewHolder viewHolder, 151 float dx) 152 { 153 if (dx > SWIPE_SUCCESS_DX) { 154 canTriggerSwipe = false; 155 onSwiped(viewHolder); 156 if (shouldTriggerSwipeFeedback) { 157 vibrate(viewHolder.itemView.getContext()); 158 } 159 recyclerView.setOnTouchListener(null); 160 } 161 recyclerView.cancelPendingInputEvents(); 162 } 163 164 private void resetProgressIfAnimationsDisabled(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { 165 if (AccessibilityUtil.areAnimationsDisabled(viewHolder.itemView.getContext())) { 166 ConversationSwipeAnimationHelper.update(requireInteractiveConversationElement(viewHolder), 167 0f, 168 getSignFromDirection(viewHolder.itemView)); 169 recyclerView.invalidate(); 170 } 171 } 172 173 private @NonNull InteractiveConversationElement requireInteractiveConversationElement(@NonNull RecyclerView.ViewHolder viewHolder) { 174 return Objects.requireNonNull(getInteractiveConversationElement(viewHolder)); 175 } 176 177 private @Nullable InteractiveConversationElement getInteractiveConversationElement(@NonNull RecyclerView.ViewHolder viewHolder) { 178 if (viewHolder instanceof InteractiveConversationElement) { 179 return (InteractiveConversationElement) viewHolder; 180 } else if (viewHolder.itemView instanceof InteractiveConversationElement) { 181 return (InteractiveConversationElement) viewHolder.itemView; 182 } else { 183 return null; 184 } 185 } 186 187 private boolean cannotSwipeViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { 188 InteractiveConversationElement element = getInteractiveConversationElement(viewHolder); 189 if (element == null) { 190 return true; 191 } 192 193 return !swipeAvailabilityProvider.isSwipeAvailable(element.getConversationMessage()) || 194 element.disallowSwipe(latestDownX, latestDownY); 195 } 196 197 private void updateLatestDownCoordinate(float x, float y) { 198 latestDownX = x; 199 latestDownY = y; 200 } 201 202 private static float getSignFromDirection(@NonNull View view) { 203 return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -1f : 1f; 204 } 205 206 private static boolean sameSign(float dX, float sign) { 207 return dX * sign > 0; 208 } 209 210 private static void vibrate(@NonNull Context context) { 211 Vibrator vibrator = ServiceUtil.getVibrator(context); 212 if (vibrator != null) vibrator.vibrate(SWIPE_SUCCESS_VIBE_TIME_MS); 213 } 214 215 public interface SwipeAvailabilityProvider { 216 boolean isSwipeAvailable(ConversationMessage conversationMessage); 217 } 218 219 public interface OnSwipeListener { 220 void onSwipe(ConversationMessage conversationMessage); 221 } 222}