From fba7f9e5070f06c1bff281e4bbf0655ec6c7f497 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 22 Dec 2023 11:08:23 +0100 Subject: [PATCH 01/11] Update RNGH and disable legacy web implementation --- index.js | 2 -- ios/Podfile.lock | 5 +++-- package-lock.json | 14 +++++++------- package.json | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 2a3de088f934..283120f50395 100644 --- a/index.js +++ b/index.js @@ -2,11 +2,9 @@ * @format */ import {AppRegistry} from 'react-native'; -import {enableLegacyWebImplementation} from 'react-native-gesture-handler'; import App from './src/App'; import Config from './src/CONFIG'; import additionalAppSetup from './src/setup'; -enableLegacyWebImplementation(true); AppRegistry.registerComponent(Config.APP_NAME, () => App); additionalAppSetup(); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0fac30a26430..eb91b7428ba5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -755,7 +755,8 @@ PODS: - React-Core - RNFS (2.20.0): - React-Core - - RNGestureHandler (2.12.0): + - RNGestureHandler (2.14.0): + - RCT-Folly (= 2021.07.22.00) - React-Core - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) @@ -1274,7 +1275,7 @@ SPEC CHECKSUMS: RNFBPerf: 389914cda4000fe0d996a752532a591132cbf3f9 RNFlashList: 236646d48f224a034f35baa0242e1b77db063b1e RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5 + RNGestureHandler: 32a01c29ecc9bb0b5bf7bc0a33547f61b4dc2741 RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64 diff --git a/package-lock.json b/package-lock.json index 8afb802751f4..88b7f8fc49ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,7 @@ "react-native-draggable-flatlist": "^4.0.1", "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", - "react-native-gesture-handler": "2.12.0", + "react-native-gesture-handler": "2.14.0", "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", @@ -44278,9 +44278,9 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.0.tgz", - "integrity": "sha512-rr+XwVzXAVpY8co25ukvyI38fKCxTQjz7WajeZktl8qUPdh1twnSExgpT47DqDi4n+m+OiJPAnHfZOkqqAQMOg==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz", + "integrity": "sha512-cOmdaqbpzjWrOLUpX3hdSjsMby5wq3PIEdMq7okJeg9DmCzanysHSrktw1cXWNc/B5MAgxAn9J7Km0/4UIqKAQ==", "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -84712,9 +84712,9 @@ } }, "react-native-gesture-handler": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.0.tgz", - "integrity": "sha512-rr+XwVzXAVpY8co25ukvyI38fKCxTQjz7WajeZktl8qUPdh1twnSExgpT47DqDi4n+m+OiJPAnHfZOkqqAQMOg==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz", + "integrity": "sha512-cOmdaqbpzjWrOLUpX3hdSjsMby5wq3PIEdMq7okJeg9DmCzanysHSrktw1cXWNc/B5MAgxAn9J7Km0/4UIqKAQ==", "requires": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", diff --git a/package.json b/package.json index 8b2db0afd546..f6318ead8c47 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "react-native-draggable-flatlist": "^4.0.1", "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", - "react-native-gesture-handler": "2.12.0", + "react-native-gesture-handler": "2.14.0", "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", From 5017d3a30d3182715af2ef9acec847156460915e Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 22 Dec 2023 11:09:43 +0100 Subject: [PATCH 02/11] Update AvatarCropModal to use the new RNGH API --- .../AvatarCropModal/AvatarCropModal.js | 85 ++++++++----------- .../AvatarCropModal/ImageCropView.js | 14 +-- src/components/AvatarCropModal/Slider.js | 37 +++++--- .../gestureHandlerPropTypes.js | 21 ----- 4 files changed, 69 insertions(+), 88 deletions(-) delete mode 100644 src/components/AvatarCropModal/gestureHandlerPropTypes.js diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index a39daeb78ba7..7bdc50a3d058 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {ActivityIndicator, Image, View} from 'react-native'; -import {GestureHandlerRootView} from 'react-native-gesture-handler'; -import {interpolate, runOnUI, useAnimatedGestureHandler, useSharedValue, useWorkletCallback} from 'react-native-reanimated'; +import {GestureHandlerRootView, Gesture} from 'react-native-gesture-handler'; +import {interpolate, runOnUI, useSharedValue, useWorkletCallback} from 'react-native-reanimated'; import Button from '@components/Button'; import HeaderGap from '@components/HeaderGap'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -202,25 +202,13 @@ function AvatarCropModal(props) { * Calculates new x & y image translate value on image panning * and updates image's offset. */ - const panGestureEventHandler = useAnimatedGestureHandler( - { - onStart: (_, context) => { - // we have to assign translate values to a context - // since that is required for proper work of turbo modules. - // eslint-disable-next-line no-param-reassign - context.translateX = translateX.value; - // eslint-disable-next-line no-param-reassign - context.translateY = translateY.value; - }, - onActive: (event, context) => { - const newX = event.translationX + context.translateX; - const newY = event.translationY + context.translateY; - - updateImageOffset(newX, newY); - }, - }, - [imageContainerSize, updateImageOffset, translateX, translateY], - ); + const panGesture = Gesture.Pan() + .onChange((event) => { + const newX = translateX.value + event.changeX; + const newY = translateY.value + event.changeY; + + updateImageOffset(newX, newY); + }); // This effect is needed to recalculate the maximum offset values // when the browser window is resized. @@ -250,32 +238,33 @@ function AvatarCropModal(props) { * Calculates new scale value and updates images offset to ensure * that image stays in the center of the container after changing scale. */ - const panSliderGestureEventHandler = useAnimatedGestureHandler( - { - onStart: (_, context) => { - // we have to assign this value to a context - // since that is required for proper work of turbo modules. - // eslint-disable-next-line no-param-reassign - context.translateSliderX = translateSlider.value; - isPressableEnabled.value = false; - }, - onActive: (event, context) => { - const newSliderValue = clamp(event.translationX + context.translateSliderX, [0, sliderContainerSize]); - const newScale = newScaleValue(newSliderValue, sliderContainerSize); - - const differential = newScale / scale.value; - - scale.value = newScale; - translateSlider.value = newSliderValue; - - const newX = translateX.value * differential; - const newY = translateY.value * differential; - updateImageOffset(newX, newY); - }, - onEnd: () => (isPressableEnabled.value = true), + const sliderPanGestureCallbacks = { + onBegin: () => { + 'worklet'; + + isPressableEnabled.value = false; }, - [imageContainerSize, clamp, translateX, translateY, translateSlider, scale, sliderContainerSize, isPressableEnabled], - ); + onChange: (event) => { + 'worklet'; + + const newSliderValue = clamp(translateSlider.value + event.changeX, [0, sliderContainerSize]); + const newScale = newScaleValue(newSliderValue, sliderContainerSize); + + const differential = newScale / scale.value; + + scale.value = newScale; + translateSlider.value = newSliderValue; + + const newX = translateX.value * differential; + const newY = translateY.value * differential; + updateImageOffset(newX, newY); + }, + onFinalize: () => { + 'worklet'; + + isPressableEnabled.value = true; + } + }; // This effect is needed to prevent the incorrect position of // the slider's knob when the window's layout changes @@ -393,7 +382,7 @@ function AvatarCropModal(props) { {}, + panGesture: Gesture.Pan(), maskImage: Expensicons.ImageCropCircleMask, }; @@ -75,7 +75,7 @@ function ImageCropView(props) { // We're preventing text selection with ControlSelection.blockElement to prevent safari // default behaviour of cursor - I-beam cursor on drag. See https://github.com/Expensify/App/issues/13688 return ( - + - + ); } diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index ba2e1471ce9e..47f4ec75207f 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -1,17 +1,16 @@ import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; -import {PanGestureHandler} from 'react-native-gesture-handler'; -import Animated, {useAnimatedStyle} from 'react-native-reanimated'; +import {Gesture, GestureDetector} from 'react-native-gesture-handler'; +import Animated, {useAnimatedStyle, runOnJS} from 'react-native-reanimated'; import Tooltip from '@components/Tooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; -import gestureHandlerPropTypes from './gestureHandlerPropTypes'; const propTypes = { - /** React-native-reanimated lib handler which executes when the user is panning slider */ - onGesture: gestureHandlerPropTypes, + /** Callbacks for react-native-gesture-handler to be executed when the user is panning slider */ + gestureCallbacks: PropTypes.shape({onBegin: PropTypes.func, onChange: PropTypes.func, onFinalize: PropTypes.func}), /** X position of the slider knob */ sliderValue: PropTypes.shape({value: PropTypes.number}), @@ -20,7 +19,11 @@ const propTypes = { }; const defaultProps = { - onGesture: () => {}, + gestureCallbacks: { + onBegin: () => {'worklet';}, + onChange: () => {'worklet';}, + onFinalize: () => {'worklet';}, + }, sliderValue: {}, }; @@ -36,6 +39,20 @@ function Slider(props) { transform: [{translateX: sliderValue.value}], })); + const panGesture = Gesture.Pan() + .minDistance(5) + .onBegin(() => { + runOnJS(setTooltipIsVisible)(false) + props.gestureCallbacks.onBegin(); + }) + .onChange((event) => { + props.gestureCallbacks.onChange(event); + }) + .onFinalize(() => { + runOnJS(setTooltipIsVisible)(true) + props.gestureCallbacks.onFinalize(); + }); + // We're preventing text selection with ControlSelection.blockElement to prevent safari // default behaviour of cursor - I-beam cursor on drag. See https://github.com/Expensify/App/issues/13688 return ( @@ -43,11 +60,7 @@ function Slider(props) { ref={ControlSelection.blockElement} style={styles.sliderBar} > - setTooltipIsVisible(false)} - onEnded={() => setTooltipIsVisible(true)} - onGestureEvent={props.onGesture} - > + {tooltipIsVisible && ( )} - + ); } diff --git a/src/components/AvatarCropModal/gestureHandlerPropTypes.js b/src/components/AvatarCropModal/gestureHandlerPropTypes.js deleted file mode 100644 index c473a162ba7e..000000000000 --- a/src/components/AvatarCropModal/gestureHandlerPropTypes.js +++ /dev/null @@ -1,21 +0,0 @@ -import PropTypes from 'prop-types'; - -export default PropTypes.oneOfType([ - // Executes once a gesture is triggered - PropTypes.func, - PropTypes.shape({ - current: PropTypes.shape({ - // Array of event names that will be handled by animation handler - eventNames: PropTypes.arrayOf(PropTypes.string), - - // Array of registered event handlers ids - registrations: PropTypes.arrayOf(PropTypes.number), - - // React tag of the node we want to manage - viewTag: PropTypes.number, - - // Executes once a gesture is triggered - worklet: PropTypes.func, - }), - }), -]); From df8ba58e13605910783ed642033389a1fa22c92f Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 22 Dec 2023 12:11:00 +0100 Subject: [PATCH 03/11] Update GrowlNotification to use the new RNGH API --- .../react-native-gesture-handler+2.14.0.patch | 82 +++++++++++++++++++ src/components/GrowlNotification/index.js | 44 +++++----- 2 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 patches/react-native-gesture-handler+2.14.0.patch diff --git a/patches/react-native-gesture-handler+2.14.0.patch b/patches/react-native-gesture-handler+2.14.0.patch new file mode 100644 index 000000000000..70bcdecb24a2 --- /dev/null +++ b/patches/react-native-gesture-handler+2.14.0.patch @@ -0,0 +1,82 @@ +diff --git a/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js b/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js +index 0a43a8f..751b4fa 100644 +--- a/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js ++++ b/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js +@@ -16,7 +16,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + const DEFAULT_MAX_DURATION_MS = 800; +-const DEFAULT_MIN_ACCEPTABLE_DELTA = 160; ++const DEFAULT_MIN_ACCEPTABLE_DELTA = 35; + const DEFAULT_DIRECTION = _constants.Direction.RIGHT; + const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; + +@@ -141,13 +141,12 @@ class FlingGestureHandler extends _GestureHandler.default { + } + + onUp(event) { +- this.tracker.removeFromTracker(event.pointerId); +- + if (this.currentState !== _State.State.BEGAN) { + return; + } + + this.endFling(); ++ this.tracker.removeFromTracker(event.pointerId); + } + + activate(force) { +diff --git a/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js b/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js +index b6dbc26..69843cb 100644 +--- a/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js ++++ b/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js +@@ -4,7 +4,7 @@ import { State } from '../../State'; + import { Direction } from '../constants'; + import GestureHandler from './GestureHandler'; + const DEFAULT_MAX_DURATION_MS = 800; +-const DEFAULT_MIN_ACCEPTABLE_DELTA = 160; ++const DEFAULT_MIN_ACCEPTABLE_DELTA = 35; + const DEFAULT_DIRECTION = Direction.RIGHT; + const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; + export default class FlingGestureHandler extends GestureHandler { +@@ -128,13 +128,12 @@ export default class FlingGestureHandler extends GestureHandler { + } + + onUp(event) { +- this.tracker.removeFromTracker(event.pointerId); +- + if (this.currentState !== State.BEGAN) { + return; + } + + this.endFling(); ++ this.tracker.removeFromTracker(event.pointerId); + } + + activate(force) { +diff --git a/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts b/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts +index 0c2a117..929c0c7 100644 +--- a/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts ++++ b/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts +@@ -5,7 +5,7 @@ import { AdaptedEvent, Config } from '../interfaces'; + import GestureHandler from './GestureHandler'; + + const DEFAULT_MAX_DURATION_MS = 800; +-const DEFAULT_MIN_ACCEPTABLE_DELTA = 160; ++const DEFAULT_MIN_ACCEPTABLE_DELTA = 35; + const DEFAULT_DIRECTION = Direction.RIGHT; + const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; + +@@ -141,11 +141,11 @@ export default class FlingGestureHandler extends GestureHandler { + } + + private onUp(event: AdaptedEvent): void { +- this.tracker.removeFromTracker(event.pointerId); + if (this.currentState !== State.BEGAN) { + return; + } + this.endFling(); ++ this.tracker.removeFromTracker(event.pointerId); + } + + public activate(force?: boolean): void { diff --git a/src/components/GrowlNotification/index.js b/src/components/GrowlNotification/index.js index bcb4a8703b94..488026fdc401 100644 --- a/src/components/GrowlNotification/index.js +++ b/src/components/GrowlNotification/index.js @@ -1,6 +1,6 @@ import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {Animated, View} from 'react-native'; -import {Directions, FlingGestureHandler, State} from 'react-native-gesture-handler'; +import {Directions, Gesture, GestureDetector} from 'react-native-gesture-handler'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Pressables from '@components/Pressable'; @@ -92,34 +92,34 @@ function GrowlNotification(_, ref) { }, duration); }, [duration, fling]); - return ( - { - if (nativeEvent.state !== State.ACTIVE) { - return; - } + // GestureDetector by default runs callbacks on UI thread using Reanimated. In this + // case we want to trgger an RN's Animated animation, which needs to be done on JS thread. + const flingGesture = Gesture.Fling() + .direction(Directions.UP) + .runOnJS(true) + .onStart(() => { + fling(); + }) - fling(); - }} - > - - - fling()} - > + return ( + + + fling()} + > + {bodyText} - - - - - + + + + + ); } From 0277318f1deaaa083ca27ac74ac397dbf2f6ea35 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 22 Dec 2023 16:33:00 +0100 Subject: [PATCH 04/11] Update MagicCodeInput to use the new RNGH API --- src/components/MagicCodeInput.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index 55a65237a691..c4852e5208af 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {StyleSheet, View} from 'react-native'; -import {TapGestureHandler} from 'react-native-gesture-handler'; +import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import _ from 'underscore'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -204,12 +204,11 @@ function MagicCodeInput(props) { }; /** - * Callback for the onPress event, updates the indexes - * of the currently focused input. - * - * @param {Number} index + * Tap gesture configuration, updates the indexes of the + * currently focused input. */ - const onPress = (index) => { + const tapGesture = Gesture.Tap().onBegin((event) => { + const index = Math.floor(event.x / (inputWidth.current / props.maxLength)); shouldFocusLast.current = false; // TapGestureHandler works differently on mobile web and native app // On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually @@ -218,7 +217,7 @@ function MagicCodeInput(props) { } setInputAndIndex(index); lastFocusedIndex.current = index; - }; + }); /** * Updates the magic inputs with the contents written in the @@ -357,11 +356,7 @@ function MagicCodeInput(props) { return ( <> - { - onPress(Math.floor(e.nativeEvent.x / (inputWidth.current / props.maxLength))); - }} - > + {/* Android does not handle touch on invisible Views so I created a wrapper around invisible TextInput just to handle taps */} - + {_.map(getInputPlaceholderSlots(props.maxLength), (index) => ( Date: Mon, 8 Jan 2024 12:05:58 +0100 Subject: [PATCH 05/11] Fix slider on mobile safari --- src/components/AvatarCropModal/Slider.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index 47f4ec75207f..cd7c8df18eee 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -67,7 +67,8 @@ function Slider(props) { text={props.translate('common.zoom')} shiftVertical={-2} > - + {/* pointerEvents='none' is a workaround to make sure the pan gesture works correctly on mobile safari */} + )} From 42ac2dc49937594dd33bd5ecf5a6df084ed4c9f8 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Mon, 8 Jan 2024 17:48:07 +0100 Subject: [PATCH 06/11] Run prettier --- .../AvatarCropModal/AvatarCropModal.js | 15 ++++++------ src/components/AvatarCropModal/Slider.js | 23 +++++++++++++------ src/components/GrowlNotification/index.js | 4 ++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 33c194ac6e61..2da3cf88c78c 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {ActivityIndicator, Image, View} from 'react-native'; -import {GestureHandlerRootView, Gesture} from 'react-native-gesture-handler'; +import {Gesture, GestureHandlerRootView} from 'react-native-gesture-handler'; import {interpolate, runOnUI, useSharedValue, useWorkletCallback} from 'react-native-reanimated'; import Button from '@components/Button'; import HeaderGap from '@components/HeaderGap'; @@ -203,13 +203,12 @@ function AvatarCropModal(props) { * Calculates new x & y image translate value on image panning * and updates image's offset. */ - const panGesture = Gesture.Pan() - .onChange((event) => { - const newX = translateX.value + event.changeX; - const newY = translateY.value + event.changeY; + const panGesture = Gesture.Pan().onChange((event) => { + const newX = translateX.value + event.changeX; + const newY = translateY.value + event.changeY; - updateImageOffset(newX, newY); - }); + updateImageOffset(newX, newY); + }); // This effect is needed to recalculate the maximum offset values // when the browser window is resized. @@ -264,7 +263,7 @@ function AvatarCropModal(props) { 'worklet'; isPressableEnabled.value = true; - } + }, }; // This effect is needed to prevent the incorrect position of diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index cd7c8df18eee..09ce8b0c95a5 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; -import Animated, {useAnimatedStyle, runOnJS} from 'react-native-reanimated'; +import Animated, {runOnJS, useAnimatedStyle} from 'react-native-reanimated'; import Tooltip from '@components/Tooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -20,9 +20,15 @@ const propTypes = { const defaultProps = { gestureCallbacks: { - onBegin: () => {'worklet';}, - onChange: () => {'worklet';}, - onFinalize: () => {'worklet';}, + onBegin: () => { + 'worklet'; + }, + onChange: () => { + 'worklet'; + }, + onFinalize: () => { + 'worklet'; + }, }, sliderValue: {}, }; @@ -42,14 +48,14 @@ function Slider(props) { const panGesture = Gesture.Pan() .minDistance(5) .onBegin(() => { - runOnJS(setTooltipIsVisible)(false) + runOnJS(setTooltipIsVisible)(false); props.gestureCallbacks.onBegin(); }) .onChange((event) => { props.gestureCallbacks.onChange(event); }) .onFinalize(() => { - runOnJS(setTooltipIsVisible)(true) + runOnJS(setTooltipIsVisible)(true); props.gestureCallbacks.onFinalize(); }); @@ -68,7 +74,10 @@ function Slider(props) { shiftVertical={-2} > {/* pointerEvents='none' is a workaround to make sure the pan gesture works correctly on mobile safari */} - + )} diff --git a/src/components/GrowlNotification/index.js b/src/components/GrowlNotification/index.js index 488026fdc401..fcaa2e03db67 100644 --- a/src/components/GrowlNotification/index.js +++ b/src/components/GrowlNotification/index.js @@ -99,7 +99,7 @@ function GrowlNotification(_, ref) { .runOnJS(true) .onStart(() => { fling(); - }) + }); return ( @@ -115,7 +115,7 @@ function GrowlNotification(_, ref) { fill={types[type].iconColor} /> {bodyText} - + From 643cf834468b99828b212bbe9ced0d583580e570 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 11 Jan 2024 13:07:27 +0100 Subject: [PATCH 07/11] Update src/components/AvatarCropModal/Slider.js Co-authored-by: Tomek Zawadzki --- src/components/AvatarCropModal/Slider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index 09ce8b0c95a5..18ea2bb0b32f 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -73,7 +73,7 @@ function Slider(props) { text={props.translate('common.zoom')} shiftVertical={-2} > - {/* pointerEvents='none' is a workaround to make sure the pan gesture works correctly on mobile safari */} + {/* pointerEvents="none" is a workaround to make sure the pan gesture works correctly on mobile Safari */} Date: Thu, 11 Jan 2024 13:07:33 +0100 Subject: [PATCH 08/11] Update src/components/GrowlNotification/index.js Co-authored-by: Tomek Zawadzki --- src/components/GrowlNotification/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/GrowlNotification/index.js b/src/components/GrowlNotification/index.js index fcaa2e03db67..ed0dd302f705 100644 --- a/src/components/GrowlNotification/index.js +++ b/src/components/GrowlNotification/index.js @@ -93,7 +93,7 @@ function GrowlNotification(_, ref) { }, [duration, fling]); // GestureDetector by default runs callbacks on UI thread using Reanimated. In this - // case we want to trgger an RN's Animated animation, which needs to be done on JS thread. + // case we want to trigger an RN's Animated animation, which needs to be done on JS thread. const flingGesture = Gesture.Fling() .direction(Directions.UP) .runOnJS(true) From 4be21af513da2e1c29c655f6b3232b3b9415ab5a Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 12 Jan 2024 14:04:19 +0100 Subject: [PATCH 09/11] Update Gesture Handler to 2.14.1 --- ios/Podfile.lock | 6 +- package-lock.json | 14 ++-- package.json | 2 +- .../react-native-gesture-handler+2.14.0.patch | 82 ------------------- 4 files changed, 11 insertions(+), 93 deletions(-) delete mode 100644 patches/react-native-gesture-handler+2.14.0.patch diff --git a/ios/Podfile.lock b/ios/Podfile.lock index acc8720dafce..e85e85e5e42d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1399,7 +1399,7 @@ PODS: - React-Core - RNFS (2.20.0): - React-Core - - RNGestureHandler (2.14.0): + - RNGestureHandler (2.14.1): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1951,7 +1951,7 @@ SPEC CHECKSUMS: RNFBPerf: 389914cda4000fe0d996a752532a591132cbf3f9 RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: 61bfdfc05db9b79dd61f894dcd29d3dcc6db3c02 + RNGestureHandler: 25b969a1ffc806b9f9ad2e170d4a3b049c6af85e RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64 @@ -1967,7 +1967,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 7d13aae043ffb38b224a0f725d1e23ca9c190fe7 - Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047 + Yoga: 13c8ef87792450193e117976337b8527b49e8c03 PODFILE CHECKSUM: 0ccbb4f2406893c6e9f266dc1e7470dcd72885d2 diff --git a/package-lock.json b/package-lock.json index 8df6ff2ebf09..d1ae67561d9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,7 +84,7 @@ "react-native-document-picker": "^8.2.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-fs": "^2.20.0", - "react-native-gesture-handler": "2.14.0", + "react-native-gesture-handler": "2.14.1", "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", @@ -46901,9 +46901,9 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz", - "integrity": "sha512-cOmdaqbpzjWrOLUpX3hdSjsMby5wq3PIEdMq7okJeg9DmCzanysHSrktw1cXWNc/B5MAgxAn9J7Km0/4UIqKAQ==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz", + "integrity": "sha512-YiM1BApV4aKeuwsM6O4C2ufwewYEKk6VMXOt0YqEZFMwABBFWhXLySFZYjBSNRU2USGppJbfHP1q1DfFQpKhdA==", "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -89629,9 +89629,9 @@ } }, "react-native-gesture-handler": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.0.tgz", - "integrity": "sha512-cOmdaqbpzjWrOLUpX3hdSjsMby5wq3PIEdMq7okJeg9DmCzanysHSrktw1cXWNc/B5MAgxAn9J7Km0/4UIqKAQ==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz", + "integrity": "sha512-YiM1BApV4aKeuwsM6O4C2ufwewYEKk6VMXOt0YqEZFMwABBFWhXLySFZYjBSNRU2USGppJbfHP1q1DfFQpKhdA==", "requires": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", diff --git a/package.json b/package.json index 2494716d55f5..cf80039c02b5 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "react-native-document-picker": "^8.2.1", "react-native-draggable-flatlist": "^4.0.1", "react-native-fs": "^2.20.0", - "react-native-gesture-handler": "2.14.0", + "react-native-gesture-handler": "2.14.1", "react-native-google-places-autocomplete": "2.5.6", "react-native-haptic-feedback": "^1.13.0", "react-native-image-pan-zoom": "^2.1.12", diff --git a/patches/react-native-gesture-handler+2.14.0.patch b/patches/react-native-gesture-handler+2.14.0.patch deleted file mode 100644 index 70bcdecb24a2..000000000000 --- a/patches/react-native-gesture-handler+2.14.0.patch +++ /dev/null @@ -1,82 +0,0 @@ -diff --git a/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js b/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js -index 0a43a8f..751b4fa 100644 ---- a/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js -+++ b/node_modules/react-native-gesture-handler/lib/commonjs/web/handlers/FlingGestureHandler.js -@@ -16,7 +16,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de - function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - - const DEFAULT_MAX_DURATION_MS = 800; --const DEFAULT_MIN_ACCEPTABLE_DELTA = 160; -+const DEFAULT_MIN_ACCEPTABLE_DELTA = 35; - const DEFAULT_DIRECTION = _constants.Direction.RIGHT; - const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; - -@@ -141,13 +141,12 @@ class FlingGestureHandler extends _GestureHandler.default { - } - - onUp(event) { -- this.tracker.removeFromTracker(event.pointerId); -- - if (this.currentState !== _State.State.BEGAN) { - return; - } - - this.endFling(); -+ this.tracker.removeFromTracker(event.pointerId); - } - - activate(force) { -diff --git a/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js b/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js -index b6dbc26..69843cb 100644 ---- a/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js -+++ b/node_modules/react-native-gesture-handler/lib/module/web/handlers/FlingGestureHandler.js -@@ -4,7 +4,7 @@ import { State } from '../../State'; - import { Direction } from '../constants'; - import GestureHandler from './GestureHandler'; - const DEFAULT_MAX_DURATION_MS = 800; --const DEFAULT_MIN_ACCEPTABLE_DELTA = 160; -+const DEFAULT_MIN_ACCEPTABLE_DELTA = 35; - const DEFAULT_DIRECTION = Direction.RIGHT; - const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; - export default class FlingGestureHandler extends GestureHandler { -@@ -128,13 +128,12 @@ export default class FlingGestureHandler extends GestureHandler { - } - - onUp(event) { -- this.tracker.removeFromTracker(event.pointerId); -- - if (this.currentState !== State.BEGAN) { - return; - } - - this.endFling(); -+ this.tracker.removeFromTracker(event.pointerId); - } - - activate(force) { -diff --git a/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts b/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts -index 0c2a117..929c0c7 100644 ---- a/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts -+++ b/node_modules/react-native-gesture-handler/src/web/handlers/FlingGestureHandler.ts -@@ -5,7 +5,7 @@ import { AdaptedEvent, Config } from '../interfaces'; - import GestureHandler from './GestureHandler'; - - const DEFAULT_MAX_DURATION_MS = 800; --const DEFAULT_MIN_ACCEPTABLE_DELTA = 160; -+const DEFAULT_MIN_ACCEPTABLE_DELTA = 35; - const DEFAULT_DIRECTION = Direction.RIGHT; - const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1; - -@@ -141,11 +141,11 @@ export default class FlingGestureHandler extends GestureHandler { - } - - private onUp(event: AdaptedEvent): void { -- this.tracker.removeFromTracker(event.pointerId); - if (this.currentState !== State.BEGAN) { - return; - } - this.endFling(); -+ this.tracker.removeFromTracker(event.pointerId); - } - - public activate(force?: boolean): void { From b97674932cdb54f4871901d74fa6a45a05bd38ff Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 12 Jan 2024 14:05:47 +0100 Subject: [PATCH 10/11] Don't use inline value --- src/components/AvatarCropModal/Slider.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index 09ce8b0c95a5..83c8577d2e55 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -73,11 +73,8 @@ function Slider(props) { text={props.translate('common.zoom')} shiftVertical={-2} > - {/* pointerEvents='none' is a workaround to make sure the pan gesture works correctly on mobile safari */} - + {/* pointerEventsNone is a workaround to make sure the pan gesture works correctly on mobile safari */} + )} From 4edc9dd74ce9f82acf675252b6f91799d6a9e540 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 17 Jan 2024 09:54:53 +0100 Subject: [PATCH 11/11] Make callback run on the JS thread --- src/components/MagicCodeInput.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index c4852e5208af..59465e34eec0 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -207,17 +207,19 @@ function MagicCodeInput(props) { * Tap gesture configuration, updates the indexes of the * currently focused input. */ - const tapGesture = Gesture.Tap().onBegin((event) => { - const index = Math.floor(event.x / (inputWidth.current / props.maxLength)); - shouldFocusLast.current = false; - // TapGestureHandler works differently on mobile web and native app - // On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually - if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { - inputRefs.current.focus(); - } - setInputAndIndex(index); - lastFocusedIndex.current = index; - }); + const tapGesture = Gesture.Tap() + .runOnJS(true) + .onBegin((event) => { + const index = Math.floor(event.x / (inputWidth.current / props.maxLength)); + shouldFocusLast.current = false; + // TapGestureHandler works differently on mobile web and native app + // On web gesture handler doesn't block interactions with textInput below so there is no need to run `focus()` manually + if (!Browser.isMobileChrome() && !Browser.isMobileSafari()) { + inputRefs.current.focus(); + } + setInputAndIndex(index); + lastFocusedIndex.current = index; + }); /** * Updates the magic inputs with the contents written in the