about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--patches/react-native-drawer-layout+4.0.4.patch876
-rw-r--r--src/screens/Hashtag.tsx6
-rw-r--r--src/view/com/pager/Pager.tsx39
-rw-r--r--src/view/screens/Home.tsx12
-rw-r--r--src/view/screens/Profile.tsx12
-rw-r--r--src/view/screens/Search/Search.tsx6
-rw-r--r--src/view/shell/index.tsx28
7 files changed, 941 insertions, 38 deletions
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 000000000..004fb4a00
--- /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<import("react-native-gesture-handler/lib/typescript/handlers/gestures/panGesture").PanGesture | undefined>;
++//# 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<import("react-native-gesture-handler/lib/typescript/handlers/gestures/panGesture").PanGesture | undefined>;
++//# 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<PanGesture | undefined>(
++  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<number | null>(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 (
+     <GestureHandlerRootView style={[styles.container, style]}>
+       <DrawerProgressContext.Provider value={progress}>
+-        <GestureDetector gesture={pan}>
+-          {/* Immediate child of gesture handler needs to be an Animated.View */}
+-          <Animated.View
+-            style={[
+-              styles.main,
+-              {
+-                flexDirection:
+-                  drawerType === 'permanent'
+-                    ? (isRight && direction === 'ltr') ||
+-                      (!isRight && direction === 'rtl')
+-                      ? 'row'
+-                      : 'row-reverse'
+-                    : 'row',
+-              },
+-            ]}
+-          >
+-            <Animated.View style={[styles.content, contentAnimatedStyle]}>
+-              <View
+-                accessibilityElementsHidden={
+-                  isOpen && drawerType !== 'permanent'
+-                }
+-                importantForAccessibility={
+-                  isOpen && drawerType !== 'permanent'
+-                    ? 'no-hide-descendants'
+-                    : 'auto'
+-                }
+-                style={styles.content}
+-              >
+-                {children}
+-              </View>
+-              {drawerType !== 'permanent' ? (
+-                <Overlay
+-                  open={open}
+-                  progress={progress}
+-                  onPress={() => toggleDrawer(false)}
+-                  style={overlayStyle}
+-                  accessibilityLabel={overlayAccessibilityLabel}
+-                />
+-              ) : null}
+-            </Animated.View>
++        <DrawerGestureContext.Provider value={pan}>
++          <GestureDetector gesture={pan}>
++            {/* Immediate child of gesture handler needs to be an Animated.View */}
+             <Animated.View
+-              removeClippedSubviews={Platform.OS !== 'ios'}
+               style={[
+-                styles.drawer,
++                styles.main,
+                 {
+-                  width: drawerWidth,
+-                  position:
+-                    drawerType === 'permanent' ? 'relative' : 'absolute',
+-                  zIndex: drawerType === 'back' ? -1 : 0,
++                  flexDirection:
++                    drawerType === 'permanent'
++                      ? (isRight && direction === 'ltr') ||
++                        (!isRight && direction === 'rtl')
++                        ? 'row'
++                        : 'row-reverse'
++                      : 'row',
+                 },
+-                drawerAnimatedStyle,
+-                drawerStyle,
+               ]}
+             >
+-              {renderDrawerContent()}
++              <Animated.View style={[styles.content, contentAnimatedStyle]}>
++                <View
++                  accessibilityElementsHidden={
++                    isOpen && drawerType !== 'permanent'
++                  }
++                  importantForAccessibility={
++                    isOpen && drawerType !== 'permanent'
++                      ? 'no-hide-descendants'
++                      : 'auto'
++                  }
++                  style={styles.content}
++                >
++                  {children}
++                </View>
++                {drawerType !== 'permanent' ? (
++                  <Overlay
++                    open={open}
++                    progress={progress}
++                    onPress={() => toggleDrawer(false)}
++                    style={overlayStyle}
++                    accessibilityLabel={overlayAccessibilityLabel}
++                  />
++                ) : null}
++              </Animated.View>
++              <Animated.View
++                removeClippedSubviews={Platform.OS !== 'ios'}
++                style={[
++                  styles.drawer,
++                  {
++                    width: drawerWidth,
++                    position:
++                      drawerType === 'permanent' ? 'relative' : 'absolute',
++                    zIndex: drawerType === 'back' ? -1 : 0,
++                  },
++                  drawerAnimatedStyle,
++                  drawerStyle,
++                ]}
++              >
++                {renderDrawerContent()}
++              </Animated.View>
+             </Animated.View>
+-          </Animated.View>
+-        </GestureDetector>
++          </GestureDetector>
++        </DrawerGestureContext.Provider>
+       </DrawerProgressContext.Provider>
+     </GestureHandlerRootView>
+   );
diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx
index a0fc3707c..a87487150 100644
--- a/src/screens/Hashtag.tsx
+++ b/src/screens/Hashtag.tsx
@@ -14,7 +14,7 @@ import {cleanError} from '#/lib/strings/errors'
 import {sanitizeHandle} from '#/lib/strings/handles'
 import {enforceLen} from '#/lib/strings/helpers'
 import {useSearchPostsQuery} from '#/state/queries/search-posts'
-import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
+import {useSetMinimalShellMode} from '#/state/shell'
 import {Pager} from '#/view/com/pager/Pager'
 import {TabBar} from '#/view/com/pager/TabBar'
 import {Post} from '#/view/com/post/Post'
@@ -63,7 +63,6 @@ export default function HashtagScreen({
 
   const [activeTab, setActiveTab] = React.useState(0)
   const setMinimalShellMode = useSetMinimalShellMode()
-  const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
 
   useFocusEffect(
     React.useCallback(() => {
@@ -74,10 +73,9 @@ export default function HashtagScreen({
   const onPageSelected = React.useCallback(
     (index: number) => {
       setMinimalShellMode(false)
-      setDrawerSwipeDisabled(index > 0)
       setActiveTab(index)
     },
-    [setDrawerSwipeDisabled, setMinimalShellMode],
+    [setMinimalShellMode],
   )
 
   const sections = React.useMemo(() => {
diff --git a/src/view/com/pager/Pager.tsx b/src/view/com/pager/Pager.tsx
index da7fd1e93..2c0bbee52 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, useCallback, 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,
@@ -13,7 +15,9 @@ import Animated, {
   useHandler,
   useSharedValue,
 } from 'react-native-reanimated'
+import {useFocusEffect} from '@react-navigation/native'
 
+import {useSetDrawerSwipeDisabled} from '#/state/shell'
 import {atoms as a, native} from '#/alf'
 
 export type PageSelectedEvent = PagerViewOnPageSelectedEvent
@@ -58,6 +62,18 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
     const [selectedPage, setSelectedPage] = React.useState(initialPage)
     const pagerView = React.useRef<PagerView>(null)
 
+    const [isIdle, setIsIdle] = React.useState(true)
+    const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
+    useFocusEffect(
+      useCallback(() => {
+        const canSwipeDrawer = selectedPage === 0 && isIdle
+        setDrawerSwipeDisabled(!canSwipeDrawer)
+        return () => {
+          setDrawerSwipeDisabled(false)
+        }
+      }, [setDrawerSwipeDisabled, selectedPage, isIdle]),
+    )
+
     React.useImperativeHandle(ref, () => ({
       setPage: (index: number) => {
         pagerView.current?.setPage(index)
@@ -96,6 +112,7 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
         },
         onPageScrollStateChanged(e: PageScrollStateChangedNativeEventData) {
           'worklet'
+          runOnJS(setIsIdle)(e.pageScrollState === 'idle')
           if (dragState.get() === 'idle' && e.pageScrollState === 'settling') {
             // This is a programmatic scroll on Android.
             // Stay "idle" to match iOS and avoid confusing downstream code.
@@ -113,6 +130,10 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
       [parentOnPageScrollStateChanged],
     )
 
+    const drawerGesture = useContext(DrawerGestureContext)!
+    const nativeGesture =
+      Gesture.Native().requireExternalGestureToFail(drawerGesture)
+
     return (
       <View testID={testID} style={[a.flex_1, native(a.overflow_hidden)]}>
         {renderTabBar({
@@ -121,13 +142,15 @@ export const Pager = forwardRef<PagerRef, React.PropsWithChildren<Props>>(
           dragProgress,
           dragState,
         })}
-        <AnimatedPagerView
-          ref={pagerView}
-          style={[a.flex_1]}
-          initialPage={initialPage}
-          onPageScroll={handlePageScroll}>
-          {children}
-        </AnimatedPagerView>
+        <GestureDetector gesture={nativeGesture}>
+          <AnimatedPagerView
+            ref={pagerView}
+            style={[a.flex_1]}
+            initialPage={initialPage}
+            onPageScroll={handlePageScroll}>
+            {children}
+          </AnimatedPagerView>
+        </GestureDetector>
       </View>
     )
   },
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 1218a5ba0..59b296730 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -19,7 +19,7 @@ import {FeedParams} from '#/state/queries/post-feed'
 import {usePreferencesQuery} from '#/state/queries/preferences'
 import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
 import {useSession} from '#/state/session'
-import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
+import {useSetMinimalShellMode} from '#/state/shell'
 import {useLoggedOutViewControls} from '#/state/shell/logged-out'
 import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
 import {FeedPage} from '#/view/com/feeds/FeedPage'
@@ -127,15 +127,10 @@ function HomeScreenReady({
 
   const {hasSession} = useSession()
   const setMinimalShellMode = useSetMinimalShellMode()
-  const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
   useFocusEffect(
     React.useCallback(() => {
       setMinimalShellMode(false)
-      setDrawerSwipeDisabled(selectedIndex > 0)
-      return () => {
-        setDrawerSwipeDisabled(false)
-      }
-    }, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]),
+    }, [setMinimalShellMode]),
   )
 
   useFocusEffect(
@@ -154,7 +149,6 @@ function HomeScreenReady({
   const onPageSelected = React.useCallback(
     (index: number) => {
       setMinimalShellMode(false)
-      setDrawerSwipeDisabled(index > 0)
       const feed = allFeeds[index]
       // Mutate the ref before setting state to avoid the imperative syncing effect
       // above from starting a loop on Android when swiping back and forth.
@@ -166,7 +160,7 @@ function HomeScreenReady({
         feedUrl: feed,
       })
     },
-    [setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
+    [setSelectedFeed, setMinimalShellMode, allFeeds],
   )
 
   const onPressSelected = React.useCallback(() => {
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 6a9b6f7f2..782e9b9c8 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -32,7 +32,7 @@ import {resetProfilePostsQueries} from '#/state/queries/post-feed'
 import {useProfileQuery} from '#/state/queries/profile'
 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
 import {useAgent, useSession} from '#/state/session'
-import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
+import {useSetMinimalShellMode} from '#/state/shell'
 import {useComposerControls} from '#/state/shell/composer'
 import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens'
 import {ProfileLists} from '#/view/com/lists/ProfileLists'
@@ -183,7 +183,6 @@ function ProfileScreenLoaded({
   })
   const [currentPage, setCurrentPage] = React.useState(0)
   const {_} = useLingui()
-  const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
 
   const [scrollViewTag, setScrollViewTag] = React.useState<number | null>(null)
 
@@ -307,15 +306,6 @@ function ProfileScreenLoaded({
     }, [setMinimalShellMode, currentPage, scrollSectionToTop]),
   )
 
-  useFocusEffect(
-    React.useCallback(() => {
-      setDrawerSwipeDisabled(currentPage > 0)
-      return () => {
-        setDrawerSwipeDisabled(false)
-      }
-    }, [setDrawerSwipeDisabled, currentPage]),
-  )
-
   // events
   // =
 
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 0871458c9..ed62c5a51 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -47,7 +47,7 @@ import {usePopularFeedsSearch} from '#/state/queries/feed'
 import {useSearchPostsQuery} from '#/state/queries/search-posts'
 import {useSession} from '#/state/session'
 import {useSetDrawerOpen} from '#/state/shell'
-import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
+import {useSetMinimalShellMode} from '#/state/shell'
 import {Pager} from '#/view/com/pager/Pager'
 import {TabBar} from '#/view/com/pager/TabBar'
 import {Post} from '#/view/com/post/Post'
@@ -471,7 +471,6 @@ let SearchScreenInner = ({
 }): React.ReactNode => {
   const pal = usePalette('default')
   const setMinimalShellMode = useSetMinimalShellMode()
-  const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
   const {hasSession} = useSession()
   const {isDesktop} = useWebMediaQueries()
   const [activeTab, setActiveTab] = React.useState(0)
@@ -480,10 +479,9 @@ let SearchScreenInner = ({
   const onPageSelected = React.useCallback(
     (index: number) => {
       setMinimalShellMode(false)
-      setDrawerSwipeDisabled(index > 0)
       setActiveTab(index)
     },
-    [setDrawerSwipeDisabled, setMinimalShellMode],
+    [setMinimalShellMode],
   )
 
   const sections = React.useMemo(() => {
diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx
index 8dbbbea6f..179e8858e 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 (
     <>
       <View style={[a.h_full]}>
@@ -98,12 +99,35 @@ function ShellInner() {
           <Drawer
             renderDrawerContent={renderDrawerContent}
             drawerStyle={{width: Math.min(400, winDim.width * 0.8)}}
+            configureGestureHandler={handler => {
+              if (swipeEnabled) {
+                if (isDrawerOpen) {
+                  return handler.activeOffsetX([-1, 1])
+                } else {
+                  return (
+                    handler
+                      // Any movement to the left is a pager swipe
+                      // so fail the drawer gesture immediately.
+                      .failOffsetX(-1)
+                      // Don't rush declaring that a movement to the right
+                      // is a drawer swipe. It could be a vertical scroll.
+                      .activeOffsetX(5)
+                  )
+                }
+              } else {
+                // Fail the gesture immediately.
+                // This seems more reliable than the `swipeEnabled` prop.
+                // With `swipeEnabled` alone, the gesture may freeze after toggling off/on.
+                return handler.failOffsetX([0, 0]).failOffsetY([0, 0])
+              }
+            }}
             open={isDrawerOpen}
             onOpen={onOpenDrawer}
             onClose={onCloseDrawer}
-            swipeEdgeWidth={winDim.width / 2}
+            swipeEdgeWidth={winDim.width}
+            swipeMinVelocity={100}
+            swipeMinDistance={10}
             drawerType={isIOS ? 'slide' : 'front'}
-            swipeEnabled={!canGoBack && hasSession && !isDrawerSwipeDisabled}
             overlayStyle={{
               backgroundColor: select(t.name, {
                 light: 'rgba(0, 57, 117, 0.1)',