diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx
index f7652334..f3925497 100644
--- a/src/view/com/util/ViewSelector.tsx
+++ b/src/view/com/util/ViewSelector.tsx
@@ -76,6 +76,7 @@ export function ViewSelector({
const data = [HEADER_ITEM, SELECTOR_ITEM, ...items]
return (
0}
diff --git a/src/view/com/util/gestures/HorzSwipe.tsx b/src/view/com/util/gestures/HorzSwipe.tsx
index 7ae1ee75..0176bd4c 100644
--- a/src/view/com/util/gestures/HorzSwipe.tsx
+++ b/src/view/com/util/gestures/HorzSwipe.tsx
@@ -12,9 +12,12 @@ import {clamp} from 'lodash'
interface Props {
panX: Animated.Value
- canSwipeLeft: boolean
- canSwipeRight: boolean
- swipeEnabled: boolean
+ canSwipeLeft?: boolean
+ canSwipeRight?: boolean
+ swipeEnabled?: boolean
+ hasPriority?: boolean // if has priority, will not release control of the gesture to another gesture
+ distThresholdDivisor?: number
+ useNativeDriver?: boolean
onSwipeStart?: () => void
onSwipeEnd?: (dx: number) => void
children: React.ReactNode
@@ -22,9 +25,12 @@ interface Props {
export function HorzSwipe({
panX,
- canSwipeLeft,
- canSwipeRight,
+ canSwipeLeft = false,
+ canSwipeRight = false,
swipeEnabled = true,
+ hasPriority = false,
+ distThresholdDivisor = 1.75,
+ useNativeDriver = false,
onSwipeStart,
onSwipeEnd,
children,
@@ -32,7 +38,7 @@ export function HorzSwipe({
const winDim = useWindowDimensions()
const swipeVelocityThreshold = 35
- const swipeDistanceThreshold = winDim.width / 1.75
+ const swipeDistanceThreshold = winDim.width / distThresholdDivisor
const isMovingHorizontally = (
_: GestureResponderEvent,
@@ -53,10 +59,10 @@ export function HorzSwipe({
}
const diffX = I18nManager.isRTL ? -gestureState.dx : gestureState.dx
- return (
+ const willHandle =
isMovingHorizontally(event, gestureState) &&
((diffX > 0 && canSwipeLeft) || (diffX < 0 && canSwipeRight))
- )
+ return willHandle
}
const startGesture = () => {
@@ -94,28 +100,42 @@ export function HorzSwipe({
_: GestureResponderEvent,
gestureState: PanResponderGestureState,
) => {
- panX.flattenOffset()
- panX.setValue(0)
if (
Math.abs(gestureState.dx) > Math.abs(gestureState.dy) &&
Math.abs(gestureState.vx) > Math.abs(gestureState.vy) &&
(Math.abs(gestureState.dx) > swipeDistanceThreshold / 3 ||
Math.abs(gestureState.vx) > swipeVelocityThreshold)
) {
- onSwipeEnd?.(((gestureState.dx / Math.abs(gestureState.dx)) * -1) | 0)
+ const final = ((gestureState.dx / Math.abs(gestureState.dx)) * -1) | 0
+ Animated.timing(panX, {
+ toValue: final,
+ duration: 100,
+ useNativeDriver,
+ }).start(() => {
+ onSwipeEnd?.(final)
+ panX.flattenOffset()
+ panX.setValue(0)
+ })
} else {
onSwipeEnd?.(0)
+ Animated.timing(panX, {
+ toValue: 0,
+ duration: 100,
+ useNativeDriver,
+ }).start(() => {
+ panX.flattenOffset()
+ panX.setValue(0)
+ })
}
}
const panResponder = PanResponder.create({
onMoveShouldSetPanResponder: canMoveScreen,
- onMoveShouldSetPanResponderCapture: canMoveScreen,
onPanResponderGrant: startGesture,
onPanResponderMove: respondToGesture,
onPanResponderTerminate: finishGesture,
onPanResponderRelease: finishGesture,
- onPanResponderTerminationRequest: () => true,
+ onPanResponderTerminationRequest: () => !hasPriority,
})
return (
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index 6bb11187..ee6c9985 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -1,7 +1,7 @@
import React, {useState, useEffect, useRef} from 'react'
import {observer} from 'mobx-react-lite'
import {
- useWindowDimensions,
+ Animated as RNAnimated,
FlatList,
GestureResponderEvent,
SafeAreaView,
@@ -9,12 +9,12 @@ import {
Text,
TouchableOpacity,
useColorScheme,
+ useWindowDimensions,
View,
ViewStyle,
} from 'react-native'
import {ScreenContainer, Screen} from 'react-native-screens'
import LinearGradient from 'react-native-linear-gradient'
-import {GestureDetector, Gesture} from 'react-native-gesture-handler'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import Animated, {
Easing,
@@ -32,6 +32,7 @@ import {NavigationModel} from '../../../state/models/navigation'
import {match, MatchResult} from '../../routes'
import {Login} from '../../screens/Login'
import {Onboard} from '../../screens/Onboard'
+import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
import {Modal} from '../../com/modals/Modal'
import {TabsSelector} from './TabsSelector'
import {Composer} from './Composer'
@@ -45,9 +46,7 @@ import {
BellIcon,
BellIconSolid,
} from '../../lib/icons'
-
-const SWIPE_GESTURE_DIST_TRIGGER = 0.3
-const SWIPE_GESTURE_VEL_TRIGGER = 2000
+import {useAnimatedValue} from '../../lib/useAnimatedValue'
const Btn = ({
icon,
@@ -120,7 +119,7 @@ export const MobileShell: React.FC = observer(() => {
const [isTabsSelectorActive, setTabsSelectorActive] = useState(false)
const scrollElRef = useRef()
const winDim = useWindowDimensions()
- const swipeGestureInterp = useSharedValue(0)
+ const swipeGestureInterp = useAnimatedValue(0)
const tabMenuInterp = useSharedValue(0)
const newTabInterp = useSharedValue(0)
const [isRunningNewTabAnim, setIsRunningNewTabAnim] = useState(false)
@@ -185,37 +184,22 @@ export const MobileShell: React.FC = observer(() => {
// navigation swipes
// =
- const goBack = () => store.nav.tab.goBack()
- const swipeGesture = Gesture.Pan()
- .enabled(store.nav.tab.canGoBack)
- .onUpdate(e => {
- if (store.nav.tab.canGoBack) {
- swipeGestureInterp.value = Math.max(e.translationX / winDim.width, 0)
- }
- })
- .onEnd(e => {
- if (
- swipeGestureInterp.value >= SWIPE_GESTURE_DIST_TRIGGER ||
- e.velocityX > SWIPE_GESTURE_VEL_TRIGGER
- ) {
- swipeGestureInterp.value = withTiming(1, {duration: 100}, () => {
- runOnJS(goBack)()
- })
- } else {
- swipeGestureInterp.value = withTiming(0, {duration: 100})
- }
- })
- useEffect(() => {
- // reset the swipe interopolation when the page changes
- swipeGestureInterp.value = 0
- }, [swipeGestureInterp, store.nav.tab.current])
-
- const swipeTransform = useAnimatedStyle(() => ({
- transform: [{translateX: swipeGestureInterp.value * winDim.width}],
- }))
- const swipeOpacity = useAnimatedStyle(() => ({
- opacity: interpolate(swipeGestureInterp.value, [0, 1.0], [0.6, 0.0]),
- }))
+ const onNavSwipeEnd = (dx: number) => {
+ if (dx < 0 && store.nav.tab.canGoBack) {
+ store.nav.tab.goBack()
+ }
+ }
+ const swipeTransform = {
+ transform: [
+ {translateX: RNAnimated.multiply(swipeGestureInterp, winDim.width * -1)},
+ ],
+ }
+ const swipeOpacity = {
+ opacity: swipeGestureInterp.interpolate({
+ inputRange: [-1, 0, 1],
+ outputRange: [0, 0.6, 0],
+ }),
+ }
const tabMenuTransform = useAnimatedStyle(() => ({
transform: [{translateY: tabMenuInterp.value * -320}],
}))
@@ -252,7 +236,13 @@ export const MobileShell: React.FC = observer(() => {
return (
-
+
{screenRenderDesc.screens.map(
({Com, navIdx, params, key, current, previous}) => {
@@ -261,20 +251,20 @@ export const MobileShell: React.FC = observer(() => {
key={key}
style={[StyleSheet.absoluteFill]}
activityState={current ? 2 : previous ? 1 : 0}>
-
-
@@ -284,13 +274,13 @@ export const MobileShell: React.FC = observer(() => {
visible={current}
scrollElRef={current ? scrollElRef : undefined}
/>
-
+
)
},
)}
-
+
{isTabsSelectorActive ? (