diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fc30bf617a24..147227e7befb 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -668,7 +668,7 @@ PODS: - React-Core - RNReactNativeHapticFeedback (1.14.0): - React-Core - - RNReanimated (3.0.0-rc.10): + - RNReanimated (3.1.0): - DoubleConversion - FBLazyVector - FBReactNativeSpec @@ -1118,7 +1118,7 @@ SPEC CHECKSUMS: RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c - RNReanimated: fbc356493970e3acddc15586b1bccb5eab3ff1ec + RNReanimated: b1220a0e5168745283ff5d53bfc7d2144b2cee1b RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9 RNSVG: 38ca962c970dbce1ca38991a5aebf26d163f9efb SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d diff --git a/jest/setup.js b/jest/setup.js index 407059251579..228f3a22f33b 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -1,3 +1,4 @@ +import 'setimmediate'; import 'react-native-gesture-handler/jestSetup'; import * as reanimatedJestUtils from 'react-native-reanimated/src/reanimated2/jestUtils'; import setupMockImages from './setupMockImages'; diff --git a/package-lock.json b/package-lock.json index 187d810df1a1..44922f08024a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,7 +84,7 @@ "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#107b3786ae6bc155dec05c7fc5ee525d3421dc21", "react-native-plaid-link-sdk": "^10.0.0", "react-native-quick-sqlite": "^8.0.0-beta.2", - "react-native-reanimated": "3.0.0-rc.10", + "react-native-reanimated": "3.1.0", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.17.0", @@ -169,6 +169,7 @@ "react-native-performance-flipper-reporter": "^2.0.0", "react-native-svg-transformer": "^1.0.0", "react-test-renderer": "18.1.0", + "setimmediate": "^1.0.5", "shellcheck": "^1.1.0", "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", @@ -34955,23 +34956,31 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.0.0-rc.10", - "license": "MIT", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.1.0.tgz", + "integrity": "sha512-8YJR7yHnrqK6yKWzkGLVEawi1WZqJ9bGIehKEnE8zG58yLrSwUZe1T220XTbftpkA3r37Sy0kJJ/HOOiaIU+HQ==", "dependencies": { "@babel/plugin-transform-object-assign": "^7.16.7", "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^1.7.0", - "invariant": "^2.2.4", - "lodash.isequal": "^4.5.0", - "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" + "convert-source-map": "^2.0.0", + "invariant": "^2.2.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", "react": "*", "react-native": "*" } }, + "node_modules/react-native-reanimated/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, "node_modules/react-native-render-html": { "version": "6.3.1", "license": "BSD-2-Clause", @@ -37273,7 +37282,8 @@ }, "node_modules/setimmediate": { "version": "1.0.5", - "license": "MIT" + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -38149,10 +38159,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string-hash-64": { - "version": "1.0.3", - "license": "MIT" - }, "node_modules/string-length": { "version": "4.0.2", "license": "MIT", @@ -64726,15 +64732,21 @@ "requires": {} }, "react-native-reanimated": { - "version": "3.0.0-rc.10", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.1.0.tgz", + "integrity": "sha512-8YJR7yHnrqK6yKWzkGLVEawi1WZqJ9bGIehKEnE8zG58yLrSwUZe1T220XTbftpkA3r37Sy0kJJ/HOOiaIU+HQ==", "requires": { "@babel/plugin-transform-object-assign": "^7.16.7", "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^1.7.0", - "invariant": "^2.2.4", - "lodash.isequal": "^4.5.0", - "setimmediate": "^1.0.5", - "string-hash-64": "^1.0.3" + "convert-source-map": "^2.0.0", + "invariant": "^2.2.4" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + } } }, "react-native-render-html": { @@ -66192,7 +66204,9 @@ } }, "setimmediate": { - "version": "1.0.5" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "setprototypeof": { "version": "1.2.0" @@ -66805,9 +66819,6 @@ "safe-buffer": "~5.1.0" } }, - "string-hash-64": { - "version": "1.0.3" - }, "string-length": { "version": "4.0.2", "requires": { diff --git a/package.json b/package.json index 4a2bfb6a9afe..f43648bffe82 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#107b3786ae6bc155dec05c7fc5ee525d3421dc21", "react-native-plaid-link-sdk": "^10.0.0", "react-native-quick-sqlite": "^8.0.0-beta.2", - "react-native-reanimated": "3.0.0-rc.10", + "react-native-reanimated": "3.1.0", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.17.0", @@ -196,14 +196,15 @@ "metro-react-native-babel-preset": "^0.73.3", "mock-fs": "^4.13.0", "onchange": "^7.1.0", - "prettier": "^2.8.8", "portfinder": "^1.0.28", + "prettier": "^2.8.8", "pusher-js-mock": "^0.3.3", "react-native-clean-project": "^4.0.0-alpha4.0", "react-native-flipper": "https://gitpkg.now.sh/facebook/flipper/react-native/react-native-flipper?9cacc9b59402550eae866e0e81e5f0c2f8203e6b", "react-native-performance-flipper-reporter": "^2.0.0", "react-native-svg-transformer": "^1.0.0", "react-test-renderer": "18.1.0", + "setimmediate": "^1.0.5", "shellcheck": "^1.1.0", "style-loader": "^2.0.0", "time-analytics-webpack-plugin": "^0.1.17", diff --git a/patches/react-native-reanimated+3.1.0.patch b/patches/react-native-reanimated+3.1.0.patch new file mode 100644 index 000000000000..6dc6e0d3bc9b --- /dev/null +++ b/patches/react-native-reanimated+3.1.0.patch @@ -0,0 +1,177 @@ +diff --git a/node_modules/react-native-reanimated/ios/REANodesManager.mm b/node_modules/react-native-reanimated/ios/REANodesManager.mm +index 26bb253..4108293 100644 +--- a/node_modules/react-native-reanimated/ios/REANodesManager.mm ++++ b/node_modules/react-native-reanimated/ios/REANodesManager.mm +@@ -85,19 +85,77 @@ - (void)runSyncUIUpdatesWithObserver:(id)observer + + @end + +-@interface REANodesManager () ++#ifndef RCT_NEW_ARCH_ENABLED + ++@interface REASyncUpdateObserver : NSObject + @end + ++@implementation REASyncUpdateObserver { ++ volatile void (^_mounting)(void); ++ volatile BOOL _waitTimedOut; ++ dispatch_semaphore_t _semaphore; ++} ++ ++- (instancetype)init ++{ ++ self = [super init]; ++ if (self) { ++ _mounting = nil; ++ _waitTimedOut = NO; ++ _semaphore = dispatch_semaphore_create(0); ++ } ++ return self; ++} ++ ++- (void)dealloc ++{ ++ RCTAssert(_mounting == nil, @"Mouting block was set but never executed. This may lead to UI inconsistencies"); ++} ++ ++- (void)unblockUIThread ++{ ++ RCTAssertUIManagerQueue(); ++ dispatch_semaphore_signal(_semaphore); ++} ++ ++- (void)waitAndMountWithTimeout:(NSTimeInterval)timeout ++{ ++ RCTAssertMainQueue(); ++ long result = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC)); ++ if (result != 0) { ++ @synchronized(self) { ++ _waitTimedOut = YES; ++ } ++ } ++ if (_mounting) { ++ _mounting(); ++ _mounting = nil; ++ } ++} ++ ++- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block ++{ ++ RCTAssertUIManagerQueue(); ++ @synchronized(self) { ++ if (_waitTimedOut) { ++ return NO; ++ } else { ++ _mounting = block; ++ return YES; ++ } ++ } ++} ++ ++@end ++ ++#endif ++ + @implementation REANodesManager { + CADisplayLink *_displayLink; + BOOL _wantRunUpdates; + NSMutableArray *_onAnimationCallbacks; + BOOL _tryRunBatchUpdatesSynchronously; + REAEventHandler _eventHandler; +- volatile void (^_mounting)(void); +- NSObject *_syncLayoutUpdatesWaitLock; +- volatile BOOL _syncLayoutUpdatesWaitTimedOut; + NSMutableDictionary *_componentUpdateBuffer; + NSMutableDictionary *_viewRegistry; + #ifdef RCT_NEW_ARCH_ENABLED +@@ -125,7 +183,6 @@ - (nonnull instancetype)initWithModule:(REAModule *)reanimatedModule + _operationsInBatch = [NSMutableDictionary new]; + _componentUpdateBuffer = [NSMutableDictionary new]; + _viewRegistry = [_uiManager valueForKey:@"_viewRegistry"]; +- _syncLayoutUpdatesWaitLock = [NSObject new]; + } + + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)]; +@@ -241,19 +298,6 @@ - (void)onAnimationFrame:(CADisplayLink *)displayLink + } + } + +-- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block +-{ +- RCTAssert(_mounting == nil, @"Mouting block is expected to not be set"); +- @synchronized(_syncLayoutUpdatesWaitLock) { +- if (_syncLayoutUpdatesWaitTimedOut) { +- return NO; +- } else { +- _mounting = block; +- return YES; +- } +- } +-} +- + - (void)performOperations + { + #ifdef RCT_NEW_ARCH_ENABLED +@@ -268,8 +312,7 @@ - (void)performOperations + _tryRunBatchUpdatesSynchronously = NO; + + __weak __typeof__(self) weakSelf = self; +- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); +- _syncLayoutUpdatesWaitTimedOut = NO; ++ REASyncUpdateObserver *syncUpdateObserver = [REASyncUpdateObserver new]; + RCTExecuteOnUIManagerQueue(^{ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { +@@ -278,7 +321,7 @@ - (void)performOperations + BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager hasEnqueuedUICommands]; + + if (!canUpdateSynchronously) { +- dispatch_semaphore_signal(semaphore); ++ [syncUpdateObserver unblockUIThread]; + } + + for (int i = 0; i < copiedOperationsQueue.count; i++) { +@@ -286,8 +329,8 @@ - (void)performOperations + } + + if (canUpdateSynchronously) { +- [strongSelf.uiManager runSyncUIUpdatesWithObserver:strongSelf]; +- dispatch_semaphore_signal(semaphore); ++ [strongSelf.uiManager runSyncUIUpdatesWithObserver:syncUpdateObserver]; ++ [syncUpdateObserver unblockUIThread]; + } + // In case canUpdateSynchronously=true we still have to send uiManagerWillPerformMounting event + // to observers because some components (e.g. TextInput) update their UIViews only on that event. +@@ -298,17 +341,7 @@ - (void)performOperations + // from CADisplayLink but it is easier to hardcode it for the time being. + // The reason why we use frame duration here is that if takes longer than one frame to complete layout tasks + // there is no point of synchronizing layout with the UI interaction as we get that one frame delay anyways. +- long result = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC)); +- if (result != 0) { +- @synchronized(_syncLayoutUpdatesWaitLock) { +- _syncLayoutUpdatesWaitTimedOut = YES; +- } +- } +- } +- +- if (_mounting) { +- _mounting(); +- _mounting = nil; ++ [syncUpdateObserver waitAndMountWithTimeout:0.016]; + } + } + _wantRunUpdates = NO; +diff --git a/node_modules/react-native-reanimated/mock.js b/node_modules/react-native-reanimated/mock.js +index 68b20d2..b088001 100644 +--- a/node_modules/react-native-reanimated/mock.js ++++ b/node_modules/react-native-reanimated/mock.js +@@ -41,6 +41,9 @@ const Reanimated = { + createAnimatedComponent: (Component) => Component, + addWhitelistedUIProps: NOOP, + addWhitelistedNativeProps: NOOP, ++ ++ // used by react-navigation fork ++ isConfigured: () => true, + }; + + module.exports = { diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js index 7605ad83da5b..3ad72bd3d674 100644 --- a/src/components/AvatarCropModal/ImageCropView.js +++ b/src/components/AvatarCropModal/ImageCropView.js @@ -52,16 +52,23 @@ const defaultProps = { const ImageCropView = (props) => { const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize); + const originalImageHeight = props.originalImageHeight; + const originalImageWidth = props.originalImageWidth; + const rotation = props.rotation; + const translateX = props.translateX; + const translateY = props.translateY; + const scale = props.scale; + // A reanimated memoized style, which updates when the image's size or scale changes. const imageStyle = useAnimatedStyle(() => { - const height = props.originalImageHeight.value; - const width = props.originalImageWidth.value; + const height = originalImageHeight.value; + const width = originalImageWidth.value; const aspectRatio = height > width ? height / width : width / height; - const rotate = interpolate(props.rotation.value, [0, 360], [0, 360]); + const rotate = interpolate(rotation.value, [0, 360], [0, 360]); return { - transform: [{translateX: props.translateX.value}, {translateY: props.translateY.value}, {scale: props.scale.value * aspectRatio}, {rotate: `${rotate}deg`}], + transform: [{translateX: translateX.value}, {translateY: translateY.value}, {scale: scale.value * aspectRatio}, {rotate: `${rotate}deg`}], }; - }, [props.originalImageHeight, props.originalImageWidth]); + }, [originalImageHeight, originalImageWidth, rotation, translateX, translateY, scale]); // 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 diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index 29bbd378f3ba..2877b3a9c917 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -26,12 +26,13 @@ const defaultProps = { // This component can't be written using class since reanimated API uses hooks. const Slider = (props) => { + const sliderValue = props.sliderValue; const [tooltipIsVisible, setTooltipIsVisible] = useState(true); // A reanimated memoized style, which tracks // a translateX shared value and updates the slider position. const rSliderStyle = useAnimatedStyle(() => ({ - transform: [{translateX: props.sliderValue.value}], + transform: [{translateX: sliderValue.value}], })); // We're preventing text selection with ControlSelection.blockElement to prevent safari diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index ce8fa004b471..934543b4b9da 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -78,10 +78,10 @@ function keyExtractor(item) { const ReportActionsList = (props) => { const opacity = useSharedValue(0); const animatedStyles = useAnimatedStyle(() => ({ - opacity: withTiming(opacity.value, {duration: 100}), + opacity: opacity.value, })); useEffect(() => { - opacity.value = 1; + opacity.value = withTiming(1, {duration: 100}); }, [opacity]); const [skeletonViewHeight, setSkeletonViewHeight] = useState(0); diff --git a/src/pages/signin/SignInPageLayout/Footer.js b/src/pages/signin/SignInPageLayout/Footer.js index cb8acb5d1e1f..b17e0faee990 100644 --- a/src/pages/signin/SignInPageLayout/Footer.js +++ b/src/pages/signin/SignInPageLayout/Footer.js @@ -25,8 +25,7 @@ const propTypes = { scrollPageToTop: PropTypes.func.isRequired, }; -const defaultProps = { -}; +const defaultProps = {}; const navigateHome = (scrollPageToTop) => { scrollPageToTop(); @@ -35,7 +34,7 @@ const navigateHome = (scrollPageToTop) => { Session.clearSignInData(); }; -const columns = ({scrollPageToTop}) => ([ +const columns = ({scrollPageToTop}) => [ { translationPath: 'footer.features', rows: [ @@ -148,7 +147,7 @@ const columns = ({scrollPageToTop}) => ([ }, ], }, -]); +]; const Footer = (props) => { const isVertical = props.isSmallScreenWidth; diff --git a/src/setup/index.js b/src/setup/index.js index 437c13cf7a54..5fc8bb348031 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -54,11 +54,4 @@ export default function () { // Perform any other platform-specific setup platformSetup(); - - // Workaround to a reanimated issue -> https://github.com/software-mansion/react-native-reanimated/issues/3355 - // We can remove it as soon as we are on > reanimated 3.0.0+ - if (process.browser) { - // eslint-disable-next-line no-underscore-dangle - window._frameTimestamp = null; - } }