diff options
Diffstat (limited to 'app/navigators')
-rw-r--r-- | app/navigators/index.ts | 4 | ||||
-rw-r--r-- | app/navigators/main-navigator.tsx | 57 | ||||
-rw-r--r-- | app/navigators/navigation-utilities.tsx | 127 | ||||
-rw-r--r-- | app/navigators/root-navigator.tsx | 59 |
4 files changed, 247 insertions, 0 deletions
diff --git a/app/navigators/index.ts b/app/navigators/index.ts new file mode 100644 index 0000000..b1b89a2 --- /dev/null +++ b/app/navigators/index.ts @@ -0,0 +1,4 @@ +export * from "./main-navigator" +export * from "./root-navigator" +export * from "./navigation-utilities" +// export other navigators from here diff --git a/app/navigators/main-navigator.tsx b/app/navigators/main-navigator.tsx new file mode 100644 index 0000000..9ad110a --- /dev/null +++ b/app/navigators/main-navigator.tsx @@ -0,0 +1,57 @@ +/** + * This is the navigator you will modify to display the logged-in screens of your app. + * You can use RootNavigator to also display an auth flow or other user flows. + * + * You'll likely spend most of your time in this file. + */ +import React from "react" +import { createStackNavigator } from "@react-navigation/stack" +import { WelcomeScreen, DemoScreen, DemoListScreen } from "../screens" + +/** + * This type allows TypeScript to know what routes are defined in this navigator + * as well as what properties (if any) they might take when navigating to them. + * + * If no params are allowed, pass through `undefined`. Generally speaking, we + * recommend using your MobX-State-Tree store(s) to keep application state + * rather than passing state through navigation params. + * + * For more information, see this documentation: + * https://reactnavigation.org/docs/params/ + * https://reactnavigation.org/docs/typescript#type-checking-the-navigator + */ +export type PrimaryParamList = { + welcome: undefined + demo: undefined + demoList: undefined +} + +// Documentation: https://reactnavigation.org/docs/stack-navigator/ +const Stack = createStackNavigator<PrimaryParamList>() + +export function MainNavigator() { + return ( + <Stack.Navigator + screenOptions={{ + cardStyle: { backgroundColor: "transparent" }, + headerShown: false, + }} + > + <Stack.Screen name="welcome" component={WelcomeScreen} /> + <Stack.Screen name="demo" component={DemoScreen} /> + <Stack.Screen name="demoList" component={DemoListScreen} /> + </Stack.Navigator> + ) +} + +/** + * A list of routes from which we're allowed to leave the app when + * the user presses the back button on Android. + * + * Anything not on this list will be a standard `back` action in + * react-navigation. + * + * `canExit` is used in ./app/app.tsx in the `useBackButtonHandler` hook. + */ +const exitRoutes = ["welcome"] +export const canExit = (routeName: string) => exitRoutes.includes(routeName) diff --git a/app/navigators/navigation-utilities.tsx b/app/navigators/navigation-utilities.tsx new file mode 100644 index 0000000..de1ea05 --- /dev/null +++ b/app/navigators/navigation-utilities.tsx @@ -0,0 +1,127 @@ +import React, { useState, useEffect, useRef } from "react" +import { BackHandler } from "react-native" +import { PartialState, NavigationState, NavigationContainerRef } from "@react-navigation/native" + +export const RootNavigation = { + navigate(name: string) { + name // eslint-disable-line no-unused-expressions + }, + goBack() {}, // eslint-disable-line @typescript-eslint/no-empty-function + resetRoot(state?: PartialState<NavigationState> | NavigationState) {}, // eslint-disable-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function + getRootState(): NavigationState { + return {} as any + }, +} + +export const setRootNavigation = (ref: React.RefObject<NavigationContainerRef>) => { + for (const method in RootNavigation) { + RootNavigation[method] = (...args: any) => { + if (ref.current) { + return ref.current[method](...args) + } + } + } +} + +/** + * Gets the current screen from any navigation state. + */ +export function getActiveRouteName(state: NavigationState | PartialState<NavigationState>) { + const route = state.routes[state.index] + + // Found the active route -- return the name + if (!route.state) return route.name + + // Recursive call to deal with nested routers + return getActiveRouteName(route.state) +} + +/** + * Hook that handles Android back button presses and forwards those on to + * the navigation or allows exiting the app. + */ +export function useBackButtonHandler( + ref: React.RefObject<NavigationContainerRef>, + canExit: (routeName: string) => boolean, +) { + const canExitRef = useRef(canExit) + + useEffect(() => { + canExitRef.current = canExit + }, [canExit]) + + useEffect(() => { + // We'll fire this when the back button is pressed on Android. + const onBackPress = () => { + const navigation = ref.current + + if (navigation == null) { + return false + } + + // grab the current route + const routeName = getActiveRouteName(navigation.getRootState()) + + // are we allowed to exit? + if (canExitRef.current(routeName)) { + // let the system know we've not handled this event + return false + } + + // we can't exit, so let's turn this into a back action + if (navigation.canGoBack()) { + navigation.goBack() + + return true + } + + return false + } + + // Subscribe when we come to life + BackHandler.addEventListener("hardwareBackPress", onBackPress) + + // Unsubscribe when we're done + return () => BackHandler.removeEventListener("hardwareBackPress", onBackPress) + }, [ref]) +} + +/** + * Custom hook for persisting navigation state. + */ +export function useNavigationPersistence(storage: any, persistenceKey: string) { + const [initialNavigationState, setInitialNavigationState] = useState() + const [isRestoringNavigationState, setIsRestoringNavigationState] = useState(true) + + const routeNameRef = useRef() + const onNavigationStateChange = (state) => { + const previousRouteName = routeNameRef.current + const currentRouteName = getActiveRouteName(state) + + if (previousRouteName !== currentRouteName) { + // track screens. + __DEV__ && console.tron.log(currentRouteName) + } + + // Save the current route name for later comparision + routeNameRef.current = currentRouteName + + // Persist state to storage + storage.save(persistenceKey, state) + } + + const restoreState = async () => { + try { + const state = await storage.load(persistenceKey) + if (state) setInitialNavigationState(state) + } finally { + setIsRestoringNavigationState(false) + } + } + + useEffect(() => { + if (isRestoringNavigationState) restoreState() + }, [isRestoringNavigationState]) + + return { onNavigationStateChange, restoreState, initialNavigationState } +} diff --git a/app/navigators/root-navigator.tsx b/app/navigators/root-navigator.tsx new file mode 100644 index 0000000..2c04f29 --- /dev/null +++ b/app/navigators/root-navigator.tsx @@ -0,0 +1,59 @@ +/** + * The root navigator is used to switch between major navigation flows of your app. + * Generally speaking, it will contain an auth flow (registration, login, forgot password) + * and a "main" flow (which is contained in your MainNavigator) which the user + * will use once logged in. + */ +import React from "react" +import { NavigationContainer, NavigationContainerRef } from "@react-navigation/native" +import { createStackNavigator } from "@react-navigation/stack" +import { MainNavigator } from "./main-navigator" +import { color } from "../theme" + +/** + * This type allows TypeScript to know what routes are defined in this navigator + * as well as what properties (if any) they might take when navigating to them. + * + * We recommend using MobX-State-Tree store(s) to handle state rather than navigation params. + * + * For more information, see this documentation: + * https://reactnavigation.org/docs/params/ + * https://reactnavigation.org/docs/typescript#type-checking-the-navigator + */ +export type RootParamList = { + mainStack: undefined +} + +const Stack = createStackNavigator<RootParamList>() + +const RootStack = () => { + return ( + <Stack.Navigator + screenOptions={{ + cardStyle: { backgroundColor: color.palette.deepPurple }, + headerShown: false, + }} + > + <Stack.Screen + name="mainStack" + component={MainNavigator} + options={{ + headerShown: false, + }} + /> + </Stack.Navigator> + ) +} + +export const RootNavigator = React.forwardRef< + NavigationContainerRef, + Partial<React.ComponentProps<typeof NavigationContainer>> +>((props, ref) => { + return ( + <NavigationContainer {...props} ref={ref}> + <RootStack /> + </NavigationContainer> + ) +}) + +RootNavigator.displayName = "RootNavigator" |