forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import * as React from 'react'
2import {View} from 'react-native'
3// Based on @react-navigation/native-stack/src/navigators/createNativeStackNavigator.ts
4// MIT License
5// Copyright (c) 2017 React Navigation Contributors
6import {
7 createNavigatorFactory,
8 type EventArg,
9 type NavigatorTypeBagBase,
10 type ParamListBase,
11 type StackActionHelpers,
12 StackActions,
13 type StackNavigationState,
14 StackRouter,
15 type StackRouterOptions,
16 type StaticConfig,
17 type TypedNavigator,
18 useNavigationBuilder,
19} from '@react-navigation/native'
20import {NativeStackView} from '@react-navigation/native-stack'
21import {
22 type NativeStackNavigationEventMap,
23 type NativeStackNavigationOptions,
24 type NativeStackNavigationProp,
25 type NativeStackNavigatorProps,
26} from '@react-navigation/native-stack'
27
28import {PWI_ENABLED} from '#/lib/build-flags'
29import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
30import {isNative, isWeb} from '#/platform/detection'
31import {useSession} from '#/state/session'
32import {useOnboardingState} from '#/state/shell'
33import {
34 useLoggedOutView,
35 useLoggedOutViewControls,
36} from '#/state/shell/logged-out'
37import {LoggedOut} from '#/view/com/auth/LoggedOut'
38import {Deactivated} from '#/screens/Deactivated'
39import {Onboarding} from '#/screens/Onboarding'
40import {SignupQueued} from '#/screens/SignupQueued'
41import {Takendown} from '#/screens/Takendown'
42import {atoms as a, useLayoutBreakpoints} from '#/alf'
43import {PolicyUpdateOverlay} from '#/components/PolicyUpdateOverlay'
44import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
45import {DesktopLeftNav} from './desktop/LeftNav'
46import {DesktopRightNav} from './desktop/RightNav'
47
48type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & {
49 requireAuth?: boolean
50}
51
52function NativeStackNavigator({
53 id,
54 initialRouteName,
55 children,
56 layout,
57 screenListeners,
58 screenOptions,
59 screenLayout,
60 ...rest
61}: NativeStackNavigatorProps) {
62 // --- this is copy and pasted from the original native stack navigator ---
63 const {state, describe, descriptors, navigation, NavigationContent} =
64 useNavigationBuilder<
65 StackNavigationState<ParamListBase>,
66 StackRouterOptions,
67 StackActionHelpers<ParamListBase>,
68 NativeStackNavigationOptionsWithAuth,
69 NativeStackNavigationEventMap
70 >(StackRouter, {
71 id,
72 initialRouteName,
73 children,
74 layout,
75 screenListeners,
76 screenOptions,
77 screenLayout,
78 })
79
80 React.useEffect(
81 () =>
82 // @ts-expect-error: there may not be a tab navigator in parent
83 navigation?.addListener?.('tabPress', (e: any) => {
84 const isFocused = navigation.isFocused()
85
86 // Run the operation in the next frame so we're sure all listeners have been run
87 // This is necessary to know if preventDefault() has been called
88 requestAnimationFrame(() => {
89 if (
90 state.index > 0 &&
91 isFocused &&
92 !(e as EventArg<'tabPress', true>).defaultPrevented
93 ) {
94 // When user taps on already focused tab and we're inside the tab,
95 // reset the stack to replicate native behaviour
96 navigation.dispatch({
97 ...StackActions.popToTop(),
98 target: state.key,
99 })
100 }
101 })
102 }),
103 [navigation, state.index, state.key],
104 )
105
106 // --- our custom logic starts here ---
107 const {hasSession, currentAccount} = useSession()
108 const activeRoute = state.routes[state.index]
109 const activeDescriptor = descriptors[activeRoute.key]
110 const activeRouteRequiresAuth = activeDescriptor.options.requireAuth ?? false
111 const onboardingState = useOnboardingState()
112 const {showLoggedOut} = useLoggedOutView()
113 const {setShowLoggedOut} = useLoggedOutViewControls()
114 const {isMobile} = useWebMediaQueries()
115 const {leftNavMinimal} = useLayoutBreakpoints()
116 if (!hasSession && (!PWI_ENABLED || activeRouteRequiresAuth || isNative)) {
117 return <LoggedOut />
118 }
119 if (hasSession && currentAccount?.signupQueued) {
120 return <SignupQueued />
121 }
122 if (hasSession && currentAccount?.status === 'takendown') {
123 return <Takendown />
124 }
125 if (showLoggedOut) {
126 return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
127 }
128 if (currentAccount?.status === 'deactivated') {
129 return <Deactivated />
130 }
131 if (onboardingState.isActive) {
132 return <Onboarding />
133 }
134 const newDescriptors: typeof descriptors = {}
135 for (let key in descriptors) {
136 const descriptor = descriptors[key]
137 const requireAuth = descriptor.options.requireAuth ?? false
138 newDescriptors[key] = {
139 ...descriptor,
140 render() {
141 if (requireAuth && !hasSession) {
142 return <View />
143 } else {
144 return descriptor.render()
145 }
146 },
147 }
148 }
149
150 // Show the bottom bar if we have a session only on mobile web. If we don't have a session, we want to show it
151 // on both tablet and mobile web so that we see the create account CTA.
152 const showBottomBar = hasSession ? isMobile : leftNavMinimal
153
154 return (
155 <NavigationContent>
156 <View role="main" style={a.flex_1}>
157 <NativeStackView
158 {...rest}
159 state={state}
160 navigation={navigation}
161 descriptors={descriptors}
162 describe={describe}
163 />
164 </View>
165 {isWeb && (
166 <>
167 {showBottomBar ? <BottomBarWeb /> : <DesktopLeftNav />}
168 {!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
169 </>
170 )}
171
172 {/* Only shown after logged in and onboaring etc are complete */}
173 {hasSession && <PolicyUpdateOverlay />}
174 </NavigationContent>
175 )
176}
177
178export function createNativeStackNavigatorWithAuth<
179 const ParamList extends ParamListBase,
180 const NavigatorID extends string | undefined = undefined,
181 const TypeBag extends NavigatorTypeBagBase = {
182 ParamList: ParamList
183 NavigatorID: NavigatorID
184 State: StackNavigationState<ParamList>
185 ScreenOptions: NativeStackNavigationOptionsWithAuth
186 EventMap: NativeStackNavigationEventMap
187 NavigationList: {
188 [RouteName in keyof ParamList]: NativeStackNavigationProp<
189 ParamList,
190 RouteName,
191 NavigatorID
192 >
193 }
194 Navigator: typeof NativeStackNavigator
195 },
196 const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
197>(config?: Config): TypedNavigator<TypeBag, Config> {
198 return createNavigatorFactory(NativeStackNavigator)(config)
199}