That fuck shit the fascists are using
at master 309 lines 10 kB view raw
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}