Add new tab animation

zio/stable
Paul Frazee 2022-11-17 15:44:54 -06:00
parent 2b98714548
commit b2160ae159
3 changed files with 51 additions and 14 deletions

View File

@ -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
} }

View File

@ -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(() => {

View File

@ -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,
} }
} }