Improve navigation gesture and fix caching issue
parent
69265753bf
commit
5ae39612d7
|
@ -1,6 +1,7 @@
|
||||||
import React, {useState, useRef, useMemo} from 'react'
|
import React, {useState, useRef, useMemo} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {
|
import {
|
||||||
|
useWindowDimensions,
|
||||||
GestureResponderEvent,
|
GestureResponderEvent,
|
||||||
Image,
|
Image,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
|
@ -16,6 +17,7 @@ import Animated, {
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
withTiming,
|
withTiming,
|
||||||
runOnJS,
|
runOnJS,
|
||||||
|
interpolate,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
||||||
|
@ -33,7 +35,7 @@ import {AVIS} from '../../lib/assets'
|
||||||
|
|
||||||
const locationIconNeedsNudgeUp = (icon: IconProp) => icon === 'house'
|
const locationIconNeedsNudgeUp = (icon: IconProp) => icon === 'house'
|
||||||
const SWIPE_GESTURE_HIT_SLOP = {left: 0, top: 0, width: 20, bottom: 0}
|
const SWIPE_GESTURE_HIT_SLOP = {left: 0, top: 0, width: 20, bottom: 0}
|
||||||
const SWIPE_GESTURE_MAX_DISTANCE = 150
|
const SWIPE_GESTURE_TRIGGER = 0.5
|
||||||
|
|
||||||
const Location = ({
|
const Location = ({
|
||||||
icon,
|
icon,
|
||||||
|
@ -106,7 +108,8 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const tabSelectorRef = useRef<{open: () => void}>()
|
const tabSelectorRef = useRef<{open: () => void}>()
|
||||||
const [isLocationMenuActive, setLocationMenuActive] = useState(false)
|
const [isLocationMenuActive, setLocationMenuActive] = useState(false)
|
||||||
const swipeGesturePosition = useSharedValue<number>(0)
|
const winDim = useWindowDimensions()
|
||||||
|
const swipeGestureInterp = useSharedValue<number>(0)
|
||||||
const screenRenderDesc = constructScreenRenderDesc(store.nav)
|
const screenRenderDesc = constructScreenRenderDesc(store.nav)
|
||||||
|
|
||||||
const onPressAvi = () =>
|
const onPressAvi = () =>
|
||||||
|
@ -145,24 +148,31 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
Gesture.Pan()
|
Gesture.Pan()
|
||||||
.hitSlop(SWIPE_GESTURE_HIT_SLOP)
|
.hitSlop(SWIPE_GESTURE_HIT_SLOP)
|
||||||
.onUpdate(e => {
|
.onUpdate(e => {
|
||||||
swipeGesturePosition.value = Math.min(
|
if (store.nav.tab.canGoBack) {
|
||||||
e.translationX,
|
swipeGestureInterp.value = Math.max(
|
||||||
SWIPE_GESTURE_MAX_DISTANCE,
|
e.translationX / winDim.width,
|
||||||
)
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.onEnd(e => {
|
.onEnd(_e => {
|
||||||
if (swipeGesturePosition.value >= SWIPE_GESTURE_MAX_DISTANCE) {
|
if (swipeGestureInterp.value >= SWIPE_GESTURE_TRIGGER) {
|
||||||
runOnJS(goBack)()
|
runOnJS(goBack)()
|
||||||
swipeGesturePosition.value = 0
|
swipeGestureInterp.value = withTiming(1, {duration: 100}, () => {
|
||||||
|
swipeGestureInterp.value = 0
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
swipeGesturePosition.value = withTiming(0, {duration: 100})
|
swipeGestureInterp.value = withTiming(0, {duration: 100})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[swipeGesturePosition, goBack],
|
[swipeGestureInterp, winDim, store.nav.tab.canGoBack, goBack],
|
||||||
)
|
)
|
||||||
|
|
||||||
const swipeViewAnimatedStyle = useAnimatedStyle(() => ({
|
const swipeTransform = useAnimatedStyle(() => ({
|
||||||
transform: [{translateX: swipeGesturePosition.value}],
|
transform: [{translateX: swipeGestureInterp.value * winDim.width}],
|
||||||
|
}))
|
||||||
|
const swipeOpacity = useAnimatedStyle(() => ({
|
||||||
|
opacity: interpolate(swipeGestureInterp.value, [0, 1.0], [0.6, 0.0]),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -181,39 +191,34 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<SafeAreaView style={styles.innerContainer}>
|
<SafeAreaView style={styles.innerContainer}>
|
||||||
<ScreenContainer style={styles.screenContainer}>
|
<GestureDetector gesture={swipeGesture}>
|
||||||
{screenRenderDesc.screens.map(({Com, params, key, current}) => {
|
<ScreenContainer style={styles.screenContainer}>
|
||||||
if (current && store.nav.tab.canGoBack) {
|
{screenRenderDesc.screens.map(
|
||||||
return (
|
({Com, params, key, current, previous}) => {
|
||||||
<Screen
|
return (
|
||||||
key={key}
|
<Screen
|
||||||
style={[StyleSheet.absoluteFill]}
|
key={key}
|
||||||
activityState={2}>
|
style={[StyleSheet.absoluteFill]}
|
||||||
<GestureDetector gesture={swipeGesture}>
|
activityState={current ? 2 : previous ? 1 : 0}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[s.flex1, styles.screen, swipeViewAnimatedStyle]}>
|
style={
|
||||||
<FontAwesomeIcon
|
current ? [styles.screenMask, swipeOpacity] : undefined
|
||||||
icon="arrow-left"
|
}
|
||||||
size={30}
|
/>
|
||||||
style={[styles.swipeGestureIcon]}
|
<Animated.View
|
||||||
/>
|
style={[
|
||||||
|
s.flex1,
|
||||||
|
styles.screen,
|
||||||
|
current ? swipeTransform : undefined,
|
||||||
|
]}>
|
||||||
<Com params={params} visible={true} />
|
<Com params={params} visible={true} />
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</GestureDetector>
|
</Screen>
|
||||||
</Screen>
|
)
|
||||||
)
|
},
|
||||||
} else {
|
)}
|
||||||
return (
|
</ScreenContainer>
|
||||||
<Screen
|
</GestureDetector>
|
||||||
key={key}
|
|
||||||
style={[StyleSheet.absoluteFill, styles.screen]}
|
|
||||||
activityState={current ? 2 : 0}>
|
|
||||||
<Com params={params} visible={current} />
|
|
||||||
</Screen>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</ScreenContainer>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
<View style={styles.bottomBar}>
|
<View style={styles.bottomBar}>
|
||||||
<Btn
|
<Btn
|
||||||
|
@ -274,6 +279,7 @@ function constructScreenRenderDesc(nav: NavigationModel): {
|
||||||
]
|
]
|
||||||
const parsedTabScreens = tabScreens.map(screen => {
|
const parsedTabScreens = tabScreens.map(screen => {
|
||||||
const isCurrent = nav.isCurrentScreen(tab.id, screen.index)
|
const isCurrent = nav.isCurrentScreen(tab.id, screen.index)
|
||||||
|
const isPrevious = nav.isCurrentScreen(tab.id, screen.index + 1)
|
||||||
const matchRes = match(screen.url)
|
const matchRes = match(screen.url)
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
icon = matchRes.icon
|
icon = matchRes.icon
|
||||||
|
@ -281,6 +287,7 @@ function constructScreenRenderDesc(nav: NavigationModel): {
|
||||||
return Object.assign(matchRes, {
|
return Object.assign(matchRes, {
|
||||||
key: `t${tab.id}-s${screen.index}`,
|
key: `t${tab.id}-s${screen.index}`,
|
||||||
current: isCurrent,
|
current: isCurrent,
|
||||||
|
previous: isPrevious,
|
||||||
}) as ScreenRenderDesc
|
}) as ScreenRenderDesc
|
||||||
})
|
})
|
||||||
screens = screens.concat(parsedTabScreens)
|
screens = screens.concat(parsedTabScreens)
|
||||||
|
@ -304,10 +311,14 @@ const styles = StyleSheet.create({
|
||||||
screen: {
|
screen: {
|
||||||
backgroundColor: colors.gray1,
|
backgroundColor: colors.gray1,
|
||||||
},
|
},
|
||||||
swipeGestureIcon: {
|
screenMask: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: -75,
|
top: 0,
|
||||||
top: '50%',
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
backgroundColor: '#000',
|
||||||
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
topBar: {
|
topBar: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
Loading…
Reference in New Issue