That fuck shit the fascists are using
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}