From ce331fbd0455c1ad6fefb30a5152fcf5c572a8cf Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 8 Dec 2024 20:25:22 +0000 Subject: [PATCH] Fix drawer swipe --- .../react-native-drawer-layout+4.0.4.patch | 876 ++++++++++++++++++ src/view/com/pager/Pager.tsx | 24 +- src/view/shell/index.tsx | 10 +- 3 files changed, 901 insertions(+), 9 deletions(-) create mode 100644 patches/react-native-drawer-layout+4.0.4.patch diff --git a/patches/react-native-drawer-layout+4.0.4.patch b/patches/react-native-drawer-layout+4.0.4.patch new file mode 100644 index 00000000000..004fb4a0019 --- /dev/null +++ b/patches/react-native-drawer-layout+4.0.4.patch @@ -0,0 +1,876 @@ +diff --git a/node_modules/react-native-drawer-layout/lib/commonjs/index.js b/node_modules/react-native-drawer-layout/lib/commonjs/index.js +index 3dce76e..6c4b3e5 100644 +--- a/node_modules/react-native-drawer-layout/lib/commonjs/index.js ++++ b/node_modules/react-native-drawer-layout/lib/commonjs/index.js +@@ -9,6 +9,12 @@ Object.defineProperty(exports, "Drawer", { + return _Drawer.Drawer; + } + }); ++Object.defineProperty(exports, "DrawerGestureContext", { ++ enumerable: true, ++ get: function () { ++ return _DrawerGestureContext.DrawerGestureContext; ++ } ++}); + Object.defineProperty(exports, "DrawerProgressContext", { + enumerable: true, + get: function () { +@@ -21,6 +27,7 @@ Object.defineProperty(exports, "useDrawerProgress", { + return _useDrawerProgress.useDrawerProgress; + } + }); ++var _DrawerGestureContext = require("./utils/DrawerGestureContext.js"); + var _DrawerProgressContext = require("./utils/DrawerProgressContext.js"); + var _useDrawerProgress = require("./utils/useDrawerProgress.js"); + var _Drawer = require("./views/Drawer"); +diff --git a/node_modules/react-native-drawer-layout/lib/commonjs/utils/DrawerGestureContext.js b/node_modules/react-native-drawer-layout/lib/commonjs/utils/DrawerGestureContext.js +new file mode 100644 +index 0000000..de6d793 +--- /dev/null ++++ b/node_modules/react-native-drawer-layout/lib/commonjs/utils/DrawerGestureContext.js +@@ -0,0 +1,11 @@ ++"use strict"; ++ ++Object.defineProperty(exports, "__esModule", { ++ value: true ++}); ++exports.DrawerGestureContext = void 0; ++var React = _interopRequireWildcard(require("react")); ++function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } ++function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } ++const DrawerGestureContext = exports.DrawerGestureContext = /*#__PURE__*/React.createContext(undefined); ++//# sourceMappingURL=DrawerGestureContext.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js b/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js +index fd65ab8..86a9a6a 100644 +--- a/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js ++++ b/node_modules/react-native-drawer-layout/lib/commonjs/views/Drawer.native.js +@@ -8,6 +8,7 @@ var React = _interopRequireWildcard(require("react")); + var _reactNative = require("react-native"); + var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated")); + var _useLatestCallback = _interopRequireDefault(require("use-latest-callback")); ++var _DrawerGestureContext = require("../utils/DrawerGestureContext.js"); + var _DrawerProgressContext = require("../utils/DrawerProgressContext.js"); + var _getDrawerWidth = require("../utils/getDrawerWidth.js"); + var _GestureHandler = require("./GestureHandler"); +@@ -79,38 +80,38 @@ function Drawer({ + return () => hideStatusBar(false); + }, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]); + const interactionHandleRef = React.useRef(null); +- const startInteraction = () => { ++ const startInteraction = React.useCallback(() => { + interactionHandleRef.current = _reactNative.InteractionManager.createInteractionHandle(); +- }; +- const endInteraction = () => { ++ }, []); ++ const endInteraction = React.useCallback(() => { + if (interactionHandleRef.current != null) { + _reactNative.InteractionManager.clearInteractionHandle(interactionHandleRef.current); + interactionHandleRef.current = null; + } +- }; +- const hideKeyboard = () => { ++ }, []); ++ const hideKeyboard = React.useCallback(() => { + if (keyboardDismissMode === 'on-drag') { + _reactNative.Keyboard.dismiss(); + } +- }; +- const onGestureBegin = () => { ++ }, [keyboardDismissMode]); ++ const onGestureBegin = React.useCallback(() => { + onGestureStart?.(); + startInteraction(); + hideKeyboard(); + hideStatusBar(true); +- }; +- const onGestureFinish = () => { ++ }, [onGestureStart, startInteraction, hideKeyboard, hideStatusBar]); ++ const onGestureFinish = React.useCallback(() => { + onGestureEnd?.(); + endInteraction(); +- }; +- const onGestureAbort = () => { ++ }, [onGestureEnd, endInteraction]); ++ const onGestureAbort = React.useCallback(() => { + onGestureCancel?.(); + endInteraction(); +- }; ++ }, [onGestureCancel, endInteraction]); + + // FIXME: Currently hitSlop is broken when on Android when drawer is on right + // https://github.com/software-mansion/react-native-gesture-handler/issues/569 +- const hitSlop = isRight ? ++ const hitSlop = React.useMemo(() => isRight ? + // Extend hitSlop to the side of the screen when drawer is closed + // This lets the user drag the drawer from the side of the screen + { +@@ -119,7 +120,7 @@ function Drawer({ + } : { + left: 0, + width: isOpen ? undefined : swipeEdgeWidth +- }; ++ }, [isRight, isOpen, swipeEdgeWidth]); + const touchStartX = (0, _reactNativeReanimated.useSharedValue)(0); + const touchX = (0, _reactNativeReanimated.useSharedValue)(0); + const translationX = (0, _reactNativeReanimated.useSharedValue)(getDrawerTranslationX(open)); +@@ -158,40 +159,43 @@ function Drawer({ + }, [getDrawerTranslationX, handleAnimationEnd, handleAnimationStart, onClose, onOpen, touchStartX, touchX, translationX]); + React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]); + const startX = (0, _reactNativeReanimated.useSharedValue)(0); +- let pan = _GestureHandler.Gesture?.Pan().onBegin(event => { +- 'worklet'; ++ const pan = React.useMemo(() => { ++ let panGesture = _GestureHandler.Gesture?.Pan().onBegin(event => { ++ 'worklet'; + +- startX.value = translationX.value; +- gestureState.value = event.state; +- touchStartX.value = event.x; +- }).onStart(() => { +- 'worklet'; ++ startX.value = translationX.value; ++ gestureState.value = event.state; ++ touchStartX.value = event.x; ++ }).onStart(() => { ++ 'worklet'; + +- (0, _reactNativeReanimated.runOnJS)(onGestureBegin)(); +- }).onChange(event => { +- 'worklet'; ++ (0, _reactNativeReanimated.runOnJS)(onGestureBegin)(); ++ }).onChange(event => { ++ 'worklet'; + +- touchX.value = event.x; +- translationX.value = startX.value + event.translationX; +- gestureState.value = event.state; +- }).onEnd((event, success) => { +- 'worklet'; ++ touchX.value = event.x; ++ translationX.value = startX.value + event.translationX; ++ gestureState.value = event.state; ++ }).onEnd((event, success) => { ++ 'worklet'; + +- gestureState.value = event.state; +- if (!success) { +- (0, _reactNativeReanimated.runOnJS)(onGestureAbort)(); ++ gestureState.value = event.state; ++ if (!success) { ++ (0, _reactNativeReanimated.runOnJS)(onGestureAbort)(); ++ } ++ const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ? ++ // If swiped to right, open the drawer, otherwise close it ++ (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 : ++ // If swiped to left, open the drawer, otherwise close it ++ (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open; ++ toggleDrawer(nextOpen, event.velocityX); ++ (0, _reactNativeReanimated.runOnJS)(onGestureFinish)(); ++ }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled); ++ if (panGesture && configureGestureHandler) { ++ panGesture = configureGestureHandler(panGesture); + } +- const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ? +- // If swiped to right, open the drawer, otherwise close it +- (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 : +- // If swiped to left, open the drawer, otherwise close it +- (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open; +- toggleDrawer(nextOpen, event.velocityX); +- (0, _reactNativeReanimated.runOnJS)(onGestureFinish)(); +- }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled); +- if (pan && configureGestureHandler) { +- pan = configureGestureHandler(pan); +- } ++ return panGesture; ++ }, [configureGestureHandler, drawerPosition, drawerType, gestureState, hitSlop, onGestureBegin, onGestureAbort, onGestureFinish, open, startX, swipeEnabled, swipeMinDistance, swipeMinVelocity, toggleDrawer, touchStartX, touchX, translationX]); + const translateX = (0, _reactNativeReanimated.useDerivedValue)(() => { + // Comment stolen from react-native-gesture-handler/DrawerLayout + // +@@ -254,35 +258,38 @@ function Drawer({ + style: [styles.container, style], + children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DrawerProgressContext.DrawerProgressContext.Provider, { + value: progress, +- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureHandler.GestureDetector, { +- gesture: pan, +- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { +- style: [styles.main, { +- flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row' +- }], +- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { +- style: [styles.content, contentAnimatedStyle], +- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { +- accessibilityElementsHidden: isOpen && drawerType !== 'permanent', +- importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto', +- style: styles.content, +- children: children +- }), drawerType !== 'permanent' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Overlay.Overlay, { +- open: open, +- progress: progress, +- onPress: () => toggleDrawer(false), +- style: overlayStyle, +- accessibilityLabel: overlayAccessibilityLabel +- }) : null] +- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, { +- removeClippedSubviews: _reactNative.Platform.OS !== 'ios', +- style: [styles.drawer, { +- width: drawerWidth, +- position: drawerType === 'permanent' ? 'relative' : 'absolute', +- zIndex: drawerType === 'back' ? -1 : 0 +- }, drawerAnimatedStyle, drawerStyle], +- children: renderDrawerContent() +- })] ++ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DrawerGestureContext.DrawerGestureContext.Provider, { ++ value: pan, ++ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_GestureHandler.GestureDetector, { ++ gesture: pan, ++ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { ++ style: [styles.main, { ++ flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row' ++ }], ++ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, { ++ style: [styles.content, contentAnimatedStyle], ++ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { ++ accessibilityElementsHidden: isOpen && drawerType !== 'permanent', ++ importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto', ++ style: styles.content, ++ children: children ++ }), drawerType !== 'permanent' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Overlay.Overlay, { ++ open: open, ++ progress: progress, ++ onPress: () => toggleDrawer(false), ++ style: overlayStyle, ++ accessibilityLabel: overlayAccessibilityLabel ++ }) : null] ++ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, { ++ removeClippedSubviews: _reactNative.Platform.OS !== 'ios', ++ style: [styles.drawer, { ++ width: drawerWidth, ++ position: drawerType === 'permanent' ? 'relative' : 'absolute', ++ zIndex: drawerType === 'back' ? -1 : 0 ++ }, drawerAnimatedStyle, drawerStyle], ++ children: renderDrawerContent() ++ })] ++ }) + }) + }) + }) +diff --git a/node_modules/react-native-drawer-layout/lib/module/index.js b/node_modules/react-native-drawer-layout/lib/module/index.js +index a600e51..f08275c 100644 +--- a/node_modules/react-native-drawer-layout/lib/module/index.js ++++ b/node_modules/react-native-drawer-layout/lib/module/index.js +@@ -1,5 +1,6 @@ + "use strict"; + ++export { DrawerGestureContext } from "./utils/DrawerGestureContext.js"; + export { DrawerProgressContext } from "./utils/DrawerProgressContext.js"; + export { useDrawerProgress } from "./utils/useDrawerProgress.js"; + export { Drawer } from './views/Drawer'; +diff --git a/node_modules/react-native-drawer-layout/lib/module/utils/DrawerGestureContext.js b/node_modules/react-native-drawer-layout/lib/module/utils/DrawerGestureContext.js +new file mode 100644 +index 0000000..1adaa9c +--- /dev/null ++++ b/node_modules/react-native-drawer-layout/lib/module/utils/DrawerGestureContext.js +@@ -0,0 +1,5 @@ ++"use strict"; ++ ++import * as React from 'react'; ++export const DrawerGestureContext = /*#__PURE__*/React.createContext(undefined); ++//# sourceMappingURL=DrawerGestureContext.js.map +\ No newline at end of file +diff --git a/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js b/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js +index 6d07126..981d9f8 100644 +--- a/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js ++++ b/node_modules/react-native-drawer-layout/lib/module/views/Drawer.native.js +@@ -4,6 +4,7 @@ import * as React from 'react'; + import { I18nManager, InteractionManager, Keyboard, Platform, StatusBar, StyleSheet, useWindowDimensions, View } from 'react-native'; + import Animated, { interpolate, ReduceMotion, runOnJS, useAnimatedStyle, useDerivedValue, useSharedValue, withSpring } from 'react-native-reanimated'; + import useLatestCallback from 'use-latest-callback'; ++import { DrawerGestureContext } from "../utils/DrawerGestureContext.js"; + import { DrawerProgressContext } from "../utils/DrawerProgressContext.js"; + import { getDrawerWidth } from "../utils/getDrawerWidth.js"; + import { Gesture, GestureDetector, GestureHandlerRootView, GestureState } from './GestureHandler'; +@@ -72,38 +73,38 @@ export function Drawer({ + return () => hideStatusBar(false); + }, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]); + const interactionHandleRef = React.useRef(null); +- const startInteraction = () => { ++ const startInteraction = React.useCallback(() => { + interactionHandleRef.current = InteractionManager.createInteractionHandle(); +- }; +- const endInteraction = () => { ++ }, []); ++ const endInteraction = React.useCallback(() => { + if (interactionHandleRef.current != null) { + InteractionManager.clearInteractionHandle(interactionHandleRef.current); + interactionHandleRef.current = null; + } +- }; +- const hideKeyboard = () => { ++ }, []); ++ const hideKeyboard = React.useCallback(() => { + if (keyboardDismissMode === 'on-drag') { + Keyboard.dismiss(); + } +- }; +- const onGestureBegin = () => { ++ }, [keyboardDismissMode]); ++ const onGestureBegin = React.useCallback(() => { + onGestureStart?.(); + startInteraction(); + hideKeyboard(); + hideStatusBar(true); +- }; +- const onGestureFinish = () => { ++ }, [onGestureStart, startInteraction, hideKeyboard, hideStatusBar]); ++ const onGestureFinish = React.useCallback(() => { + onGestureEnd?.(); + endInteraction(); +- }; +- const onGestureAbort = () => { ++ }, [onGestureEnd, endInteraction]); ++ const onGestureAbort = React.useCallback(() => { + onGestureCancel?.(); + endInteraction(); +- }; ++ }, [onGestureCancel, endInteraction]); + + // FIXME: Currently hitSlop is broken when on Android when drawer is on right + // https://github.com/software-mansion/react-native-gesture-handler/issues/569 +- const hitSlop = isRight ? ++ const hitSlop = React.useMemo(() => isRight ? + // Extend hitSlop to the side of the screen when drawer is closed + // This lets the user drag the drawer from the side of the screen + { +@@ -112,7 +113,7 @@ export function Drawer({ + } : { + left: 0, + width: isOpen ? undefined : swipeEdgeWidth +- }; ++ }, [isRight, isOpen, swipeEdgeWidth]); + const touchStartX = useSharedValue(0); + const touchX = useSharedValue(0); + const translationX = useSharedValue(getDrawerTranslationX(open)); +@@ -151,40 +152,43 @@ export function Drawer({ + }, [getDrawerTranslationX, handleAnimationEnd, handleAnimationStart, onClose, onOpen, touchStartX, touchX, translationX]); + React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]); + const startX = useSharedValue(0); +- let pan = Gesture?.Pan().onBegin(event => { +- 'worklet'; ++ const pan = React.useMemo(() => { ++ let panGesture = Gesture?.Pan().onBegin(event => { ++ 'worklet'; + +- startX.value = translationX.value; +- gestureState.value = event.state; +- touchStartX.value = event.x; +- }).onStart(() => { +- 'worklet'; ++ startX.value = translationX.value; ++ gestureState.value = event.state; ++ touchStartX.value = event.x; ++ }).onStart(() => { ++ 'worklet'; + +- runOnJS(onGestureBegin)(); +- }).onChange(event => { +- 'worklet'; ++ runOnJS(onGestureBegin)(); ++ }).onChange(event => { ++ 'worklet'; + +- touchX.value = event.x; +- translationX.value = startX.value + event.translationX; +- gestureState.value = event.state; +- }).onEnd((event, success) => { +- 'worklet'; ++ touchX.value = event.x; ++ translationX.value = startX.value + event.translationX; ++ gestureState.value = event.state; ++ }).onEnd((event, success) => { ++ 'worklet'; + +- gestureState.value = event.state; +- if (!success) { +- runOnJS(onGestureAbort)(); ++ gestureState.value = event.state; ++ if (!success) { ++ runOnJS(onGestureAbort)(); ++ } ++ const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ? ++ // If swiped to right, open the drawer, otherwise close it ++ (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 : ++ // If swiped to left, open the drawer, otherwise close it ++ (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open; ++ toggleDrawer(nextOpen, event.velocityX); ++ runOnJS(onGestureFinish)(); ++ }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled); ++ if (panGesture && configureGestureHandler) { ++ panGesture = configureGestureHandler(panGesture); + } +- const nextOpen = Math.abs(event.translationX) > SWIPE_MIN_OFFSET && Math.abs(event.translationX) > swipeMinVelocity || Math.abs(event.translationX) > swipeMinDistance ? drawerPosition === 'left' ? +- // If swiped to right, open the drawer, otherwise close it +- (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 : +- // If swiped to left, open the drawer, otherwise close it +- (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 : open; +- toggleDrawer(nextOpen, event.velocityX); +- runOnJS(onGestureFinish)(); +- }).activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]).hitSlop(hitSlop).enabled(drawerType !== 'permanent' && swipeEnabled); +- if (pan && configureGestureHandler) { +- pan = configureGestureHandler(pan); +- } ++ return panGesture; ++ }, [configureGestureHandler, drawerPosition, drawerType, gestureState, hitSlop, onGestureBegin, onGestureAbort, onGestureFinish, open, startX, swipeEnabled, swipeMinDistance, swipeMinVelocity, toggleDrawer, touchStartX, touchX, translationX]); + const translateX = useDerivedValue(() => { + // Comment stolen from react-native-gesture-handler/DrawerLayout + // +@@ -247,35 +251,38 @@ export function Drawer({ + style: [styles.container, style], + children: /*#__PURE__*/_jsx(DrawerProgressContext.Provider, { + value: progress, +- children: /*#__PURE__*/_jsx(GestureDetector, { +- gesture: pan, +- children: /*#__PURE__*/_jsxs(Animated.View, { +- style: [styles.main, { +- flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row' +- }], +- children: [/*#__PURE__*/_jsxs(Animated.View, { +- style: [styles.content, contentAnimatedStyle], +- children: [/*#__PURE__*/_jsx(View, { +- accessibilityElementsHidden: isOpen && drawerType !== 'permanent', +- importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto', +- style: styles.content, +- children: children +- }), drawerType !== 'permanent' ? /*#__PURE__*/_jsx(Overlay, { +- open: open, +- progress: progress, +- onPress: () => toggleDrawer(false), +- style: overlayStyle, +- accessibilityLabel: overlayAccessibilityLabel +- }) : null] +- }), /*#__PURE__*/_jsx(Animated.View, { +- removeClippedSubviews: Platform.OS !== 'ios', +- style: [styles.drawer, { +- width: drawerWidth, +- position: drawerType === 'permanent' ? 'relative' : 'absolute', +- zIndex: drawerType === 'back' ? -1 : 0 +- }, drawerAnimatedStyle, drawerStyle], +- children: renderDrawerContent() +- })] ++ children: /*#__PURE__*/_jsx(DrawerGestureContext.Provider, { ++ value: pan, ++ children: /*#__PURE__*/_jsx(GestureDetector, { ++ gesture: pan, ++ children: /*#__PURE__*/_jsxs(Animated.View, { ++ style: [styles.main, { ++ flexDirection: drawerType === 'permanent' ? isRight && direction === 'ltr' || !isRight && direction === 'rtl' ? 'row' : 'row-reverse' : 'row' ++ }], ++ children: [/*#__PURE__*/_jsxs(Animated.View, { ++ style: [styles.content, contentAnimatedStyle], ++ children: [/*#__PURE__*/_jsx(View, { ++ accessibilityElementsHidden: isOpen && drawerType !== 'permanent', ++ importantForAccessibility: isOpen && drawerType !== 'permanent' ? 'no-hide-descendants' : 'auto', ++ style: styles.content, ++ children: children ++ }), drawerType !== 'permanent' ? /*#__PURE__*/_jsx(Overlay, { ++ open: open, ++ progress: progress, ++ onPress: () => toggleDrawer(false), ++ style: overlayStyle, ++ accessibilityLabel: overlayAccessibilityLabel ++ }) : null] ++ }), /*#__PURE__*/_jsx(Animated.View, { ++ removeClippedSubviews: Platform.OS !== 'ios', ++ style: [styles.drawer, { ++ width: drawerWidth, ++ position: drawerType === 'permanent' ? 'relative' : 'absolute', ++ zIndex: drawerType === 'back' ? -1 : 0 ++ }, drawerAnimatedStyle, drawerStyle], ++ children: renderDrawerContent() ++ })] ++ }) + }) + }) + }) +diff --git a/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts +index 7e978f0..a8bce18 100644 +--- a/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts ++++ b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/index.d.ts +@@ -1,3 +1,4 @@ ++export { DrawerGestureContext } from './utils/DrawerGestureContext'; + export { DrawerProgressContext } from './utils/DrawerProgressContext'; + export { useDrawerProgress } from './utils/useDrawerProgress'; + export { Drawer } from './views/Drawer'; +diff --git a/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/utils/DrawerGestureContext.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/utils/DrawerGestureContext.d.ts +new file mode 100644 +index 0000000..33ffbeb +--- /dev/null ++++ b/node_modules/react-native-drawer-layout/lib/typescript/commonjs/src/utils/DrawerGestureContext.d.ts +@@ -0,0 +1,3 @@ ++import * as React from 'react'; ++export declare const DrawerGestureContext: React.Context; ++//# sourceMappingURL=DrawerGestureContext.d.ts.map +\ No newline at end of file +diff --git a/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts +index 7e978f0..a8bce18 100644 +--- a/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts ++++ b/node_modules/react-native-drawer-layout/lib/typescript/module/src/index.d.ts +@@ -1,3 +1,4 @@ ++export { DrawerGestureContext } from './utils/DrawerGestureContext'; + export { DrawerProgressContext } from './utils/DrawerProgressContext'; + export { useDrawerProgress } from './utils/useDrawerProgress'; + export { Drawer } from './views/Drawer'; +diff --git a/node_modules/react-native-drawer-layout/lib/typescript/module/src/utils/DrawerGestureContext.d.ts b/node_modules/react-native-drawer-layout/lib/typescript/module/src/utils/DrawerGestureContext.d.ts +new file mode 100644 +index 0000000..33ffbeb +--- /dev/null ++++ b/node_modules/react-native-drawer-layout/lib/typescript/module/src/utils/DrawerGestureContext.d.ts +@@ -0,0 +1,3 @@ ++import * as React from 'react'; ++export declare const DrawerGestureContext: React.Context; ++//# sourceMappingURL=DrawerGestureContext.d.ts.map +\ No newline at end of file +diff --git a/node_modules/react-native-drawer-layout/src/index.tsx b/node_modules/react-native-drawer-layout/src/index.tsx +index 0aa6f53..61a37cf 100644 +--- a/node_modules/react-native-drawer-layout/src/index.tsx ++++ b/node_modules/react-native-drawer-layout/src/index.tsx +@@ -1,3 +1,4 @@ ++export { DrawerGestureContext } from './utils/DrawerGestureContext'; + export { DrawerProgressContext } from './utils/DrawerProgressContext'; + export { useDrawerProgress } from './utils/useDrawerProgress'; + export { Drawer } from './views/Drawer'; +diff --git a/node_modules/react-native-drawer-layout/src/utils/DrawerGestureContext.tsx b/node_modules/react-native-drawer-layout/src/utils/DrawerGestureContext.tsx +new file mode 100644 +index 0000000..3aac957 +--- /dev/null ++++ b/node_modules/react-native-drawer-layout/src/utils/DrawerGestureContext.tsx +@@ -0,0 +1,6 @@ ++import * as React from 'react'; ++import type { PanGesture } from 'react-native-gesture-handler'; ++ ++export const DrawerGestureContext = React.createContext( ++ undefined ++); +diff --git a/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx b/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx +index 9c40f41..ee5d075 100644 +--- a/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx ++++ b/node_modules/react-native-drawer-layout/src/views/Drawer.native.tsx +@@ -21,6 +21,7 @@ import Animated, { + import useLatestCallback from 'use-latest-callback'; + + import type { DrawerProps } from '../types'; ++import { DrawerGestureContext } from '../utils/DrawerGestureContext'; + import { DrawerProgressContext } from '../utils/DrawerProgressContext'; + import { getDrawerWidth } from '../utils/getDrawerWidth'; + import { +@@ -110,47 +111,51 @@ export function Drawer({ + + const interactionHandleRef = React.useRef(null); + +- const startInteraction = () => { ++ const startInteraction = React.useCallback(() => { + interactionHandleRef.current = InteractionManager.createInteractionHandle(); +- }; ++ }, []); + +- const endInteraction = () => { ++ const endInteraction = React.useCallback(() => { + if (interactionHandleRef.current != null) { + InteractionManager.clearInteractionHandle(interactionHandleRef.current); + interactionHandleRef.current = null; + } +- }; ++ }, []); + +- const hideKeyboard = () => { ++ const hideKeyboard = React.useCallback(() => { + if (keyboardDismissMode === 'on-drag') { + Keyboard.dismiss(); + } +- }; ++ }, [keyboardDismissMode]); + +- const onGestureBegin = () => { ++ const onGestureBegin = React.useCallback(() => { + onGestureStart?.(); + startInteraction(); + hideKeyboard(); + hideStatusBar(true); +- }; ++ }, [onGestureStart, startInteraction, hideKeyboard, hideStatusBar]); + +- const onGestureFinish = () => { ++ const onGestureFinish = React.useCallback(() => { + onGestureEnd?.(); + endInteraction(); +- }; ++ }, [onGestureEnd, endInteraction]); + +- const onGestureAbort = () => { ++ const onGestureAbort = React.useCallback(() => { + onGestureCancel?.(); + endInteraction(); +- }; ++ }, [onGestureCancel, endInteraction]); + + // FIXME: Currently hitSlop is broken when on Android when drawer is on right + // https://github.com/software-mansion/react-native-gesture-handler/issues/569 +- const hitSlop = isRight +- ? // Extend hitSlop to the side of the screen when drawer is closed +- // This lets the user drag the drawer from the side of the screen +- { right: 0, width: isOpen ? undefined : swipeEdgeWidth } +- : { left: 0, width: isOpen ? undefined : swipeEdgeWidth }; ++ const hitSlop = React.useMemo( ++ () => ++ isRight ++ ? // Extend hitSlop to the side of the screen when drawer is closed ++ // This lets the user drag the drawer from the side of the screen ++ { right: 0, width: isOpen ? undefined : swipeEdgeWidth } ++ : { left: 0, width: isOpen ? undefined : swipeEdgeWidth }, ++ [isRight, isOpen, swipeEdgeWidth] ++ ); + + const touchStartX = useSharedValue(0); + const touchX = useSharedValue(0); +@@ -217,53 +222,76 @@ export function Drawer({ + + const startX = useSharedValue(0); + +- let pan = Gesture?.Pan() +- .onBegin((event) => { +- 'worklet'; +- startX.value = translationX.value; +- gestureState.value = event.state; +- touchStartX.value = event.x; +- }) +- .onStart(() => { +- 'worklet'; +- runOnJS(onGestureBegin)(); +- }) +- .onChange((event) => { +- 'worklet'; +- touchX.value = event.x; +- translationX.value = startX.value + event.translationX; +- gestureState.value = event.state; +- }) +- .onEnd((event, success) => { +- 'worklet'; +- gestureState.value = event.state; +- +- if (!success) { +- runOnJS(onGestureAbort)(); +- } +- +- const nextOpen = +- (Math.abs(event.translationX) > SWIPE_MIN_OFFSET && +- Math.abs(event.translationX) > swipeMinVelocity) || +- Math.abs(event.translationX) > swipeMinDistance +- ? drawerPosition === 'left' +- ? // If swiped to right, open the drawer, otherwise close it +- (event.velocityX === 0 ? event.translationX : event.velocityX) > 0 +- : // If swiped to left, open the drawer, otherwise close it +- (event.velocityX === 0 ? event.translationX : event.velocityX) < 0 +- : open; +- +- toggleDrawer(nextOpen, event.velocityX); +- runOnJS(onGestureFinish)(); +- }) +- .activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]) +- .failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]) +- .hitSlop(hitSlop) +- .enabled(drawerType !== 'permanent' && swipeEnabled); +- +- if (pan && configureGestureHandler) { +- pan = configureGestureHandler(pan); +- } ++ const pan = React.useMemo(() => { ++ let panGesture = Gesture?.Pan() ++ .onBegin((event) => { ++ 'worklet'; ++ startX.value = translationX.value; ++ gestureState.value = event.state; ++ touchStartX.value = event.x; ++ }) ++ .onStart(() => { ++ 'worklet'; ++ runOnJS(onGestureBegin)(); ++ }) ++ .onChange((event) => { ++ 'worklet'; ++ touchX.value = event.x; ++ translationX.value = startX.value + event.translationX; ++ gestureState.value = event.state; ++ }) ++ .onEnd((event, success) => { ++ 'worklet'; ++ gestureState.value = event.state; ++ ++ if (!success) { ++ runOnJS(onGestureAbort)(); ++ } ++ ++ const nextOpen = ++ (Math.abs(event.translationX) > SWIPE_MIN_OFFSET && ++ Math.abs(event.translationX) > swipeMinVelocity) || ++ Math.abs(event.translationX) > swipeMinDistance ++ ? drawerPosition === 'left' ++ ? // If swiped to right, open the drawer, otherwise close it ++ (event.velocityX === 0 ? event.translationX : event.velocityX) > ++ 0 ++ : // If swiped to left, open the drawer, otherwise close it ++ (event.velocityX === 0 ? event.translationX : event.velocityX) < ++ 0 ++ : open; ++ ++ toggleDrawer(nextOpen, event.velocityX); ++ runOnJS(onGestureFinish)(); ++ }) ++ .activeOffsetX([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]) ++ .failOffsetY([-SWIPE_MIN_OFFSET, SWIPE_MIN_OFFSET]) ++ .hitSlop(hitSlop) ++ .enabled(drawerType !== 'permanent' && swipeEnabled); ++ ++ if (panGesture && configureGestureHandler) { ++ panGesture = configureGestureHandler(panGesture); ++ } ++ return panGesture; ++ }, [ ++ configureGestureHandler, ++ drawerPosition, ++ drawerType, ++ gestureState, ++ hitSlop, ++ onGestureBegin, ++ onGestureAbort, ++ onGestureFinish, ++ open, ++ startX, ++ swipeEnabled, ++ swipeMinDistance, ++ swipeMinVelocity, ++ toggleDrawer, ++ touchStartX, ++ touchX, ++ translationX, ++ ]); + + const translateX = useDerivedValue(() => { + // Comment stolen from react-native-gesture-handler/DrawerLayout +@@ -376,64 +404,66 @@ export function Drawer({ + return ( + + +- +- {/* Immediate child of gesture handler needs to be an Animated.View */} +- +- +- +- {children} +- +- {drawerType !== 'permanent' ? ( +- toggleDrawer(false)} +- style={overlayStyle} +- accessibilityLabel={overlayAccessibilityLabel} +- /> +- ) : null} +- ++ ++ ++ {/* Immediate child of gesture handler needs to be an Animated.View */} + +- {renderDrawerContent()} ++ ++ ++ {children} ++ ++ {drawerType !== 'permanent' ? ( ++ toggleDrawer(false)} ++ style={overlayStyle} ++ accessibilityLabel={overlayAccessibilityLabel} ++ /> ++ ) : null} ++ ++ ++ {renderDrawerContent()} ++ + +- +- ++ ++ + + + ); diff --git a/src/view/com/pager/Pager.tsx b/src/view/com/pager/Pager.tsx index da7fd1e936b..463044c4304 100644 --- a/src/view/com/pager/Pager.tsx +++ b/src/view/com/pager/Pager.tsx @@ -1,5 +1,7 @@ -import React, {forwardRef} from 'react' +import React, {forwardRef, useContext} from 'react' import {View} from 'react-native' +import {DrawerGestureContext} from 'react-native-drawer-layout' +import {Gesture, GestureDetector} from 'react-native-gesture-handler' import PagerView, { PagerViewOnPageScrollEventData, PagerViewOnPageSelectedEvent, @@ -113,6 +115,10 @@ export const Pager = forwardRef>( [parentOnPageScrollStateChanged], ) + const drawerGesture = useContext(DrawerGestureContext)! + const nativeGesture = + Gesture.Native().requireExternalGestureToFail(drawerGesture) + return ( {renderTabBar({ @@ -121,13 +127,15 @@ export const Pager = forwardRef>( dragProgress, dragState, })} - - {children} - + + + {children} + + ) }, diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 8dbbbea6f27..f4c064cd8d0 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -90,6 +90,7 @@ function ShellInner() { } }, [dedupe, navigation]) + const swipeEnabled = !canGoBack && hasSession && !isDrawerSwipeDisabled return ( <> @@ -98,12 +99,19 @@ function ShellInner() { { + if (swipeEnabled && !isDrawerOpen) { + return handler.activeOffsetX(1).failOffsetX(-1) + } else { + return handler + } + }} open={isDrawerOpen} onOpen={onOpenDrawer} onClose={onCloseDrawer} swipeEdgeWidth={winDim.width / 2} drawerType={isIOS ? 'slide' : 'front'} - swipeEnabled={!canGoBack && hasSession && !isDrawerSwipeDisabled} + swipeEnabled={swipeEnabled} overlayStyle={{ backgroundColor: select(t.name, { light: 'rgba(0, 57, 117, 0.1)',