PWI: Refactor Shell (#1989)
* Vendor createNativeStackNavigator for further tweaks * Completely disable withAuthRequired * Render LoggedOut for protected routes * Move web shell into the navigator * Simplify the logic * Add login modal * Delete withAuthRequired * Reset app state on session change * Move TS suppression
This commit is contained in:
parent
4b59a21cac
commit
f2d164ec23
29 changed files with 1627 additions and 1665 deletions
150
src/view/shell/createNativeStackNavigatorWithAuth.tsx
Normal file
150
src/view/shell/createNativeStackNavigatorWithAuth.tsx
Normal file
|
@ -0,0 +1,150 @@
|
|||
import * as React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts
|
||||
// MIT License
|
||||
// Copyright (c) 2017 React Navigation Contributors
|
||||
|
||||
import {
|
||||
createNavigatorFactory,
|
||||
EventArg,
|
||||
ParamListBase,
|
||||
StackActionHelpers,
|
||||
StackActions,
|
||||
StackNavigationState,
|
||||
StackRouter,
|
||||
StackRouterOptions,
|
||||
useNavigationBuilder,
|
||||
} from '@react-navigation/native'
|
||||
import type {
|
||||
NativeStackNavigationEventMap,
|
||||
NativeStackNavigationOptions,
|
||||
} from '@react-navigation/native-stack'
|
||||
import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types'
|
||||
import {NativeStackView} from '@react-navigation/native-stack'
|
||||
|
||||
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
|
||||
import {DesktopLeftNav} from './desktop/LeftNav'
|
||||
import {DesktopRightNav} from './desktop/RightNav'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {useOnboardingState} from '#/state/shell'
|
||||
import {
|
||||
useLoggedOutView,
|
||||
useLoggedOutViewControls,
|
||||
} from '#/state/shell/logged-out'
|
||||
import {useSession} from '#/state/session'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {LoggedOut} from '../com/auth/LoggedOut'
|
||||
import {Onboarding} from '../com/auth/Onboarding'
|
||||
|
||||
type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & {
|
||||
requireAuth?: boolean
|
||||
}
|
||||
|
||||
function NativeStackNavigator({
|
||||
id,
|
||||
initialRouteName,
|
||||
children,
|
||||
screenListeners,
|
||||
screenOptions,
|
||||
...rest
|
||||
}: NativeStackNavigatorProps) {
|
||||
// --- this is copy and pasted from the original native stack navigator ---
|
||||
const {state, descriptors, navigation, NavigationContent} =
|
||||
useNavigationBuilder<
|
||||
StackNavigationState<ParamListBase>,
|
||||
StackRouterOptions,
|
||||
StackActionHelpers<ParamListBase>,
|
||||
NativeStackNavigationOptionsWithAuth,
|
||||
NativeStackNavigationEventMap
|
||||
>(StackRouter, {
|
||||
id,
|
||||
initialRouteName,
|
||||
children,
|
||||
screenListeners,
|
||||
screenOptions,
|
||||
})
|
||||
React.useEffect(
|
||||
() =>
|
||||
// @ts-expect-error: there may not be a tab navigator in parent
|
||||
navigation?.addListener?.('tabPress', (e: any) => {
|
||||
const isFocused = navigation.isFocused()
|
||||
|
||||
// Run the operation in the next frame so we're sure all listeners have been run
|
||||
// This is necessary to know if preventDefault() has been called
|
||||
requestAnimationFrame(() => {
|
||||
if (
|
||||
state.index > 0 &&
|
||||
isFocused &&
|
||||
!(e as EventArg<'tabPress', true>).defaultPrevented
|
||||
) {
|
||||
// When user taps on already focused tab and we're inside the tab,
|
||||
// reset the stack to replicate native behaviour
|
||||
navigation.dispatch({
|
||||
...StackActions.popToTop(),
|
||||
target: state.key,
|
||||
})
|
||||
}
|
||||
})
|
||||
}),
|
||||
[navigation, state.index, state.key],
|
||||
)
|
||||
|
||||
// --- our custom logic starts here ---
|
||||
const {hasSession} = useSession()
|
||||
const activeRoute = state.routes[state.index]
|
||||
const activeDescriptor = descriptors[activeRoute.key]
|
||||
const activeRouteRequiresAuth = activeDescriptor.options.requireAuth ?? false
|
||||
const onboardingState = useOnboardingState()
|
||||
const {showLoggedOut} = useLoggedOutView()
|
||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (activeRouteRequiresAuth && !hasSession) {
|
||||
return <LoggedOut />
|
||||
}
|
||||
if (showLoggedOut) {
|
||||
return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
|
||||
}
|
||||
if (onboardingState.isActive) {
|
||||
return <Onboarding />
|
||||
}
|
||||
const newDescriptors: typeof descriptors = {}
|
||||
for (let key in descriptors) {
|
||||
const descriptor = descriptors[key]
|
||||
const requireAuth = descriptor.options.requireAuth ?? false
|
||||
newDescriptors[key] = {
|
||||
...descriptor,
|
||||
render() {
|
||||
if (requireAuth && !hasSession) {
|
||||
return <View />
|
||||
} else {
|
||||
return descriptor.render()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
return (
|
||||
<NavigationContent>
|
||||
<NativeStackView
|
||||
{...rest}
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={newDescriptors}
|
||||
/>
|
||||
{isWeb && isMobile && <BottomBarWeb />}
|
||||
{isWeb && !isMobile && (
|
||||
<>
|
||||
<DesktopLeftNav />
|
||||
<DesktopRightNav />
|
||||
</>
|
||||
)}
|
||||
</NavigationContent>
|
||||
)
|
||||
}
|
||||
|
||||
export const createNativeStackNavigatorWithAuth = createNavigatorFactory<
|
||||
StackNavigationState<ParamListBase>,
|
||||
NativeStackNavigationOptionsWithAuth,
|
||||
NativeStackNavigationEventMap,
|
||||
typeof NativeStackNavigator
|
||||
>(NativeStackNavigator)
|
|
@ -1,7 +1,5 @@
|
|||
import React, {useEffect} from 'react'
|
||||
import {View, StyleSheet, TouchableOpacity} from 'react-native'
|
||||
import {DesktopLeftNav} from './desktop/LeftNav'
|
||||
import {DesktopRightNav} from './desktop/RightNav'
|
||||
import {ErrorBoundary} from '../com/util/ErrorBoundary'
|
||||
import {Lightbox} from '../com/lightbox/Lightbox'
|
||||
import {ModalsContainer} from '../com/modals/Modal'
|
||||
|
@ -11,27 +9,19 @@ import {s, colors} from 'lib/styles'
|
|||
import {RoutesContainer, FlatNavigator} from '../../Navigation'
|
||||
import {DrawerContent} from './Drawer'
|
||||
import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries'
|
||||
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {useAuxClick} from 'lib/hooks/useAuxClick'
|
||||
import {t} from '@lingui/macro'
|
||||
import {
|
||||
useIsDrawerOpen,
|
||||
useSetDrawerOpen,
|
||||
useOnboardingState,
|
||||
} from '#/state/shell'
|
||||
import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
|
||||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {useLoggedOutView} from '#/state/shell/logged-out'
|
||||
|
||||
function ShellInner() {
|
||||
const isDrawerOpen = useIsDrawerOpen()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
const onboardingState = useOnboardingState()
|
||||
const {isDesktop, isMobile} = useWebMediaQueries()
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
const navigator = useNavigation<NavigationProp>()
|
||||
const closeAllActiveElements = useCloseAllActiveElements()
|
||||
const {showLoggedOut} = useLoggedOutView()
|
||||
|
||||
useAuxClick()
|
||||
|
||||
|
@ -42,8 +32,6 @@ function ShellInner() {
|
|||
return unsubscribe
|
||||
}, [navigator, closeAllActiveElements])
|
||||
|
||||
const showBottomBar = isMobile && !onboardingState.isActive
|
||||
const showSideNavs = !isMobile && !onboardingState.isActive && !showLoggedOut
|
||||
return (
|
||||
<View style={[s.hContentRegion, {overflow: 'hidden'}]}>
|
||||
<View style={s.hContentRegion}>
|
||||
|
@ -51,22 +39,9 @@ function ShellInner() {
|
|||
<FlatNavigator />
|
||||
</ErrorBoundary>
|
||||
</View>
|
||||
|
||||
{showSideNavs && (
|
||||
<>
|
||||
<DesktopLeftNav />
|
||||
<DesktopRightNav />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Composer winHeight={0} />
|
||||
|
||||
{showBottomBar && <BottomBarWeb />}
|
||||
|
||||
<ModalsContainer />
|
||||
|
||||
<Lightbox />
|
||||
|
||||
{!isDesktop && isDrawerOpen && (
|
||||
<TouchableOpacity
|
||||
onPress={() => setDrawerOpen(false)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue