From 99a4e9315e7fbf8d62ee6c9ca3e3462ad5c00fc8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 2 Nov 2024 11:16:05 +0800 Subject: [PATCH 01/14] fix typo --- src/components/MultiGestureCanvas/usePanGesture.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index b31e310055ae..bb77449e7b3c 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -87,14 +87,14 @@ const usePanGesture = ({ }; // If the horizontal/vertical offset is the same after clamping to the min/max boundaries, the content is within the boundaries - const isInHoriztontalBoundary = clampedOffset.x === offsetX.value; + const isInHorizontalBoundary = clampedOffset.x === offsetX.value; const isInVerticalBoundary = clampedOffset.y === offsetY.value; return { horizontalBoundaries, verticalBoundaries, clampedOffset, - isInHoriztontalBoundary, + isInHorizontalBoundary, isInVerticalBoundary, }; }, [canvasSize.width, canvasSize.height]); @@ -108,11 +108,11 @@ const usePanGesture = ({ return; } - const {clampedOffset, isInHoriztontalBoundary, isInVerticalBoundary, horizontalBoundaries, verticalBoundaries} = getBounds(); + const {clampedOffset, isInHorizontalBoundary, isInVerticalBoundary, horizontalBoundaries, verticalBoundaries} = getBounds(); // If the content is within the horizontal/vertical boundaries of the canvas, we can smoothly phase out the animation // If not, we need to snap back to the boundaries - if (isInHoriztontalBoundary) { + if (isInHorizontalBoundary) { // If the (absolute) velocity is 0, we don't need to run an animation if (Math.abs(panVelocityX.value) !== 0) { // Phase out the pan animation From 4835a6ade968665d4b4ca36733d312146a2a5ba4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 2 Nov 2024 11:21:10 +0800 Subject: [PATCH 02/14] fix swiping image doesn't move back to its position --- src/components/MultiGestureCanvas/usePanGesture.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index bb77449e7b3c..f02047447a0b 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -7,6 +7,7 @@ import * as Browser from '@libs/Browser'; import {SPRING_CONFIG} from './constants'; import type {MultiGestureCanvasVariables} from './types'; import * as MultiGestureCanvasUtils from './utils'; +import { useCallback } from 'react'; // This value determines how fast the pan animation should phase out // We're using a "withDecay" animation to smoothly phase out the pan animation @@ -66,7 +67,8 @@ const usePanGesture = ({ // Calculates bounds of the scaled content // Can we pan left/right/up/down // Can be used to limit gesture or implementing tension effect - const getBounds = useWorkletCallback(() => { + const getBounds = useCallback(() => { + 'worklet'; let horizontalBoundary = 0; let verticalBoundary = 0; @@ -97,12 +99,13 @@ const usePanGesture = ({ isInHorizontalBoundary, isInVerticalBoundary, }; - }, [canvasSize.width, canvasSize.height]); + }, [canvasSize.width, canvasSize.height, zoomedContentWidth, zoomedContentHeight, offsetX, offsetY]); // We want to smoothly decay/end the gesture by phasing out the pan animation // In case the content is outside of the boundaries of the canvas, // we need to move the content back into the boundaries - const finishPanGesture = useWorkletCallback(() => { + const finishPanGesture = useCallback(() => { + 'worklet'; // If the content is centered within the canvas, we don't need to run any animations if (offsetX.value === 0 && offsetY.value === 0 && panTranslateX.value === 0 && panTranslateY.value === 0) { return; @@ -161,7 +164,7 @@ const usePanGesture = ({ // Reset velocity variables after we finished the pan gesture panVelocityX.value = 0; panVelocityY.value = 0; - }); + }, [offsetX, offsetY, panTranslateX, panTranslateY, panVelocityX, panVelocityY, zoomScale, isSwipingDownToClose, getBounds, onSwipeDown]); const panGesture = Gesture.Pan() .manualActivation(true) From 9b8c5ded12ede6a712a99cc9297ad3e2e271a553 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 2 Nov 2024 11:36:58 +0800 Subject: [PATCH 03/14] replace all useWorkletCallback to useCallback --- .../AvatarCropModal/AvatarCropModal.tsx | 22 ++++-- src/components/MultiGestureCanvas/index.tsx | 73 ++++++++++--------- .../MultiGestureCanvas/usePanGesture.ts | 4 +- .../MultiGestureCanvas/usePinchGesture.ts | 19 +++-- .../MultiGestureCanvas/useTapGestures.ts | 8 +- 5 files changed, 70 insertions(+), 56 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index dca0d08d11d5..cc5909fda4ab 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -4,7 +4,7 @@ import type {LayoutChangeEvent} from 'react-native'; import {Gesture, GestureHandlerRootView} from 'react-native-gesture-handler'; import type {GestureUpdateEvent, PanGestureChangeEventPayload, PanGestureHandlerEventPayload} from 'react-native-gesture-handler'; import ImageSize from 'react-native-image-size'; -import {interpolate, runOnUI, useSharedValue, useWorkletCallback} from 'react-native-reanimated'; +import {interpolate, runOnUI, useSharedValue} from 'react-native-reanimated'; import Button from '@components/Button'; import HeaderGap from '@components/HeaderGap'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -144,12 +144,16 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose /** * Validates that value is within the provided mix/max range. */ - const clamp = useWorkletCallback((value: number, [min, max]) => interpolate(value, [min, max], [min, max], 'clamp'), []); + const clamp = useCallback((value: number, [min, max]: [number, number]) => { + 'worklet'; + return interpolate(value, [min, max], [min, max], 'clamp'); + }, []); /** * Returns current image size taking into account scale and rotation. */ - const getDisplayedImageSize = useWorkletCallback(() => { + const getDisplayedImageSize = useCallback(() => { + 'worklet'; let height = imageContainerSize * scale.value; let width = imageContainerSize * scale.value; @@ -162,13 +166,14 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose } return {height, width}; - }, [imageContainerSize, scale]); + }, [imageContainerSize, scale, originalImageWidth, originalImageHeight]); /** * Validates the offset to prevent overflow, and updates the image offset. */ - const updateImageOffset = useWorkletCallback( + const updateImageOffset = useCallback( (offsetX: number, offsetY: number) => { + 'worklet'; const {height, width} = getDisplayedImageSize(); const maxOffsetX = (width - imageContainerSize) / 2; const maxOffsetY = (height - imageContainerSize) / 2; @@ -177,13 +182,14 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose prevMaxOffsetX.value = maxOffsetX; prevMaxOffsetY.value = maxOffsetY; }, - [imageContainerSize, scale, clamp], + [getDisplayedImageSize, imageContainerSize, scale, translateX, translateY, prevMaxOffsetX, prevMaxOffsetY, clamp], ); - const newScaleValue = useWorkletCallback((newSliderValue: number, containerSize: number) => { + const newScaleValue = useCallback((newSliderValue: number, containerSize: number) => { + 'worklet'; const {MAX_SCALE, MIN_SCALE} = CONST.AVATAR_CROP_MODAL; return (newSliderValue / containerSize) * (MAX_SCALE - MIN_SCALE) + MIN_SCALE; - }); + }, []); /** * Calculates new x & y image translate value on image panning diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index ff9566839d59..a77a168e9c33 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -1,12 +1,12 @@ import type {ForwardedRef} from 'react'; -import React, {useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import type {GestureType} from 'react-native-gesture-handler'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import type {GestureRef} from 'react-native-gesture-handler/lib/typescript/handlers/gestures/gesture'; import type PagerView from 'react-native-pager-view'; import type {SharedValue} from 'react-native-reanimated'; -import Animated, {cancelAnimation, runOnUI, useAnimatedStyle, useDerivedValue, useSharedValue, useWorkletCallback, withSpring} from 'react-native-reanimated'; +import Animated, {cancelAnimation, runOnUI, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring} from 'react-native-reanimated'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -110,44 +110,49 @@ function MultiGestureCanvas({ /** * Stops any currently running decay animation from panning */ - const stopAnimation = useWorkletCallback(() => { + const stopAnimation = useCallback(() => { + 'worklet'; cancelAnimation(offsetX); cancelAnimation(offsetY); - }); + }, [offsetX, offsetY]); /** * Resets the canvas to the initial state and animates back smoothly */ - const reset = useWorkletCallback((animated: boolean, callback?: () => void) => { - stopAnimation(); - - // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = 0; - offsetY.value = 0; - pinchScale.value = 1; - - if (animated) { - panTranslateX.value = withSpring(0, SPRING_CONFIG); - panTranslateY.value = withSpring(0, SPRING_CONFIG); - pinchTranslateX.value = withSpring(0, SPRING_CONFIG); - pinchTranslateY.value = withSpring(0, SPRING_CONFIG); - zoomScale.value = withSpring(1, SPRING_CONFIG, callback); - - return; - } - - panTranslateX.value = 0; - panTranslateY.value = 0; - pinchTranslateX.value = 0; - pinchTranslateY.value = 0; - zoomScale.value = 1; - - if (callback === undefined) { - return; - } - - callback(); - }); + const reset = useCallback( + (animated: boolean, callback?: () => void) => { + 'worklet'; + stopAnimation(); + + // eslint-disable-next-line react-compiler/react-compiler + offsetX.value = 0; + offsetY.value = 0; + pinchScale.value = 1; + + if (animated) { + panTranslateX.value = withSpring(0, SPRING_CONFIG); + panTranslateY.value = withSpring(0, SPRING_CONFIG); + pinchTranslateX.value = withSpring(0, SPRING_CONFIG); + pinchTranslateY.value = withSpring(0, SPRING_CONFIG); + zoomScale.value = withSpring(1, SPRING_CONFIG, callback); + + return; + } + + panTranslateX.value = 0; + panTranslateY.value = 0; + pinchTranslateX.value = 0; + pinchTranslateY.value = 0; + zoomScale.value = 1; + + if (callback === undefined) { + return; + } + + callback(); + }, + [stopAnimation, offsetX, offsetY, panTranslateX, panTranslateY, pinchTranslateX, pinchTranslateY, zoomScale], + ); const {singleTapGesture: baseSingleTapGesture, doubleTapGesture} = useTapGestures({ canvasSize, diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index f02047447a0b..2f03050b3c0f 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -1,13 +1,13 @@ /* eslint-disable no-param-reassign */ +import {useCallback} from 'react'; import {Dimensions} from 'react-native'; import type {PanGesture} from 'react-native-gesture-handler'; import {Gesture} from 'react-native-gesture-handler'; -import {runOnJS, useDerivedValue, useSharedValue, useWorkletCallback, withDecay, withSpring} from 'react-native-reanimated'; +import {runOnJS, useDerivedValue, useSharedValue, withDecay, withSpring} from 'react-native-reanimated'; import * as Browser from '@libs/Browser'; import {SPRING_CONFIG} from './constants'; import type {MultiGestureCanvasVariables} from './types'; import * as MultiGestureCanvasUtils from './utils'; -import { useCallback } from 'react'; // This value determines how fast the pan animation should phase out // We're using a "withDecay" animation to smoothly phase out the pan animation diff --git a/src/components/MultiGestureCanvas/usePinchGesture.ts b/src/components/MultiGestureCanvas/usePinchGesture.ts index 46a5e28e5732..e557e199ac50 100644 --- a/src/components/MultiGestureCanvas/usePinchGesture.ts +++ b/src/components/MultiGestureCanvas/usePinchGesture.ts @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import {useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import type {PinchGesture} from 'react-native-gesture-handler'; import {Gesture} from 'react-native-gesture-handler'; -import {runOnJS, useAnimatedReaction, useSharedValue, useWorkletCallback, withSpring} from 'react-native-reanimated'; +import {runOnJS, useAnimatedReaction, useSharedValue, withSpring} from 'react-native-reanimated'; import {SPRING_CONFIG, ZOOM_RANGE_BOUNCE_FACTORS} from './constants'; import type {MultiGestureCanvasVariables} from './types'; @@ -78,12 +78,15 @@ const usePinchGesture = ({ * Calculates the adjusted focal point of the pinch gesture, * based on the canvas size and the current offset */ - const getAdjustedFocal = useWorkletCallback( - (focalX: number, focalY: number) => ({ - x: focalX - (canvasSize.width / 2 + offsetX.value), - y: focalY - (canvasSize.height / 2 + offsetY.value), - }), - [canvasSize.width, canvasSize.height], + const getAdjustedFocal = useCallback( + (focalX: number, focalY: number) => { + 'worklet'; + return { + x: focalX - (canvasSize.width / 2 + offsetX.value), + y: focalY - (canvasSize.height / 2 + offsetY.value), + }; + }, + [canvasSize.width, canvasSize.height, offsetX, offsetY], ); // The pinch gesture is disabled when we release one of the fingers diff --git a/src/components/MultiGestureCanvas/useTapGestures.ts b/src/components/MultiGestureCanvas/useTapGestures.ts index e4bb02bd5d34..4faacc8ac972 100644 --- a/src/components/MultiGestureCanvas/useTapGestures.ts +++ b/src/components/MultiGestureCanvas/useTapGestures.ts @@ -1,8 +1,8 @@ /* eslint-disable no-param-reassign */ -import {useMemo} from 'react'; +import {useCallback, useMemo} from 'react'; import type {TapGesture} from 'react-native-gesture-handler'; import {Gesture} from 'react-native-gesture-handler'; -import {runOnJS, useWorkletCallback, withSpring} from 'react-native-reanimated'; +import {runOnJS, withSpring} from 'react-native-reanimated'; import {DOUBLE_TAP_SCALE, SPRING_CONFIG} from './constants'; import type {MultiGestureCanvasVariables} from './types'; import * as MultiGestureCanvasUtils from './utils'; @@ -46,7 +46,7 @@ const useTapGestures = ({ // On double tap the content should be zoomed to fill, but at least zoomed by DOUBLE_TAP_SCALE const doubleTapScale = useMemo(() => Math.max(DOUBLE_TAP_SCALE, maxContentScale / minContentScale), [maxContentScale, minContentScale]); - const zoomToCoordinates = useWorkletCallback( + const zoomToCoordinates = useCallback( (focalX: number, focalY: number, callback: () => void) => { 'worklet'; @@ -117,7 +117,7 @@ const useTapGestures = ({ zoomScale.value = withSpring(doubleTapScale, SPRING_CONFIG, callback); pinchScale.value = doubleTapScale; }, - [scaledContentWidth, scaledContentHeight, canvasSize, doubleTapScale], + [stopAnimation, scaledContentWidth, scaledContentHeight, canvasSize, doubleTapScale, offsetX, offsetY, zoomScale, pinchScale], ); const doubleTapGesture = Gesture.Tap() From 27374bca99e616fe2447137061343a002fccf141 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 2 Nov 2024 11:54:37 +0800 Subject: [PATCH 04/14] lint --- src/components/AvatarCropModal/AvatarCropModal.tsx | 7 ++++++- src/components/MultiGestureCanvas/index.tsx | 6 +++++- src/components/MultiGestureCanvas/usePanGesture.ts | 3 +++ src/components/MultiGestureCanvas/usePinchGesture.ts | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index cc5909fda4ab..52b335f29cab 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -146,6 +146,7 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose */ const clamp = useCallback((value: number, [min, max]: [number, number]) => { 'worklet'; + return interpolate(value, [min, max], [min, max], 'clamp'); }, []); @@ -154,6 +155,7 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose */ const getDisplayedImageSize = useCallback(() => { 'worklet'; + let height = imageContainerSize * scale.value; let width = imageContainerSize * scale.value; @@ -174,19 +176,22 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const updateImageOffset = useCallback( (offsetX: number, offsetY: number) => { 'worklet'; + const {height, width} = getDisplayedImageSize(); const maxOffsetX = (width - imageContainerSize) / 2; const maxOffsetY = (height - imageContainerSize) / 2; + // eslint-disable-next-line react-compiler/react-compiler translateX.value = clamp(offsetX, [maxOffsetX * -1, maxOffsetX]); translateY.value = clamp(offsetY, [maxOffsetY * -1, maxOffsetY]); prevMaxOffsetX.value = maxOffsetX; prevMaxOffsetY.value = maxOffsetY; }, - [getDisplayedImageSize, imageContainerSize, scale, translateX, translateY, prevMaxOffsetX, prevMaxOffsetY, clamp], + [getDisplayedImageSize, imageContainerSize, translateX, translateY, prevMaxOffsetX, prevMaxOffsetY, clamp], ); const newScaleValue = useCallback((newSliderValue: number, containerSize: number) => { 'worklet'; + const {MAX_SCALE, MIN_SCALE} = CONST.AVATAR_CROP_MODAL; return (newSliderValue / containerSize) * (MAX_SCALE - MIN_SCALE) + MIN_SCALE; }, []); diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index a77a168e9c33..53548ad8447f 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -112,6 +112,7 @@ function MultiGestureCanvas({ */ const stopAnimation = useCallback(() => { 'worklet'; + cancelAnimation(offsetX); cancelAnimation(offsetY); }, [offsetX, offsetY]); @@ -122,6 +123,7 @@ function MultiGestureCanvas({ const reset = useCallback( (animated: boolean, callback?: () => void) => { 'worklet'; + stopAnimation(); // eslint-disable-next-line react-compiler/react-compiler @@ -151,7 +153,7 @@ function MultiGestureCanvas({ callback(); }, - [stopAnimation, offsetX, offsetY, panTranslateX, panTranslateY, pinchTranslateX, pinchTranslateY, zoomScale], + [stopAnimation, offsetX, offsetY, pinchScale, panTranslateX, panTranslateY, pinchTranslateX, pinchTranslateY, zoomScale], ); const {singleTapGesture: baseSingleTapGesture, doubleTapGesture} = useTapGestures({ @@ -169,6 +171,7 @@ function MultiGestureCanvas({ onTap, shouldDisableTransformationGestures, }); + // eslint-disable-next-line react-compiler/react-compiler const singleTapGesture = baseSingleTapGesture.requireExternalGestureToFail(doubleTapGesture, panGestureRef); const panGestureSimultaneousList = useMemo( @@ -191,6 +194,7 @@ function MultiGestureCanvas({ onSwipeDown, }) .simultaneousWithExternalGesture(...panGestureSimultaneousList) + // eslint-disable-next-line react-compiler/react-compiler .withRef(panGestureRef); const pinchGesture = usePinchGesture({ diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index 2f03050b3c0f..b94ed77f150b 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -69,6 +69,7 @@ const usePanGesture = ({ // Can be used to limit gesture or implementing tension effect const getBounds = useCallback(() => { 'worklet'; + let horizontalBoundary = 0; let verticalBoundary = 0; @@ -106,6 +107,7 @@ const usePanGesture = ({ // we need to move the content back into the boundaries const finishPanGesture = useCallback(() => { 'worklet'; + // If the content is centered within the canvas, we don't need to run any animations if (offsetX.value === 0 && offsetY.value === 0 && panTranslateX.value === 0 && panTranslateY.value === 0) { return; @@ -186,6 +188,7 @@ const usePanGesture = ({ if (Math.abs(velocityY) > velocityX && velocityY > 20) { state.activate(); + // eslint-disable-next-line react-compiler/react-compiler isSwipingDownToClose.value = true; previousTouch.value = null; diff --git a/src/components/MultiGestureCanvas/usePinchGesture.ts b/src/components/MultiGestureCanvas/usePinchGesture.ts index e557e199ac50..01be2d00194a 100644 --- a/src/components/MultiGestureCanvas/usePinchGesture.ts +++ b/src/components/MultiGestureCanvas/usePinchGesture.ts @@ -81,6 +81,7 @@ const usePinchGesture = ({ const getAdjustedFocal = useCallback( (focalX: number, focalY: number) => { 'worklet'; + return { x: focalX - (canvasSize.width / 2 + offsetX.value), y: focalY - (canvasSize.height / 2 + offsetY.value), From 7306c7d0ddcdae5edff1f14aaf0f02c752bdd46f Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 2 Nov 2024 12:00:36 +0800 Subject: [PATCH 05/14] lint --- src/components/AvatarCropModal/AvatarCropModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index 52b335f29cab..7911255ba49c 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -180,9 +180,9 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const {height, width} = getDisplayedImageSize(); const maxOffsetX = (width - imageContainerSize) / 2; const maxOffsetY = (height - imageContainerSize) / 2; - // eslint-disable-next-line react-compiler/react-compiler translateX.value = clamp(offsetX, [maxOffsetX * -1, maxOffsetX]); translateY.value = clamp(offsetY, [maxOffsetY * -1, maxOffsetY]); + // eslint-disable-next-line react-compiler/react-compiler prevMaxOffsetX.value = maxOffsetX; prevMaxOffsetY.value = maxOffsetY; }, From 0e4bee696be397d3beda2f3ebd7c3d3e41707bb0 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 10 Nov 2024 15:03:36 +0800 Subject: [PATCH 06/14] disable pager scrolling when swiping image down --- src/components/Lightbox/index.tsx | 6 +++++- src/components/MultiGestureCanvas/index.tsx | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index c5a77f9d5ec4..7bcb5ef3b9e8 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -50,6 +50,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); + const isScrollingEnabledFallback = useSharedValue(true); const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); @@ -63,12 +64,14 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan onScaleChanged: onScaleChangedContext, onSwipeDown, pagerRef, + isScrollEnabled, } = useMemo(() => { if (attachmentCarouselPagerContext === null) { return { isUsedInCarousel: false, isSingleCarouselItem: true, isPagerScrolling: isPagerScrollingFallback, + isScrollEnabled: isScrollingEnabledFallback, page: 0, activePage: 0, onTap: () => {}, @@ -85,7 +88,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan isSingleCarouselItem: attachmentCarouselPagerContext.pagerItems.length === 1, page: foundPage, }; - }, [attachmentCarouselPagerContext, isPagerScrollingFallback, uri]); + }, [attachmentCarouselPagerContext, isPagerScrollingFallback, isScrollingEnabledFallback, uri]); /** Whether the Lightbox is used within an attachment carousel and there are more than one page in the carousel */ const hasSiblingCarouselItems = isUsedInCarousel && !isSingleCarouselItem; @@ -216,6 +219,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan zoomRange={zoomRange} pagerRef={pagerRef} shouldDisableTransformationGestures={isPagerScrolling} + isPagerScrollEnabled={isScrollEnabled} onTap={onTap} onScaleChanged={scaleChange} onSwipeDown={onSwipeDown} diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index 53548ad8447f..ad3779135509 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -6,7 +6,7 @@ import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import type {GestureRef} from 'react-native-gesture-handler/lib/typescript/handlers/gestures/gesture'; import type PagerView from 'react-native-pager-view'; import type {SharedValue} from 'react-native-reanimated'; -import Animated, {cancelAnimation, runOnUI, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring} from 'react-native-reanimated'; +import Animated, {cancelAnimation, runOnUI, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring} from 'react-native-reanimated'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -40,6 +40,9 @@ type MultiGestureCanvasProps = ChildrenProps & { /** A shared value of type boolean, that indicates disabled the transformation gestures (pinch, pan, double tap) */ shouldDisableTransformationGestures?: SharedValue; + /** A shared value to enable/disable the pager scroll */ + isPagerScrollEnabled: SharedValue; + /** If there is a pager wrapping the canvas, we need to disable the pan gesture in case the pager is swiping */ pagerRef?: ForwardedRef; // TODO: For TS migration: Exclude @@ -63,6 +66,7 @@ function MultiGestureCanvas({ children, pagerRef, shouldDisableTransformationGestures: shouldDisableTransformationGesturesProp, + isPagerScrollEnabled, onTap, onScaleChanged, onSwipeDown, @@ -107,6 +111,13 @@ function MultiGestureCanvas({ const offsetX = useSharedValue(0); const offsetY = useSharedValue(0); + useAnimatedReaction( + () => isSwipingDownToClose.value, + (current) => { + isPagerScrollEnabled.value = !current; + }, + ); + /** * Stops any currently running decay animation from panning */ From 69c5001a6f658aad8333f2d04c0b3ce8c31c9923 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 10 Nov 2024 15:28:28 +0800 Subject: [PATCH 07/14] suppress lint --- src/components/MultiGestureCanvas/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index ad3779135509..d0737e220569 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -114,6 +114,7 @@ function MultiGestureCanvas({ useAnimatedReaction( () => isSwipingDownToClose.value, (current) => { + // eslint-disable-next-line no-param-reassign isPagerScrollEnabled.value = !current; }, ); From 67438d0f1ef32974c2d1351fa2d535779ff4e1a8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 10 Nov 2024 16:07:31 +0800 Subject: [PATCH 08/14] suppress lint --- src/components/MultiGestureCanvas/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index d0737e220569..b9c74964cc18 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -114,7 +114,7 @@ function MultiGestureCanvas({ useAnimatedReaction( () => isSwipingDownToClose.value, (current) => { - // eslint-disable-next-line no-param-reassign + // eslint-disable-next-line react-compiler/react-compiler no-param-reassign isPagerScrollEnabled.value = !current; }, ); From 7fbe39d76c6404b4a0047ba19155c994bfd2c920 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 10 Nov 2024 16:44:59 +0800 Subject: [PATCH 09/14] suppress lint --- src/components/MultiGestureCanvas/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index b9c74964cc18..99bc7c521e5c 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -114,7 +114,7 @@ function MultiGestureCanvas({ useAnimatedReaction( () => isSwipingDownToClose.value, (current) => { - // eslint-disable-next-line react-compiler/react-compiler no-param-reassign + // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign isPagerScrollEnabled.value = !current; }, ); From ca558c198c9fbc4aff4c9bd142a80e89eacd6853 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 12 Nov 2024 12:08:54 +0800 Subject: [PATCH 10/14] change fallback value to false --- src/components/Lightbox/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 7bcb5ef3b9e8..7331518e2bb0 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -50,7 +50,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan * we need to create a shared value that can be used in the render function. */ const isPagerScrollingFallback = useSharedValue(false); - const isScrollingEnabledFallback = useSharedValue(true); + const isScrollingEnabledFallback = useSharedValue(false); const {isOffline} = useNetwork(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); From 7e32422c9e89bec29ee0d6ba315793201330b513 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 12 Nov 2024 12:09:15 +0800 Subject: [PATCH 11/14] fix can't swipe down on non carousel attachment --- src/components/Lightbox/index.tsx | 1 + src/components/MultiGestureCanvas/index.tsx | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/components/Lightbox/index.tsx b/src/components/Lightbox/index.tsx index 7331518e2bb0..9e1b007321cc 100644 --- a/src/components/Lightbox/index.tsx +++ b/src/components/Lightbox/index.tsx @@ -218,6 +218,7 @@ function Lightbox({isAuthTokenRequired = false, uri, onScaleChanged: onScaleChan contentSize={contentSize} zoomRange={zoomRange} pagerRef={pagerRef} + isUsedInCarousel={isUsedInCarousel} shouldDisableTransformationGestures={isPagerScrolling} isPagerScrollEnabled={isScrollEnabled} onTap={onTap} diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index 99bc7c521e5c..cfbd5215f5cc 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -46,6 +46,9 @@ type MultiGestureCanvasProps = ChildrenProps & { /** If there is a pager wrapping the canvas, we need to disable the pan gesture in case the pager is swiping */ pagerRef?: ForwardedRef; // TODO: For TS migration: Exclude + /** Whether the component is being used inside a carousel */ + isUsedInCarousel: boolean; + /** Handles scale changed event */ onScaleChanged?: OnScaleChangedCallback; @@ -65,6 +68,7 @@ function MultiGestureCanvas({ isActive = true, children, pagerRef, + isUsedInCarousel, shouldDisableTransformationGestures: shouldDisableTransformationGesturesProp, isPagerScrollEnabled, onTap, @@ -114,6 +118,9 @@ function MultiGestureCanvas({ useAnimatedReaction( () => isSwipingDownToClose.value, (current) => { + if (!isUsedInCarousel) { + return; + } // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign isPagerScrollEnabled.value = !current; }, From 4e53b33313c3deb950e4952b77fea2649fe259fe Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Nov 2024 00:26:53 +0800 Subject: [PATCH 12/14] fix can't swipe down on mweb --- .../Attachments/AttachmentCarousel/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index d9c4f7e93fbe..c7af769419f7 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -61,7 +61,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi const [attachments, setAttachments] = useState([]); const [activeSource, setActiveSource] = useState(source); const {shouldShowArrows, setShouldShowArrows, autoHideArrows, cancelAutoHideArrows} = useCarouselArrows(); - const {handleTap, handleScaleChange, scale} = useCarouselContextEvents(setShouldShowArrows); + const {handleTap, handleScaleChange, isScrollEnabled} = useCarouselContextEvents(setShouldShowArrows); useEffect(() => { if (!canUseTouchScreen) { @@ -201,12 +201,12 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi activePage: 0, pagerRef, isPagerScrolling: nope, - isScrollEnabled: nope, + isScrollEnabled, onTap: handleTap, onScaleChanged: handleScaleChange, onSwipeDown: onClose, }), - [source, nope, handleTap, handleScaleChange, onClose], + [source, nope, isScrollEnabled, handleTap, handleScaleChange, onClose], ); /** Defines how a single attachment should be rendered */ @@ -229,14 +229,14 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi Gesture.Pan() .enabled(canUseTouchScreen) .onUpdate(({translationX}) => { - if (scale.current !== 1) { + if (!isScrollEnabled.value) { return; } scrollTo(scrollRef, page * cellWidth - translationX, 0, false); }) .onEnd(({translationX, velocityX}) => { - if (scale.current !== 1) { + if (!isScrollEnabled.value) { return; } @@ -257,7 +257,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi }) // eslint-disable-next-line react-compiler/react-compiler .withRef(pagerRef as MutableRefObject), - [attachments.length, canUseTouchScreen, cellWidth, page, scale, scrollRef], + [attachments.length, canUseTouchScreen, cellWidth, page, isScrollEnabled, scrollRef], ); return ( From d55ddeeca65a87dccfe964887869c016bfb9c3ac Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Nov 2024 10:45:07 +0800 Subject: [PATCH 13/14] prevent swiping down when scrolling --- .../Attachments/AttachmentCarousel/index.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index c7af769419f7..2f94667d9c18 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -46,7 +46,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi const styles = useThemeStyles(); const {isFullScreenRef} = useFullScreenContext(); const scrollRef = useAnimatedRef>>(); - const nope = useSharedValue(false); + const isPagerScrolling = useSharedValue(false); const pagerRef = useRef(null); const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, {canEvict: false}); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, {canEvict: false}); @@ -200,13 +200,13 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi pagerItems: [{source, index: 0, isActive: true}], activePage: 0, pagerRef, - isPagerScrolling: nope, + isPagerScrolling, isScrollEnabled, onTap: handleTap, onScaleChanged: handleScaleChange, onSwipeDown: onClose, }), - [source, nope, isScrollEnabled, handleTap, handleScaleChange, onClose], + [source, isPagerScrolling, isScrollEnabled, handleTap, handleScaleChange, onClose], ); /** Defines how a single attachment should be rendered */ @@ -233,6 +233,10 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi return; } + if (translationX !== 0) { + isPagerScrolling.value = true; + } + scrollTo(scrollRef, page * cellWidth - translationX, 0, false); }) .onEnd(({translationX, velocityX}) => { @@ -253,6 +257,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi newIndex = Math.min(attachments.length - 1, Math.max(0, page + delta)); } + isPagerScrolling.value = false; scrollTo(scrollRef, newIndex * cellWidth, 0, true); }) // eslint-disable-next-line react-compiler/react-compiler From 5ecd45eefcc6db298e0a99a619d465e1da28f7e0 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Nov 2024 10:51:09 +0800 Subject: [PATCH 14/14] lint --- src/components/Attachments/AttachmentCarousel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 2f94667d9c18..b578da242d88 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -262,7 +262,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi }) // eslint-disable-next-line react-compiler/react-compiler .withRef(pagerRef as MutableRefObject), - [attachments.length, canUseTouchScreen, cellWidth, page, isScrollEnabled, scrollRef], + [attachments.length, canUseTouchScreen, cellWidth, page, isScrollEnabled, scrollRef, isPagerScrolling], ); return (