diff --git a/native/media/camera-modal.react.js b/native/media/camera-modal.react.js --- a/native/media/camera-modal.react.js +++ b/native/media/camera-modal.react.js @@ -15,19 +15,12 @@ } from 'react-native'; import { RNCamera } from 'react-native-camera'; import filesystem from 'react-native-fs'; -import { - PinchGestureHandler, - State as GestureState, - type PinchGestureEvent, - Gesture, - GestureDetector, -} from 'react-native-gesture-handler'; +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Orientation from 'react-native-orientation-locker'; import type { Orientations } from 'react-native-orientation-locker'; import Reanimated, { EasingNode as ReanimatedEasingNode, Easing as ReanimatedEasing, - type EventResult, useAnimatedReaction, useAnimatedStyle, useSharedValue, @@ -36,13 +29,13 @@ withTiming, cancelAnimation, runOnJS, + interpolate, } from 'react-native-reanimated'; import { SafeAreaView } from 'react-native-safe-area-context'; import { pathFromURI, filenameFromPathOrURI } from 'lib/media/file-utils.js'; import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js'; import type { PhotoCapture } from 'lib/types/media-types.js'; -import type { ReactRef } from 'lib/types/react-types.js'; import type { Dispatch } from 'lib/types/redux-types.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; @@ -66,27 +59,9 @@ type ViewStyle, type AnimatedViewStyle, } from '../types/styles.js'; -import { gestureJustEnded } from '../utils/animation-utils.js'; - -const { - Value, - Node, - event, - Extrapolate, - block, - set, - call, - cond, - or, - eq, - greaterThan, - sub, - multiply, - divide, - abs, - interpolateNode, - timing, -} = Reanimated; +import { clampV2 } from '../utils/animation-utils.js'; + +const { Value, Extrapolate, interpolateNode, timing } = Reanimated; const maxZoom = 16; const zoomUpdateFactor = (() => { @@ -182,7 +157,6 @@ +focusOnPoint: (input: [number, number]) => void, +zoom: number, +setZoom: (zoom: number) => void, - +updateZoom: (zoom: [number]) => void, +stagingMode: boolean, +setStagingMode: (stagingMode: boolean) => void, +pendingPhotoCapture: ?PhotoCapture, @@ -208,10 +182,6 @@ }; class CameraModal extends React.PureComponent { - pinchEvent: EventResult; - pinchHandler: ReactRef = React.createRef(); - animationCode: Node; - stagingModeProgress: Value = new Value(0); sendButtonProgress: Animated.Value = new Animated.Value(0); sendButtonStyle: ViewStyle; @@ -238,61 +208,6 @@ ...styles.overlay, opacity: overlayOpacity, }; - - const pinchState = new Value(-1); - const pinchScale = new Value(1); - this.pinchEvent = event([ - { - nativeEvent: { - state: pinchState, - scale: pinchScale, - }, - }, - ]); - - this.animationCode = block([ - this.zoomAnimationCode(pinchState, pinchScale), - ]); - } - - zoomAnimationCode(pinchState: Node, pinchScale: Node): Node { - const pinchJustEnded = gestureJustEnded(pinchState); - - const zoomBase = new Value(1); - const zoomReported = new Value(1); - - const currentZoom = interpolateNode(multiply(zoomBase, pinchScale), { - inputRange: [1, 8], - outputRange: [1, 8], - extrapolate: Extrapolate.CLAMP, - }); - const cameraZoomFactor = interpolateNode(zoomReported, { - inputRange: [1, 8], - outputRange: [0, 1], - extrapolate: Extrapolate.CLAMP, - }); - const resolvedZoom = cond( - eq(pinchState, GestureState.ACTIVE), - currentZoom, - zoomBase, - ); - - return block([ - cond(pinchJustEnded, set(zoomBase, currentZoom)), - cond( - or( - pinchJustEnded, - greaterThan( - abs(sub(divide(resolvedZoom, zoomReported), 1)), - zoomUpdateFactor, - ), - ), - [ - set(zoomReported, resolvedZoom), - call([cameraZoomFactor], this.props.updateZoom), - ], - ), - ]); } componentDidUpdate(prevProps: Props) { @@ -488,7 +403,6 @@ return ( {statusBar} - { + const updateZoom = React.useCallback((nextZoom: number) => { setZoom(nextZoom); }, []); @@ -1080,9 +994,48 @@ [focusIndicatorAnimatedStyle], ); + const zoomBase = useSharedValue(1); + const zoomReported = useSharedValue(1); + const currentZoom = useSharedValue(1); + + const onPinchUpdate = React.useCallback( + (pinchScale: number) => { + 'worklet'; + currentZoom.value = clampV2(zoomBase.value * pinchScale, 1, 8); + if ( + Math.abs(currentZoom.value / zoomReported.value - 1) > + zoomUpdateFactor + ) { + zoomReported.value = currentZoom.value; + const cameraZoomFactor = interpolate( + zoomReported.value, + [1, 8], + [0, 1], + Extrapolate.CLAMP, + ); + runOnJS(updateZoom)(cameraZoomFactor); + } + }, + [currentZoom, updateZoom, zoomBase.value, zoomReported], + ); + + const onPinchEnd = React.useCallback(() => { + 'worklet'; + zoomReported.value = currentZoom.value; + zoomBase.value = currentZoom.value; + const cameraZoomFactor = interpolate( + zoomReported.value, + [1, 8], + [0, 1], + Extrapolate.CLAMP, + ); + runOnJS(updateZoom)(cameraZoomFactor); + }, [currentZoom, updateZoom, zoomBase, zoomReported]); + const gesture = React.useMemo(() => { - // TODO: we'll use this in the next diffs - const pinchGesture = Gesture.Pinch().onUpdate((/* { scale } */) => {}); + const pinchGesture = Gesture.Pinch() + .onUpdate(({ scale }) => onPinchUpdate(scale)) + .onEnd(() => onPinchEnd()); const tapGesture = Gesture.Tap().onStart(({ x, y }) => { if (outsideButtons(x, y)) { runOnJS(focusOnPoint)([x, y]); @@ -1090,7 +1043,13 @@ } }); return Gesture.Exclusive(pinchGesture, tapGesture); - }, [focusOnPoint, outsideButtons, startFocusAnimation]); + }, [ + focusOnPoint, + onPinchEnd, + onPinchUpdate, + outsideButtons, + startFocusAnimation, + ]); return (