-
Notifications
You must be signed in to change notification settings - Fork 591
/
Copy pathnavigate.ts
175 lines (144 loc) · 5.55 KB
/
navigate.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
171
172
173
174
175
import { EventEmitter } from "events"
import { StackActions, TabActions } from "@react-navigation/native"
import { tabsTracks } from "app/Navigation/AuthenticatedRoutes/Tabs"
import { internal_navigationRef } from "app/Navigation/Navigation"
import { modules } from "app/Navigation/utils/modules"
import { BottomTabType } from "app/Scenes/BottomTabs/BottomTabType"
import { GlobalStore } from "app/store/GlobalStore"
import { getValidTargetURL } from "app/system/navigation/utils/getValidTargetURL"
import { matchRoute } from "app/system/navigation/utils/matchRoute"
import { postEventToProviders } from "app/utils/track/providers"
import { visualize } from "app/utils/visualizer"
import { InteractionManager, Linking, Platform } from "react-native"
export interface GoBackProps {
previousScreen?: string
}
export interface NavigateOptions {
modal?: boolean
passProps?: {
backProps?: GoBackProps
[key: string]: any
}
replaceActiveModal?: boolean
replaceActiveScreen?: boolean
// Only when onlyShowInTabName specified
popToRootTabView?: boolean
ignoreDebounce?: boolean
showInTabName?: BottomTabType
}
let lastInvocation = { url: "", timestamp: 0 }
// Method used to quit early if the user pressed too many times in a row
function shouldQuit(options: NavigateOptions, targetURL: string): boolean {
// Debounce double taps
const ignoreDebounce = options.ignoreDebounce ?? false
if (
lastInvocation.url === targetURL &&
Date.now() - lastInvocation.timestamp < 1000 &&
!ignoreDebounce
) {
return true
}
lastInvocation = { url: targetURL, timestamp: Date.now() }
return false
}
export async function navigate(url: string, options: NavigateOptions = {}) {
const targetURL = await getValidTargetURL(url)
visualize("NAV", targetURL, { targetURL, options }, "DTShowNavigationVisualiser")
const result = matchRoute(targetURL)
if (result.type === "external_url") {
Linking.openURL(result.url)
return
}
const module = modules[result.module]
if (shouldQuit(options, targetURL)) {
return
}
if (!internal_navigationRef.current || !internal_navigationRef.current?.isReady()) {
if (__DEV__) {
throw new Error(
`You are attempting to navigate before the navigation is ready.{\n}
Make sure that the App NavigationContainer isReady is ready`
)
}
return
}
const props = { ...result.params, ...options.passProps }
if (options.replaceActiveModal || options.replaceActiveScreen) {
internal_navigationRef.current.dispatch(StackActions.replace(result.module, props))
} else {
if (module.options?.onlyShowInTabName) {
switchTab(module.options?.onlyShowInTabName, props)
if (!module.options?.isRootViewForTabName) {
// We wait for a frame to allow the tab to be switched before we navigate
// This allows us to also override the back button behavior in the tab
requestAnimationFrame(() => {
internal_navigationRef.current?.dispatch(StackActions.push(result.module, props))
})
}
} else {
internal_navigationRef.current?.dispatch(StackActions.push(result.module, props))
}
}
}
export const navigationEvents = new EventEmitter()
export function switchTab(tab: BottomTabType, props?: object) {
// root tabs are only mounted once so cannot be tracked
// like other screens manually track screen views here
// home handles this on its own since it is default tab
if (tab !== "home") {
postEventToProviders(tabsTracks.tabScreenView(tab))
}
if (props) {
GlobalStore.actions.bottomTabs.setTabProps({ tab, props })
}
GlobalStore.actions.bottomTabs.setSelectedTab(tab)
internal_navigationRef?.current?.dispatch(TabActions.jumpTo(tab, props))
}
export function dismissModal(after?: () => void) {
// We wait for interaction to finish before dismissing the modal, otherwise,
// we might get a race condition that causes the UI to freeze
InteractionManager.runAfterInteractions(() => {
internal_navigationRef?.current?.dispatch(StackActions.pop())
if (Platform.OS === "android") {
navigationEvents.emit("modalDismissed")
}
after?.()
})
}
export function goBack(backProps?: GoBackProps) {
navigationEvents.emit("goBack", backProps)
if (internal_navigationRef.current?.isReady()) {
internal_navigationRef.current.dispatch(StackActions.pop())
}
}
export function popToRoot() {
internal_navigationRef?.current?.dispatch(StackActions.popToTop())
}
export enum EntityType {
Partner = "partner",
Fair = "fair",
}
export enum SlugType {
ProfileID = "profileID",
FairID = "fairID",
}
export const PartnerNavigationProps = { entity: EntityType.Partner, slugType: SlugType.ProfileID }
export function navigateToPartner(href: string) {
navigate(href, {
passProps: PartnerNavigationProps,
})
}
/**
* Looks up the entity by slug passed in and presents appropriate viewController
* @param component: ignored, kept for compatibility
* @param slug: identifier for the entity to be presented
* @param entity: type of entity we are routing to, this is currently used to determine what loading
* state to show, either 'fair' or 'partner'
* @param slugType: type of slug or id being passed, this determines how the entity is looked up
* in the api, if we have a fairID we can route directly to fair component and load the fair, if
* we have a profileID we must first fetch the profile and find the ownerType which can be a fair
* partner or other.
*/
export function navigateToEntity(slug: string, entity: EntityType, slugType: SlugType) {
navigate(slug, { passProps: { entity, slugType } })
}