diff --git a/TestsExample/App.js b/TestsExample/App.js index dba8aabdb0..9c41f7d1b8 100644 --- a/TestsExample/App.js +++ b/TestsExample/App.js @@ -37,6 +37,7 @@ import Test852 from './src/Test852'; import Test861 from './src/Test861'; import Test865 from './src/Test865'; import Test881 from './src/Test881'; +import Test898 from './src/Test898'; export default function App() { return ; diff --git a/TestsExample/android/app/build.gradle b/TestsExample/android/app/build.gradle index 59a85f981f..1f5359c242 100644 --- a/TestsExample/android/app/build.gradle +++ b/TestsExample/android/app/build.gradle @@ -78,7 +78,7 @@ import com.android.build.OutputFile */ project.ext.react = [ - enableHermes: false, // clean and rebuild if changing + enableHermes: true, // clean and rebuild if changing ] apply from: "../../node_modules/react-native/react.gradle" diff --git a/TestsExample/android/app/src/main/java/com/testsexample/MainApplication.java b/TestsExample/android/app/src/main/java/com/testsexample/MainApplication.java index 87165af509..ca5acfe231 100644 --- a/TestsExample/android/app/src/main/java/com/testsexample/MainApplication.java +++ b/TestsExample/android/app/src/main/java/com/testsexample/MainApplication.java @@ -11,6 +11,8 @@ import java.lang.reflect.InvocationTargetException; import java.util.List; import com.swmansion.rnscreens.RNScreensPackage; +import com.facebook.react.bridge.JSIModulePackage; +import com.swmansion.reanimated.ReanimatedJSIModulePackage; public class MainApplication extends Application implements ReactApplication { @@ -35,6 +37,11 @@ protected List getPackages() { protected String getJSMainModuleName() { return "index"; } + + @Override + protected JSIModulePackage getJSIModulePackage() { + return new ReanimatedJSIModulePackage(); + } }; @Override diff --git a/TestsExample/babel.config.js b/TestsExample/babel.config.js index c5b2760932..dfd47830c0 100644 --- a/TestsExample/babel.config.js +++ b/TestsExample/babel.config.js @@ -9,5 +9,6 @@ module.exports = { }, }, ], + 'react-native-reanimated/plugin', ] }; diff --git a/TestsExample/ios/Podfile.lock b/TestsExample/ios/Podfile.lock index 30c1cd19d1..9cd8544b60 100644 --- a/TestsExample/ios/Podfile.lock +++ b/TestsExample/ios/Podfile.lock @@ -326,9 +326,36 @@ PODS: - React - RNGestureHandler (1.8.0): - React - - RNReanimated (1.13.1): + - RNReanimated (2.1.0): + - DoubleConversion + - FBLazyVector + - FBReactNativeSpec + - glog + - RCT-Folly + - RCTRequired + - RCTTypeSafety - React - - RNScreens (3.0.0): + - React-callinvoker + - React-Core + - React-Core/DevSupport + - React-Core/RCTWebSocket + - React-CoreModules + - React-cxxreact + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-RCTActionSheet + - React-RCTAnimation + - React-RCTBlob + - React-RCTImage + - React-RCTLinking + - React-RCTNetwork + - React-RCTSettings + - React-RCTText + - React-RCTVibration + - ReactCommon/turbomodule/core + - Yoga + - RNScreens (3.1.1): - React-Core - RNSearchBar (3.5.1): - React @@ -494,7 +521,7 @@ SPEC CHECKSUMS: CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: cde416483dac037923206447da6e1454df403714 FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5 - FBReactNativeSpec: 749bd5fcb59a424f4f71bebcc6aebf985d791535 + FBReactNativeSpec: 5a7daebd6daac176b1060ad4b7f921db88c14acd Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021 Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 Flipper-Folly: f7a3caafbd74bda4827954fd7a6e000e36355489 @@ -533,8 +560,8 @@ SPEC CHECKSUMS: ReactCommon: cfe2b7fd20e0dbd2d1185cd7d8f99633fbc5ff05 RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39 - RNReanimated: dd8c286ab5dd4ba36d3a7fef8bff7e08711b5476 - RNScreens: e8e8dd0588b5da0ab57dcca76ab9b2d8987757e0 + RNReanimated: b8c8004b43446e3c2709fe64b2b41072f87428ad + RNScreens: bd1523c3bde7069b8e958e5a16e1fc7722ad0bdd RNSearchBar: 9860431356b7d12a8449d2fddb2b5f3c78d1e99f RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59 Yoga: 8c8436d4171c87504c648ae23b1d81242bdf3bbf diff --git a/TestsExample/package.json b/TestsExample/package.json index 2ca79562bd..7b864f606e 100644 --- a/TestsExample/package.json +++ b/TestsExample/package.json @@ -25,7 +25,7 @@ "react-native-appearance": "^0.3.4", "react-native-gesture-handler": "^1.8.0", "react-native-paper": "^4.3.1", - "react-native-reanimated": "^1.13.1", + "react-native-reanimated": "^2.1.0", "react-native-redash": "^15.11.1", "react-native-safe-area-context": "^3.1.9", "react-native-search-bar": "^3.5.1", diff --git a/TestsExample/src/Test898.tsx b/TestsExample/src/Test898.tsx new file mode 100644 index 0000000000..1db1b428ab --- /dev/null +++ b/TestsExample/src/Test898.tsx @@ -0,0 +1,353 @@ +import React, { useState, useEffect, RefObject } from 'react'; +import Animated, { + useSharedValue, + useAnimatedStyle, + useAnimatedGestureHandler, + interpolate, + Extrapolate, + withTiming, + Easing, + useAnimatedRef, + measure, + runOnJS, +} from 'react-native-reanimated'; +import { + Dimensions, + StyleSheet, + View, + Image, + Platform, +} from 'react-native'; +import { + ScrollView, + PanGestureHandler, + TapGestureHandler, + TapGestureHandlerGestureEvent, +} from 'react-native-gesture-handler'; +import {createNativeStackNavigator} from 'react-native-screens/native-stack'; +import {NavigationContainer} from '@react-navigation/native'; +import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; + + +const AnimatedImage = Animated.createAnimatedComponent(Image); + +const dimensions = Dimensions.get('window'); +const GUTTER_WIDTH = 3; +const NUMBER_OF_IMAGES = 4; +const IMAGE_SIZE = + (dimensions.width - GUTTER_WIDTH * (NUMBER_OF_IMAGES - 1)) / NUMBER_OF_IMAGES; + +type ExampleImage = { + uri: string; + width: number; + height: number; +}; +type ActiveExampleImageProperties = { + x: Animated.SharedValue; + y: Animated.SharedValue; + width: Animated.SharedValue; + height: Animated.SharedValue; + imageOpacity: Animated.SharedValue; +}; +type ActiveExampleImage = ActiveExampleImageProperties & { + // @ts-ignore: FIXME AnimatedImage type + animatedRef: RefObject; + item: ExampleImage; +}; + +type onItemPressFn = ( + animatedRef: RefObject, + item: ExampleImage, + svs: ActiveExampleImageProperties +) => void; +function ImageList({ + images, + onItemPress, +}: { + images: ExampleImage[]; + onItemPress: onItemPressFn; +}) { + return ( + + {images.map((item, i) => ( + + ))} + + ); +} + +type ListItemProps = { + item: ExampleImage; + index: number; + onPress: onItemPressFn; +}; +function ListItem({ item, index, onPress }: ListItemProps) { + // @ts-ignore: FIXME(TS) correct type for createAnimatedComponent + const ref = useAnimatedRef(); + const opacity = useSharedValue(1); + const statusBarInset = useSafeAreaInsets().top; // inset of the status bar + const headerHeight = statusBarInset + 44; + + const containerStyle = { + marginRight: (index + 1) % 4 === 0 ? 0 : GUTTER_WIDTH, + marginBottom: GUTTER_WIDTH, + }; + + const styles = useAnimatedStyle(() => { + return { + width: IMAGE_SIZE, + height: IMAGE_SIZE, + opacity: opacity.value, + }; + }); + + const width = useSharedValue(0); + const height = useSharedValue(0); + const x = useSharedValue(0); + const y = useSharedValue(0); + + function handlePress() { + onPress(ref, item, { imageOpacity: opacity, width, height, x, y }); + } + + const handler = useAnimatedGestureHandler({ + onFinish: (_evt, _ctx, isCanceledOrFailed) => { + if (isCanceledOrFailed) { + return; + } + + // measure the image + // width/height and position to animate from it to the full screen one + const measurements = measure(ref); + + width.value = measurements.width; + height.value = measurements.height; + x.value = measurements.pageX; + y.value = measurements.pageY - headerHeight; + + runOnJS(handlePress)(); + }, + }); + + return ( + + + + + + ); +} + +const timingConfig = { + duration: 240, + easing: Easing.bezier(0.33, 0.01, 0, 1), +}; + +function ImageTransition({ + activeImage, + onClose, +}: { + activeImage: ActiveExampleImage; + onClose: () => void; +}) { + const { item, x, y, width, height, imageOpacity } = activeImage; + const { uri } = item; + + const targetWidth = dimensions.width; + const scaleFactor = item.width / targetWidth; + const targetHeight = item.height / scaleFactor; + + const statusBarInset = useSafeAreaInsets().top; // inset of the status bar + const headerHeight = statusBarInset + 44; + + const animationProgress = useSharedValue(0); + + const backdropOpacity = useSharedValue(0); + const scale = useSharedValue(1); + + const targetX = useSharedValue(0); + const targetY = useSharedValue( + (dimensions.height - targetHeight) / 2 - headerHeight + ); + + const translateX = useSharedValue(0); + const translateY = useSharedValue(0); + + const onPan = useAnimatedGestureHandler({ + onActive: (event) => { + translateX.value = event.translationX; + translateY.value = event.translationY; + + scale.value = interpolate( + translateY.value, + [-200, 0, 200], + [0.65, 1, 0.65], + Extrapolate.CLAMP + ); + + backdropOpacity.value = interpolate( + translateY.value, + [-100, 0, 100], + [0, 1, 0], + Extrapolate.CLAMP + ); + }, + + onEnd: (_event, _ctx) => { + if (Math.abs(translateY.value) > 40) { + targetX.value = translateX.value - targetX.value * -1; + targetY.value = translateY.value - targetY.value * -1; + + translateX.value = 0; + translateY.value = 0; + + animationProgress.value = withTiming(0, timingConfig, () => { + imageOpacity.value = withTiming( + 1, + { + duration: 16, + }, + () => { + runOnJS(onClose)(); + } + ); + }); + + backdropOpacity.value = withTiming(0, timingConfig); + } else { + backdropOpacity.value = withTiming(1, timingConfig); + translateX.value = withTiming(0, timingConfig); + translateY.value = withTiming(0, timingConfig); + } + + scale.value = withTiming(1, timingConfig); + }, + }); + + const imageStyles = useAnimatedStyle(() => { + const interpolateProgress = (range: [number, number]) => + interpolate(animationProgress.value, [0, 1], range, Extrapolate.CLAMP); + + const top = + translateY.value + interpolateProgress([y.value, targetY.value]); + const left = + translateX.value + interpolateProgress([x.value, targetX.value]); + + return { + position: 'absolute', + top, + left, + width: interpolateProgress([width.value, targetWidth]), + height: interpolateProgress([height.value, targetHeight]), + transform: [ + { + scale: scale.value, + }, + ], + }; + }); + + const backdropStyles = useAnimatedStyle(() => { + return { + opacity: backdropOpacity.value, + }; + }); + + useEffect(() => { + // fixes flickering + requestAnimationFrame(() => { + imageOpacity.value = 0; + }); + + animationProgress.value = withTiming(1, timingConfig); + backdropOpacity.value = withTiming(1, timingConfig); + }, []); + + return ( + + + + + + + + + + ); +} + +const images: ExampleImage[] = Array.from({ length: 30 }, (_, index) => { + return { + uri: `https://picsum.photos/id/${index + 10}/400/400`, + width: dimensions.width, + height: 400, + }; +}); + +function LightboxExample(): React.ReactElement { + const [activeImage, setActiveImage] = useState( + null + ); + + function onItemPress( + // @ts-ignore: FIXME AnimatedImage type + animatedRef: RefObject, + item: ExampleImage, + svs: ActiveExampleImageProperties + ) { + setActiveImage({ + animatedRef, + item, + ...svs, + }); + } + + function onClose() { + setActiveImage(null); + } + + const statusBarInset = useSafeAreaInsets().top; // inset of the status bar + const headerHeight = statusBarInset + 44; + const height = + Platform.OS === 'web' ? dimensions.height - headerHeight : undefined; + + return ( + + + + {activeImage && ( + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + paddingTop: 0, + }, + + scrollContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + }, + + backdrop: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'black', + }, +}); + +const Stack = createNativeStackNavigator(); + +export default function App() { + return( + + + + + + + + ) +} diff --git a/TestsExample/yarn.lock b/TestsExample/yarn.lock index a5bc49cf71..e6cba4e551 100644 --- a/TestsExample/yarn.lock +++ b/TestsExample/yarn.lock @@ -958,6 +958,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-object-assign@^7.10.4": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.12.13.tgz#d9b9200a69e03403a813e44a933ad9f4bddfd050" + integrity sha512-4QxDMc0lAOkIBSfCrnSGbAJ+4epDBF2XXwcLXuBcG1xl9u7LrktNVD4+LwhL47XuKVPQ7R25e/WdcV+h97HyZA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz#4ea08696b8d2e65841d0c7706482b048bed1066e" @@ -2889,11 +2896,6 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.4.1: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2909,6 +2911,13 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +cross-fetch@^3.0.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== + dependencies: + node-fetch "2.6.1" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3624,14 +3633,13 @@ fbjs@^0.8.4: setimmediate "^1.0.5" ua-parser-js "^0.7.18" -fbjs@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" - integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== +fbjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" + integrity sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg== dependencies: - core-js "^2.4.1" + cross-fetch "^3.0.4" fbjs-css-vars "^1.0.0" - isomorphic-fetch "^2.1.1" loose-envify "^1.0.0" object-assign "^4.1.0" promise "^7.1.1" @@ -5726,6 +5734,11 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.5" +mockdate@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" + integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5795,6 +5808,11 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" +node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -5803,11 +5821,6 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-fetch@^2.2.0, node-fetch@^2.6.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -6479,12 +6492,15 @@ react-native-paper@^4.3.1: color "^3.1.2" react-native-safe-area-view "^0.14.9" -react-native-reanimated@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-1.13.1.tgz#c370c32cc4d447ae896cb029bb9c6a2f7608c5b4" - integrity sha512-3sF46jts9MbktgIasf0sTM8uhOYO5a5Q3YyQ4X1jjSE82n/fY2nW3XTFsLGfLEpK2ir4XSDhQWVgFHazaXZTww== +react-native-reanimated@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.1.0.tgz#b9ad04aee490e1e030d0a6cdaa43a14895d9a54d" + integrity sha512-tlPvvcdf+X7HGQ7g/7npBFhwMznfdk7MHUc9gUB/kp2abSscXNe/kOVKlrNEOO4DS11rNOXc+llFxVFMuNk0zA== dependencies: - fbjs "^1.0.0" + "@babel/plugin-transform-object-assign" "^7.10.4" + fbjs "^3.0.0" + mockdate "^3.0.2" + string-hash-64 "^1.0.3" react-native-redash@^15.11.1: version "15.11.1" @@ -7362,6 +7378,11 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= +string-hash-64@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322" + integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"