Skip to content

Commit

Permalink
Merge pull request #34101 from software-mansion-labs/@jpiasecki/use-n…
Browse files Browse the repository at this point in the history
…ew-rngh-api

Upgrade RNGestureHandler and use the new API everywhere
  • Loading branch information
rlinoz authored Jan 19, 2024
2 parents 2a03320 + 4edc9dd commit 0baad0a
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 143 deletions.
2 changes: 0 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1951,7 +1951,7 @@ SPEC CHECKSUMS:
RNFBPerf: 389914cda4000fe0d996a752532a591132cbf3f9
RNFlashList: 4b4b6b093afc0df60ae08f9cbf6ccd4c836c667a
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 61bfdfc05db9b79dd61f894dcd29d3dcc6db3c02
RNGestureHandler: 25b969a1ffc806b9f9ad2e170d4a3b049c6af85e
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81
rnmapbox-maps: 6f638ec002aa6e906a6f766d69cd45f968d98e64
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
84 changes: 36 additions & 48 deletions src/components/AvatarCropModal/AvatarCropModal.js
Original file line number Diff line number Diff line change
@@ -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 {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';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand Down Expand Up @@ -203,25 +203,12 @@ 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.
Expand Down Expand Up @@ -251,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
Expand Down Expand Up @@ -394,7 +382,7 @@ function AvatarCropModal(props) {
<ImageCropView
imageUri={props.imageUri}
containerSize={imageContainerSize}
panGestureEventHandler={panGestureEventHandler}
panGesture={panGesture}
originalImageHeight={originalImageHeight}
originalImageWidth={originalImageWidth}
scale={scale}
Expand All @@ -418,7 +406,7 @@ function AvatarCropModal(props) {
>
<Slider
sliderValue={translateSlider}
onGesture={panSliderGestureEventHandler}
gestureCallbacks={sliderPanGestureCallbacks}
/>
</PressableWithoutFeedback>
<Tooltip
Expand Down
14 changes: 7 additions & 7 deletions src/components/AvatarCropModal/ImageCropView.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import {PanGestureHandler} from 'react-native-gesture-handler';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import ControlSelection from '@libs/ControlSelection';
import gestureHandlerPropTypes from './gestureHandlerPropTypes';

const propTypes = {
/** Link to image for cropping */
Expand All @@ -35,8 +34,9 @@ const propTypes = {
/** The scale factor of the image */
scale: PropTypes.shape({value: PropTypes.number}).isRequired,

/** React-native-reanimated lib handler which executes when the user is panning image */
panGestureEventHandler: gestureHandlerPropTypes,
/** Configuration object for pan gesture for handling image panning */
// eslint-disable-next-line react/forbid-prop-types
panGesture: PropTypes.object,

/** Image crop vector mask */
maskImage: PropTypes.func,
Expand All @@ -45,7 +45,7 @@ const propTypes = {
const defaultProps = {
imageUri: '',
containerSize: 0,
panGestureEventHandler: () => {},
panGesture: Gesture.Pan(),
maskImage: Expensicons.ImageCropCircleMask,
};

Expand Down Expand Up @@ -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 (
<PanGestureHandler onGestureEvent={props.panGestureEventHandler}>
<GestureDetector gesture={props.panGesture}>
<Animated.View
ref={ControlSelection.blockElement}
style={[containerStyle, styles.imageCropContainer]}
Expand All @@ -95,7 +95,7 @@ function ImageCropView(props) {
/>
</View>
</Animated.View>
</PanGestureHandler>
</GestureDetector>
);
}

Expand Down
46 changes: 33 additions & 13 deletions src/components/AvatarCropModal/Slider.js
Original file line number Diff line number Diff line change
@@ -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, {runOnJS, useAnimatedStyle} 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}),
Expand All @@ -20,7 +19,17 @@ const propTypes = {
};

const defaultProps = {
onGesture: () => {},
gestureCallbacks: {
onBegin: () => {
'worklet';
},
onChange: () => {
'worklet';
},
onFinalize: () => {
'worklet';
},
},
sliderValue: {},
};

Expand All @@ -36,29 +45,40 @@ 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 (
<View
ref={ControlSelection.blockElement}
style={styles.sliderBar}
>
<PanGestureHandler
onBegan={() => setTooltipIsVisible(false)}
onEnded={() => setTooltipIsVisible(true)}
onGestureEvent={props.onGesture}
>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.sliderKnob, rSliderStyle]}>
{tooltipIsVisible && (
<Tooltip
text={props.translate('common.zoom')}
shiftVertical={-2}
>
<View style={[styles.sliderKnobTooltipView]} />
{/* pointerEventsNone is a workaround to make sure the pan gesture works correctly on mobile safari */}
<View style={[styles.sliderKnobTooltipView, styles.pointerEventsNone]} />
</Tooltip>
)}
</Animated.View>
</PanGestureHandler>
</GestureDetector>
</View>
);
}
Expand Down
21 changes: 0 additions & 21 deletions src/components/AvatarCropModal/gestureHandlerPropTypes.js

This file was deleted.

Loading

0 comments on commit 0baad0a

Please sign in to comment.