From 95ce988fb764d180d672300479494d9973dd4167 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Wed, 29 Jul 2026 19:57:21 +0200 Subject: [PATCH] WIP --- package.json | 2 +- .../react-native-drawer-layout@4.2.3.patch | 9 ++ pnpm-lock.yaml | 55 ++------ src/components/ContextMenu/index.tsx | 103 +++++++-------- src/components/ContextMenu/types.ts | 11 +- src/components/DraggableList/index.tsx | 23 ++-- .../pager/ImageItem/ImageItem.android.tsx | 77 ++++++----- .../pager/ImageItem/ImageItem.ios.tsx | 22 ++-- src/components/Lightbox/pager/ImagePager.tsx | 21 +-- src/components/dms/MessageContextMenu.tsx | 4 +- src/components/dms/SwipeToReply.tsx | 123 ++++++++---------- .../custom-animations/GestureActionView.tsx | 27 ++-- src/screens/VideoFeed/components/Scrubber.tsx | 72 +++++----- src/screens/VideoFeed/index.tsx | 4 +- src/state/global-gesture-events/index.tsx | 39 +++--- 15 files changed, 279 insertions(+), 313 deletions(-) diff --git a/package.json b/package.json index 042c1b3d91..c2f80655ff 100644 --- a/package.json +++ b/package.json @@ -234,7 +234,7 @@ "react-native-device-attest": "^0.1.6", "react-native-drawer-layout": "^4.2.3", "react-native-edge-to-edge": "^1.8.1", - "react-native-gesture-handler": "~2.32.0", + "react-native-gesture-handler": "~3.1.0", "react-native-keyboard-controller": "1.21.9", "react-native-mmkv": "^3.3.3", "react-native-pager-view": "6.8.0", diff --git a/patches/react-native-drawer-layout@4.2.3.patch b/patches/react-native-drawer-layout@4.2.3.patch index bb4b3381b1..55ff7943a0 100644 --- a/patches/react-native-drawer-layout@4.2.3.patch +++ b/patches/react-native-drawer-layout@4.2.3.patch @@ -36,3 +36,12 @@ index 38470a658878a63919186257041098747ec55473..b9d25798c9be229efeb676166f89bfbc if (open) { runOnJS(onOpen)(); } else { +diff --git a/lib/typescript/src/types.d.ts b/lib/typescript/src/types.d.ts +--- a/lib/typescript/src/types.d.ts ++++ b/lib/typescript/src/types.d.ts +@@ -1,4 +1,4 @@ + import * as React from 'react'; + import type { StyleProp, View, ViewStyle } from 'react-native'; +-import type { PanGesture } from 'react-native-gesture-handler'; ++import type { LegacyPanGesture as PanGesture } from 'react-native-gesture-handler'; + import type { SharedValue } from 'react-native-reanimated'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fda57cac7c..0f972f06ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -228,7 +228,7 @@ patchedDependencies: expo@57.0.8: 1722861d12907a2d412d04230545b2fcbfa5547a79f99d763cb3e8de6f3a6f2a react-native-date-picker@5.0.13: 92943fb79d17d7342a29bbb12b0d8ee3cf6f7bca12ed322dc8d124a3e9fb75bd react-native-dotenv@3.4.11: 16b34eb974399935d3cf7c2526534f906991ccd876215176658347059cdd2021 - react-native-drawer-layout@4.2.3: 74f2c043cc22ab87054f219e7c7373a509b779b18bfa79d27e7d051d73355130 + react-native-drawer-layout@4.2.3: 2a67e397c87df9fb947b1931c9b00724b802a8dca5d3013617e6155f1ae3188e react-native-keyboard-controller@1.21.9: ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271 react-native-pager-view@6.8.0: c8316acd33c9aef6531e8c272c2bfab7bd02af1255969eeaab2bad5ab48531cc react-native-screens@4.26.2: ba3295d44434a729f143a6b2c24e4a9b2f911ab200b2bc47e46cdfeaa1536f7c @@ -640,13 +640,13 @@ importers: version: 0.1.6(expo@57.0.8)(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-drawer-layout: specifier: ^4.2.3 - version: 4.2.3(patch_hash=74f2c043cc22ab87054f219e7c7373a509b779b18bfa79d27e7d051d73355130)(react-native-gesture-handler@2.32.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-reanimated@4.6.0(92d1a74bc123f334ae9968fa1c80814f))(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + version: 4.2.3(patch_hash=2a67e397c87df9fb947b1931c9b00724b802a8dca5d3013617e6155f1ae3188e)(react-native-gesture-handler@3.1.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-reanimated@4.6.0(92d1a74bc123f334ae9968fa1c80814f))(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-edge-to-edge: specifier: ^1.8.1 version: 1.8.1(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-gesture-handler: - specifier: ~2.32.0 - version: 2.32.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + specifier: ~3.1.0 + version: 3.1.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-keyboard-controller: specifier: 1.21.9 version: 1.21.9(patch_hash=ac52bf7502ff3ad34a8594b5e1a916b96bf87a2523b6abc87c31f03607d52271)(react-native-reanimated@4.6.0(92d1a74bc123f334ae9968fa1c80814f))(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) @@ -715,7 +715,7 @@ importers: version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) sonner-native: specifier: 0.26.4 - version: 0.26.4(a1115538eb9b7f01f00730be61406201) + version: 0.26.4(58a7fe7e7df4354448cb52f8cc2a8c5c) tippy.js: specifier: ^6.3.7 version: 6.3.7 @@ -1764,10 +1764,6 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@egjs/hammerjs@2.0.17': - resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} - engines: {node: '>=0.8.0'} - '@emnapi/runtime@1.11.3': resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} @@ -3990,9 +3986,6 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - '@types/hammerjs@2.0.46': - resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} - '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -4080,9 +4073,6 @@ packages: '@types/react-test-renderer@19.1.0': resolution: {integrity: sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==} - '@types/react@19.1.17': - resolution: {integrity: sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==} - '@types/react@19.2.18': resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} @@ -6312,9 +6302,6 @@ packages: hls.js@1.6.16: resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -8369,8 +8356,8 @@ packages: react: '*' react-native: '*' - react-native-gesture-handler@2.32.0: - resolution: {integrity: sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==} + react-native-gesture-handler@3.1.0: + resolution: {integrity: sha512-+kWVyZ6vLdtyFa1/aOc/sZncLAVPUKxhji/ttbiLI7hOaSU44bLjhI7p+Ns+pMl2r3RqPXssl5qHReYet9G77g==} peerDependencies: react: '*' react-native: '*' @@ -10964,10 +10951,6 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@egjs/hammerjs@2.0.17': - dependencies: - '@types/hammerjs': 2.0.46 - '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 @@ -13580,8 +13563,6 @@ snapshots: dependencies: '@types/node': 24.12.4 - '@types/hammerjs@2.0.46': {} - '@types/html-minifier-terser@6.1.0': {} '@types/http-errors@2.0.5': {} @@ -13673,11 +13654,7 @@ snapshots: '@types/react-test-renderer@19.1.0': dependencies: - '@types/react': 19.1.17 - - '@types/react@19.1.17': - dependencies: - csstype: 3.2.3 + '@types/react': 19.2.18 '@types/react@19.2.18': dependencies: @@ -16164,10 +16141,6 @@ snapshots: hls.js@1.6.16: {} - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -18481,12 +18454,12 @@ snapshots: '@babel/runtime': 7.29.2 dotenv: 16.6.1 - ? react-native-drawer-layout@4.2.3(patch_hash=74f2c043cc22ab87054f219e7c7373a509b779b18bfa79d27e7d051d73355130)(react-native-gesture-handler@2.32.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-reanimated@4.6.0(92d1a74bc123f334ae9968fa1c80814f))(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + ? react-native-drawer-layout@4.2.3(patch_hash=2a67e397c87df9fb947b1931c9b00724b802a8dca5d3013617e6155f1ae3188e)(react-native-gesture-handler@3.1.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-reanimated@4.6.0(92d1a74bc123f334ae9968fa1c80814f))(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) : dependencies: color: 4.2.3 react: 19.2.3 react-native: 0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1) - react-native-gesture-handler: 2.32.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + react-native-gesture-handler: 3.1.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-reanimated: 4.6.0(92d1a74bc123f334ae9968fa1c80814f) use-latest-callback: 0.2.6(react@19.2.3) @@ -18495,11 +18468,9 @@ snapshots: react: 19.2.3 react-native: 0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1) - react-native-gesture-handler@2.32.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3): + react-native-gesture-handler@3.1.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3): dependencies: - '@egjs/hammerjs': 2.0.17 '@types/react-test-renderer': 19.1.0 - hoist-non-react-statics: 3.3.2 invariant: 2.2.4 react: 19.2.3 react-native: 0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1) @@ -19164,11 +19135,11 @@ snapshots: dependencies: atomic-sleep: 1.0.0 - sonner-native@0.26.4(a1115538eb9b7f01f00730be61406201): + sonner-native@0.26.4(58a7fe7e7df4354448cb52f8cc2a8c5c): dependencies: react: 19.2.3 react-native: 0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1) - react-native-gesture-handler: 2.32.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) + react-native-gesture-handler: 3.1.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-reanimated: 4.6.0(92d1a74bc123f334ae9968fa1c80814f) react-native-safe-area-context: 5.7.0(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) react-native-screens: 4.26.2(patch_hash=ba3295d44434a729f143a6b2c24e4a9b2f911ab200b2bc47e46cdfeaa1536f7c)(react-native@0.86.0(patch_hash=239208992c380d6785576d1fae2e3f60f60f509d7b09d56bf552be29d6c42a06)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/jest-preset@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.18)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3) diff --git a/src/components/ContextMenu/index.tsx b/src/components/ContextMenu/index.tsx index 294ddbd96e..883215866a 100644 --- a/src/components/ContextMenu/index.tsx +++ b/src/components/ContextMenu/index.tsx @@ -20,11 +20,12 @@ import { type ViewStyle, } from 'react-native' import { - Gesture, GestureDetector, - type GestureStateChangeEvent, - type GestureUpdateEvent, - type PanGestureHandlerEventPayload, + type PanGestureActiveEvent, + useCompetingGestures, + useExclusiveGestures, + usePanGesture, + useTapGesture, } from 'react-native-gesture-handler' import {KeyboardEvents} from 'react-native-keyboard-controller' import Animated, { @@ -299,24 +300,19 @@ export function Trigger({ } }, [context, insets]) - const tapGesture = useMemo(() => { - const gesture = Gesture.Tap() - .numberOfTaps(1) - .cancelsTouchesInView(false) - .runOnJS(true) - if (onTap) { - gesture.onEnd(() => void onTap()) - } - return gesture - }, [onTap]) + const tapGesture = useTapGesture({ + numberOfTaps: 1, + cancelsTouchesInView: false, + runOnJS: true, + onDeactivate: onTap ? () => void onTap() : undefined, + }) - const doubleTapGesture = useMemo(() => { - return Gesture.Tap() - .numberOfTaps(2) - .hitSlop(HITSLOP_10) - .onEnd(() => void open('auxiliary-only')) - .runOnJS(true) - }, [open]) + const doubleTapGesture = useTapGesture({ + numberOfTaps: 2, + hitSlop: HITSLOP_10, + runOnJS: true, + onDeactivate: () => void open('auxiliary-only'), + }) const { hoverablesSV, @@ -336,34 +332,33 @@ export function Trigger({ }, ) - const pressAndHoldGesture = useMemo(() => { - return Gesture.Pan() - .activateAfterLongPress(500) - .cancelsTouchesInView(false) - .averageTouches(true) - .onStart(() => { - 'worklet' - scheduleOnRN(open, 'full') - }) - .onUpdate(evt => { - 'worklet' - const item = getHoveredHoverable(evt, hoverablesSV, translationSV) - hoveredItemSV.set(item) - }) - .onEnd(() => { - 'worklet' - // don't recalculate hovered item - if they haven't moved their finger from - // the initial press, it's jarring to then select the item underneath - // as the menu may have slid into place beneath their finger - const item = hoveredItemSV.get() - if (item) { - scheduleOnRN(onTouchUpMenuItem, item) - } - }) - }, [open, hoverablesSV, onTouchUpMenuItem, hoveredItemSV, translationSV]) + const pressAndHoldGesture = usePanGesture({ + activateAfterLongPress: 500, + cancelsTouchesInView: false, + averageTouches: true, + onActivate: () => { + 'worklet' + scheduleOnRN(open, 'full') + }, + onUpdate: evt => { + 'worklet' + const item = getHoveredHoverable(evt, hoverablesSV, translationSV) + hoveredItemSV.set(item) + }, + onDeactivate: () => { + 'worklet' + // don't recalculate hovered item - if they haven't moved their finger from + // the initial press, it's jarring to then select the item underneath + // as the menu may have slid into place beneath their finger + const item = hoveredItemSV.get() + if (item) { + scheduleOnRN(onTouchUpMenuItem, item) + } + }, + }) // Order matters here: doubleTapGesture must come before tapGesture. - const tapAndHoldGestures = Gesture.Exclusive( + const tapAndHoldGestures = useExclusiveGestures( doubleTapGesture, tapGesture, pressAndHoldGesture, @@ -371,11 +366,13 @@ export function Trigger({ // An optional swipe gesture (e.g. swipe-to-reply) races against the tap/hold // group: whichever activates first wins and cancels the rest, so they're - // mutually exclusive. Race (not Exclusive) avoids a held-but-not-yet-moved + // mutually exclusive. Competing (not Exclusive) avoids a held-but-not-yet-moved // swipe Pan blocking the long-press from firing. - const composedGestures = swipeGesture - ? Gesture.Race(swipeGesture, tapAndHoldGestures) - : tapAndHoldGestures + const composedGestures = useCompetingGestures( + ...(swipeGesture + ? [swipeGesture, tapAndHoldGestures] + : [tapAndHoldGestures]), + ) const measurement = context.measurement || pendingMeasurement?.measurement @@ -990,9 +987,7 @@ function measureView( } function getHoveredHoverable( - evt: - | GestureStateChangeEvent - | GestureUpdateEvent, + evt: PanGestureActiveEvent, hoverables: SharedValue>, translation: SharedValue, ) { diff --git a/src/components/ContextMenu/types.ts b/src/components/ContextMenu/types.ts index 6fb97b6816..79a758234e 100644 --- a/src/components/ContextMenu/types.ts +++ b/src/components/ContextMenu/types.ts @@ -4,7 +4,7 @@ import { type StyleProp, type ViewStyle, } from 'react-native' -import {type GestureType} from 'react-native-gesture-handler' +import {type PanGesture} from 'react-native-gesture-handler' import {type SharedValue} from 'react-native-reanimated' import type * as Dialog from '#/components/Dialog' @@ -89,7 +89,7 @@ export type TriggerProps = { style?: StyleProp /** * Callback for single taps. Composed with the double-tap and - * press-and-hold gestures via `Gesture.Exclusive`, so a double tap + * press-and-hold gestures via `useExclusiveGestures`, so a double tap * does not also fire this handler. * * @platform ios, android @@ -97,12 +97,13 @@ export type TriggerProps = { onTap?: () => void /** * An optional gesture (e.g. swipe-to-reply) composed into the same - * arbitration group as the tap and press-and-hold gestures via `Gesture.Race`, - * making it mutually exclusive with them - only one can win a given touch. + * arbitration group as the tap and press-and-hold gestures via + * `useCompetingGestures`, making it mutually exclusive with them - only one + * can win a given touch. * * @platform ios, android */ - swipeGesture?: GestureType + swipeGesture?: PanGesture } export type TriggerChildProps = | { diff --git a/src/components/DraggableList/index.tsx b/src/components/DraggableList/index.tsx index ebdf621b95..9a6c988ab9 100644 --- a/src/components/DraggableList/index.tsx +++ b/src/components/DraggableList/index.tsx @@ -1,5 +1,5 @@ import {useLayoutEffect, useRef} from 'react' -import {Gesture, GestureDetector} from 'react-native-gesture-handler' +import {GestureDetector, usePanGesture} from 'react-native-gesture-handler' import Animated, { type AnimatedRef, measure, @@ -253,8 +253,8 @@ function SortableItem({ const lastHapticSlot = useSharedValue(-1) - const gesture = Gesture.Pan() - .onStart(() => { + const gesture = usePanGesture({ + onActivate: () => { 'worklet' const s = state.get() const mySlot = s.slots[itemKey] @@ -268,8 +268,8 @@ function SortableItem({ scheduleOnRN(onDragStart) } scheduleOnRN(playHaptic) - }) - .onChange(e => { + }, + onUpdate: e => { 'worklet' const startSlot = state.get().dragStartSlot const minY = -startSlot * itemHeight @@ -287,8 +287,8 @@ function SortableItem({ lastHapticSlot.set(clampedSlot) scheduleOnRN(playHaptic, 'Light') } - }) - .onEnd(() => { + }, + onDeactivate: () => { 'worklet' // Stop auto-scroll BEFORE the snap animation. isGestureActive.set(false) @@ -338,9 +338,9 @@ function SortableItem({ } }), ) - }) - // Reset if the gesture is cancelled without onEnd firing. - .onFinalize(() => { + }, + // Reset if the gesture is cancelled without onDeactivate firing. + onFinalize: () => { 'worklet' isGestureActive.set(false) if (state.get().activeKey === itemKey && dragY.get() === 0) { @@ -350,7 +350,8 @@ function SortableItem({ scheduleOnRN(onDragEnd) } } - }) + }, + }) // All vertical positioning is via translateY (no `top`). This avoids // discrete jumps when slots change — Reanimated smoothly animates from diff --git a/src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx b/src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx index 6227eeb588..9be39c348e 100644 --- a/src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx +++ b/src/components/Lightbox/pager/ImageItem/ImageItem.android.tsx @@ -1,9 +1,14 @@ import {memo, useState} from 'react' import {ActivityIndicator, StyleSheet} from 'react-native' import { - Gesture, GestureDetector, type PanGesture, + useExclusiveGestures, + useManualGesture, + usePanGesture, + usePinchGesture, + useSimultaneousGestures, + useTapGesture, } from 'react-native-gesture-handler' import Animated, { type AnimatableValue, @@ -137,16 +142,16 @@ const ImageItem = ({ return [dx, dy] } - const pinch = Gesture.Pinch() - .onStart(e => { + const pinch = usePinchGesture({ + onActivate: e => { 'worklet' const screenSize = measureSafeArea() pinchOrigin.set({ x: e.focalX - screenSize.width / 2, y: e.focalY - screenSize.height / 2, }) - }) - .onChange(e => { + }, + onUpdate: e => { 'worklet' const screenSize = measureSafeArea() if (!imageDimensions) { @@ -181,8 +186,8 @@ const ImageItem = ({ y: pt.y + dy, }) } - }) - .onEnd(() => { + }, + onDeactivate: () => { 'worklet' // Commit just the pinch. let t = createTransform() @@ -200,13 +205,14 @@ const ImageItem = ({ pinchScale.set(1) pinchOrigin.set({x: 0, y: 0}) pinchTranslation.set({x: 0, y: 0}) - }) + }, + }) - const pan = Gesture.Pan() - .averageTouches(true) - // Unlike .enabled(isScaled), this ensures that an initial pinch can turn into a pan midway: - .minPointers(isScaled ? 1 : 2) - .onChange(e => { + const pan = usePanGesture({ + averageTouches: true, + // Unlike enabled: isScaled, this ensures that an initial pinch can turn into a pan midway: + minPointers: isScaled ? 1 : 2, + onUpdate: e => { 'worklet' const screenSize = measureSafeArea() if (!imageDimensions) { @@ -229,8 +235,8 @@ const ImageItem = ({ nextPanTranslation.x += dx nextPanTranslation.y += dy panTranslation.set(nextPanTranslation) - }) - .onEnd(() => { + }, + onDeactivate: () => { 'worklet' // Commit just the pan. let t = createTransform() @@ -241,16 +247,19 @@ const ImageItem = ({ // Reset just the pan. panTranslation.set({x: 0, y: 0}) - }) - - const singleTap = Gesture.Tap().onEnd(() => { - 'worklet' - scheduleOnRN(onTap) + }, }) - const doubleTap = Gesture.Tap() - .numberOfTaps(2) - .onEnd(e => { + const singleTap = useTapGesture({ + onDeactivate: () => { + 'worklet' + scheduleOnRN(onTap) + }, + }) + + const doubleTap = useTapGesture({ + numberOfTaps: 2, + onDeactivate: e => { 'worklet' const screenSize = measureSafeArea() if (!imageDimensions || !imageAspect) { @@ -295,17 +304,21 @@ const ImageItem = ({ const finalTransform = createTransform() prependPinch(finalTransform, scale, origin, {x: dx, y: dy}) committedTransform.set(withClampedSpring(finalTransform)) - }) + }, + }) + // If the parent is not at rest, provide a no-op gesture. + const noopGesture = useManualGesture() + const pinchAndPan = useSimultaneousGestures(pinch, pan) + const exclusiveGesture = useExclusiveGestures( + dismissSwipePan, + pinchAndPan, + doubleTap, + singleTap, + ) const composedGesture = isScrollViewBeingDragged - ? // If the parent is not at rest, provide a no-op gesture. - Gesture.Manual() - : Gesture.Exclusive( - dismissSwipePan, - Gesture.Simultaneous(pinch, pan), - doubleTap, - singleTap, - ) + ? noopGesture + : exclusiveGesture const containerStyle = useAnimatedStyle(() => { const {scaleAndMoveTransform, isHidden} = transforms.get() diff --git a/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx b/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx index c4a842a74e..e5df270f63 100644 --- a/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx +++ b/src/components/Lightbox/pager/ImageItem/ImageItem.ios.tsx @@ -9,9 +9,10 @@ import {memo, useState} from 'react' import {ActivityIndicator, StyleSheet} from 'react-native' import { - Gesture, GestureDetector, type PanGesture, + useExclusiveGestures, + useTapGesture, } from 'react-native-gesture-handler' import Animated, { type SharedValue, @@ -115,14 +116,16 @@ const ImageItem = ({ }) } - const singleTap = Gesture.Tap().onEnd(() => { - 'worklet' - scheduleOnRN(onTap) + const singleTap = useTapGesture({ + onDeactivate: () => { + 'worklet' + scheduleOnRN(onTap) + }, }) - const doubleTap = Gesture.Tap() - .numberOfTaps(2) - .onEnd(e => { + const doubleTap = useTapGesture({ + numberOfTaps: 2, + onDeactivate: e => { 'worklet' const screenSize = measureSafeArea() const {absoluteX, absoluteY} = e @@ -142,9 +145,10 @@ const ImageItem = ({ ) } scheduleOnRN(zoomTo, nextZoomRect) - }) + }, + }) - const composedGesture = Gesture.Exclusive( + const composedGesture = useExclusiveGestures( dismissSwipePan, doubleTap, singleTap, diff --git a/src/components/Lightbox/pager/ImagePager.tsx b/src/components/Lightbox/pager/ImagePager.tsx index d89bb74d85..12fa3bcb80 100644 --- a/src/components/Lightbox/pager/ImagePager.tsx +++ b/src/components/Lightbox/pager/ImagePager.tsx @@ -10,7 +10,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {PixelRatio, StyleSheet, useWindowDimensions, View} from 'react-native' import {SystemBars} from 'react-native-edge-to-edge' -import {Gesture} from 'react-native-gesture-handler' +import {usePanGesture} from 'react-native-gesture-handler' import PagerView, { type PagerViewOnPageSelectedEvent, type PageScrollStateChangedNativeEvent, @@ -566,19 +566,19 @@ function LightboxImage({ } }) - const dismissSwipePan = Gesture.Pan() - .enabled(isActive && !isScaled) - .activeOffsetY([-10, 10]) - .failOffsetX([-10, 10]) - .maxPointers(1) - .onUpdate(e => { + const dismissSwipePan = usePanGesture({ + enabled: isActive && !isScaled, + activeOffsetY: [-10, 10], + failOffsetX: [-10, 10], + maxPointers: 1, + onUpdate: e => { 'worklet' if (openProgress.get() !== 1 || isFlyingAway.get()) { return } dismissSwipeTranslateY.set(e.translationY) - }) - .onEnd(e => { + }, + onDeactivate: e => { 'worklet' if (openProgress.get() !== 1 || isFlyingAway.get()) { return @@ -608,7 +608,8 @@ function LightboxImage({ }), ) } - }) + }, + }) return ( { const {t: l, i18n} = useLingui() const ax = useAnalytics() diff --git a/src/components/dms/SwipeToReply.tsx b/src/components/dms/SwipeToReply.tsx index dbdbf9fd65..b2d7c79580 100644 --- a/src/components/dms/SwipeToReply.tsx +++ b/src/components/dms/SwipeToReply.tsx @@ -1,6 +1,5 @@ -import {useMemo} from 'react' import {View} from 'react-native' -import {Gesture, type GestureType} from 'react-native-gesture-handler' +import {type PanGesture, usePanGesture} from 'react-native-gesture-handler' import Animated, { clamp, interpolate, @@ -52,8 +51,9 @@ function applyResistance(value: number) { * * The pan gesture is built here but handed to `children` via a render prop so * the consumer can compose it into the message's context-menu gestures with - * `Gesture.Exclusive`. Sharing one arbitration group is what makes the swipe - * and the long-press mutually exclusive - only one can ever win the touch. + * `useCompetingGestures`. Sharing one arbitration group is what makes the + * swipe and the long-press mutually exclusive - only one can ever win the + * touch. */ export function SwipeToReply({ isFromSelf, @@ -64,7 +64,7 @@ export function SwipeToReply({ isFromSelf: boolean onReply: () => void enabled?: boolean - children: (swipeGesture: GestureType) => React.ReactNode + children: (swipeGesture: PanGesture) => React.ReactNode }) { const t = useTheme() const playHaptic = useHaptics() @@ -74,73 +74,56 @@ export function SwipeToReply({ const hit = useSharedValue(false) const iconScale = useSharedValue(1) - const swipeGesture = useMemo(() => { - const runPop = () => { - 'worklet' - if (isReducedMotion) return - iconScale.set( - withSequence( - withTiming(1.2, {duration: 175}), - withTiming(1, {duration: 100}), - ), - ) - } - - return ( - Gesture.Pan() - .enabled(enabled) - // Arm only on the inward axis; the outward direction is effectively - // disabled so the bubble can't be dragged off its own edge. - .activeOffsetX( - isFromSelf - ? [-10, EFFECTIVELY_DISABLED_OFFSET] - : [-EFFECTIVELY_DISABLED_OFFSET, 10], - ) - .activeOffsetY([ - -EFFECTIVELY_DISABLED_OFFSET, - EFFECTIVELY_DISABLED_OFFSET, - ]) - .onChange(e => { - 'worklet' - const dir = isFromSelf - ? Math.min(e.translationX, 0) - : Math.max(e.translationX, 0) - transX.set(applyResistance(dir)) - - const pastThreshold = Math.abs(transX.get()) >= ACTIVATION_THRESHOLD - if (pastThreshold && !hit.get()) { - hit.set(true) - runPop() - scheduleOnRN(playHaptic, 'Medium') - } else if (!pastThreshold && hit.get()) { - hit.set(false) - } - }) - .onEnd(() => { - 'worklet' - // Only a clean end (finger lifted past threshold) triggers the reply. - if (hit.get()) { - scheduleOnRN(onReply) - } - }) - .onFinalize(() => { - 'worklet' - // Runs on both end and cancellation, so the bubble always animates - // home even if the gesture is interrupted mid-swipe. - transX.set(withTiming(0, {duration: 200})) - hit.set(false) - }) + const runPop = () => { + 'worklet' + if (isReducedMotion) return + iconScale.set( + withSequence( + withTiming(1.2, {duration: 175}), + withTiming(1, {duration: 100}), + ), ) - }, [ - isFromSelf, + } + + const swipeGesture = usePanGesture({ enabled, - onReply, - playHaptic, - isReducedMotion, - transX, - hit, - iconScale, - ]) + // Arm only on the inward axis; the outward direction is effectively + // disabled so the bubble can't be dragged off its own edge. + activeOffsetX: isFromSelf + ? [-10, EFFECTIVELY_DISABLED_OFFSET] + : [-EFFECTIVELY_DISABLED_OFFSET, 10], + activeOffsetY: [-EFFECTIVELY_DISABLED_OFFSET, EFFECTIVELY_DISABLED_OFFSET], + onUpdate: e => { + 'worklet' + const dir = isFromSelf + ? Math.min(e.translationX, 0) + : Math.max(e.translationX, 0) + transX.set(applyResistance(dir)) + + const pastThreshold = Math.abs(transX.get()) >= ACTIVATION_THRESHOLD + if (pastThreshold && !hit.get()) { + hit.set(true) + runPop() + scheduleOnRN(playHaptic, 'Medium') + } else if (!pastThreshold && hit.get()) { + hit.set(false) + } + }, + onDeactivate: () => { + 'worklet' + // Only a clean end (finger lifted past threshold) triggers the reply. + if (hit.get()) { + scheduleOnRN(onReply) + } + }, + onFinalize: () => { + 'worklet' + // Runs on both end and cancellation, so the bubble always animates + // home even if the gesture is interrupted mid-swipe. + transX.set(withTiming(0, {duration: 200})) + hit.set(false) + }, + }) const contentStyle = useAnimatedStyle(() => ({ transform: [{translateX: transX.get()}], diff --git a/src/lib/custom-animations/GestureActionView.tsx b/src/lib/custom-animations/GestureActionView.tsx index 6f3efcf4bd..093a2ae4a3 100644 --- a/src/lib/custom-animations/GestureActionView.tsx +++ b/src/lib/custom-animations/GestureActionView.tsx @@ -1,6 +1,6 @@ import {useMemo, useState} from 'react' import {type ColorValue, Dimensions, View} from 'react-native' -import {Gesture, GestureDetector} from 'react-native-gesture-handler' +import {GestureDetector, usePanGesture} from 'react-native-gesture-handler' import Animated, { clamp, interpolate, @@ -106,17 +106,17 @@ export function GestureActionView({ // Absurdly high value so it doesn't interfere with the pan gestures above (i.e., scroll) // reanimated doesn't offer great support for disabling y/x axes :/ const effectivelyDisabledOffset = 200 - const panGesture = Gesture.Pan() - .activeOffsetX([ + const panGesture = usePanGesture({ + activeOffsetX: [ actions.leftFirst ? -10 : -effectivelyDisabledOffset, actions.rightFirst ? 10 : effectivelyDisabledOffset, - ]) - .activeOffsetY([-effectivelyDisabledOffset, effectivelyDisabledOffset]) - .onStart(() => { + ], + activeOffsetY: [-effectivelyDisabledOffset, effectivelyDisabledOffset], + onActivate: () => { 'worklet' isActive.set(true) - }) - .onChange(e => { + }, + onUpdate: e => { 'worklet' transX.set(e.translationX) @@ -189,8 +189,8 @@ export function GestureActionView({ } } } - }) - .onEnd(e => { + }, + onDeactivate: e => { 'worklet' if (e.translationX < 0) { if (hitSecond.get() && actions.leftSecond) { @@ -209,9 +209,8 @@ export function GestureActionView({ hitFirst.set(false) hitSecond.set(false) isActive.set(false) - }) - - const composedGesture = Gesture.Simultaneous(panGesture) + }, + }) const animatedSliderStyle = useAnimatedStyle(() => { return { @@ -286,7 +285,7 @@ export function GestureActionView({ }) return ( - + { - return Gesture.Pan() - .blocksExternalGesture(scrollGesture) - .activeOffsetX([-10, 10]) - .failOffsetY([-10, 10]) - .onStart(() => { - 'worklet' - seekProgressSV.set(currentTimeSV.get()) - isSeekingSV.set(true) - seekingAnimationSV.set(withTiming(1, {duration: 500})) - }) - .onUpdate(evt => { - 'worklet' - const progress = evt.x / screenWidth - seekProgressSV.set( - clamp(progress * durationSV.get(), 0, durationSV.get()), - ) - }) - .onEnd(evt => { - 'worklet' - isSeekingSV.get() + const scrubPanGesture = usePanGesture({ + block: scrollGesture, + activeOffsetX: [-10, 10], + failOffsetY: [-10, 10], + onActivate: () => { + 'worklet' + seekProgressSV.set(currentTimeSV.get()) + isSeekingSV.set(true) + seekingAnimationSV.set(withTiming(1, {duration: 500})) + }, + onUpdate: evt => { + 'worklet' + const progress = evt.x / screenWidth + seekProgressSV.set( + clamp(progress * durationSV.get(), 0, durationSV.get()), + ) + }, + onDeactivate: evt => { + 'worklet' + isSeekingSV.get() - const progress = evt.x / screenWidth - const newTime = clamp(progress * durationSV.get(), 0, durationSV.get()) + const progress = evt.x / screenWidth + const newTime = clamp(progress * durationSV.get(), 0, durationSV.get()) - // optimisically set the progress bar - seekProgressSV.set(newTime) + // optimisically set the progress bar + seekProgressSV.set(newTime) - // it's seek by, so offset by the current time - // seekBy sets isSeekingSV back to false, so no need to do that here - scheduleOnRN(seekBy, newTime - currentTimeSV.get()) - }) - }, [ - scrollGesture, - seekingAnimationSV, - seekBy, - screenWidth, - currentTimeSV, - durationSV, - isSeekingSV, - seekProgressSV, - ]) + // it's seek by, so offset by the current time + // seekBy sets isSeekingSV back to false, so no need to do that here + scheduleOnRN(seekBy, newTime - currentTimeSV.get()) + }, + }) const timeStyle = useAnimatedStyle(() => { return { diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 9c662fdf13..172a629589 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -7,9 +7,9 @@ import { View, } from 'react-native' import { - Gesture, GestureDetector, type NativeGesture, + useNativeGesture, } from 'react-native-gesture-handler' import Animated, { useAnimatedStyle, @@ -258,7 +258,7 @@ function Feed() { const [currentIndex, setCurrentIndex] = useState(0) - const scrollGesture = useMemo(() => Gesture.Native(), []) + const scrollGesture = useNativeGesture() const renderItem: ListRenderItem = useCallback( ({item, index}) => { diff --git a/src/state/global-gesture-events/index.tsx b/src/state/global-gesture-events/index.tsx index 4d3e9795dc..40ed792237 100644 --- a/src/state/global-gesture-events/index.tsx +++ b/src/state/global-gesture-events/index.tsx @@ -1,19 +1,18 @@ import {createContext, useContext, useMemo, useRef, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' import { - Gesture, GestureDetector, - type GestureStateChangeEvent, - type GestureUpdateEvent, - type PanGestureHandlerEventPayload, + type PanGestureActiveEvent, + type PanGestureEvent, + usePanGesture, } from 'react-native-gesture-handler' import {EventEmitter} from 'eventemitter3' export type GlobalGestureEvents = { - begin: GestureStateChangeEvent - update: GestureUpdateEvent - end: GestureStateChangeEvent - finalize: GestureStateChangeEvent + begin: PanGestureEvent + update: PanGestureActiveEvent + end: PanGestureActiveEvent & {canceled: boolean} + finalize: PanGestureEvent & {canceled: boolean} } const Context = createContext<{ @@ -55,22 +54,22 @@ export function GlobalGestureEventsProvider({ }), [events, setEnabled], ) - const gesture = Gesture.Pan() - .runOnJS(true) - .enabled(enabled) - .simultaneousWithExternalGesture() - .onBegin(e => { + const gesture = usePanGesture({ + runOnJS: true, + enabled, + onBegin: e => { events.emit('begin', e) - }) - .onUpdate(e => { + }, + onUpdate: e => { events.emit('update', e) - }) - .onEnd(e => { + }, + onDeactivate: e => { events.emit('end', e) - }) - .onFinalize(e => { + }, + onFinalize: e => { events.emit('finalize', e) - }) + }, + }) return (