diff options
-rw-r--r-- | src/state/shell/drawer-open.tsx | 12 | ||||
-rw-r--r-- | src/view/com/home/HomeHeader.tsx | 14 | ||||
-rw-r--r-- | src/view/com/pager/Pager.tsx | 31 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 135 |
4 files changed, 107 insertions, 85 deletions
diff --git a/src/state/shell/drawer-open.tsx b/src/state/shell/drawer-open.tsx index 87650a09c..7ce4189c3 100644 --- a/src/state/shell/drawer-open.tsx +++ b/src/state/shell/drawer-open.tsx @@ -1,15 +1,15 @@ -import React from 'react' +import {createContext, useContext, useState} from 'react' type StateContext = boolean type SetContext = (v: boolean) => void -const stateContext = React.createContext<StateContext>(false) +const stateContext = createContext<StateContext>(false) stateContext.displayName = 'DrawerOpenStateContext' -const setContext = React.createContext<SetContext>((_: boolean) => {}) +const setContext = createContext<SetContext>((_: boolean) => {}) setContext.displayName = 'DrawerOpenSetContext' export function Provider({children}: React.PropsWithChildren<{}>) { - const [state, setState] = React.useState(false) + const [state, setState] = useState(false) return ( <stateContext.Provider value={state}> @@ -19,9 +19,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { } export function useIsDrawerOpen() { - return React.useContext(stateContext) + return useContext(stateContext) } export function useSetDrawerOpen() { - return React.useContext(setContext) + return useContext(setContext) } diff --git a/src/view/com/home/HomeHeader.tsx b/src/view/com/home/HomeHeader.tsx index 0ec9ac753..4ae344549 100644 --- a/src/view/com/home/HomeHeader.tsx +++ b/src/view/com/home/HomeHeader.tsx @@ -1,10 +1,10 @@ import React from 'react' import {useNavigation} from '@react-navigation/native' -import {NavigationProp} from '#/lib/routes/types' -import {FeedSourceInfo} from '#/state/queries/feed' +import {type NavigationProp} from '#/lib/routes/types' +import {type FeedSourceInfo} from '#/state/queries/feed' import {useSession} from '#/state/session' -import {RenderTabBarFnProps} from '#/view/com/pager/Pager' +import {type RenderTabBarFnProps} from '#/view/com/pager/Pager' import {TabBar} from '../pager/TabBar' import {HomeHeaderLayout} from './HomeHeaderLayout' @@ -15,7 +15,7 @@ export function HomeHeader( feeds: FeedSourceInfo[] }, ) { - const {feeds} = props + const {feeds, onSelect: onSelectProp} = props const {hasSession} = useSession() const navigation = useNavigation<NavigationProp>() @@ -43,11 +43,11 @@ export function HomeHeader( (index: number) => { if (!hasPinnedCustom && index === items.length - 1) { onPressFeedsLink() - } else if (props.onSelect) { - props.onSelect(index) + } else if (onSelectProp) { + onSelectProp(index) } }, - [items.length, onPressFeedsLink, props, hasPinnedCustom], + [items.length, onPressFeedsLink, onSelectProp, hasPinnedCustom], ) return ( diff --git a/src/view/com/pager/Pager.tsx b/src/view/com/pager/Pager.tsx index 8cc346903..5eb7d7608 100644 --- a/src/view/com/pager/Pager.tsx +++ b/src/view/com/pager/Pager.tsx @@ -1,7 +1,9 @@ import { + memo, useCallback, useContext, useImperativeHandle, + useMemo, useRef, useState, } from 'react' @@ -56,6 +58,7 @@ interface Props { } const AnimatedPagerView = Animated.createAnimatedComponent(PagerView) +const MemoizedAnimatedPagerView = memo(AnimatedPagerView) export function Pager({ ref, @@ -139,10 +142,6 @@ export function Pager({ [parentOnPageScrollStateChanged], ) - const drawerGesture = useContext(DrawerGestureContext) ?? Gesture.Native() // noop for web - const nativeGesture = - Gesture.Native().requireExternalGestureToFail(drawerGesture) - return ( <View testID={testID} style={[a.flex_1, native(a.overflow_hidden)]}> {renderTabBar({ @@ -151,19 +150,33 @@ export function Pager({ dragProgress, dragState, })} - <GestureDetector gesture={nativeGesture}> - <AnimatedPagerView + <DrawerGestureRequireFail> + <MemoizedAnimatedPagerView ref={pagerView} - style={[a.flex_1]} + style={a.flex_1} initialPage={initialPage} onPageScroll={handlePageScroll}> {children} - </AnimatedPagerView> - </GestureDetector> + </MemoizedAnimatedPagerView> + </DrawerGestureRequireFail> </View> ) } +function DrawerGestureRequireFail({children}: {children: React.ReactNode}) { + const drawerGesture = useContext(DrawerGestureContext) + + const nativeGesture = useMemo(() => { + const gesture = Gesture.Native() + if (drawerGesture) { + gesture.requireExternalGestureToFail(drawerGesture) + } + return gesture + }, [drawerGesture]) + + return <GestureDetector gesture={nativeGesture}>{children}</GestureDetector> +} + function usePagerHandlers( handlers: { onPageScroll: (e: PagerViewOnPageScrollEventData) => void diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 277e5c523..5075f05cb 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -45,25 +45,10 @@ import {Composer} from './Composer' import {DrawerContent} from './Drawer' function ShellInner() { - const t = useTheme() - const isDrawerOpen = useIsDrawerOpen() - const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled() - const setIsDrawerOpen = useSetDrawerOpen() const winDim = useWindowDimensions() const insets = useSafeAreaInsets() const {state: policyUpdateState} = usePolicyUpdateContext() - const renderDrawerContent = useCallback(() => <DrawerContent />, []) - const onOpenDrawer = useCallback( - () => setIsDrawerOpen(true), - [setIsDrawerOpen], - ) - const onCloseDrawer = useCallback( - () => setIsDrawerOpen(false), - [setIsDrawerOpen], - ) - const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) - const {hasSession} = useSession() const closeAnyActiveElement = useCloseAnyActiveElement() useNotificationsRegistration() @@ -102,60 +87,14 @@ function ShellInner() { } }, [dedupe, navigation]) - const swipeEnabled = !canGoBack && hasSession && !isDrawerSwipeDisabled - const [trendingScrollGesture] = useState(() => Gesture.Native()) return ( <> <View style={[a.h_full]}> <ErrorBoundary style={{paddingTop: insets.top, paddingBottom: insets.bottom}}> - <Drawer - renderDrawerContent={renderDrawerContent} - drawerStyle={{width: Math.min(400, winDim.width * 0.8)}} - configureGestureHandler={handler => { - handler = handler.requireExternalGestureToFail( - trendingScrollGesture, - ) - - 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} - swipeMinVelocity={100} - swipeMinDistance={10} - drawerType={isIOS ? 'slide' : 'front'} - overlayStyle={{ - backgroundColor: select(t.name, { - light: 'rgba(0, 57, 117, 0.1)', - dark: isAndroid - ? 'rgba(16, 133, 254, 0.1)' - : 'rgba(1, 82, 168, 0.1)', - dim: 'rgba(10, 13, 16, 0.8)', - }), - }}> + <DrawerLayout> <TabsNavigator /> - </Drawer> + </DrawerLayout> </ErrorBoundary> </View> @@ -182,6 +121,76 @@ function ShellInner() { ) } +function DrawerLayout({children}: {children: React.ReactNode}) { + const t = useTheme() + const isDrawerOpen = useIsDrawerOpen() + const setIsDrawerOpen = useSetDrawerOpen() + const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled() + const winDim = useWindowDimensions() + + const canGoBack = useNavigationState(state => !isStateAtTabRoot(state)) + const {hasSession} = useSession() + + const swipeEnabled = !canGoBack && hasSession && !isDrawerSwipeDisabled + const [trendingScrollGesture] = useState(() => Gesture.Native()) + + const renderDrawerContent = useCallback(() => <DrawerContent />, []) + const onOpenDrawer = useCallback( + () => setIsDrawerOpen(true), + [setIsDrawerOpen], + ) + const onCloseDrawer = useCallback( + () => setIsDrawerOpen(false), + [setIsDrawerOpen], + ) + + return ( + <Drawer + renderDrawerContent={renderDrawerContent} + drawerStyle={{width: Math.min(400, winDim.width * 0.8)}} + configureGestureHandler={handler => { + handler = handler.requireExternalGestureToFail(trendingScrollGesture) + + 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} + swipeMinVelocity={100} + swipeMinDistance={10} + drawerType={isIOS ? 'slide' : 'front'} + overlayStyle={{ + backgroundColor: select(t.name, { + light: 'rgba(0, 57, 117, 0.1)', + dark: isAndroid ? 'rgba(16, 133, 254, 0.1)' : 'rgba(1, 82, 168, 0.1)', + dim: 'rgba(10, 13, 16, 0.8)', + }), + }}> + {children} + </Drawer> + ) +} + export function Shell() { const t = useTheme() const {status: geolocation} = useGeolocationStatus() |