diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-03-13 16:01:43 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-13 16:01:43 -0500 |
commit | 56cf890debeb9872f791ccb992a5587f2c05fd9e (patch) | |
tree | 929453b41274a712d8b2fce441e98a0cd030d305 /src/Navigation.tsx | |
parent | 503e03d91e1de4bfeabec1eb2d97dcdceb13fcc5 (diff) | |
download | voidsky-56cf890debeb9872f791ccb992a5587f2c05fd9e.tar.zst |
Move to expo and react-navigation (#288)
* WIP - adding expo * WIP - adding expo 2 * Fix tsc * Finish adding expo * Disable the 'require cycle' warning * Tweak plist * Modify some dependency versions to make expo happy * Fix icon fill * Get Web compiling for expo * 1.7 * Switch to react-navigation in expo2 (#287) * WIP Switch to react-navigation * WIP Switch to react-navigation 2 * WIP Switch to react-navigation 3 * Convert all screens to react navigation * Update BottomBar for react navigation * Update mobile menu to be react-native drawer * Fixes to drawer and bottombar * Factor out some helpers * Replace the navigation model with react-navigation * Restructure the shell folder and fix the header positioning * Restore the error boundary * Fix tsc * Implement not-found page * Remove react-native-gesture-handler (no longer used) * Handle notifee card presses * Handle all navigations from the state layer * Fix drawer behaviors * Fix two linking issues * Switch to our react-native-progress fork to fix an svg rendering issue * Get Web working with react-navigation * Refactor routes and navigation for a bit more clarity * Remove dead code * Rework Web shell to left/right nav to make this easier * Fix ViewHeader for desktop web * Hide profileheader back btn on desktop web * Move the compose button to the left nav * Implement reply prompt in threads for desktop web * Composer refactors * Factor out all platform-specific text input behaviors from the composer * Small fix * Update the web build to use tiptap for the composer * Tune up the mention autocomplete dropdown * Simplify the default avatar and banner * Fixes to link cards in web composer * Fix dropdowns on web * Tweak load latest on desktop * Add web beta message and feedback link * Fix up links in desktop web
Diffstat (limited to 'src/Navigation.tsx')
-rw-r--r-- | src/Navigation.tsx | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx new file mode 100644 index 000000000..22d8d8b21 --- /dev/null +++ b/src/Navigation.tsx @@ -0,0 +1,287 @@ +import * as React from 'react' +import {StyleSheet} from 'react-native' +import { + NavigationContainer, + createNavigationContainerRef, + StackActions, +} from '@react-navigation/native' +import {createNativeStackNavigator} from '@react-navigation/native-stack' +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs' +import { + HomeTabNavigatorParams, + SearchTabNavigatorParams, + NotificationsTabNavigatorParams, + FlatNavigatorParams, + AllNavigatorParams, +} from 'lib/routes/types' +import {BottomBar} from './view/shell/BottomBar' +import {buildStateObject} from 'lib/routes/helpers' +import {State, RouteParams} from 'lib/routes/types' +import {colors} from 'lib/styles' +import {isNative} from 'platform/detection' +import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {router} from './routes' + +import {HomeScreen} from './view/screens/Home' +import {SearchScreen} from './view/screens/Search' +import {NotificationsScreen} from './view/screens/Notifications' +import {NotFoundScreen} from './view/screens/NotFound' +import {SettingsScreen} from './view/screens/Settings' +import {ProfileScreen} from './view/screens/Profile' +import {ProfileFollowersScreen} from './view/screens/ProfileFollowers' +import {ProfileFollowsScreen} from './view/screens/ProfileFollows' +import {PostThreadScreen} from './view/screens/PostThread' +import {PostUpvotedByScreen} from './view/screens/PostUpvotedBy' +import {PostRepostedByScreen} from './view/screens/PostRepostedBy' +import {DebugScreen} from './view/screens/Debug' +import {LogScreen} from './view/screens/Log' + +const navigationRef = createNavigationContainerRef<AllNavigatorParams>() + +const HomeTab = createNativeStackNavigator<HomeTabNavigatorParams>() +const SearchTab = createNativeStackNavigator<SearchTabNavigatorParams>() +const NotificationsTab = + createNativeStackNavigator<NotificationsTabNavigatorParams>() +const Flat = createNativeStackNavigator<FlatNavigatorParams>() +const Tab = createBottomTabNavigator() + +/** + * These "common screens" are reused across stacks. + */ +function commonScreens(Stack: typeof HomeTab) { + return ( + <> + <Stack.Screen name="NotFound" component={NotFoundScreen} /> + <Stack.Screen name="Settings" component={SettingsScreen} /> + <Stack.Screen name="Profile" component={ProfileScreen} /> + <Stack.Screen + name="ProfileFollowers" + component={ProfileFollowersScreen} + /> + <Stack.Screen name="ProfileFollows" component={ProfileFollowsScreen} /> + <Stack.Screen name="PostThread" component={PostThreadScreen} /> + <Stack.Screen name="PostUpvotedBy" component={PostUpvotedByScreen} /> + <Stack.Screen name="PostRepostedBy" component={PostRepostedByScreen} /> + <Stack.Screen name="Debug" component={DebugScreen} /> + <Stack.Screen name="Log" component={LogScreen} /> + </> + ) +} + +/** + * The TabsNavigator is used by native mobile to represent the routes + * in 3 distinct tab-stacks with a different root screen on each. + */ +function TabsNavigator() { + const tabBar = React.useCallback(props => <BottomBar {...props} />, []) + return ( + <Tab.Navigator + initialRouteName="HomeTab" + backBehavior="initialRoute" + screenOptions={{headerShown: false}} + tabBar={tabBar}> + <Tab.Screen name="HomeTab" component={HomeTabNavigator} /> + <Tab.Screen + name="NotificationsTab" + component={NotificationsTabNavigator} + /> + <Tab.Screen name="SearchTab" component={SearchTabNavigator} /> + </Tab.Navigator> + ) +} + +function HomeTabNavigator() { + const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark) + return ( + <HomeTab.Navigator + screenOptions={{ + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + animationDuration: 250, + contentStyle, + }}> + <HomeTab.Screen name="Home" component={HomeScreen} /> + {commonScreens(HomeTab)} + </HomeTab.Navigator> + ) +} + +function SearchTabNavigator() { + const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark) + return ( + <SearchTab.Navigator + screenOptions={{ + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + animationDuration: 250, + contentStyle, + }}> + <SearchTab.Screen name="Search" component={SearchScreen} /> + {commonScreens(SearchTab as typeof HomeTab)} + </SearchTab.Navigator> + ) +} + +function NotificationsTabNavigator() { + const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark) + return ( + <NotificationsTab.Navigator + screenOptions={{ + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + animationDuration: 250, + contentStyle, + }}> + <NotificationsTab.Screen + name="Notifications" + component={NotificationsScreen} + /> + {commonScreens(NotificationsTab as typeof HomeTab)} + </NotificationsTab.Navigator> + ) +} + +/** + * The FlatNavigator is used by Web to represent the routes + * in a single ("flat") stack. + */ +function FlatNavigator() { + return ( + <Flat.Navigator + screenOptions={{ + gestureEnabled: true, + fullScreenGestureEnabled: true, + headerShown: false, + animationDuration: 250, + contentStyle: {backgroundColor: 'white'}, + }}> + <Flat.Screen name="Home" component={HomeScreen} /> + <Flat.Screen name="Search" component={SearchScreen} /> + <Flat.Screen name="Notifications" component={NotificationsScreen} /> + {commonScreens(Flat as typeof HomeTab)} + </Flat.Navigator> + ) +} + +/** + * The RoutesContainer should wrap all components which need access + * to the navigation context. + */ + +const LINKING = { + prefixes: ['bsky://', 'https://bsky.app'], + + getPathFromState(state: State) { + // find the current node in the navigation tree + let node = state.routes[state.index || 0] + while (node.state?.routes && typeof node.state?.index === 'number') { + node = node.state?.routes[node.state?.index] + } + + // build the path + const route = router.matchName(node.name) + if (typeof route === 'undefined') { + return '/' // default to home + } + return route.build((node.params || {}) as RouteParams) + }, + + getStateFromPath(path: string) { + const [name, params] = router.matchPath(path) + if (isNative) { + if (name === 'Search') { + return buildStateObject('SearchTab', 'Search', params) + } + if (name === 'Notifications') { + return buildStateObject('NotificationsTab', 'Notifications', params) + } + return buildStateObject('HomeTab', name, params) + } else { + return buildStateObject('Flat', name, params) + } + }, +} + +function RoutesContainer({children}: React.PropsWithChildren<{}>) { + return ( + <NavigationContainer ref={navigationRef} linking={LINKING}> + {children} + </NavigationContainer> + ) +} + +/** + * These helpers can be used from outside of the RoutesContainer + * (eg in the state models). + */ + +function navigate<K extends keyof AllNavigatorParams>( + name: K, + params?: AllNavigatorParams[K], +) { + if (navigationRef.isReady()) { + // @ts-ignore I dont know what would make typescript happy but I have a life -prf + navigationRef.navigate(name, params) + } +} + +function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') { + if (navigationRef.isReady()) { + navigate(tabName) + navigationRef.dispatch(StackActions.popToTop()) + } +} + +function handleLink(url: string) { + let path + if (url.startsWith('/')) { + path = url + } else if (url.startsWith('http')) { + try { + path = new URL(url).pathname + } catch (e) { + console.error('Invalid url', url, e) + return + } + } else { + console.error('Invalid url', url) + return + } + + const [name, params] = router.matchPath(path) + if (isNative) { + if (name === 'Search') { + resetToTab('SearchTab') + } else if (name === 'Notifications') { + resetToTab('NotificationsTab') + } else { + resetToTab('HomeTab') + // @ts-ignore matchPath doesnt give us type-checked output -prf + navigate(name, params) + } + } else { + // @ts-ignore matchPath doesnt give us type-checked output -prf + navigate(name, params) + } +} + +const styles = StyleSheet.create({ + bgDark: { + backgroundColor: colors.black, + }, + bgLight: { + backgroundColor: colors.gray1, + }, +}) + +export { + navigate, + resetToTab, + handleLink, + TabsNavigator, + FlatNavigator, + RoutesContainer, +} |