Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix drawer swipe #7007

Merged
merged 7 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
876 changes: 876 additions & 0 deletions patches/react-native-drawer-layout+4.0.4.patch

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions src/screens/Hashtag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -63,7 +63,6 @@ export default function HashtagScreen({

const [activeTab, setActiveTab] = React.useState(0)
const setMinimalShellMode = useSetMinimalShellMode()
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()

useFocusEffect(
React.useCallback(() => {
Expand All @@ -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(() => {
Expand Down
39 changes: 31 additions & 8 deletions src/view/com/pager/Pager.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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({
Expand All @@ -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>
)
},
Expand Down
12 changes: 3 additions & 9 deletions src/view/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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(
Expand All @@ -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.
Expand All @@ -166,7 +160,7 @@ function HomeScreenReady({
feedUrl: feed,
})
},
[setDrawerSwipeDisabled, setSelectedFeed, setMinimalShellMode, allFeeds],
[setSelectedFeed, setMinimalShellMode, allFeeds],
)

const onPressSelected = React.useCallback(() => {
Expand Down
12 changes: 1 addition & 11 deletions src/view/screens/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -307,15 +306,6 @@ function ProfileScreenLoaded({
}, [setMinimalShellMode, currentPage, scrollSectionToTop]),
)

useFocusEffect(
React.useCallback(() => {
setDrawerSwipeDisabled(currentPage > 0)
return () => {
setDrawerSwipeDisabled(false)
}
}, [setDrawerSwipeDisabled, currentPage]),
)

// events
// =

Expand Down
6 changes: 2 additions & 4 deletions src/view/screens/Search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
Expand All @@ -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(() => {
Expand Down
28 changes: 26 additions & 2 deletions src/view/shell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function ShellInner() {
}
}, [dedupe, navigation])

const swipeEnabled = !canGoBack && hasSession && !isDrawerSwipeDisabled
return (
<>
<View style={[a.h_full]}>
Expand All @@ -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)',
Expand Down
Loading