Bluesky app fork with some witchin' additions 馃挮
at main 6.5 kB view raw
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}