From 56cf890debeb9872f791ccb992a5587f2c05fd9e Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 13 Mar 2023 16:01:43 -0500 Subject: 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 --- src/Navigation.tsx | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 src/Navigation.tsx (limited to 'src/Navigation.tsx') 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() + +const HomeTab = createNativeStackNavigator() +const SearchTab = createNativeStackNavigator() +const NotificationsTab = + createNativeStackNavigator() +const Flat = createNativeStackNavigator() +const Tab = createBottomTabNavigator() + +/** + * These "common screens" are reused across stacks. + */ +function commonScreens(Stack: typeof HomeTab) { + return ( + <> + + + + + + + + + + + + ) +} + +/** + * 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 => , []) + return ( + + + + + + ) +} + +function HomeTabNavigator() { + const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark) + return ( + + + {commonScreens(HomeTab)} + + ) +} + +function SearchTabNavigator() { + const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark) + return ( + + + {commonScreens(SearchTab as typeof HomeTab)} + + ) +} + +function NotificationsTabNavigator() { + const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark) + return ( + + + {commonScreens(NotificationsTab as typeof HomeTab)} + + ) +} + +/** + * The FlatNavigator is used by Web to represent the routes + * in a single ("flat") stack. + */ +function FlatNavigator() { + return ( + + + + + {commonScreens(Flat as typeof HomeTab)} + + ) +} + +/** + * 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 ( + + {children} + + ) +} + +/** + * These helpers can be used from outside of the RoutesContainer + * (eg in the state models). + */ + +function navigate( + 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, +} -- cgit 1.4.1