-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
navigationUtilities.ts
170 lines (140 loc) · 4.91 KB
/
navigationUtilities.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { useState, useEffect, useRef } from "react"
import { BackHandler, Platform } from "react-native"
import {
PartialState,
NavigationState,
NavigationAction,
createNavigationContainerRef,
} from "@react-navigation/native"
import Config from "../config"
import type { PersistNavigationConfig } from "../config/config.base"
import { useIsMounted } from "../utils/useIsMounted"
/* eslint-disable */
export const RootNavigation = {
navigate(_name: string, _params?: any) {},
goBack() {},
resetRoot(_state?: PartialState<NavigationState> | NavigationState) {},
getRootState(): NavigationState {
return {} as any
},
dispatch(_action: NavigationAction) {},
}
/* eslint-enable */
export const navigationRef = createNavigationContainerRef()
/**
* 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(canExit: (routeName: string) => boolean) {
// ignore if iOS ... no back button!
if (Platform.OS === "ios") return
// The reason we're using a ref here is because we need to be able
// to update the canExit function without re-setting up all the listeners
const canExitRef = useRef(canExit)
useEffect(() => {
canExitRef.current = canExit
}, [canExit])
useEffect(() => {
// We'll fire this when the back button is pressed on Android.
const onBackPress = () => {
if (!navigationRef.isReady()) {
return false
}
// grab the current route
const routeName = getActiveRouteName(navigationRef.getRootState())
// are we allowed to exit?
if (canExitRef.current(routeName)) {
// exit and let the system know we've handled the event
BackHandler.exitApp()
return true
}
// we can't exit, so let's turn this into a back action
if (navigationRef.canGoBack()) {
navigationRef.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)
}, [])
}
/**
* This helper function will determine whether we should enable navigation persistence
* based on a config setting and the __DEV__ environment (dev or prod).
*/
function navigationRestoredDefaultState(persistNavigation: PersistNavigationConfig) {
if (persistNavigation === "always") return false
if (persistNavigation === "dev" && __DEV__) return false
if (persistNavigation === "prod" && !__DEV__) return false
// all other cases, disable restoration by returning true
return true
}
/**
* Custom hook for persisting navigation state.
*/
export function useNavigationPersistence(storage: any, persistenceKey: string) {
const [initialNavigationState, setInitialNavigationState] = useState()
const isMounted = useIsMounted()
const initNavState = navigationRestoredDefaultState(Config.persistNavigation)
const [isRestored, setIsRestored] = useState(initNavState)
const routeNameRef = useRef<string | undefined>()
const onNavigationStateChange = (state) => {
const previousRouteName = routeNameRef.current
const currentRouteName = getActiveRouteName(state)
if (previousRouteName !== currentRouteName) {
// track screens.
if (__DEV__) {
console.tron.log(currentRouteName)
}
}
// Save the current route name for later comparison
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 {
if (isMounted()) setIsRestored(true)
}
}
useEffect(() => {
if (!isRestored) restoreState()
}, [isRestored])
return { onNavigationStateChange, restoreState, isRestored, initialNavigationState }
}
/**
* use this to navigate without the navigation
* prop. If you have access to the navigation prop, do not use this.
* More info: https://reactnavigation.org/docs/navigating-without-navigation-prop/
*/
export function navigate(name: any, params?: any) {
if (navigationRef.isReady()) {
navigationRef.navigate(name as never, params as never)
}
}
export function goBack() {
if (navigationRef.isReady() && navigationRef.canGoBack()) {
navigationRef.goBack()
}
}
export function resetRoot(params = { index: 0, routes: [] }) {
if (navigationRef.isReady()) {
navigationRef.resetRoot(params)
}
}