That fuck shit the fascists are using
1package org.tm.archive.mediasend;
2
3import android.content.Context;
4import android.content.res.TypedArray;
5import android.graphics.Canvas;
6import android.graphics.Color;
7import android.graphics.Paint;
8import android.graphics.Rect;
9import android.graphics.RectF;
10import android.util.AttributeSet;
11import android.view.MotionEvent;
12import android.view.View;
13import android.view.animation.Animation;
14import android.view.animation.AnimationUtils;
15import android.view.animation.DecelerateInterpolator;
16import android.view.animation.Interpolator;
17
18import androidx.annotation.NonNull;
19import androidx.annotation.Nullable;
20
21import org.signal.core.util.DimensionUnit;
22import org.tm.archive.R;
23import org.tm.archive.util.Util;
24import org.tm.archive.util.ViewUtil;
25
26public class CameraButtonView extends View {
27
28 private enum CameraButtonMode { IMAGE, MIXED }
29
30 private static final float CAPTURE_ARC_STROKE_WIDTH = 3.5f;
31 private static final int CAPTURE_FILL_PROTECTION = 10;
32 private static final int PROGRESS_ARC_STROKE_WIDTH = 4;
33 private static final int HALF_PROGRESS_ARC_STROKE_WIDTH = PROGRESS_ARC_STROKE_WIDTH / 2;
34 private static final float DEADZONE_REDUCTION_PERCENT = 0.35f;
35 private static final int DRAG_DISTANCE_MULTIPLIER = 3;
36 private static final Interpolator ZOOM_INTERPOLATOR = new DecelerateInterpolator();
37
38 private final @NonNull Paint outlinePaint = outlinePaint();
39 private final @NonNull Paint backgroundPaint = backgroundPaint();
40 private final @NonNull Paint arcPaint = arcPaint();
41 private final @NonNull Paint recordPaint = recordPaint();
42 private final @NonNull Paint progressPaint = progressPaint();
43 private final @NonNull Paint captureFillPaint = captureFillPaint();
44
45 private Animation growAnimation;
46 private Animation shrinkAnimation;
47
48 private boolean isRecordingVideo;
49 private float progressPercent = 0f;
50
51 private @NonNull CameraButtonMode cameraButtonMode = CameraButtonMode.IMAGE;
52 private @Nullable VideoCaptureListener videoCaptureListener;
53
54 private final float imageCaptureSize;
55 private final float recordSize;
56 private final RectF progressRect = new RectF();
57 private final Rect deadzoneRect = new Rect();
58
59 private final @NonNull OnLongClickListener internalLongClickListener = v -> {
60 notifyVideoCaptureStarted();
61 shrinkAnimation.cancel();
62 setScaleX(1f);
63 setScaleY(1f);
64 isRecordingVideo = true;
65 return true;
66 };
67
68 public CameraButtonView(@NonNull Context context) {
69 this(context, null);
70 }
71
72 public CameraButtonView(@NonNull Context context, @Nullable AttributeSet attrs) {
73 this(context, attrs, 0);
74 }
75
76 public CameraButtonView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
77 super(context, attrs, defStyleAttr);
78
79 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraButtonView, defStyleAttr, 0);
80
81 imageCaptureSize = a.getDimensionPixelSize(R.styleable.CameraButtonView_imageCaptureSize, -1);
82 recordSize = a.getDimensionPixelSize(R.styleable.CameraButtonView_recordSize, -1);
83 a.recycle();
84
85 initializeImageAnimations();
86 }
87
88 private static Paint recordPaint() {
89 Paint recordPaint = new Paint();
90 recordPaint.setColor(0xFFF44336);
91 recordPaint.setAntiAlias(true);
92 recordPaint.setStyle(Paint.Style.FILL);
93 return recordPaint;
94 }
95
96 private static Paint outlinePaint() {
97 Paint outlinePaint = new Paint();
98 outlinePaint.setColor(0x26000000);
99 outlinePaint.setAntiAlias(true);
100 outlinePaint.setStyle(Paint.Style.STROKE);
101 outlinePaint.setStrokeWidth(ViewUtil.dpToPx(4));
102 return outlinePaint;
103 }
104
105 private static Paint backgroundPaint() {
106 Paint backgroundPaint = new Paint();
107 backgroundPaint.setColor(0x4CFFFFFF);
108 backgroundPaint.setAntiAlias(true);
109 backgroundPaint.setStyle(Paint.Style.FILL);
110 return backgroundPaint;
111 }
112
113 private static Paint arcPaint() {
114 Paint arcPaint = new Paint();
115 arcPaint.setColor(0xFFFFFFFF);
116 arcPaint.setAntiAlias(true);
117 arcPaint.setStyle(Paint.Style.STROKE);
118 arcPaint.setStrokeWidth(DimensionUnit.DP.toPixels(CAPTURE_ARC_STROKE_WIDTH));
119 return arcPaint;
120 }
121
122 private static Paint captureFillPaint() {
123 Paint arcPaint = new Paint();
124 arcPaint.setColor(0xFFFFFFFF);
125 arcPaint.setAntiAlias(true);
126 arcPaint.setStyle(Paint.Style.FILL);
127 return arcPaint;
128 }
129
130
131 private static Paint progressPaint() {
132 Paint progressPaint = new Paint();
133 progressPaint.setColor(0xFFFFFFFF);
134 progressPaint.setAntiAlias(true);
135 progressPaint.setStyle(Paint.Style.STROKE);
136 progressPaint.setStrokeWidth(ViewUtil.dpToPx(PROGRESS_ARC_STROKE_WIDTH));
137 progressPaint.setShadowLayer(4, 0, 2, 0x40000000);
138 return progressPaint;
139 }
140
141 private void initializeImageAnimations() {
142 shrinkAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_shrink);
143 growAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_grow);
144
145 shrinkAnimation.setFillAfter(true);
146 shrinkAnimation.setFillEnabled(true);
147 growAnimation.setFillAfter(true);
148 growAnimation.setFillEnabled(true);
149 }
150
151 @Override
152 protected void onDraw(Canvas canvas) {
153 super.onDraw(canvas);
154
155 if (isRecordingVideo) {
156 drawForVideoCapture(canvas);
157 } else {
158 drawForImageCapture(canvas);
159 }
160 }
161
162 private void drawForImageCapture(Canvas canvas) {
163 float centerX = getWidth() / 2f;
164 float centerY = getHeight() / 2f;
165
166 float radius = imageCaptureSize / 2f;
167 canvas.drawCircle(centerX, centerY, radius, backgroundPaint);
168 canvas.drawCircle(centerX, centerY, radius, arcPaint);
169 canvas.drawCircle(centerX, centerY, radius - DimensionUnit.DP.toPixels(CAPTURE_FILL_PROTECTION), captureFillPaint);
170 }
171
172 private void drawForVideoCapture(Canvas canvas) {
173 float centerX = getWidth() / 2f;
174 float centerY = getHeight() / 2f;
175
176 canvas.drawCircle(centerX, centerY, centerY, backgroundPaint);
177 canvas.drawCircle(centerX, centerY, centerY, outlinePaint);
178
179 canvas.drawCircle(centerX, centerY, recordSize / 2f, recordPaint);
180
181 progressRect.top = ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
182 progressRect.left = ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
183 progressRect.right = getWidth() - ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
184 progressRect.bottom = getHeight() - ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
185
186 canvas.drawArc(progressRect, 270f, 360f * progressPercent, false, progressPaint);
187 }
188
189 public void setVideoCaptureListener(@Nullable VideoCaptureListener videoCaptureListener) {
190 if (isRecordingVideo) throw new IllegalStateException("Cannot set video capture listener while recording");
191
192 if (videoCaptureListener != null) {
193 this.cameraButtonMode = CameraButtonMode.MIXED;
194 this.videoCaptureListener = videoCaptureListener;
195 super.setOnLongClickListener(internalLongClickListener);
196 } else {
197 this.cameraButtonMode = CameraButtonMode.IMAGE;
198 this.videoCaptureListener = null;
199 super.setOnLongClickListener(null);
200 }
201 }
202
203 public void setProgress(float percentage) {
204 progressPercent = Util.clamp(percentage, 0f, 1f);
205 invalidate();
206 }
207
208 @Override
209 public boolean onTouchEvent(MotionEvent event) {
210 if (cameraButtonMode == CameraButtonMode.IMAGE) {
211 return handleImageModeTouchEvent(event);
212 }
213
214 boolean eventWasHandled = handleVideoModeTouchEvent(event);
215 int action = event.getAction();
216
217 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
218 isRecordingVideo = false;
219 }
220
221 return eventWasHandled;
222 }
223
224 @Override
225 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
226 super.onLayout(changed, left, top, right, bottom);
227
228 getLocalVisibleRect(deadzoneRect);
229 deadzoneRect.left += (int) (getWidth() * DEADZONE_REDUCTION_PERCENT / 2f);
230 deadzoneRect.top += (int) (getHeight() * DEADZONE_REDUCTION_PERCENT / 2f);
231 deadzoneRect.right -= (int) (getWidth() * DEADZONE_REDUCTION_PERCENT / 2f);
232 deadzoneRect.bottom -= (int) (getHeight() * DEADZONE_REDUCTION_PERCENT / 2f);
233 }
234
235 private boolean handleImageModeTouchEvent(MotionEvent event) {
236 int action = event.getAction();
237 switch (action) {
238 case MotionEvent.ACTION_DOWN:
239 if (isEnabled()) {
240 startAnimation(shrinkAnimation);
241 performClick();
242 }
243 return true;
244 case MotionEvent.ACTION_UP:
245 startAnimation(growAnimation);
246 return true;
247 default:
248 return super.onTouchEvent(event);
249 }
250 }
251
252 private boolean handleVideoModeTouchEvent(MotionEvent event) {
253 int action = event.getAction();
254 switch (action) {
255 case MotionEvent.ACTION_DOWN:
256 if (isEnabled()) {
257 startAnimation(shrinkAnimation);
258 }
259 case MotionEvent.ACTION_MOVE:
260 if (isRecordingVideo && eventIsNotInsideDeadzone(event)) {
261
262 float maxRange = getHeight() * DRAG_DISTANCE_MULTIPLIER;
263 float deltaY = Math.abs(event.getY() - deadzoneRect.top);
264 float increment = Math.min(1f, deltaY / maxRange);
265
266 notifyZoomPercent(ZOOM_INTERPOLATOR.getInterpolation(increment));
267 invalidate();
268 }
269 break;
270 case MotionEvent.ACTION_CANCEL:
271 case MotionEvent.ACTION_UP:
272 if (!isRecordingVideo) {
273 startAnimation(growAnimation);
274 }
275 notifyVideoCaptureEnded();
276 break;
277 }
278
279 return super.onTouchEvent(event);
280 }
281
282 private boolean eventIsNotInsideDeadzone(MotionEvent event) {
283 return Math.round(event.getY()) < deadzoneRect.top;
284 }
285
286 private void notifyVideoCaptureStarted() {
287 if (!isRecordingVideo && videoCaptureListener != null) {
288 videoCaptureListener.onVideoCaptureStarted();
289 }
290 }
291
292 private void notifyVideoCaptureEnded() {
293 if (isRecordingVideo && videoCaptureListener != null) {
294 videoCaptureListener.onVideoCaptureComplete();
295 }
296 }
297
298 private void notifyZoomPercent(float percent) {
299 if (isRecordingVideo && videoCaptureListener != null) {
300 videoCaptureListener.onZoomIncremented(percent);
301 }
302 }
303
304 interface VideoCaptureListener {
305 void onVideoCaptureStarted();
306 void onVideoCaptureComplete();
307 void onZoomIncremented(float percent);
308 }
309}