Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add new tab animation

+51 -14
+6
src/state/models/navigation.ts
··· 16 16 id = genTabId() 17 17 history: HistoryItem[] = [{url: '/', ts: Date.now()}] 18 18 index = 0 19 + isNewTab = false 19 20 20 21 constructor() { 21 22 makeAutoObservable(this, { ··· 112 113 this.current.title = title 113 114 } 114 115 116 + setIsNewTab(v: boolean) { 117 + this.isNewTab = v 118 + } 119 + 115 120 // persistence 116 121 // = 117 122 ··· 208 213 newTab(url: string, title?: string) { 209 214 const tab = new NavigationTabModel() 210 215 tab.navigate(url, title) 216 + tab.isNewTab = true 211 217 this.tabs.push(tab) 212 218 this.tabIndex = this.tabs.length - 1 213 219 }
+1 -13
src/view/shell/mobile/Composer.tsx
··· 1 1 import React, {useEffect} from 'react' 2 2 import {observer} from 'mobx-react-lite' 3 - import { 4 - StyleSheet, 5 - Text, 6 - TouchableOpacity, 7 - TouchableWithoutFeedback, 8 - View, 9 - } from 'react-native' 3 + import {StyleSheet, View} from 'react-native' 10 4 import Animated, { 11 5 useSharedValue, 12 6 useAnimatedStyle, ··· 14 8 interpolate, 15 9 Easing, 16 10 } from 'react-native-reanimated' 17 - import {IconProp} from '@fortawesome/fontawesome-svg-core' 18 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 19 - import {HomeIcon, UserGroupIcon, BellIcon} from '../../lib/icons' 20 11 import {ComposePost} from '../../com/composer/ComposePost' 21 - import {useStores} from '../../../state' 22 12 import {ComposerOpts} from '../../../state/models/shell-ui' 23 - import {s, colors} from '../../lib/styles' 24 13 25 14 export const Composer = observer( 26 15 ({ ··· 36 25 onPost?: ComposerOpts['onPost'] 37 26 onClose: () => void 38 27 }) => { 39 - const store = useStores() 40 28 const initInterp = useSharedValue<number>(0) 41 29 42 30 useEffect(() => {
+44 -1
src/view/shell/mobile/index.tsx
··· 17 17 import {GestureDetector, Gesture} from 'react-native-gesture-handler' 18 18 import {useSafeAreaInsets} from 'react-native-safe-area-context' 19 19 import Animated, { 20 + Easing, 20 21 useSharedValue, 21 22 useAnimatedStyle, 22 23 withTiming, ··· 133 134 const winDim = useWindowDimensions() 134 135 const swipeGestureInterp = useSharedValue<number>(0) 135 136 const tabMenuInterp = useSharedValue<number>(0) 137 + const newTabInterp = useSharedValue<number>(0) 138 + const [isRunningNewTabAnim, setIsRunningNewTabAnim] = useState(false) 136 139 const colorScheme = useColorScheme() 137 140 const safeAreaInsets = useSafeAreaInsets() 138 141 const screenRenderDesc = constructScreenRenderDesc(store.nav) ··· 149 152 const onPressNotifications = () => store.nav.navigate('/notifications') 150 153 const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive) 151 154 155 + // tab selector animation 156 + // = 152 157 const closeTabsSelector = () => setTabsSelectorActive(false) 153 158 const toggleTabsMenu = (active: boolean) => { 154 159 if (active) { ··· 168 173 } 169 174 }, [isTabsSelectorActive]) 170 175 176 + // new tab animation 177 + // = 178 + useEffect(() => { 179 + if (screenRenderDesc.hasNewTab && !isRunningNewTabAnim) { 180 + setIsRunningNewTabAnim(true) 181 + } 182 + }, [screenRenderDesc.hasNewTab]) 183 + useEffect(() => { 184 + if (isRunningNewTabAnim) { 185 + const reset = () => { 186 + store.nav.tab.setIsNewTab(false) 187 + setIsRunningNewTabAnim(false) 188 + } 189 + newTabInterp.value = withTiming( 190 + 1, 191 + {duration: 250, easing: Easing.out(Easing.exp)}, 192 + () => runOnJS(reset)(), 193 + ) 194 + } else { 195 + newTabInterp.value = 0 196 + } 197 + }, [isRunningNewTabAnim]) 198 + 199 + // navigation swipes 200 + // = 171 201 const goBack = () => store.nav.tab.goBack() 172 202 const swipeGesture = Gesture.Pan() 173 203 .onUpdate(e => { ··· 200 230 })) 201 231 const tabMenuTransform = useAnimatedStyle(() => ({ 202 232 transform: [{translateY: tabMenuInterp.value * -320}], 233 + })) 234 + const newTabTransform = useAnimatedStyle(() => ({ 235 + transform: [{scale: newTabInterp.value}], 203 236 })) 204 237 205 238 if (!store.session.isAuthed) { ··· 251 284 s.flex1, 252 285 styles.screen, 253 286 current 254 - ? [swipeTransform, tabMenuTransform] 287 + ? [ 288 + swipeTransform, 289 + tabMenuTransform, 290 + isRunningNewTabAnim ? newTabTransform : undefined, 291 + ] 255 292 : undefined, 256 293 ]}> 257 294 <Com ··· 326 363 key: string 327 364 current: boolean 328 365 previous: boolean 366 + isNewTab: boolean 329 367 } 330 368 function constructScreenRenderDesc(nav: NavigationModel): { 331 369 icon: IconProp 370 + hasNewTab: boolean 332 371 screens: ScreenRenderDesc[] 333 372 } { 373 + let hasNewTab = false 334 374 let icon: IconProp = 'magnifying-glass' 335 375 let screens: ScreenRenderDesc[] = [] 336 376 for (const tab of nav.tabs) { ··· 345 385 if (isCurrent) { 346 386 icon = matchRes.icon 347 387 } 388 + hasNewTab = hasNewTab || tab.isNewTab 348 389 return Object.assign(matchRes, { 349 390 key: `t${tab.id}-s${screen.index}`, 350 391 current: isCurrent, 351 392 previous: isPrevious, 393 + isNewTab: tab.isNewTab, 352 394 }) as ScreenRenderDesc 353 395 }) 354 396 screens = screens.concat(parsedTabScreens) 355 397 } 356 398 return { 357 399 icon, 400 + hasNewTab, 358 401 screens, 359 402 } 360 403 }