diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 21633b432c12..1f1c87db2176 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2503,7 +2503,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNReanimated (3.16.1): + - RNReanimated (3.16.3): - DoubleConversion - glog - hermes-engine @@ -2523,10 +2523,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 3.16.1) - - RNReanimated/worklets (= 3.16.1) + - RNReanimated/reanimated (= 3.16.3) + - RNReanimated/worklets (= 3.16.3) - Yoga - - RNReanimated/reanimated (3.16.1): + - RNReanimated/reanimated (3.16.3): - DoubleConversion - glog - hermes-engine @@ -2546,9 +2546,9 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated/apple (= 3.16.1) + - RNReanimated/reanimated/apple (= 3.16.3) - Yoga - - RNReanimated/reanimated/apple (3.16.1): + - RNReanimated/reanimated/apple (3.16.3): - DoubleConversion - glog - hermes-engine @@ -2569,7 +2569,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNReanimated/worklets (3.16.1): + - RNReanimated/worklets (3.16.3): - DoubleConversion - glog - hermes-engine @@ -3291,7 +3291,7 @@ SPEC CHECKSUMS: rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4 RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28 RNReactNativeHapticFeedback: 73756a3477a5a622fa16862a3ab0d0fc5e5edff5 - RNReanimated: 2d728bad3a69119be89c3431ee0ccda026ecffdc + RNReanimated: 03ba2447d5a7789e2843df2ee05108d93b6441d6 RNScreens: de6e57426ba0e6cbc3fb5b4f496e7f08cb2773c2 RNShare: bd4fe9b95d1ee89a200778cc0753ebe650154bb0 RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 diff --git a/package-lock.json b/package-lock.json index ebf54a3acbfe..134e1db15247 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-nitro-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", - "react-native-reanimated": "3.16.1", + "react-native-reanimated": "3.16.3", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.10.9", @@ -35894,9 +35894,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.16.1", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.1.tgz", - "integrity": "sha512-Wnbo7toHZ6kPLAD8JWKoKCTfNoqYOMW5vUEP76Rr4RBmJCrdXj6oauYP0aZnZq8NCbiP5bwwu7+RECcWtoetnQ==", + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.3.tgz", + "integrity": "sha512-OWlA6e1oHhytTpc7WiSZ7Tmb8OYwLKYZz29Sz6d6WAg60Hm5GuAiKIWUG7Ako7FLcYhFkA0pEQ2xPMEYUo9vlw==", "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", diff --git a/package.json b/package.json index fbbf705459aa..0b7bd0d5ccbf 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "react-native-plaid-link-sdk": "11.11.0", "react-native-qrcode-svg": "6.3.11", "react-native-quick-sqlite": "git+https://github.com/margelo/react-native-nitro-sqlite#99f34ebefa91698945f3ed26622e002bd79489e0", - "react-native-reanimated": "3.16.1", + "react-native-reanimated": "3.16.3", "react-native-release-profiler": "^0.2.1", "react-native-render-html": "6.3.1", "react-native-safe-area-context": "4.10.9", diff --git a/patches/react-native-reanimated+3.16.1+003+include-missing-header.patch b/patches/react-native-reanimated+3.16.1+003+include-missing-header.patch deleted file mode 100644 index 80244991a890..000000000000 --- a/patches/react-native-reanimated+3.16.1+003+include-missing-header.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp b/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp -index 475ec7a..832fb06 100644 ---- a/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp -+++ b/node_modules/react-native-reanimated/Common/cpp/reanimated/NativeModules/NativeReanimatedModule.cpp -@@ -32,6 +32,8 @@ - - #ifdef RCT_NEW_ARCH_ENABLED - #include -+#include -+#include - #endif // RCT_NEW_ARCH_ENABLED - - // Standard `__cplusplus` macro reference: \ No newline at end of file diff --git a/patches/react-native-reanimated+3.16.1+001+hybrid-app.patch b/patches/react-native-reanimated+3.16.3+001+hybrid-app.patch similarity index 100% rename from patches/react-native-reanimated+3.16.1+001+hybrid-app.patch rename to patches/react-native-reanimated+3.16.3+001+hybrid-app.patch diff --git a/patches/react-native-reanimated+3.16.1+002+dontWhitelistTextProp.patch b/patches/react-native-reanimated+3.16.3+002+dontWhitelistTextProp.patch similarity index 100% rename from patches/react-native-reanimated+3.16.1+002+dontWhitelistTextProp.patch rename to patches/react-native-reanimated+3.16.3+002+dontWhitelistTextProp.patch diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx index 0baab49d3010..68d6591c0df6 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx @@ -70,14 +70,13 @@ function AttachmentCarouselPager( const pageScrollHandler = usePageScrollHandler((e) => { 'worklet'; - // eslint-disable-next-line react-compiler/react-compiler - activePage.value = e.position; - isPagerScrolling.value = e.offset !== 0; + activePage.set(e.position); + isPagerScrolling.set(e.offset !== 0); }, []); useEffect(() => { setActivePageIndex(initialPage); - activePage.value = initialPage; + activePage.set(initialPage); }, [activePage, initialPage]); /** The `pagerItems` object that passed down to the context. Later used to detect current page, whether it's a single image gallery etc. */ @@ -106,7 +105,7 @@ function AttachmentCarouselPager( ); const animatedProps = useAnimatedProps(() => ({ - scrollEnabled: isScrollEnabled.value, + scrollEnabled: isScrollEnabled.get(), })); /** diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index 3a7540f65055..f169416f1812 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -253,18 +253,18 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi Gesture.Pan() .enabled(canUseTouchScreen) .onUpdate(({translationX}) => { - if (!isScrollEnabled.value) { + if (!isScrollEnabled.get()) { return; } if (translationX !== 0) { - isPagerScrolling.value = true; + isPagerScrolling.set(true); } scrollTo(scrollRef, page * cellWidth - translationX, 0, false); }) .onEnd(({translationX, velocityX}) => { - if (!isScrollEnabled.value) { + if (!isScrollEnabled.get()) { return; } @@ -281,7 +281,7 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi newIndex = Math.min(attachments.length - 1, Math.max(0, page + delta)); } - isPagerScrolling.value = false; + isPagerScrolling.set(false); scrollTo(scrollRef, newIndex * cellWidth, 0, true); }) // eslint-disable-next-line react-compiler/react-compiler diff --git a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts index 1c54d7841347..3311f6476194 100644 --- a/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts +++ b/src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts @@ -35,12 +35,11 @@ function useCarouselContextEvents(setShouldShowArrows: (show?: SetStateAction { - if (!isScrollEnabled.value) { + if (!isScrollEnabled.get()) { return; } onRequestToggleArrows(); - }, [isScrollEnabled.value, onRequestToggleArrows]); + }, [isScrollEnabled, onRequestToggleArrows]); return {handleTap, handleScaleChange, scale, isScrollEnabled}; } diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx index 7c1d350fc307..4da481809b46 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx @@ -58,7 +58,7 @@ function BaseAttachmentViewPdf({ onPressProp(event); } - if (attachmentCarouselPagerContext !== null && isScrollEnabled?.value) { + if (attachmentCarouselPagerContext !== null && isScrollEnabled?.get()) { attachmentCarouselPagerContext.onTap(); } }, diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx index c6e7984b793f..c756345664cc 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx @@ -32,32 +32,32 @@ function AttachmentViewPdf(props: AttachmentViewPdfProps) { const Pan = Gesture.Pan() .manualActivation(true) .onTouchesMove((evt) => { - if (offsetX.value !== 0 && offsetY.value !== 0 && isScrollEnabled && scale.value === 1) { - const translateX = Math.abs((evt.allTouches.at(0)?.absoluteX ?? 0) - offsetX.value); - const translateY = Math.abs((evt.allTouches.at(0)?.absoluteY ?? 0) - offsetY.value); - const allowEnablingScroll = !isPanGestureActive.value || isScrollEnabled.value; + if (offsetX.get() !== 0 && offsetY.get() !== 0 && isScrollEnabled && scale.get() === 1) { + const translateX = Math.abs((evt.allTouches.at(0)?.absoluteX ?? 0) - offsetX.get()); + const translateY = Math.abs((evt.allTouches.at(0)?.absoluteY ?? 0) - offsetY.get()); + const allowEnablingScroll = !isPanGestureActive.get() || isScrollEnabled.get(); // if the value of X is greater than Y and the pdf is not zoomed in, // enable the pager scroll so that the user // can swipe to the next attachment otherwise disable it. if (translateX > translateY && translateX > SCROLL_THRESHOLD && allowEnablingScroll) { // eslint-disable-next-line react-compiler/react-compiler - isScrollEnabled.value = true; + isScrollEnabled.set(true); } else if (translateY > SCROLL_THRESHOLD) { - isScrollEnabled.value = false; + isScrollEnabled.set(false); } } - isPanGestureActive.value = true; - offsetX.value = evt.allTouches.at(0)?.absoluteX ?? 0; - offsetY.value = evt.allTouches.at(0)?.absoluteY ?? 0; + isPanGestureActive.set(true); + offsetX.set(evt.allTouches.at(0)?.absoluteX ?? 0); + offsetY.set(evt.allTouches.at(0)?.absoluteY ?? 0); }) .onTouchesUp(() => { - isPanGestureActive.value = false; + isPanGestureActive.set(false); if (!isScrollEnabled) { return; } - isScrollEnabled.value = scale.value === 1; + isScrollEnabled.set(scale.get() === 1); }); const Content = useMemo( @@ -69,7 +69,7 @@ function AttachmentViewPdf(props: AttachmentViewPdfProps) { // The react-native-pdf's onScaleChanged event will sometimes give us scale values of e.g. 0.99... instead of 1, // even though we're not pinching/zooming // Rounding the scale value to 2 decimal place fixes this issue, since pinching will still be possible but very small pinches are ignored. - scale.value = Math.round(newScale * 1e2) / 1e2; + scale.set(Math.round(newScale * 1e2) / 1e2); }} /> ), diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 2d22a2560bb0..abc221ed646a 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -50,24 +50,27 @@ function BaseAutoCompleteSuggestions({ const innerHeight = CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT * suggestions.length; const animatedStyles = useAnimatedStyle(() => ({ - opacity: fadeInOpacity.value, - ...StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.value), + opacity: fadeInOpacity.get(), + ...StyleUtils.getAutoCompleteSuggestionContainerStyle(rowHeight.get()), })); useEffect(() => { if (measuredHeightOfSuggestionRows === prevRowHeightRef.current) { - // eslint-disable-next-line react-compiler/react-compiler - fadeInOpacity.value = withTiming(1, { - duration: 70, - easing: Easing.inOut(Easing.ease), - }); - rowHeight.value = measuredHeightOfSuggestionRows; + fadeInOpacity.set( + withTiming(1, { + duration: 70, + easing: Easing.inOut(Easing.ease), + }), + ); + rowHeight.set(measuredHeightOfSuggestionRows); } else { - fadeInOpacity.value = 1; - rowHeight.value = withTiming(measuredHeightOfSuggestionRows, { - duration: 100, - easing: Easing.bezier(0.25, 0.1, 0.25, 1), - }); + fadeInOpacity.set(1); + rowHeight.set( + withTiming(measuredHeightOfSuggestionRows, { + duration: 100, + easing: Easing.bezier(0.25, 0.1, 0.25, 1), + }), + ); } prevRowHeightRef.current = measuredHeightOfSuggestionRows; @@ -103,7 +106,7 @@ function BaseAutoCompleteSuggestions({ renderItem={renderItem} keyExtractor={keyExtractor} removeClippedSubviews={false} - showsVerticalScrollIndicator={innerHeight > rowHeight.value} + showsVerticalScrollIndicator={innerHeight > rowHeight.get()} extraData={[highlightedSuggestionIndex, renderSuggestionMenuItem]} /> diff --git a/src/components/AvatarCropModal/AvatarCropModal.tsx b/src/components/AvatarCropModal/AvatarCropModal.tsx index 7911255ba49c..3ff9ccc4e3f8 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.tsx +++ b/src/components/AvatarCropModal/AvatarCropModal.tsx @@ -97,16 +97,16 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Changes the modal state values to initial const resetState = useCallback(() => { - originalImageWidth.value = CONST.AVATAR_CROP_MODAL.INITIAL_SIZE; - originalImageHeight.value = CONST.AVATAR_CROP_MODAL.INITIAL_SIZE; - translateY.value = 0; - translateX.value = 0; - scale.value = CONST.AVATAR_CROP_MODAL.MIN_SCALE; - rotation.value = 0; - translateSlider.value = 0; - prevMaxOffsetX.value = 0; - prevMaxOffsetY.value = 0; - isLoading.value = false; + originalImageWidth.set(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); + originalImageHeight.set(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); + translateY.set(0); + translateX.set(0); + scale.set(CONST.AVATAR_CROP_MODAL.MIN_SCALE); + rotation.set(0); + translateSlider.set(0); + prevMaxOffsetX.set(0); + prevMaxOffsetY.set(0); + isLoading.set(false); setImageContainerSize(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); setSliderContainerSize(CONST.AVATAR_CROP_MODAL.INITIAL_SIZE); setIsImageContainerInitialized(false); @@ -123,12 +123,11 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose ImageSize.getSize(imageUri).then(({width, height, rotation: orginalRotation}) => { // On Android devices ImageSize library returns also rotation parameter. if (orginalRotation === 90 || orginalRotation === 270) { - // eslint-disable-next-line react-compiler/react-compiler - originalImageHeight.value = width; - originalImageWidth.value = height; + originalImageHeight.set(width); + originalImageWidth.set(height); } else { - originalImageHeight.value = height; - originalImageWidth.value = width; + originalImageHeight.set(height); + originalImageWidth.set(width); } setIsImageInitialized(true); @@ -136,8 +135,8 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Because the reanimated library has some internal optimizations, // sometimes when the modal is hidden styles of the image and slider might not be updated. // To trigger the update we need to slightly change the following values: - translateSlider.value += 0.01; - rotation.value += 360; + translateSlider.set((value) => value + 0.01); + rotation.set((value) => value + 360); }); }, [imageUri, originalImageHeight, originalImageWidth, rotation, translateSlider]); @@ -156,19 +155,19 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const getDisplayedImageSize = useCallback(() => { 'worklet'; - let height = imageContainerSize * scale.value; - let width = imageContainerSize * scale.value; + let height = imageContainerSize * scale.get(); + let width = imageContainerSize * scale.get(); // Since the smaller side will be always equal to the imageContainerSize multiplied by scale, // another side can be calculated with aspect ratio. - if (originalImageWidth.value > originalImageHeight.value) { - width *= originalImageWidth.value / originalImageHeight.value; + if (originalImageWidth.get() > originalImageHeight.get()) { + width *= originalImageWidth.get() / originalImageHeight.get(); } else { - height *= originalImageHeight.value / originalImageWidth.value; + height *= originalImageHeight.get() / originalImageWidth.get(); } return {height, width}; - }, [imageContainerSize, scale, originalImageWidth, originalImageHeight]); + }, [imageContainerSize, originalImageHeight, originalImageWidth, scale]); /** * Validates the offset to prevent overflow, and updates the image offset. @@ -180,13 +179,12 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const {height, width} = getDisplayedImageSize(); const maxOffsetX = (width - imageContainerSize) / 2; const maxOffsetY = (height - imageContainerSize) / 2; - 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; + translateX.set(clamp(offsetX, [maxOffsetX * -1, maxOffsetX])); + translateY.set(clamp(offsetY, [maxOffsetY * -1, maxOffsetY])); + prevMaxOffsetX.set(maxOffsetX); + prevMaxOffsetY.set(maxOffsetY); }, - [getDisplayedImageSize, imageContainerSize, translateX, translateY, prevMaxOffsetX, prevMaxOffsetY, clamp], + [getDisplayedImageSize, imageContainerSize, translateX, clamp, translateY, prevMaxOffsetX, prevMaxOffsetY], ); const newScaleValue = useCallback((newSliderValue: number, containerSize: number) => { @@ -201,8 +199,8 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose * and updates image's offset. */ const panGesture = Gesture.Pan().onChange((event) => { - const newX = translateX.value + event.changeX; - const newY = translateY.value + event.changeY; + const newX = translateX.get() + event.changeX; + const newY = translateY.get() + event.changeY; updateImageOffset(newX, newY); }); @@ -211,7 +209,7 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // when the browser window is resized. useEffect(() => { // If no panning has happened and the value is 0, do an early return. - if (!prevMaxOffsetX.value && !prevMaxOffsetY.value) { + if (!prevMaxOffsetX.get() && !prevMaxOffsetY.get()) { return; } const {height, width} = getDisplayedImageSize(); @@ -221,14 +219,14 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose // Since interpolation is expensive, we only want to do it if // image has been panned across X or Y axis by the user. if (prevMaxOffsetX) { - translateX.value = interpolate(translateX.value, [prevMaxOffsetX.value * -1, prevMaxOffsetX.value], [maxOffsetX * -1, maxOffsetX]); + translateX.set(interpolate(translateX.get(), [prevMaxOffsetX.get() * -1, prevMaxOffsetX.get()], [maxOffsetX * -1, maxOffsetX])); } if (prevMaxOffsetY) { - translateY.value = interpolate(translateY.value, [prevMaxOffsetY.value * -1, prevMaxOffsetY.value], [maxOffsetY * -1, maxOffsetY]); + translateY.set(interpolate(translateY.get(), [prevMaxOffsetY.get() * -1, prevMaxOffsetY.get()], [maxOffsetY * -1, maxOffsetY])); } - prevMaxOffsetX.value = maxOffsetX; - prevMaxOffsetY.value = maxOffsetY; + prevMaxOffsetX.set(maxOffsetX); + prevMaxOffsetY.set(maxOffsetY); }, [getDisplayedImageSize, imageContainerSize, prevMaxOffsetX, prevMaxOffsetY, translateX, translateY]); /** @@ -239,65 +237,69 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose onBegin: () => { 'worklet'; - isPressableEnabled.value = false; + isPressableEnabled.set(false); }, onChange: (event: GestureUpdateEvent) => { 'worklet'; - const newSliderValue = clamp(translateSlider.value + event.changeX, [0, sliderContainerSize]); + const newSliderValue = clamp(translateSlider.get() + event.changeX, [0, sliderContainerSize]); const newScale = newScaleValue(newSliderValue, sliderContainerSize); - const differential = newScale / scale.value; + const differential = newScale / scale.get(); - scale.value = newScale; - translateSlider.value = newSliderValue; + scale.set(newScale); + translateSlider.set(newSliderValue); - const newX = translateX.value * differential; - const newY = translateY.value * differential; + const newX = translateX.get() * differential; + const newY = translateY.get() * differential; updateImageOffset(newX, newY); }, onFinalize: () => { 'worklet'; - isPressableEnabled.value = true; + isPressableEnabled.set(true); }, }; // This effect is needed to prevent the incorrect position of // the slider's knob when the window's layout changes useEffect(() => { - translateSlider.value = interpolate(scale.value, [CONST.AVATAR_CROP_MODAL.MIN_SCALE, CONST.AVATAR_CROP_MODAL.MAX_SCALE], [0, sliderContainerSize]); - }, [scale.value, sliderContainerSize, translateSlider]); + translateSlider.set(interpolate(scale.get(), [CONST.AVATAR_CROP_MODAL.MIN_SCALE, CONST.AVATAR_CROP_MODAL.MAX_SCALE], [0, sliderContainerSize])); + }, [scale, sliderContainerSize, translateSlider]); // Rotates the image by changing the rotation value by 90 degrees // and updating the position so the image remains in the same place after rotation const rotateImage = useCallback(() => { - rotation.value -= 90; + runOnUI(() => { + rotation.set((value) => value - 90); - // Rotating 2d coordinates by applying the formula (x, y) → (-y, x). - [translateX.value, translateY.value] = [translateY.value, translateX.value * -1]; + const oldTranslateX = translateX.get(); + translateX.set(translateY.get()); + translateY.set(oldTranslateX * -1); - // Since we rotated the image by 90 degrees, now width becomes height and vice versa. - [originalImageHeight.value, originalImageWidth.value] = [originalImageWidth.value, originalImageHeight.value]; - }, [originalImageHeight.value, originalImageWidth.value, rotation, translateX.value, translateY.value]); + const oldOriginalImageHeight = originalImageHeight.get(); + originalImageHeight.set(originalImageWidth.get()); + originalImageWidth.set(oldOriginalImageHeight); + })(); + }, [originalImageHeight, originalImageWidth, rotation, translateX, translateY]); // Crops an image that was provided in the imageUri prop, using the current position/scale // then calls onSave and onClose callbacks const cropAndSaveImage = useCallback(() => { - if (isLoading.value) { + if (isLoading.get()) { return; } - isLoading.value = true; - const smallerSize = Math.min(originalImageHeight.value, originalImageWidth.value); - const size = smallerSize / scale.value; - const imageCenterX = originalImageWidth.value / 2; - const imageCenterY = originalImageHeight.value / 2; + isLoading.set(true); + const smallerSize = Math.min(originalImageHeight.get(), originalImageWidth.get()); + const size = smallerSize / scale.get(); + const imageCenterX = originalImageWidth.get() / 2; + const imageCenterY = originalImageHeight.get() / 2; const apothem = size / 2; // apothem for squares is equals to half of it size // Since the translate value is only a distance from the image center, we are able to calculate // the originX and the originY - start coordinates of cropping view. - const originX = imageCenterX - apothem - (translateX.value / imageContainerSize / scale.value) * smallerSize; - const originY = imageCenterY - apothem - (translateY.value / imageContainerSize / scale.value) * smallerSize; + const originX = imageCenterX - apothem - (translateX.get() / imageContainerSize / scale.get()) * smallerSize; + const originY = imageCenterY - apothem - (translateY.get() / imageContainerSize / scale.get()) * smallerSize; const crop = { height: size, @@ -312,29 +314,15 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose const name = isSvg ? 'fileName.png' : imageName; const type = isSvg ? 'image/png' : imageType; - cropOrRotateImage(imageUri, [{rotate: rotation.value % 360}, {crop}], {compress: 1, name, type}) + cropOrRotateImage(imageUri, [{rotate: rotation.get() % 360}, {crop}], {compress: 1, name, type}) .then((newImage) => { onClose?.(); onSave?.(newImage); }) .catch(() => { - isLoading.value = false; + isLoading.set(false); }); - }, [ - imageUri, - imageName, - imageType, - onClose, - onSave, - originalImageHeight.value, - originalImageWidth.value, - scale.value, - translateX.value, - imageContainerSize, - translateY.value, - rotation.value, - isLoading, - ]); + }, [isLoading, originalImageHeight, originalImageWidth, scale, translateX, imageContainerSize, translateY, imageType, imageName, imageUri, rotation, onClose, onSave]); const sliderOnPress = (locationX: number) => { // We are using the worklet directive here and running on the UI thread to ensure the Reanimated @@ -342,17 +330,16 @@ function AvatarCropModal({imageUri = '', imageName = '', imageType = '', onClose 'worklet'; - if (!locationX || !isPressableEnabled.value) { + if (!locationX || !isPressableEnabled.get()) { return; } const newSliderValue = clamp(locationX, [0, sliderContainerSize]); const newScale = newScaleValue(newSliderValue, sliderContainerSize); - // eslint-disable-next-line react-compiler/react-compiler - translateSlider.value = newSliderValue; - const differential = newScale / scale.value; - scale.value = newScale; - const newX = translateX.value * differential; - const newY = translateY.value * differential; + translateSlider.set(newSliderValue); + const differential = newScale / scale.get(); + scale.set(newScale); + const newX = translateX.get() * differential; + const newY = translateY.get() * differential; updateImageOffset(newX, newY); }; diff --git a/src/components/AvatarCropModal/ImageCropView.tsx b/src/components/AvatarCropModal/ImageCropView.tsx index 5bfb0d5f6557..1f11986a99f9 100644 --- a/src/components/AvatarCropModal/ImageCropView.tsx +++ b/src/components/AvatarCropModal/ImageCropView.tsx @@ -59,12 +59,12 @@ function ImageCropView({imageUri = '', containerSize = 0, panGesture = Gesture.P const imageStyle = useAnimatedStyle(() => { 'worklet'; - const height = originalImageHeight.value; - const width = originalImageWidth.value; + const height = originalImageHeight.get(); + const width = originalImageWidth.get(); const aspectRatio = height > width ? height / width : width / height; - const rotate = interpolate(rotation.value, [0, 360], [0, 360]); + const rotate = interpolate(rotation.get(), [0, 360], [0, 360]); return { - transform: [{translateX: translateX.value}, {translateY: translateY.value}, {scale: scale.value * aspectRatio}, {rotate: `${rotate}deg`}], + transform: [{translateX: translateX.get()}, {translateY: translateY.get()}, {scale: scale.get() * aspectRatio}, {rotate: `${rotate}deg`}], }; }, [originalImageHeight, originalImageWidth, rotation, translateX, translateY, scale]); diff --git a/src/components/AvatarCropModal/Slider.tsx b/src/components/AvatarCropModal/Slider.tsx index bac581da25e6..2f8a8fb6ef53 100644 --- a/src/components/AvatarCropModal/Slider.tsx +++ b/src/components/AvatarCropModal/Slider.tsx @@ -34,7 +34,7 @@ function Slider({sliderValue, gestureCallbacks}: SliderProps) { 'worklet'; return { - transform: [{translateX: sliderValue.value}], + transform: [{translateX: sliderValue.get()}], }; }); diff --git a/src/components/CustomStatusBarAndBackground/index.tsx b/src/components/CustomStatusBarAndBackground/index.tsx index ec52f07d211c..ac1fc77dff96 100644 --- a/src/components/CustomStatusBarAndBackground/index.tsx +++ b/src/components/CustomStatusBarAndBackground/index.tsx @@ -44,14 +44,14 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack const statusBarAnimation = useSharedValue(0); useAnimatedReaction( - () => statusBarAnimation.value, + () => statusBarAnimation.get(), (current, previous) => { // Do not run if either of the animated value is null // or previous animated value is greater than or equal to the current one if (previous === null || current === null || current <= previous) { return; } - const backgroundColor = interpolateColor(statusBarAnimation.value, [0, 1], [prevStatusBarBackgroundColor.value, statusBarBackgroundColor.value]); + const backgroundColor = interpolateColor(statusBarAnimation.get(), [0, 1], [prevStatusBarBackgroundColor.get(), statusBarBackgroundColor.get()]); runOnJS(updateStatusBarAppearance)({backgroundColor}); }, ); @@ -92,8 +92,8 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack currentScreenBackgroundColor = backgroundColorFromRoute || pageTheme.backgroundColor; } - prevStatusBarBackgroundColor.value = statusBarBackgroundColor.value; - statusBarBackgroundColor.value = currentScreenBackgroundColor; + prevStatusBarBackgroundColor.set(statusBarBackgroundColor.get()); + statusBarBackgroundColor.set(currentScreenBackgroundColor); const callUpdateStatusBarAppearance = () => { updateStatusBarAppearance({statusBarStyle: newStatusBarStyle}); @@ -101,8 +101,8 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack }; const callUpdateStatusBarBackgroundColor = () => { - statusBarAnimation.value = 0; - statusBarAnimation.value = withDelay(300, withTiming(1)); + statusBarAnimation.set(0); + statusBarAnimation.set(withDelay(300, withTiming(1))); }; // Don't update the status bar style if it's the same as the current one, to prevent flashing. @@ -121,7 +121,7 @@ function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBack callUpdateStatusBarAppearance(); } - if (currentScreenBackgroundColor !== theme.appBG || prevStatusBarBackgroundColor.value !== theme.appBG) { + if (currentScreenBackgroundColor !== theme.appBG || prevStatusBarBackgroundColor.get() !== theme.appBG) { callUpdateStatusBarBackgroundColor(); } }, diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index ecf72f89134b..3c831301db8b 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -60,18 +60,19 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo const buttonRef = ref; useEffect(() => { - // eslint-disable-next-line react-compiler/react-compiler - sharedValue.value = withTiming(isActive ? 1 : 0, { - duration: 340, - easing: Easing.inOut(Easing.ease), - }); + sharedValue.set( + withTiming(isActive ? 1 : 0, { + duration: 340, + easing: Easing.inOut(Easing.ease), + }), + ); }, [isActive, sharedValue]); const animatedStyle = useAnimatedStyle(() => { - const backgroundColor = interpolateColor(sharedValue.value, [0, 1], [success, buttonDefaultBG]); + const backgroundColor = interpolateColor(sharedValue.get(), [0, 1], [success, buttonDefaultBG]); return { - transform: [{rotate: `${sharedValue.value * 135}deg`}], + transform: [{rotate: `${sharedValue.get() * 135}deg`}], backgroundColor, borderRadius, }; @@ -79,7 +80,7 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo const animatedProps = useAnimatedProps( () => { - const fill = interpolateColor(sharedValue.value, [0, 1], [textLight, textDark]); + const fill = interpolateColor(sharedValue.get(), [0, 1], [textLight, textDark]); return { fill, diff --git a/src/components/KeyboardAvoidingView/index.android.tsx b/src/components/KeyboardAvoidingView/index.android.tsx index e8eb79d18bbd..4d758511d7ad 100644 --- a/src/components/KeyboardAvoidingView/index.android.tsx +++ b/src/components/KeyboardAvoidingView/index.android.tsx @@ -9,8 +9,8 @@ const useKeyboardAnimation = () => { const {reanimated} = useKeyboardContext(); // calculate it only once on mount, to avoid `SharedValue` reads during a render - const [initialHeight] = useState(() => -reanimated.height.value); - const [initialProgress] = useState(() => reanimated.progress.value); + const [initialHeight] = useState(() => -reanimated.height.get()); + const [initialProgress] = useState(() => reanimated.progress.get()); const heightWhenOpened = useSharedValue(initialHeight); const height = useSharedValue(initialHeight); @@ -22,22 +22,20 @@ const useKeyboardAnimation = () => { onStart: (e) => { 'worklet'; - progress.value = e.progress; - height.value = e.height; + progress.set(e.progress); + height.set(e.height); if (e.height > 0) { - // eslint-disable-next-line react-compiler/react-compiler - isClosed.value = false; - heightWhenOpened.value = e.height; + isClosed.set(false); + heightWhenOpened.set(e.height); } }, onEnd: (e) => { 'worklet'; - isClosed.value = e.height === 0; - - height.value = e.height; - progress.value = e.progress; + isClosed.set(e.height === 0); + height.set(e.height); + progress.set(e.progress); }, }, [], @@ -63,7 +61,7 @@ const defaultLayout: LayoutRectangle = { const KeyboardAvoidingView = forwardRef>( ({behavior, children, contentContainerStyle, enabled = true, keyboardVerticalOffset = 0, style, onLayout: onLayoutProps, ...props}, ref) => { const initialFrame = useSharedValue(null); - const frame = useDerivedValue(() => initialFrame.value ?? defaultLayout); + const frame = useDerivedValue(() => initialFrame.get() ?? defaultLayout); const keyboard = useKeyboardAnimation(); const {height: screenHeight} = useSafeAreaFrame(); @@ -71,21 +69,21 @@ const KeyboardAvoidingView = forwardRef { 'worklet'; - const keyboardY = screenHeight - keyboard.heightWhenOpened.value - keyboardVerticalOffset; + const keyboardY = screenHeight - keyboard.heightWhenOpened.get() - keyboardVerticalOffset; - return Math.max(frame.value.y + frame.value.height - keyboardY, 0); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [screenHeight, keyboardVerticalOffset]); + return Math.max(frame.get().y + frame.get().height - keyboardY, 0); + }, [screenHeight, keyboard.heightWhenOpened, keyboardVerticalOffset, frame]); - const onLayoutWorklet = useCallback((layout: LayoutRectangle) => { - 'worklet'; + const onLayoutWorklet = useCallback( + (layout: LayoutRectangle) => { + 'worklet'; - if (keyboard.isClosed.value || initialFrame.value === null) { - // eslint-disable-next-line react-compiler/react-compiler - initialFrame.value = layout; - } - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, []); + if (keyboard.isClosed.get() || initialFrame.get() === null) { + initialFrame.set(layout); + } + }, + [initialFrame, keyboard.isClosed], + ); const onLayout = useCallback>( (e) => { runOnUI(onLayoutWorklet)(e.nativeEvent.layout); @@ -95,14 +93,14 @@ const KeyboardAvoidingView = forwardRef { - const bottom = interpolate(keyboard.progress.value, [0, 1], [0, relativeKeyboardHeight()]); + const bottom = interpolate(keyboard.progress.get(), [0, 1], [0, relativeKeyboardHeight()]); const bottomHeight = enabled ? bottom : 0; switch (behavior) { case 'height': - if (!keyboard.isClosed.value) { + if (!keyboard.isClosed.get()) { return { - height: frame.value.height - bottomHeight, + height: frame.get().height - bottomHeight, flex: 0, }; } diff --git a/src/components/MultiGestureCanvas/index.tsx b/src/components/MultiGestureCanvas/index.tsx index cfbd5215f5cc..7e5c5d2365d8 100644 --- a/src/components/MultiGestureCanvas/index.tsx +++ b/src/components/MultiGestureCanvas/index.tsx @@ -100,7 +100,7 @@ function MultiGestureCanvas({ // Adding together zoom scale and the initial scale to fit the content into the canvas // Using the minimum content scale, so that the image is not bigger than the canvas // and not smaller than needed to fit - const totalScale = useDerivedValue(() => zoomScale.value * minContentScale, [minContentScale]); + const totalScale = useDerivedValue(() => zoomScale.get() * minContentScale, [minContentScale]); const panTranslateX = useSharedValue(0); const panTranslateY = useSharedValue(0); @@ -116,13 +116,13 @@ function MultiGestureCanvas({ const offsetY = useSharedValue(0); useAnimatedReaction( - () => isSwipingDownToClose.value, + () => isSwipingDownToClose.get(), (current) => { if (!isUsedInCarousel) { return; } // eslint-disable-next-line react-compiler/react-compiler, no-param-reassign - isPagerScrollEnabled.value = !current; + isPagerScrollEnabled.set(!current); }, ); @@ -145,26 +145,25 @@ function MultiGestureCanvas({ stopAnimation(); - // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = 0; - offsetY.value = 0; - pinchScale.value = 1; + offsetX.set(0); + offsetY.set(0); + pinchScale.set(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); + panTranslateX.set(withSpring(0, SPRING_CONFIG)); + panTranslateY.set(withSpring(0, SPRING_CONFIG)); + pinchTranslateX.set(withSpring(0, SPRING_CONFIG)); + pinchTranslateY.set(withSpring(0, SPRING_CONFIG)); + zoomScale.set(withSpring(1, SPRING_CONFIG, callback)); return; } - panTranslateX.value = 0; - panTranslateY.value = 0; - pinchTranslateX.value = 0; - pinchTranslateY.value = 0; - zoomScale.value = 1; + panTranslateX.set(0); + panTranslateY.set(0); + pinchTranslateX.set(0); + pinchTranslateY.set(0); + zoomScale.set(1); if (callback === undefined) { return; @@ -172,7 +171,7 @@ function MultiGestureCanvas({ callback(); }, - [stopAnimation, offsetX, offsetY, pinchScale, panTranslateX, panTranslateY, pinchTranslateX, pinchTranslateY, zoomScale], + [offsetX, offsetY, panTranslateX, panTranslateY, pinchScale, pinchTranslateX, pinchTranslateY, stopAnimation, zoomScale], ); const {singleTapGesture: baseSingleTapGesture, doubleTapGesture} = useTapGestures({ @@ -245,8 +244,8 @@ function MultiGestureCanvas({ // Animate the x and y position of the content within the canvas based on all of the gestures const animatedStyles = useAnimatedStyle(() => { - const x = pinchTranslateX.value + panTranslateX.value + offsetX.value; - const y = pinchTranslateY.value + panTranslateY.value + offsetY.value; + const x = pinchTranslateX.get() + panTranslateX.get() + offsetX.get(); + const y = pinchTranslateY.get() + panTranslateY.get() + offsetY.get(); return { transform: [ @@ -256,7 +255,7 @@ function MultiGestureCanvas({ { translateY: y, }, - {scale: totalScale.value}, + {scale: totalScale.get()}, ], // Hide the image if the size is not ready yet opacity: contentSizeProp?.width ? 1 : 0, diff --git a/src/components/MultiGestureCanvas/usePanGesture.ts b/src/components/MultiGestureCanvas/usePanGesture.ts index b94ed77f150b..f2f33aa87e7e 100644 --- a/src/components/MultiGestureCanvas/usePanGesture.ts +++ b/src/components/MultiGestureCanvas/usePanGesture.ts @@ -48,8 +48,8 @@ const usePanGesture = ({ onSwipeDown, }: UsePanGestureProps): PanGesture => { // The content size after fitting it to the canvas and zooming - const zoomedContentWidth = useDerivedValue(() => contentSize.width * totalScale.value, [contentSize.width]); - const zoomedContentHeight = useDerivedValue(() => contentSize.height * totalScale.value, [contentSize.height]); + const zoomedContentWidth = useDerivedValue(() => contentSize.width * totalScale.get(), [contentSize.width]); + const zoomedContentHeight = useDerivedValue(() => contentSize.height * totalScale.get(), [contentSize.height]); // Used to track previous touch position for the "swipe down to close" gesture const previousTouch = useSharedValue<{x: number; y: number} | null>(null); @@ -62,7 +62,7 @@ const usePanGesture = ({ const isMobileBrowser = Browser.isMobile(); // Disable "swipe down to close" gesture when content is bigger than the canvas - const enableSwipeDownToClose = useDerivedValue(() => canvasSize.height < zoomedContentHeight.value, [canvasSize.height]); + const enableSwipeDownToClose = useDerivedValue(() => canvasSize.height < zoomedContentHeight.get(), [canvasSize.height]); // Calculates bounds of the scaled content // Can we pan left/right/up/down @@ -73,25 +73,25 @@ const usePanGesture = ({ let horizontalBoundary = 0; let verticalBoundary = 0; - if (canvasSize.width < zoomedContentWidth.value) { - horizontalBoundary = Math.abs(canvasSize.width - zoomedContentWidth.value) / 2; + if (canvasSize.width < zoomedContentWidth.get()) { + horizontalBoundary = Math.abs(canvasSize.width - zoomedContentWidth.get()) / 2; } - if (canvasSize.height < zoomedContentHeight.value) { - verticalBoundary = Math.abs(zoomedContentHeight.value - canvasSize.height) / 2; + if (canvasSize.height < zoomedContentHeight.get()) { + verticalBoundary = Math.abs(zoomedContentHeight.get() - canvasSize.height) / 2; } const horizontalBoundaries = {min: -horizontalBoundary, max: horizontalBoundary}; const verticalBoundaries = {min: -verticalBoundary, max: verticalBoundary}; const clampedOffset = { - x: MultiGestureCanvasUtils.clamp(offsetX.value, horizontalBoundaries.min, horizontalBoundaries.max), - y: MultiGestureCanvasUtils.clamp(offsetY.value, verticalBoundaries.min, verticalBoundaries.max), + x: MultiGestureCanvasUtils.clamp(offsetX.get(), horizontalBoundaries.min, horizontalBoundaries.max), + y: MultiGestureCanvasUtils.clamp(offsetY.get(), verticalBoundaries.min, verticalBoundaries.max), }; // If the horizontal/vertical offset is the same after clamping to the min/max boundaries, the content is within the boundaries - const isInHorizontalBoundary = clampedOffset.x === offsetX.value; - const isInVerticalBoundary = clampedOffset.y === offsetY.value; + const isInHorizontalBoundary = clampedOffset.x === offsetX.get(); + const isInVerticalBoundary = clampedOffset.y === offsetY.get(); return { horizontalBoundaries, @@ -109,7 +109,7 @@ const usePanGesture = ({ '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) { + if (offsetX.get() === 0 && offsetY.get() === 0 && panTranslateX.get() === 0 && panTranslateY.get() === 0) { return; } @@ -119,88 +119,96 @@ const usePanGesture = ({ // If not, we need to snap back to the boundaries if (isInHorizontalBoundary) { // If the (absolute) velocity is 0, we don't need to run an animation - if (Math.abs(panVelocityX.value) !== 0) { + if (Math.abs(panVelocityX.get()) !== 0) { // Phase out the pan animation // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = withDecay({ - velocity: panVelocityX.value, - clamp: [horizontalBoundaries.min, horizontalBoundaries.max], - deceleration: PAN_DECAY_DECELARATION, - rubberBandEffect: false, - }); + offsetX.set( + withDecay({ + velocity: panVelocityX.get(), + clamp: [horizontalBoundaries.min, horizontalBoundaries.max], + deceleration: PAN_DECAY_DECELARATION, + rubberBandEffect: false, + }), + ); } } else { // Animated back to the boundary - offsetX.value = withSpring(clampedOffset.x, SPRING_CONFIG); + offsetX.set(withSpring(clampedOffset.x, SPRING_CONFIG)); } if (isInVerticalBoundary) { // If the (absolute) velocity is 0, we don't need to run an animation - if (Math.abs(panVelocityY.value) !== 0) { + if (Math.abs(panVelocityY.get()) !== 0) { // Phase out the pan animation - offsetY.value = withDecay({ - velocity: panVelocityY.value, - clamp: [verticalBoundaries.min, verticalBoundaries.max], - deceleration: PAN_DECAY_DECELARATION, - }); + offsetY.set( + withDecay({ + velocity: panVelocityY.get(), + clamp: [verticalBoundaries.min, verticalBoundaries.max], + deceleration: PAN_DECAY_DECELARATION, + }), + ); } } else { - const finalTranslateY = offsetY.value + panVelocityY.value * 0.2; - - if (finalTranslateY > SNAP_POINT && zoomScale.value <= 1) { - offsetY.value = withSpring(SNAP_POINT_HIDDEN, SPRING_CONFIG, () => { - isSwipingDownToClose.value = false; - - if (onSwipeDown) { - runOnJS(onSwipeDown)(); - } - }); + const finalTranslateY = offsetY.get() + panVelocityY.get() * 0.2; + + if (finalTranslateY > SNAP_POINT && zoomScale.get() <= 1) { + offsetY.set( + withSpring(SNAP_POINT_HIDDEN, SPRING_CONFIG, () => { + isSwipingDownToClose.set(false); + + if (onSwipeDown) { + runOnJS(onSwipeDown)(); + } + }), + ); } else { // Animated back to the boundary - offsetY.value = withSpring(clampedOffset.y, SPRING_CONFIG, () => { - isSwipingDownToClose.value = false; - }); + offsetY.set( + withSpring(clampedOffset.y, SPRING_CONFIG, () => { + isSwipingDownToClose.set(false); + }), + ); } } // 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]); + panVelocityX.set(0); + panVelocityY.set(0); + }, [getBounds, isSwipingDownToClose, offsetX, offsetY, onSwipeDown, panTranslateX, panTranslateY, panVelocityX, panVelocityY, zoomScale]); const panGesture = Gesture.Pan() .manualActivation(true) .averageTouches(true) .onTouchesUp(() => { - previousTouch.value = null; + previousTouch.set(null); }) .onTouchesMove((evt, state) => { // We only allow panning when the content is zoomed in - if (zoomScale.value > 1 && !shouldDisableTransformationGestures.value) { + if (zoomScale.get() > 1 && !shouldDisableTransformationGestures.get()) { state.activate(); } // TODO: this needs tuning to work properly - if (!shouldDisableTransformationGestures.value && zoomScale.value === 1 && previousTouch.value !== null) { - const velocityX = Math.abs((evt.allTouches.at(0)?.x ?? 0) - previousTouch.value.x); - const velocityY = (evt.allTouches.at(0)?.y ?? 0) - previousTouch.value.y; + const previousTouchValue = previousTouch.get(); + if (!shouldDisableTransformationGestures.get() && zoomScale.get() === 1 && previousTouchValue !== null) { + const velocityX = Math.abs((evt.allTouches.at(0)?.x ?? 0) - previousTouchValue.x); + const velocityY = (evt.allTouches.at(0)?.y ?? 0) - previousTouchValue.y; if (Math.abs(velocityY) > velocityX && velocityY > 20) { state.activate(); - // eslint-disable-next-line react-compiler/react-compiler - isSwipingDownToClose.value = true; - previousTouch.value = null; + isSwipingDownToClose.set(true); + previousTouch.set(null); return; } } - if (previousTouch.value === null) { - previousTouch.value = { + if (previousTouch.get() === null) { + previousTouch.set({ x: evt.allTouches.at(0)?.x ?? 0, y: evt.allTouches.at(0)?.y ?? 0, - }; + }); } }) .onStart(() => { @@ -213,31 +221,31 @@ const usePanGesture = ({ return; } - panVelocityX.value = evt.velocityX; - panVelocityY.value = evt.velocityY; + panVelocityX.set(evt.velocityX); + panVelocityY.set(evt.velocityY); - if (!isSwipingDownToClose.value) { - if (!isMobileBrowser || (isMobileBrowser && zoomScale.value !== 1)) { - panTranslateX.value += evt.changeX; + if (!isSwipingDownToClose.get()) { + if (!isMobileBrowser || (isMobileBrowser && zoomScale.get() !== 1)) { + panTranslateX.set((value) => value + evt.changeX); } } - if (enableSwipeDownToClose.value || isSwipingDownToClose.value) { - panTranslateY.value += evt.changeY; + if (enableSwipeDownToClose.get() || isSwipingDownToClose.get()) { + panTranslateY.set((value) => value + evt.changeY); } }) .onEnd(() => { // Add pan translation to total offset and reset gesture variables - offsetX.value += panTranslateX.value; - offsetY.value += panTranslateY.value; + offsetX.set((value) => value + panTranslateX.get()); + offsetY.set((value) => value + panTranslateY.get()); // Reset pan gesture variables - panTranslateX.value = 0; - panTranslateY.value = 0; - previousTouch.value = null; + panTranslateX.set(0); + panTranslateY.set(0); + previousTouch.set(null); // If we are swiping (in the pager), we don't want to return to boundaries - if (shouldDisableTransformationGestures.value) { + if (shouldDisableTransformationGestures.get()) { return; } diff --git a/src/components/MultiGestureCanvas/usePinchGesture.ts b/src/components/MultiGestureCanvas/usePinchGesture.ts index 01be2d00194a..7f5cecc4e949 100644 --- a/src/components/MultiGestureCanvas/usePinchGesture.ts +++ b/src/components/MultiGestureCanvas/usePinchGesture.ts @@ -61,16 +61,16 @@ const usePinchGesture = ({ return; } - runOnJS(onScaleChanged)(zoomScale.value); + runOnJS(onScaleChanged)(zoomScale.get()); }; // Update the total (pinch) translation based on the regular pinch + bounce useAnimatedReaction( - () => [pinchTranslateX.value, pinchTranslateY.value, pinchBounceTranslateX.value, pinchBounceTranslateY.value], + () => [pinchTranslateX.get(), pinchTranslateY.get(), pinchBounceTranslateX.get(), pinchBounceTranslateY.get()], ([translateX, translateY, bounceX, bounceY]) => { // eslint-disable-next-line react-compiler/react-compiler - totalPinchTranslateX.value = translateX + bounceX; - totalPinchTranslateY.value = translateY + bounceY; + totalPinchTranslateX.set(translateX + bounceX); + totalPinchTranslateY.set(translateY + bounceY); }, ); @@ -83,8 +83,8 @@ const usePinchGesture = ({ 'worklet'; return { - x: focalX - (canvasSize.width / 2 + offsetX.value), - y: focalY - (canvasSize.height / 2 + offsetY.value), + x: focalX - (canvasSize.width / 2 + offsetX.get()), + y: focalY - (canvasSize.height / 2 + offsetY.get()), }; }, [canvasSize.width, canvasSize.height, offsetX, offsetY], @@ -105,7 +105,7 @@ const usePinchGesture = ({ // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { // We don't want to activate pinch gesture when we are swiping in the pager - if (!shouldDisableTransformationGestures.value) { + if (!shouldDisableTransformationGestures.get()) { return; } @@ -116,8 +116,8 @@ const usePinchGesture = ({ // Set the origin focal point of the pinch gesture at the start of the gesture const adjustedFocal = getAdjustedFocal(evt.focalX, evt.focalY); - pinchOrigin.x.value = adjustedFocal.x; - pinchOrigin.y.value = adjustedFocal.y; + pinchOrigin.x.set(adjustedFocal.x); + pinchOrigin.y.set(adjustedFocal.y); }) .onChange((evt) => { // Disable the pinch gesture if one finger is released, @@ -127,58 +127,57 @@ const usePinchGesture = ({ return; } - const newZoomScale = pinchScale.value * evt.scale; - + const newZoomScale = pinchScale.get() * evt.scale; // Limit the zoom scale to zoom range including bounce range - if (zoomScale.value >= zoomRange.min * ZOOM_RANGE_BOUNCE_FACTORS.min && zoomScale.value <= zoomRange.max * ZOOM_RANGE_BOUNCE_FACTORS.max) { - zoomScale.value = newZoomScale; - currentPinchScale.value = evt.scale; + if (zoomScale.get() >= zoomRange.min * ZOOM_RANGE_BOUNCE_FACTORS.min && zoomScale.get() <= zoomRange.max * ZOOM_RANGE_BOUNCE_FACTORS.max) { + zoomScale.set(newZoomScale); + currentPinchScale.set(evt.scale); triggerScaleChangedEvent(); } // Calculate new pinch translation const adjustedFocal = getAdjustedFocal(evt.focalX, evt.focalY); - const newPinchTranslateX = adjustedFocal.x + currentPinchScale.value * pinchOrigin.x.value * -1; - const newPinchTranslateY = adjustedFocal.y + currentPinchScale.value * pinchOrigin.y.value * -1; + const newPinchTranslateX = adjustedFocal.x + currentPinchScale.get() * pinchOrigin.x.get() * -1; + const newPinchTranslateY = adjustedFocal.y + currentPinchScale.get() * pinchOrigin.y.get() * -1; // If the zoom scale is within the zoom range, we perform the regular pinch translation // Otherwise it means that we are "overzoomed" or "underzoomed", so we need to bounce back - if (zoomScale.value >= zoomRange.min && zoomScale.value <= zoomRange.max) { - pinchTranslateX.value = newPinchTranslateX; - pinchTranslateY.value = newPinchTranslateY; + if (zoomScale.get() >= zoomRange.min && zoomScale.get() <= zoomRange.max) { + pinchTranslateX.set(newPinchTranslateX); + pinchTranslateY.set(newPinchTranslateY); } else { // Store x and y translation that is produced while bouncing // so we can revert the bounce once pinch gesture is released - pinchBounceTranslateX.value = newPinchTranslateX - pinchTranslateX.value; - pinchBounceTranslateY.value = newPinchTranslateY - pinchTranslateY.value; + pinchBounceTranslateX.set(newPinchTranslateX - pinchTranslateX.get()); + pinchBounceTranslateY.set(newPinchTranslateY - pinchTranslateY.get()); } }) .onEnd(() => { // Add pinch translation to total offset and reset gesture variables - offsetX.value += pinchTranslateX.value; - offsetY.value += pinchTranslateY.value; - pinchTranslateX.value = 0; - pinchTranslateY.value = 0; - currentPinchScale.value = 1; + offsetX.set((value) => value + pinchTranslateX.get()); + offsetY.set((value) => value + pinchTranslateY.get()); + pinchTranslateX.set(0); + pinchTranslateY.set(0); + currentPinchScale.set(1); // If the content was "overzoomed" or "underzoomed", we need to bounce back with an animation - if (pinchBounceTranslateX.value !== 0 || pinchBounceTranslateY.value !== 0) { - pinchBounceTranslateX.value = withSpring(0, SPRING_CONFIG); - pinchBounceTranslateY.value = withSpring(0, SPRING_CONFIG); + if (pinchBounceTranslateX.get() !== 0 || pinchBounceTranslateY.get() !== 0) { + pinchBounceTranslateX.set(withSpring(0, SPRING_CONFIG)); + pinchBounceTranslateY.set(withSpring(0, SPRING_CONFIG)); } - if (zoomScale.value < zoomRange.min) { + if (zoomScale.get() < zoomRange.min) { // If the zoom scale is less than the minimum zoom scale, we need to set the zoom scale to the minimum - pinchScale.value = zoomRange.min; - zoomScale.value = withSpring(zoomRange.min, SPRING_CONFIG, triggerScaleChangedEvent); - } else if (zoomScale.value > zoomRange.max) { + pinchScale.set(zoomRange.min); + zoomScale.set(withSpring(zoomRange.min, SPRING_CONFIG, triggerScaleChangedEvent)); + } else if (zoomScale.get() > zoomRange.max) { // If the zoom scale is higher than the maximum zoom scale, we need to set the zoom scale to the maximum - pinchScale.value = zoomRange.max; - zoomScale.value = withSpring(zoomRange.max, SPRING_CONFIG, triggerScaleChangedEvent); + pinchScale.set(zoomRange.max); + zoomScale.set(withSpring(zoomRange.max, SPRING_CONFIG, triggerScaleChangedEvent)); } else { // Otherwise, we just update the pinch scale offset - pinchScale.value = zoomScale.value; + pinchScale.set(zoomScale.get()); triggerScaleChangedEvent(); } }); diff --git a/src/components/MultiGestureCanvas/useTapGestures.ts b/src/components/MultiGestureCanvas/useTapGestures.ts index 4faacc8ac972..a918310d2862 100644 --- a/src/components/MultiGestureCanvas/useTapGestures.ts +++ b/src/components/MultiGestureCanvas/useTapGestures.ts @@ -111,19 +111,18 @@ const useTapGestures = ({ offsetAfterZooming.y = 0; } - // eslint-disable-next-line react-compiler/react-compiler - offsetX.value = withSpring(offsetAfterZooming.x, SPRING_CONFIG); - offsetY.value = withSpring(offsetAfterZooming.y, SPRING_CONFIG); - zoomScale.value = withSpring(doubleTapScale, SPRING_CONFIG, callback); - pinchScale.value = doubleTapScale; + offsetX.set(withSpring(offsetAfterZooming.x, SPRING_CONFIG)); + offsetY.set(withSpring(offsetAfterZooming.y, SPRING_CONFIG)); + zoomScale.set(withSpring(doubleTapScale, SPRING_CONFIG, callback)); + pinchScale.set(doubleTapScale); }, - [stopAnimation, scaledContentWidth, scaledContentHeight, canvasSize, doubleTapScale, offsetX, offsetY, zoomScale, pinchScale], + [stopAnimation, canvasSize.width, canvasSize.height, scaledContentWidth, scaledContentHeight, doubleTapScale, offsetX, offsetY, zoomScale, pinchScale], ); const doubleTapGesture = Gesture.Tap() // The first argument is not used, but must be defined .onTouchesDown((_evt, state) => { - if (!shouldDisableTransformationGestures.value) { + if (!shouldDisableTransformationGestures.get()) { return; } @@ -137,13 +136,13 @@ const useTapGestures = ({ 'worklet'; if (onScaleChanged != null) { - runOnJS(onScaleChanged)(zoomScale.value); + runOnJS(onScaleChanged)(zoomScale.get()); } }; // If the content is already zoomed, we want to reset the zoom, // otherwise we want to zoom in - if (zoomScale.value > 1) { + if (zoomScale.get() > 1) { reset(true, triggerScaleChangedEvent); } else { zoomToCoordinates(evt.x, evt.y, triggerScaleChangedEvent); diff --git a/src/components/OpacityView.tsx b/src/components/OpacityView.tsx index f4884fd3c0f8..6c7aa26d05ba 100644 --- a/src/components/OpacityView.tsx +++ b/src/components/OpacityView.tsx @@ -44,16 +44,11 @@ function OpacityView({ }: OpacityViewProps) { const opacity = useSharedValue(1); const opacityStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, + opacity: opacity.get(), })); React.useEffect(() => { - if (shouldDim) { - // eslint-disable-next-line react-compiler/react-compiler - opacity.value = withTiming(dimmingValue, {duration: dimAnimationDuration}); - } else { - opacity.value = withTiming(1, {duration: dimAnimationDuration}); - } + opacity.set(withTiming(shouldDim ? dimmingValue : 1, {duration: dimAnimationDuration})); }, [shouldDim, dimmingValue, opacity, dimAnimationDuration]); return ( diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index c4bf2aa3fcd9..03f143f5c2a0 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -136,7 +136,7 @@ function ReportPreview({ const iouSettled = ReportUtils.isSettled(iouReportID) || action?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED; const previewMessageOpacity = useSharedValue(1); const previewMessageStyle = useAnimatedStyle(() => ({ - opacity: previewMessageOpacity.value, + opacity: previewMessageOpacity.get(), })); const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); @@ -428,11 +428,11 @@ function ReportPreview({ return; } - // eslint-disable-next-line react-compiler/react-compiler - previewMessageOpacity.value = withTiming(0.75, {duration: CONST.ANIMATION_PAID_DURATION / 2}, () => { - // eslint-disable-next-line react-compiler/react-compiler - previewMessageOpacity.value = withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION / 2}); - }); + previewMessageOpacity.set( + withTiming(0.75, {duration: CONST.ANIMATION_PAID_DURATION / 2}, () => { + previewMessageOpacity.set(withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION / 2})); + }), + ); // We only want to animate the text when the text changes // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [previewMessage, previewMessageOpacity]); @@ -442,12 +442,7 @@ function ReportPreview({ return; } - if (isPaidAnimationRunning) { - // eslint-disable-next-line react-compiler/react-compiler - checkMarkScale.value = withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})); - } else { - checkMarkScale.value = 1; - } + checkMarkScale.set(isPaidAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1); }, [isPaidAnimationRunning, iouSettled, checkMarkScale]); return ( diff --git a/src/components/SettlementButton/AnimatedSettlementButton.tsx b/src/components/SettlementButton/AnimatedSettlementButton.tsx index 5de528d741a2..65c2fd2f493b 100644 --- a/src/components/SettlementButton/AnimatedSettlementButton.tsx +++ b/src/components/SettlementButton/AnimatedSettlementButton.tsx @@ -23,20 +23,20 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is const height = useSharedValue(variables.componentSizeNormal); const buttonMarginTop = useSharedValue(styles.expenseAndReportPreviewTextButtonContainer.gap); const buttonStyles = useAnimatedStyle(() => ({ - transform: [{scale: buttonScale.value}], - opacity: buttonOpacity.value, + transform: [{scale: buttonScale.get()}], + opacity: buttonOpacity.get(), })); const paymentCompleteTextStyles = useAnimatedStyle(() => ({ - transform: [{scale: paymentCompleteTextScale.value}], - opacity: paymentCompleteTextOpacity.value, + transform: [{scale: paymentCompleteTextScale.get()}], + opacity: paymentCompleteTextOpacity.get(), position: 'absolute', alignSelf: 'center', })); const containerStyles = useAnimatedStyle(() => ({ - height: height.value, + height: height.get(), justifyContent: 'center', overflow: 'hidden', - marginTop: buttonMarginTop.value, + marginTop: buttonMarginTop.get(), })); const buttonDisabledStyle = isPaidAnimationRunning ? { @@ -46,13 +46,12 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is : undefined; const resetAnimation = useCallback(() => { - // eslint-disable-next-line react-compiler/react-compiler - buttonScale.value = 1; - buttonOpacity.value = 1; - paymentCompleteTextScale.value = 0; - paymentCompleteTextOpacity.value = 1; - height.value = variables.componentSizeNormal; - buttonMarginTop.value = styles.expenseAndReportPreviewTextButtonContainer.gap; + buttonScale.set(1); + buttonOpacity.set(1); + paymentCompleteTextScale.set(0); + paymentCompleteTextOpacity.set(1); + height.set(variables.componentSizeNormal); + buttonMarginTop.set(styles.expenseAndReportPreviewTextButtonContainer.gap); }, [buttonScale, buttonOpacity, paymentCompleteTextScale, paymentCompleteTextOpacity, height, buttonMarginTop, styles.expenseAndReportPreviewTextButtonContainer.gap]); useEffect(() => { @@ -60,19 +59,20 @@ function AnimatedSettlementButton({isPaidAnimationRunning, onAnimationFinish, is resetAnimation(); return; } - // eslint-disable-next-line react-compiler/react-compiler - buttonScale.value = withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}); - buttonOpacity.value = withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}); - paymentCompleteTextScale.value = withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION}); + buttonScale.set(withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); + buttonOpacity.set(withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); + paymentCompleteTextScale.set(withTiming(1, {duration: CONST.ANIMATION_PAID_DURATION})); // Wait for the above animation + 1s delay before hiding the component const totalDelay = CONST.ANIMATION_PAID_DURATION + CONST.ANIMATION_PAID_BUTTON_HIDE_DELAY; - height.value = withDelay( - totalDelay, - withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()), + height.set( + withDelay( + totalDelay, + withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}, () => runOnJS(onAnimationFinish)()), + ), ); - buttonMarginTop.value = withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); - paymentCompleteTextOpacity.value = withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION})); + buttonMarginTop.set(withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}))); + paymentCompleteTextOpacity.set(withDelay(totalDelay, withTiming(0, {duration: CONST.ANIMATION_PAID_DURATION}))); }, [isPaidAnimationRunning, onAnimationFinish, buttonOpacity, buttonScale, height, paymentCompleteTextOpacity, paymentCompleteTextScale, buttonMarginTop, resetAnimation]); return ( diff --git a/src/components/SpacerView.tsx b/src/components/SpacerView.tsx index bb762da1226b..3ca874e8f260 100644 --- a/src/components/SpacerView.tsx +++ b/src/components/SpacerView.tsx @@ -22,9 +22,9 @@ function SpacerView({shouldShow, style}: SpacerViewProps) { const prevShouldShow = usePrevious(shouldShow); const duration = CONST.ANIMATED_TRANSITION; const animatedStyles = useAnimatedStyle(() => ({ - borderBottomWidth: withTiming(borderBottomWidth.value, {duration}), - marginTop: withTiming(marginVertical.value, {duration}), - marginBottom: withTiming(marginVertical.value, {duration}), + borderBottomWidth: withTiming(borderBottomWidth.get(), {duration}), + marginTop: withTiming(marginVertical.get(), {duration}), + marginBottom: withTiming(marginVertical.get(), {duration}), })); React.useEffect(() => { @@ -35,9 +35,8 @@ function SpacerView({shouldShow, style}: SpacerViewProps) { marginVertical: shouldShow ? CONST.HORIZONTAL_SPACER.DEFAULT_MARGIN_VERTICAL : CONST.HORIZONTAL_SPACER.HIDDEN_MARGIN_VERTICAL, borderBottomWidth: shouldShow ? CONST.HORIZONTAL_SPACER.DEFAULT_BORDER_BOTTOM_WIDTH : CONST.HORIZONTAL_SPACER.HIDDEN_BORDER_BOTTOM_WIDTH, }; - // eslint-disable-next-line react-compiler/react-compiler - marginVertical.value = values.marginVertical; - borderBottomWidth.value = values.borderBottomWidth; + marginVertical.set(values.marginVertical); + borderBottomWidth.set(values.borderBottomWidth); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we only need to trigger when shouldShow prop is changed }, [shouldShow, prevShouldShow]); diff --git a/src/components/SplashScreenHider/index.native.tsx b/src/components/SplashScreenHider/index.native.tsx index 3586c7a88062..7fa6d3ab0786 100644 --- a/src/components/SplashScreenHider/index.native.tsx +++ b/src/components/SplashScreenHider/index.native.tsx @@ -16,10 +16,10 @@ function SplashScreenHider({onHide = () => {}}: SplashScreenHiderProps): SplashS const scale = useSharedValue(1); const opacityStyle = useAnimatedStyle(() => ({ - opacity: opacity.value, + opacity: opacity.get(), })); const scaleStyle = useAnimatedStyle(() => ({ - transform: [{scale: scale.value}], + transform: [{scale: scale.get()}], })); const hideHasBeenCalled = useRef(false); @@ -33,19 +33,22 @@ function SplashScreenHider({onHide = () => {}}: SplashScreenHiderProps): SplashS hideHasBeenCalled.current = true; BootSplash.hide().then(() => { - // eslint-disable-next-line react-compiler/react-compiler - scale.value = withTiming(0, { - duration: 200, - easing: Easing.back(2), - }); + scale.set( + withTiming(0, { + duration: 200, + easing: Easing.back(2), + }), + ); - opacity.value = withTiming( - 0, - { - duration: 250, - easing: Easing.out(Easing.ease), - }, - () => runOnJS(onHide)(), + opacity.set( + withTiming( + 0, + { + duration: 250, + easing: Easing.out(Easing.ease), + }, + () => runOnJS(onHide)(), + ), ); }); }, [opacity, scale, onHide]); diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index b1a829b4cc4b..140d3f5eccc4 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -79,7 +79,7 @@ function BaseVideoPlayer({ const [controlStatusState, setControlStatusState] = useState(controlsStatus); const controlsOpacity = useSharedValue(1); const controlsAnimatedStyle = useAnimatedStyle(() => ({ - opacity: controlsOpacity.value, + opacity: controlsOpacity.get(), })); const videoPlayerRef = useRef(null); @@ -111,8 +111,8 @@ function BaseVideoPlayer({ if (isEnded) { return; } - // eslint-disable-next-line react-compiler/react-compiler - controlsOpacity.value = withTiming(0, {duration: 500}, () => runOnJS(setControlStatusState)(CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE)); + + controlsOpacity.set(withTiming(0, {duration: 500}, () => runOnJS(setControlStatusState)(CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE))); }, [controlsOpacity, isEnded]); const debouncedHideControl = useMemo(() => debounce(hideControl, 1500), [hideControl]); @@ -149,7 +149,7 @@ function BaseVideoPlayer({ return; } setControlStatusState(CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW); - controlsOpacity.value = 1; + controlsOpacity.set(1); }, [controlStatusState, controlsOpacity, hideControl]); const showPopoverMenu = (event?: GestureResponderEvent | KeyboardEvent) => { @@ -206,7 +206,7 @@ function BaseVideoPlayer({ if (status.didJustFinish) { setIsEnded(status.didJustFinish && !status.isLooping); setControlStatusState(CONST.VIDEO_PLAYER.CONTROLS_STATUS.SHOW); - controlsOpacity.value = 1; + controlsOpacity.set(1); } else if (status.isPlaying && isEnded) { setIsEnded(false); } diff --git a/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx index c0b8c32cedcb..97e837658a3e 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx @@ -30,13 +30,12 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { const wasVideoPlayingOnCheck = useSharedValue(false); const onCheckVideoPlaying = (isPlaying: boolean) => { - // eslint-disable-next-line react-compiler/react-compiler - wasVideoPlayingOnCheck.value = isPlaying; + wasVideoPlayingOnCheck.set(isPlaying); }; const progressBarInteraction = (event: GestureUpdateEvent | GestureStateChangeEvent) => { const progress = getProgress(event.x, sliderWidth); - progressWidth.value = progress; + progressWidth.set(progress); runOnJS(seekPosition)((progress * duration) / 100); }; @@ -57,7 +56,7 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { }) .onFinalize(() => { setIsSliderPressed(false); - if (!wasVideoPlayingOnCheck.value) { + if (!wasVideoPlayingOnCheck.get()) { return; } playVideo(); @@ -67,10 +66,10 @@ function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { if (isSliderPressed) { return; } - progressWidth.value = getProgress(position, duration); + progressWidth.set(getProgress(position, duration)); }, [duration, isSliderPressed, position, progressWidth]); - const progressBarStyle: ViewStyle = useAnimatedStyle(() => ({width: `${progressWidth.value}%`})); + const progressBarStyle: ViewStyle = useAnimatedStyle(() => ({width: `${progressWidth.get()}%`})); return ( diff --git a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx index 751bd1b1df26..a6e44607ea62 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx @@ -35,7 +35,7 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { const {translate} = useLocalize(); const {updateVolume, volume} = useVolumeContext(); const [sliderHeight, setSliderHeight] = useState(1); - const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.value)}); + const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.get())}); const [isSliderBeingUsed, setIsSliderBeingUsed] = useState(false); const onSliderLayout = useCallback((event: LayoutChangeEvent) => { @@ -45,8 +45,7 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { const changeVolumeOnPan = useCallback( (event: GestureStateChangeEvent | GestureUpdateEvent) => { const val = NumberUtils.roundToTwoDecimalPlaces(1 - event.y / sliderHeight); - // eslint-disable-next-line react-compiler/react-compiler - volume.value = NumberUtils.clamp(val, 0, 1); + volume.set(NumberUtils.clamp(val, 0, 1)); }, [sliderHeight, volume], ); @@ -63,15 +62,15 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { runOnJS(setIsSliderBeingUsed)(false); }); - const progressBarStyle = useAnimatedStyle(() => ({height: `${volume.value * 100}%`})); + const progressBarStyle = useAnimatedStyle(() => ({height: `${volume.get() * 100}%`})); const updateIcon = useCallback((vol: number) => { setVolumeIcon({icon: getVolumeIcon(vol)}); }, []); useDerivedValue(() => { - runOnJS(updateVolume)(volume.value); - runOnJS(updateIcon)(volume.value); + runOnJS(updateVolume)(volume.get()); + runOnJS(updateIcon)(volume.get()); }, [volume]); return ( @@ -95,8 +94,8 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { )} updateVolume(volume.value === 0 ? 1 : 0)} + tooltipText={volume.get() === 0 ? translate('videoPlayer.unmute') : translate('videoPlayer.mute')} + onPress={() => updateVolume(volume.get() === 0 ? 1 : 0)} src={volumeIcon.icon} small={small} shouldForceRenderingTooltipBelow diff --git a/src/components/VideoPlayerContexts/VolumeContext.tsx b/src/components/VideoPlayerContexts/VolumeContext.tsx index f22b524848de..1f7b3bf66551 100644 --- a/src/components/VideoPlayerContexts/VolumeContext.tsx +++ b/src/components/VideoPlayerContexts/VolumeContext.tsx @@ -16,8 +16,7 @@ function VolumeContextProvider({children}: ChildrenProps) { return; } currentVideoPlayerRef.current.setStatusAsync({volume: newVolume, isMuted: newVolume === 0}); - // eslint-disable-next-line react-compiler/react-compiler - volume.value = newVolume; + volume.set(newVolume); }, [currentVideoPlayerRef, volume], ); @@ -28,8 +27,8 @@ function VolumeContextProvider({children}: ChildrenProps) { if (!originalParent) { return; } - updateVolume(volume.value); - }, [originalParent, updateVolume, volume.value]); + updateVolume(volume.get()); + }, [originalParent, updateVolume, volume]); const contextValue = useMemo(() => ({updateVolume, volume}), [updateVolume, volume]); return {children}; diff --git a/src/hooks/useAnimatedHighlightStyle/index.ts b/src/hooks/useAnimatedHighlightStyle/index.ts index e17f30fc60bf..4c9099c7f1ba 100644 --- a/src/hooks/useAnimatedHighlightStyle/index.ts +++ b/src/hooks/useAnimatedHighlightStyle/index.ts @@ -66,9 +66,9 @@ export default function useAnimatedHighlightStyle({ const theme = useTheme(); const highlightBackgroundStyle = useAnimatedStyle(() => ({ - backgroundColor: interpolateColor(repeatableProgress.value, [0, 1], [backgroundColor ?? theme.appBG, highlightColor ?? theme.border]), - height: height ? interpolate(nonRepeatableProgress.value, [0, 1], [0, height]) : 'auto', - opacity: interpolate(nonRepeatableProgress.value, [0, 1], [0, 1]), + backgroundColor: interpolateColor(repeatableProgress.get(), [0, 1], [backgroundColor ?? theme.appBG, highlightColor ?? theme.border]), + height: height ? interpolate(nonRepeatableProgress.get(), [0, 1], [0, height]) : 'auto', + opacity: interpolate(nonRepeatableProgress.get(), [0, 1], [0, 1]), borderRadius, })); @@ -90,19 +90,22 @@ export default function useAnimatedHighlightStyle({ setStartHighlight(false); InteractionManager.runAfterInteractions(() => { runOnJS(() => { - nonRepeatableProgress.value = withDelay( - itemEnterDelay, - withTiming(1, {duration: itemEnterDuration, easing: Easing.inOut(Easing.ease)}, (finished) => { - if (!finished) { - return; - } - - // eslint-disable-next-line react-compiler/react-compiler - repeatableProgress.value = withSequence( - withDelay(highlightStartDelay, withTiming(1, {duration: highlightStartDuration, easing: Easing.inOut(Easing.ease)})), - withDelay(highlightEndDelay, withTiming(0, {duration: highlightEndDuration, easing: Easing.inOut(Easing.ease)})), - ); - }), + nonRepeatableProgress.set( + withDelay( + itemEnterDelay, + withTiming(1, {duration: itemEnterDuration, easing: Easing.inOut(Easing.ease)}, (finished) => { + if (!finished) { + return; + } + + repeatableProgress.set( + withSequence( + withDelay(highlightStartDelay, withTiming(1, {duration: highlightStartDuration, easing: Easing.inOut(Easing.ease)})), + withDelay(highlightEndDelay, withTiming(0, {duration: highlightEndDuration, easing: Easing.inOut(Easing.ease)})), + ), + ); + }), + ), ); })(); }); diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index a91a64d3a8ed..ebbac60dc0bd 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -37,7 +37,7 @@ function SearchPageBottomTab() { const scrollOffset = useSharedValue(0); const topBarOffset = useSharedValue(variables.searchHeaderHeight); const topBarAnimatedStyle = useAnimatedStyle(() => ({ - top: topBarOffset.value, + top: topBarOffset.get(), })); const scrollHandler = useAnimatedScrollHandler({ @@ -47,15 +47,14 @@ function SearchPageBottomTab() { return; } const currentOffset = contentOffset.y; - const isScrollingDown = currentOffset > scrollOffset.value; - const distanceScrolled = currentOffset - scrollOffset.value; + const isScrollingDown = currentOffset > scrollOffset.get(); + const distanceScrolled = currentOffset - scrollOffset.get(); if (isScrollingDown && contentOffset.y > TOO_CLOSE_TO_TOP_DISTANCE) { - // eslint-disable-next-line react-compiler/react-compiler - topBarOffset.value = clamp(topBarOffset.value - distanceScrolled, variables.minimalTopBarOffset, variables.searchHeaderHeight); + topBarOffset.set(clamp(topBarOffset.get() - distanceScrolled, variables.minimalTopBarOffset, variables.searchHeaderHeight)); } else if (!isScrollingDown && distanceScrolled < 0 && contentOffset.y + layoutMeasurement.height < contentSize.height - TOO_CLOSE_TO_BOTTOM_DISTANCE) { - topBarOffset.value = withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}); + topBarOffset.set(withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); } - scrollOffset.value = currentOffset; + scrollOffset.set(currentOffset); }, }); @@ -113,7 +112,7 @@ function SearchPageBottomTab() { { - topBarOffset.value = withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS}); + topBarOffset.set(withTiming(variables.searchHeaderHeight, {duration: ANIMATION_DURATION_IN_MS})); }} /> diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx index d277862ebf2c..56ae27de5ea1 100644 --- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx +++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx @@ -35,13 +35,12 @@ function AnimatedEmptyStateBackground() { * We use x and y gyroscope velocity and add it to position offset to move background based on device movements. * Position the phone was in while entering the screen is the initial position for background image. */ - const {x, y} = animatedSensor.sensor.value; + const {x, y} = animatedSensor.sensor.get(); // The x vs y here seems wrong but is the way to make it feel right to the user - // eslint-disable-next-line react-compiler/react-compiler - xOffset.value = clamp(xOffset.value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X); - yOffset.value = clamp(yOffset.value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y); + xOffset.set((value) => clamp(value + y * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_X, IMAGE_OFFSET_X)); + yOffset.set((value) => clamp(value - x * CONST.ANIMATION_GYROSCOPE_VALUE, -IMAGE_OFFSET_Y, IMAGE_OFFSET_Y)); return { - transform: [{translateX: withSpring(xOffset.value)}, {translateY: withSpring(yOffset.value, {overshootClamping: true})}, {scale: 1.15}], + transform: [{translateX: withSpring(xOffset.get())}, {translateY: withSpring(yOffset.get(), {overshootClamping: true})}, {scale: 1.15}], }; }, [isReducedMotionEnabled]); diff --git a/src/pages/home/report/FloatingMessageCounter.tsx b/src/pages/home/report/FloatingMessageCounter.tsx index 0d92946e3d66..a196704d2119 100644 --- a/src/pages/home/report/FloatingMessageCounter.tsx +++ b/src/pages/home/report/FloatingMessageCounter.tsx @@ -30,13 +30,13 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const show = useCallback(() => { 'worklet'; - translateY.value = withSpring(MARKER_ACTIVE_TRANSLATE_Y); + translateY.set(withSpring(MARKER_ACTIVE_TRANSLATE_Y)); }, [translateY]); const hide = useCallback(() => { 'worklet'; - translateY.value = withSpring(MARKER_INACTIVE_TRANSLATE_Y); + translateY.set(withSpring(MARKER_INACTIVE_TRANSLATE_Y)); }, [translateY]); useEffect(() => { @@ -49,7 +49,7 @@ function FloatingMessageCounter({isActive = false, onClick = () => {}}: Floating const wrapperStyle = useAnimatedStyle(() => ({ ...styles.floatingMessageCounterWrapper, - transform: [{translateY: translateY.value}], + transform: [{translateY: translateY.get()}], })); return ( diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 701bbce6bf39..7b0d6663facf 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -701,20 +701,19 @@ function ComposerWithSuggestions( useEffect(() => { // We use the tag to store the native ID of the text input. Later, we use it in onSelectionChange to pick up the proper text input data. + tag.set(findNodeHandle(textInputRef.current) ?? -1); + }, [tag]); - tag.value = findNodeHandle(textInputRef.current) ?? -1; - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, []); useFocusedInputHandler( { onSelectionChange: (event) => { 'worklet'; - if (event.target === tag.value) { - cursorPositionValue.value = { + if (event.target === tag.get()) { + cursorPositionValue.set({ x: event.selection.end.x, y: event.selection.end.y, - }; + }); } }, }, @@ -723,7 +722,7 @@ function ComposerWithSuggestions( const measureParentContainerAndReportCursor = useCallback( (callback: MeasureParentContainerAndCursorCallback) => { const {scrollValue} = getScrollPosition({mobileInputScrollPosition, textInputRef}); - const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.value, positionOnWeb: selection}); + const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.get(), positionOnWeb: selection}); measureParentContainer((x, y, width, height) => { callback({ x, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 227b27aec1eb..59d1b4c00683 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -343,7 +343,7 @@ function ReportActionCompose({ const handleSendMessage = useCallback(() => { 'worklet'; - const clearComposer = composerRefShared.value.clear; + const clearComposer = composerRefShared.get().clear; if (!clearComposer) { throw new Error('The composerRefShared.clear function is not set yet. This should never happen, and indicates a developer error.'); } @@ -468,10 +468,9 @@ function ReportActionCompose({ { composerRef.current = ref ?? undefined; - // eslint-disable-next-line react-compiler/react-compiler - composerRefShared.value = { + composerRefShared.set({ clear: ref?.clear, - }; + }); }} suggestionsRef={suggestionsRef} isNextModalWillOpenRef={isNextModalWillOpenRef} diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index fbe51fa27acf..9f8d968bbc59 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -417,7 +417,7 @@ function ReportActionItemMessageEdit( const measureParentContainerAndReportCursor = useCallback( (callback: MeasureParentContainerAndCursorCallback) => { const {scrollValue} = getScrollPosition({mobileInputScrollPosition, textInputRef}); - const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.value, positionOnWeb: selection}); + const {x: xPosition, y: yPosition} = getCursorPosition({positionOnMobile: cursorPositionValue.get(), positionOnWeb: selection}); measureContainer((x, y, width, height) => { callback({ x, @@ -429,25 +429,23 @@ function ReportActionItemMessageEdit( }); }); }, - [cursorPositionValue.value, measureContainer, selection], + [cursorPositionValue, measureContainer, selection], ); useEffect(() => { // We use the tag to store the native ID of the text input. Later, we use it in onSelectionChange to pick up the proper text input data. - - // eslint-disable-next-line react-compiler/react-compiler - tag.value = findNodeHandle(textInputRef.current) ?? -1; + tag.set(findNodeHandle(textInputRef.current) ?? -1); }, [tag]); useFocusedInputHandler( { onSelectionChange: (event) => { 'worklet'; - if (event.target === tag.value) { - cursorPositionValue.value = { + if (event.target === tag.get()) { + cursorPositionValue.set({ x: event.selection.end.x, y: event.selection.end.y, - }; + }); } }, }, diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index d491c196e9c2..71ffc1e0ecb0 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -124,8 +124,8 @@ function IOURequestStepScan({ const focusIndicatorPosition = useSharedValue({x: 0, y: 0}); const cameraFocusIndicatorAnimatedStyle = useAnimatedStyle(() => ({ - opacity: focusIndicatorOpacity.value, - transform: [{translateX: focusIndicatorPosition.value.x}, {translateY: focusIndicatorPosition.value.y}, {scale: focusIndicatorScale.value}], + opacity: focusIndicatorOpacity.get(), + transform: [{translateX: focusIndicatorPosition.get().x}, {translateY: focusIndicatorPosition.get().y}, {scale: focusIndicatorScale.get()}], })); const focusCamera = (point: Point) => { @@ -147,11 +147,10 @@ function IOURequestStepScan({ .onStart((ev: {x: number; y: number}) => { const point = {x: ev.x, y: ev.y}; - // eslint-disable-next-line react-compiler/react-compiler - focusIndicatorOpacity.value = withSequence(withTiming(0.8, {duration: 250}), withDelay(1000, withTiming(0, {duration: 250}))); - focusIndicatorScale.value = 2; - focusIndicatorScale.value = withSpring(1, {damping: 10, stiffness: 200}); - focusIndicatorPosition.value = point; + focusIndicatorOpacity.set(withSequence(withTiming(0.8, {duration: 250}), withDelay(1000, withTiming(0, {duration: 250})))); + focusIndicatorScale.set(2); + focusIndicatorScale.set(withSpring(1, {damping: 10, stiffness: 200})); + focusIndicatorPosition.set(point); runOnJS(focusCamera)(point); }); diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx b/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx index 75e0be7e5f7c..fc28dc08fb15 100644 --- a/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/index.ios.tsx @@ -20,14 +20,15 @@ function BackgroundImage({width, transitionDuration, isSmallScreen = false}: Bac const isAnonymous = isAnonymousUser(); const opacity = useSharedValue(0); - const animatedStyle = useAnimatedStyle(() => ({opacity: opacity.value})); + const animatedStyle = useAnimatedStyle(() => ({opacity: opacity.get()})); // This sets the opacity animation for the background image once it has loaded. function setOpacityAnimation() { - // eslint-disable-next-line react-compiler/react-compiler - opacity.value = withTiming(1, { - duration: CONST.MICROSECONDS_PER_MS, - easing: Easing.ease, - }); + opacity.set( + withTiming(1, { + duration: CONST.MICROSECONDS_PER_MS, + easing: Easing.ease, + }), + ); } useEffect(() => { diff --git a/src/types/modules/react-native-reanimated.d.ts b/src/types/modules/react-native-reanimated.d.ts new file mode 100644 index 000000000000..7786a904786f --- /dev/null +++ b/src/types/modules/react-native-reanimated.d.ts @@ -0,0 +1,12 @@ +import type {SharedValue as ReanimatedSharedValue} from 'react-native-reanimated'; + +// Remove this augmentation once `value` property is deprecated in reanimated +declare module 'react-native-reanimated' { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface SharedValue extends ReanimatedSharedValue { + /** + * @deprecated Use the new `.get()` and `.set(value)` methods instead. + */ + value: Value; + } +}