Replace reanimated for tabs selector
parent
efbef238a8
commit
273e6d2973
|
@ -1,6 +1,7 @@
|
||||||
import React, {createRef, useRef, useMemo, useEffect, useState} from 'react'
|
import React, {createRef, useRef, useMemo, useState} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {
|
import {
|
||||||
|
Animated,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Share,
|
Share,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
@ -9,20 +10,13 @@ import {
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import Animated, {
|
|
||||||
interpolate,
|
|
||||||
SharedValue,
|
|
||||||
useSharedValue,
|
|
||||||
useAnimatedStyle,
|
|
||||||
withTiming,
|
|
||||||
runOnJS,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import Swipeable from 'react-native-gesture-handler/Swipeable'
|
import Swipeable from 'react-native-gesture-handler/Swipeable'
|
||||||
import {useStores} from '../../../state'
|
import {useStores} from '../../../state'
|
||||||
import {s, colors} from '../../lib/styles'
|
import {s, colors} from '../../lib/styles'
|
||||||
import {toShareUrl} from '../../../lib/strings'
|
import {toShareUrl} from '../../../lib/strings'
|
||||||
import {match} from '../../routes'
|
import {match} from '../../routes'
|
||||||
|
import {useAnimatedValue} from '../../lib/useAnimatedValue'
|
||||||
|
|
||||||
const TAB_HEIGHT = 42
|
const TAB_HEIGHT = 42
|
||||||
|
|
||||||
|
@ -33,7 +27,7 @@ export const TabsSelector = observer(
|
||||||
onClose,
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
active: boolean
|
active: boolean
|
||||||
tabMenuInterp: SharedValue<number>
|
tabMenuInterp: Animated.Value
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
@ -41,7 +35,7 @@ export const TabsSelector = observer(
|
||||||
const [closingTabIndex, setClosingTabIndex] = useState<number | undefined>(
|
const [closingTabIndex, setClosingTabIndex] = useState<number | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
)
|
)
|
||||||
const closeInterp = useSharedValue<number>(0)
|
const closeInterp = useAnimatedValue(0)
|
||||||
const tabsRef = useRef<ScrollView>(null)
|
const tabsRef = useRef<ScrollView>(null)
|
||||||
const tabRefs = useMemo(
|
const tabRefs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -51,11 +45,16 @@ export const TabsSelector = observer(
|
||||||
[store.nav.tabs.length],
|
[store.nav.tabs.length],
|
||||||
)
|
)
|
||||||
|
|
||||||
const wrapperAnimStyle = useAnimatedStyle(() => ({
|
const wrapperAnimStyle = {
|
||||||
transform: [
|
transform: [
|
||||||
{translateY: interpolate(tabMenuInterp.value, [0, 1.0], [320, 0])},
|
{
|
||||||
|
translateY: tabMenuInterp.interpolate({
|
||||||
|
inputRange: [0, 1.0],
|
||||||
|
outputRange: [320, 0],
|
||||||
|
}),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}))
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
// =
|
// =
|
||||||
|
@ -76,13 +75,16 @@ export const TabsSelector = observer(
|
||||||
store.nav.setActiveTab(tabIndex)
|
store.nav.setActiveTab(tabIndex)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
const doCloseTab = (index: number) => store.nav.closeTab(index)
|
|
||||||
const onCloseTab = (tabIndex: number) => {
|
const onCloseTab = (tabIndex: number) => {
|
||||||
setClosingTabIndex(tabIndex)
|
setClosingTabIndex(tabIndex)
|
||||||
closeInterp.value = 0
|
closeInterp.setValue(0)
|
||||||
closeInterp.value = withTiming(1, {duration: 300}, () => {
|
Animated.timing(closeInterp, {
|
||||||
runOnJS(setClosingTabIndex)(undefined)
|
toValue: 1,
|
||||||
runOnJS(doCloseTab)(tabIndex)
|
duration: 300,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}).start(() => {
|
||||||
|
setClosingTabIndex(undefined)
|
||||||
|
store.nav.closeTab(tabIndex)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onLayout = () => {
|
const onLayout = () => {
|
||||||
|
@ -107,11 +109,11 @@ export const TabsSelector = observer(
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTabIndex = store.nav.tabIndex
|
const currentTabIndex = store.nav.tabIndex
|
||||||
const closingTabAnimStyle = useAnimatedStyle(() => ({
|
const closingTabAnimStyle = {
|
||||||
height: TAB_HEIGHT * (1 - closeInterp.value),
|
height: Animated.multiply(TAB_HEIGHT, Animated.subtract(1, closeInterp)),
|
||||||
opacity: 1 - closeInterp.value,
|
opacity: Animated.subtract(1, closeInterp),
|
||||||
marginBottom: 4 * (1 - closeInterp.value),
|
marginBottom: Animated.multiply(4, Animated.subtract(1, closeInterp)),
|
||||||
}))
|
}
|
||||||
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
return <View />
|
return <View />
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, {useState, useEffect, useRef} from 'react'
|
import React, {useState, useEffect, useRef} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {
|
import {
|
||||||
Animated as RNAnimated,
|
Animated,
|
||||||
|
Easing,
|
||||||
FlatList,
|
FlatList,
|
||||||
GestureResponderEvent,
|
GestureResponderEvent,
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
|
@ -16,13 +17,6 @@ import {
|
||||||
import {ScreenContainer, Screen} from 'react-native-screens'
|
import {ScreenContainer, Screen} from 'react-native-screens'
|
||||||
import LinearGradient from 'react-native-linear-gradient'
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {
|
|
||||||
Easing,
|
|
||||||
useSharedValue,
|
|
||||||
useAnimatedStyle,
|
|
||||||
withTiming,
|
|
||||||
runOnJS,
|
|
||||||
} 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'
|
||||||
import {TABS_ENABLED} from '../../../build-flags'
|
import {TABS_ENABLED} from '../../../build-flags'
|
||||||
|
@ -119,8 +113,8 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
const scrollElRef = useRef<FlatList | undefined>()
|
const scrollElRef = useRef<FlatList | undefined>()
|
||||||
const winDim = useWindowDimensions()
|
const winDim = useWindowDimensions()
|
||||||
const swipeGestureInterp = useAnimatedValue(0)
|
const swipeGestureInterp = useAnimatedValue(0)
|
||||||
const tabMenuInterp = useSharedValue<number>(0)
|
const tabMenuInterp = useAnimatedValue(0)
|
||||||
const newTabInterp = useSharedValue<number>(0)
|
const newTabInterp = useAnimatedValue(0)
|
||||||
const [isRunningNewTabAnim, setIsRunningNewTabAnim] = useState(false)
|
const [isRunningNewTabAnim, setIsRunningNewTabAnim] = useState(false)
|
||||||
const colorScheme = useColorScheme()
|
const colorScheme = useColorScheme()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
|
@ -139,22 +133,29 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
|
|
||||||
// tab selector animation
|
// tab selector animation
|
||||||
// =
|
// =
|
||||||
const closeTabsSelector = () => setTabsSelectorActive(false)
|
|
||||||
const toggleTabsMenu = (active: boolean) => {
|
const toggleTabsMenu = (active: boolean) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
// will trigger the animation below
|
// will trigger the animation below
|
||||||
setTabsSelectorActive(true)
|
setTabsSelectorActive(true)
|
||||||
} else {
|
} else {
|
||||||
tabMenuInterp.value = withTiming(0, {duration: 100}, () => {
|
Animated.timing(tabMenuInterp, {
|
||||||
|
toValue: 0,
|
||||||
|
duration: 100,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}).start(() => {
|
||||||
// hide once the animation has finished
|
// hide once the animation has finished
|
||||||
runOnJS(closeTabsSelector)()
|
setTabsSelectorActive(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isTabsSelectorActive) {
|
if (isTabsSelectorActive) {
|
||||||
// trigger the animation once the tabs selector is rendering
|
// trigger the animation once the tabs selector is rendering
|
||||||
tabMenuInterp.value = withTiming(1, {duration: 100})
|
Animated.timing(tabMenuInterp, {
|
||||||
|
toValue: 1,
|
||||||
|
duration: 100,
|
||||||
|
useNativeDriver: false,
|
||||||
|
}).start()
|
||||||
}
|
}
|
||||||
}, [isTabsSelectorActive])
|
}, [isTabsSelectorActive])
|
||||||
|
|
||||||
|
@ -171,13 +172,16 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
store.nav.tab.setIsNewTab(false)
|
store.nav.tab.setIsNewTab(false)
|
||||||
setIsRunningNewTabAnim(false)
|
setIsRunningNewTabAnim(false)
|
||||||
}
|
}
|
||||||
newTabInterp.value = withTiming(
|
Animated.timing(newTabInterp, {
|
||||||
1,
|
toValue: 1,
|
||||||
{duration: 250, easing: Easing.out(Easing.exp)},
|
duration: 250,
|
||||||
() => runOnJS(reset)(),
|
easing: Easing.out(Easing.exp),
|
||||||
)
|
useNativeDriver: false,
|
||||||
|
}).start(() => {
|
||||||
|
reset()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
newTabInterp.value = 0
|
newTabInterp.setValue(0)
|
||||||
}
|
}
|
||||||
}, [isRunningNewTabAnim])
|
}, [isRunningNewTabAnim])
|
||||||
|
|
||||||
|
@ -190,7 +194,7 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
}
|
}
|
||||||
const swipeTransform = {
|
const swipeTransform = {
|
||||||
transform: [
|
transform: [
|
||||||
{translateX: RNAnimated.multiply(swipeGestureInterp, winDim.width * -1)},
|
{translateX: Animated.multiply(swipeGestureInterp, winDim.width * -1)},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
const swipeOpacity = {
|
const swipeOpacity = {
|
||||||
|
@ -199,12 +203,12 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
outputRange: [0, 0.6, 0],
|
outputRange: [0, 0.6, 0],
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
const tabMenuTransform = useAnimatedStyle(() => ({
|
const tabMenuTransform = {
|
||||||
transform: [{translateY: tabMenuInterp.value * -320}],
|
transform: [{translateY: Animated.multiply(tabMenuInterp.value, -320)}],
|
||||||
}))
|
}
|
||||||
const newTabTransform = useAnimatedStyle(() => ({
|
const newTabTransform = {
|
||||||
transform: [{scale: newTabInterp.value}],
|
transform: [{scale: newTabInterp}],
|
||||||
}))
|
}
|
||||||
|
|
||||||
if (!store.session.hasSession) {
|
if (!store.session.hasSession) {
|
||||||
return (
|
return (
|
||||||
|
@ -250,12 +254,12 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
key={key}
|
key={key}
|
||||||
style={[StyleSheet.absoluteFill]}
|
style={[StyleSheet.absoluteFill]}
|
||||||
activityState={current ? 2 : previous ? 1 : 0}>
|
activityState={current ? 2 : previous ? 1 : 0}>
|
||||||
<RNAnimated.View
|
<Animated.View
|
||||||
style={
|
style={
|
||||||
current ? [styles.screenMask, swipeOpacity] : undefined
|
current ? [styles.screenMask, swipeOpacity] : undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<RNAnimated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
s.flex1,
|
s.flex1,
|
||||||
styles.screen,
|
styles.screen,
|
||||||
|
@ -273,7 +277,7 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
visible={current}
|
visible={current}
|
||||||
scrollElRef={current ? scrollElRef : undefined}
|
scrollElRef={current ? scrollElRef : undefined}
|
||||||
/>
|
/>
|
||||||
</RNAnimated.View>
|
</Animated.View>
|
||||||
</Screen>
|
</Screen>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue