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
|
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 }
}
|