summaryrefslogtreecommitdiff
path: root/app/navigators
diff options
context:
space:
mode:
Diffstat (limited to 'app/navigators')
-rw-r--r--app/navigators/index.ts4
-rw-r--r--app/navigators/main-navigator.tsx57
-rw-r--r--app/navigators/navigation-utilities.tsx127
-rw-r--r--app/navigators/root-navigator.tsx59
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"