diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9765bc89e635..4830623a40b5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -672,11 +672,12 @@ PODS: - React-Core - RNReactNativeHapticFeedback (1.14.0): - React-Core - - RNReanimated (3.1.0): + - RNReanimated (3.4.0): - DoubleConversion - FBLazyVector - FBReactNativeSpec - glog + - hermes-engine - RCT-Folly - RCTRequired - RCTTypeSafety @@ -686,6 +687,7 @@ PODS: - React-Core/RCTWebSocket - React-CoreModules - React-cxxreact + - React-hermes - React-jsi - React-jsiexecutor - React-jsinspector @@ -1130,7 +1132,7 @@ SPEC CHECKSUMS: RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c - RNReanimated: b1220a0e5168745283ff5d53bfc7d2144b2cee1b + RNReanimated: 532818a3578a0832d95ea9d0a6a9dc0e73bbce29 RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9 RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d diff --git a/package-lock.json b/package-lock.json index 5a9fb7c36c17..34040afb0aea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,7 +87,7 @@ "react-native-plaid-link-sdk": "^10.0.0", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-sqlite": "^8.0.0-beta.2", - "react-native-reanimated": "3.1.0", + "react-native-reanimated": "3.4.0", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.17.0", @@ -36914,9 +36914,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.1.0.tgz", - "integrity": "sha512-8YJR7yHnrqK6yKWzkGLVEawi1WZqJ9bGIehKEnE8zG58yLrSwUZe1T220XTbftpkA3r37Sy0kJJ/HOOiaIU+HQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.4.0.tgz", + "integrity": "sha512-B5cZJseoIkYlZTRBRN0xuU1NBxUza/6GSHhiEBQfbOufWVlUMMcWUecIRVglW49l8d2wXbfCdQlNyVoFqmHkaQ==", "dependencies": { "@babel/plugin-transform-object-assign": "^7.16.7", "@babel/preset-typescript": "^7.16.7", @@ -68624,9 +68624,9 @@ "requires": {} }, "react-native-reanimated": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.1.0.tgz", - "integrity": "sha512-8YJR7yHnrqK6yKWzkGLVEawi1WZqJ9bGIehKEnE8zG58yLrSwUZe1T220XTbftpkA3r37Sy0kJJ/HOOiaIU+HQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.4.0.tgz", + "integrity": "sha512-B5cZJseoIkYlZTRBRN0xuU1NBxUza/6GSHhiEBQfbOufWVlUMMcWUecIRVglW49l8d2wXbfCdQlNyVoFqmHkaQ==", "requires": { "@babel/plugin-transform-object-assign": "^7.16.7", "@babel/preset-typescript": "^7.16.7", diff --git a/package.json b/package.json index 52e9c5abb059..c73ddb2aa217 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "react-native-plaid-link-sdk": "^10.0.0", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-sqlite": "^8.0.0-beta.2", - "react-native-reanimated": "3.1.0", + "react-native-reanimated": "3.4.0", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.17.0", diff --git a/patches/react-native-reanimated+3.1.0.patch b/patches/react-native-reanimated+3.1.0.patch deleted file mode 100644 index 6dc6e0d3bc9b..000000000000 --- a/patches/react-native-reanimated+3.1.0.patch +++ /dev/null @@ -1,177 +0,0 @@ -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/EmojiPicker/EmojiPickerMenu/index.native.js b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js index 88e1c7df6986..6092c9d301da 100644 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.native.js @@ -1,11 +1,11 @@ -import React, {Component} from 'react'; -import {View, findNodeHandle} from 'react-native'; +import React, {useState, useMemo} from 'react'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import Animated, {runOnUI, _scrollTo} from 'react-native-reanimated'; +import Animated, {runOnUI, scrollTo, useAnimatedRef} from 'react-native-reanimated'; +import useWindowDimensions from '../../../hooks/useWindowDimensions'; import compose from '../../../libs/compose'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../withWindowDimensions'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; @@ -27,9 +27,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - /** Props related to the dimensions of the window */ - ...windowDimensionsPropTypes, - /** Props related to translation */ ...withLocalizePropTypes, }; @@ -38,109 +35,71 @@ const defaultProps = { preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, }; -class EmojiPickerMenu extends Component { - constructor(props) { - super(props); - - // Ref for emoji FlatList - this.emojiList = undefined; - - this.emojis = EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis); - - // Get the header emojis along with the code, index and icon. - // index is the actual header index starting at the first emoji and counting each one - this.headerEmojis = EmojiUtils.getHeaderEmojis(this.emojis); - - // This is the indices of each header's Row - // The positions are static, and are calculated as index/numColumns (8 in our case) - // This is because each row of 8 emojis counts as one index to the flatlist - this.headerRowIndices = _.map(this.headerEmojis, (headerEmoji) => Math.floor(headerEmoji.index / CONST.EMOJI_NUM_PER_ROW)); +function EmojiPickerMenu({preferredLocale, onEmojiSelected, preferredSkinTone, translate}) { + const emojiList = useAnimatedRef(); + const allEmojis = useMemo(() => EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis), []); + const headerEmojis = useMemo(() => EmojiUtils.getHeaderEmojis(allEmojis), [allEmojis]); + const headerRowIndices = useMemo(() => _.map(headerEmojis, (headerEmoji) => Math.floor(headerEmoji.index / CONST.EMOJI_NUM_PER_ROW)), [headerEmojis]); + const [filteredEmojis, setFilteredEmojis] = useState(allEmojis); + const [headerIndices, setHeaderIndices] = useState(headerRowIndices); + const {windowWidth} = useWindowDimensions(); - this.renderItem = this.renderItem.bind(this); - this.isMobileLandscape = this.isMobileLandscape.bind(this); - this.updatePreferredSkinTone = this.updatePreferredSkinTone.bind(this); - this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300); - this.scrollToHeader = this.scrollToHeader.bind(this); - this.getItemLayout = this.getItemLayout.bind(this); - - this.state = { - filteredEmojis: this.emojis, - headerIndices: this.headerRowIndices, - }; - } - - getItemLayout(data, index) { - return {length: CONST.EMOJI_PICKER_ITEM_HEIGHT, offset: CONST.EMOJI_PICKER_ITEM_HEIGHT * index, index}; - } + const getItemLayout = (data, index) => ({length: CONST.EMOJI_PICKER_ITEM_HEIGHT, offset: CONST.EMOJI_PICKER_ITEM_HEIGHT * index, index}); /** * Filter the entire list of emojis to only emojis that have the search term in their keywords * * @param {String} searchTerm */ - filterEmojis(searchTerm) { + const filterEmojis = _.debounce((searchTerm) => { const normalizedSearchTerm = searchTerm.toLowerCase().trim().replaceAll(':', ''); - if (this.emojiList) { - this.emojiList.scrollToOffset({offset: 0, animated: false}); + if (emojiList.current) { + emojiList.current.scrollToOffset({offset: 0, animated: false}); } if (normalizedSearchTerm === '') { - this.setState({ - filteredEmojis: this.emojis, - headerIndices: this.headerRowIndices, - }); + setFilteredEmojis(allEmojis); + setHeaderIndices(headerRowIndices); return; } - const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, this.props.preferredLocale, this.emojis.length); + const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale, allEmojis.length); - this.setState({ - filteredEmojis: newFilteredEmojiList, - headerIndices: undefined, - }); - } + setFilteredEmojis(newFilteredEmojiList); + setHeaderIndices(undefined); + }, 300); /** * @param {String} emoji * @param {Object} emojiObject */ - addToFrequentAndSelectEmoji(emoji, emojiObject) { + const addToFrequentAndSelectEmoji = (emoji, emojiObject) => { const frequentEmojiList = EmojiUtils.getFrequentlyUsedEmojis(emojiObject); User.updateFrequentlyUsedEmojis(frequentEmojiList); - this.props.onEmojiSelected(emoji, emojiObject); - } - - /** - * Check if its a landscape mode of mobile device - * - * @returns {Boolean} - */ - isMobileLandscape() { - return this.props.windowWidth >= this.props.windowHeight; - } + onEmojiSelected(emoji, emojiObject); + }; /** * @param {Number} skinTone */ - updatePreferredSkinTone(skinTone) { - if (this.props.preferredSkinTone === skinTone) { + const updatePreferredSkinTone = (skinTone) => { + if (preferredSkinTone === skinTone) { return; } User.updatePreferredSkinTone(skinTone); - } + }; - scrollToHeader(headerIndex) { + const scrollToHeader = (headerIndex) => { const calculatedOffset = Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW) * CONST.EMOJI_PICKER_HEADER_HEIGHT; - this.emojiList.flashScrollIndicators(); - const node = findNodeHandle(this.emojiList); + emojiList.current.flashScrollIndicators(); runOnUI(() => { 'worklet'; - _scrollTo(node, 0, calculatedOffset, true); + scrollTo(emojiList, 0, calculatedOffset, true); })(); - } + }; /** * Return a unique key for each emoji item @@ -149,9 +108,7 @@ class EmojiPickerMenu extends Component { * @param {Number} index * @returns {String} */ - keyExtractor(item, index) { - return `${index}${item.code}`; - } + const keyExtractor = (item, index) => `${index}${item.code}`; /** * Given an emoji item object, render a component based on its type. @@ -161,7 +118,7 @@ class EmojiPickerMenu extends Component { * @param {Object} item * @returns {*} */ - renderItem({item}) { + const renderItem = ({item}) => { const {code, types} = item; if (item.spacer) { return null; @@ -170,75 +127,74 @@ class EmojiPickerMenu extends Component { if (item.header) { return ( - {this.props.translate(`emojiPicker.headers.${code}`)} + {translate(`emojiPicker.headers.${code}`)} ); } - const emojiCode = types && types[this.props.preferredSkinTone] ? types[this.props.preferredSkinTone] : code; + const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code; return ( this.addToFrequentAndSelectEmoji(emoji, item)} + onPress={(emoji) => addToFrequentAndSelectEmoji(emoji, item)} emoji={emojiCode} /> ); - } - - render() { - const isFiltered = this.emojis.length !== this.state.filteredEmojis.length; - return ( - - - - - {!isFiltered && ( - - )} - (this.emojiList = el)} - keyboardShouldPersistTaps="handled" - data={this.state.filteredEmojis} - renderItem={this.renderItem} - keyExtractor={this.keyExtractor} - numColumns={CONST.EMOJI_NUM_PER_ROW} - style={[ - StyleUtils.getEmojiPickerListHeight(isFiltered), - { - width: this.props.windowWidth, - }, - ]} - stickyHeaderIndices={this.state.headerIndices} - getItemLayout={this.getItemLayout} - showsVerticalScrollIndicator - // used because of a bug in RN where stickyHeaderIndices can't be updated after the list is rendered https://github.com/facebook/react-native/issues/25157 - removeClippedSubviews={false} - contentContainerStyle={styles.flexGrow1} - ListEmptyComponent={{this.props.translate('common.noResultsFound')}} - alwaysBounceVertical={this.state.filteredEmojis.length !== 0} - /> - + + - ); - } + {!isFiltered && ( + + )} + {translate('common.noResultsFound')}} + alwaysBounceVertical={filteredEmojis.length !== 0} + /> + + + ); } +EmojiPickerMenu.displayName = 'EmojiPickerMenu'; EmojiPickerMenu.propTypes = propTypes; EmojiPickerMenu.defaultProps = defaultProps; export default compose( - withWindowDimensions, withLocalize, withOnyx({ preferredSkinTone: { diff --git a/src/libs/updatePropsPaperWorklet/index.native.js b/src/libs/updatePropsPaperWorklet/index.native.js index f346562b2cc7..ed79b38ffab5 100644 --- a/src/libs/updatePropsPaperWorklet/index.native.js +++ b/src/libs/updatePropsPaperWorklet/index.native.js @@ -3,5 +3,11 @@ export default function (viewTag, viewName, updates) { // _updatePropsPaper is a function that is worklet function from react-native-reanimated which is not available on web // eslint-disable-next-line no-undef - _updatePropsPaper(viewTag, viewName, updates); + _updatePropsPaper([ + { + tag: viewTag, + name: viewName, + updates, + }, + ]); }