Add new tab animation
parent
2b98714548
commit
b2160ae159
|
@ -16,6 +16,7 @@ export class NavigationTabModel {
|
||||||
id = genTabId()
|
id = genTabId()
|
||||||
history: HistoryItem[] = [{url: '/', ts: Date.now()}]
|
history: HistoryItem[] = [{url: '/', ts: Date.now()}]
|
||||||
index = 0
|
index = 0
|
||||||
|
isNewTab = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this, {
|
makeAutoObservable(this, {
|
||||||
|
@ -112,6 +113,10 @@ export class NavigationTabModel {
|
||||||
this.current.title = title
|
this.current.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsNewTab(v: boolean) {
|
||||||
|
this.isNewTab = v
|
||||||
|
}
|
||||||
|
|
||||||
// persistence
|
// persistence
|
||||||
// =
|
// =
|
||||||
|
|
||||||
|
@ -208,6 +213,7 @@ export class NavigationModel {
|
||||||
newTab(url: string, title?: string) {
|
newTab(url: string, title?: string) {
|
||||||
const tab = new NavigationTabModel()
|
const tab = new NavigationTabModel()
|
||||||
tab.navigate(url, title)
|
tab.navigate(url, title)
|
||||||
|
tab.isNewTab = true
|
||||||
this.tabs.push(tab)
|
this.tabs.push(tab)
|
||||||
this.tabIndex = this.tabs.length - 1
|
this.tabIndex = this.tabs.length - 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import React, {useEffect} from 'react'
|
import React, {useEffect} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {
|
import {StyleSheet, View} from 'react-native'
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
TouchableWithoutFeedback,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import Animated, {
|
import Animated, {
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
|
@ -14,13 +8,8 @@ import Animated, {
|
||||||
interpolate,
|
interpolate,
|
||||||
Easing,
|
Easing,
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
import {IconProp} from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {HomeIcon, UserGroupIcon, BellIcon} from '../../lib/icons'
|
|
||||||
import {ComposePost} from '../../com/composer/ComposePost'
|
import {ComposePost} from '../../com/composer/ComposePost'
|
||||||
import {useStores} from '../../../state'
|
|
||||||
import {ComposerOpts} from '../../../state/models/shell-ui'
|
import {ComposerOpts} from '../../../state/models/shell-ui'
|
||||||
import {s, colors} from '../../lib/styles'
|
|
||||||
|
|
||||||
export const Composer = observer(
|
export const Composer = observer(
|
||||||
({
|
({
|
||||||
|
@ -36,7 +25,6 @@ export const Composer = observer(
|
||||||
onPost?: ComposerOpts['onPost']
|
onPost?: ComposerOpts['onPost']
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const store = useStores()
|
|
||||||
const initInterp = useSharedValue<number>(0)
|
const initInterp = useSharedValue<number>(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import LinearGradient from 'react-native-linear-gradient'
|
||||||
import {GestureDetector, Gesture} from 'react-native-gesture-handler'
|
import {GestureDetector, Gesture} from 'react-native-gesture-handler'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
|
Easing,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
withTiming,
|
withTiming,
|
||||||
|
@ -133,6 +134,8 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
const winDim = useWindowDimensions()
|
const winDim = useWindowDimensions()
|
||||||
const swipeGestureInterp = useSharedValue<number>(0)
|
const swipeGestureInterp = useSharedValue<number>(0)
|
||||||
const tabMenuInterp = useSharedValue<number>(0)
|
const tabMenuInterp = useSharedValue<number>(0)
|
||||||
|
const newTabInterp = useSharedValue<number>(0)
|
||||||
|
const [isRunningNewTabAnim, setIsRunningNewTabAnim] = useState(false)
|
||||||
const colorScheme = useColorScheme()
|
const colorScheme = useColorScheme()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const screenRenderDesc = constructScreenRenderDesc(store.nav)
|
const screenRenderDesc = constructScreenRenderDesc(store.nav)
|
||||||
|
@ -149,6 +152,8 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
const onPressNotifications = () => store.nav.navigate('/notifications')
|
const onPressNotifications = () => store.nav.navigate('/notifications')
|
||||||
const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive)
|
const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive)
|
||||||
|
|
||||||
|
// tab selector animation
|
||||||
|
// =
|
||||||
const closeTabsSelector = () => setTabsSelectorActive(false)
|
const closeTabsSelector = () => setTabsSelectorActive(false)
|
||||||
const toggleTabsMenu = (active: boolean) => {
|
const toggleTabsMenu = (active: boolean) => {
|
||||||
if (active) {
|
if (active) {
|
||||||
|
@ -168,6 +173,31 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
}
|
}
|
||||||
}, [isTabsSelectorActive])
|
}, [isTabsSelectorActive])
|
||||||
|
|
||||||
|
// new tab animation
|
||||||
|
// =
|
||||||
|
useEffect(() => {
|
||||||
|
if (screenRenderDesc.hasNewTab && !isRunningNewTabAnim) {
|
||||||
|
setIsRunningNewTabAnim(true)
|
||||||
|
}
|
||||||
|
}, [screenRenderDesc.hasNewTab])
|
||||||
|
useEffect(() => {
|
||||||
|
if (isRunningNewTabAnim) {
|
||||||
|
const reset = () => {
|
||||||
|
store.nav.tab.setIsNewTab(false)
|
||||||
|
setIsRunningNewTabAnim(false)
|
||||||
|
}
|
||||||
|
newTabInterp.value = withTiming(
|
||||||
|
1,
|
||||||
|
{duration: 250, easing: Easing.out(Easing.exp)},
|
||||||
|
() => runOnJS(reset)(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
newTabInterp.value = 0
|
||||||
|
}
|
||||||
|
}, [isRunningNewTabAnim])
|
||||||
|
|
||||||
|
// navigation swipes
|
||||||
|
// =
|
||||||
const goBack = () => store.nav.tab.goBack()
|
const goBack = () => store.nav.tab.goBack()
|
||||||
const swipeGesture = Gesture.Pan()
|
const swipeGesture = Gesture.Pan()
|
||||||
.onUpdate(e => {
|
.onUpdate(e => {
|
||||||
|
@ -201,6 +231,9 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
const tabMenuTransform = useAnimatedStyle(() => ({
|
const tabMenuTransform = useAnimatedStyle(() => ({
|
||||||
transform: [{translateY: tabMenuInterp.value * -320}],
|
transform: [{translateY: tabMenuInterp.value * -320}],
|
||||||
}))
|
}))
|
||||||
|
const newTabTransform = useAnimatedStyle(() => ({
|
||||||
|
transform: [{scale: newTabInterp.value}],
|
||||||
|
}))
|
||||||
|
|
||||||
if (!store.session.isAuthed) {
|
if (!store.session.isAuthed) {
|
||||||
return (
|
return (
|
||||||
|
@ -251,7 +284,11 @@ export const MobileShell: React.FC = observer(() => {
|
||||||
s.flex1,
|
s.flex1,
|
||||||
styles.screen,
|
styles.screen,
|
||||||
current
|
current
|
||||||
? [swipeTransform, tabMenuTransform]
|
? [
|
||||||
|
swipeTransform,
|
||||||
|
tabMenuTransform,
|
||||||
|
isRunningNewTabAnim ? newTabTransform : undefined,
|
||||||
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
]}>
|
]}>
|
||||||
<Com
|
<Com
|
||||||
|
@ -326,11 +363,14 @@ type ScreenRenderDesc = MatchResult & {
|
||||||
key: string
|
key: string
|
||||||
current: boolean
|
current: boolean
|
||||||
previous: boolean
|
previous: boolean
|
||||||
|
isNewTab: boolean
|
||||||
}
|
}
|
||||||
function constructScreenRenderDesc(nav: NavigationModel): {
|
function constructScreenRenderDesc(nav: NavigationModel): {
|
||||||
icon: IconProp
|
icon: IconProp
|
||||||
|
hasNewTab: boolean
|
||||||
screens: ScreenRenderDesc[]
|
screens: ScreenRenderDesc[]
|
||||||
} {
|
} {
|
||||||
|
let hasNewTab = false
|
||||||
let icon: IconProp = 'magnifying-glass'
|
let icon: IconProp = 'magnifying-glass'
|
||||||
let screens: ScreenRenderDesc[] = []
|
let screens: ScreenRenderDesc[] = []
|
||||||
for (const tab of nav.tabs) {
|
for (const tab of nav.tabs) {
|
||||||
|
@ -345,16 +385,19 @@ function constructScreenRenderDesc(nav: NavigationModel): {
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
icon = matchRes.icon
|
icon = matchRes.icon
|
||||||
}
|
}
|
||||||
|
hasNewTab = hasNewTab || tab.isNewTab
|
||||||
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,
|
previous: isPrevious,
|
||||||
|
isNewTab: tab.isNewTab,
|
||||||
}) as ScreenRenderDesc
|
}) as ScreenRenderDesc
|
||||||
})
|
})
|
||||||
screens = screens.concat(parsedTabScreens)
|
screens = screens.concat(parsedTabScreens)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
icon,
|
icon,
|
||||||
|
hasNewTab,
|
||||||
screens,
|
screens,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue