Bluesky app fork with some witchin' additions 馃挮
at post-text-option 203 lines 6.3 kB view raw
1import {useCallback, useEffect, useLayoutEffect, useState} from 'react' 2import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' 3import {msg} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5import {useNavigation} from '@react-navigation/native' 6import {RemoveScrollBar} from 'react-remove-scroll-bar' 7 8import {useIntentHandler} from '#/lib/hooks/useIntentHandler' 9import {type NavigationProp} from '#/lib/routes/types' 10import {useSession} from '#/state/session' 11import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' 12import {useComposerKeyboardShortcut} from '#/state/shell/composer/useComposerKeyboardShortcut' 13import {useCloseAllActiveElements} from '#/state/util' 14import {Lightbox} from '#/view/com/lightbox/Lightbox' 15import {ModalsContainer} from '#/view/com/modals/Modal' 16import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 17import {Deactivated} from '#/screens/Deactivated' 18import {Takendown} from '#/screens/Takendown' 19import {atoms as a, select, useBreakpoints, useTheme} from '#/alf' 20import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog' 21import {EmailDialog} from '#/components/dialogs/EmailDialog' 22import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' 23import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 24import {NuxDialogs} from '#/components/dialogs/nuxs' 25import {SigninDialog} from '#/components/dialogs/Signin' 26import {useWelcomeModal} from '#/components/hooks/useWelcomeModal' 27import { 28 Outlet as PolicyUpdateOverlayPortalOutlet, 29 usePolicyUpdateContext, 30} from '#/components/PolicyUpdateOverlay' 31import {Outlet as PortalOutlet} from '#/components/Portal' 32import {WelcomeModal} from '#/components/WelcomeModal' 33import {useAgeAssurance} from '#/ageAssurance' 34import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen' 35import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' 36import {FlatNavigator, RoutesContainer} from '#/Navigation' 37import {Composer} from './Composer.web' 38import {DrawerContent} from './Drawer' 39 40function ShellInner() { 41 const navigator = useNavigation<NavigationProp>() 42 const closeAllActiveElements = useCloseAllActiveElements() 43 const {state: policyUpdateState} = usePolicyUpdateContext() 44 const welcomeModalControl = useWelcomeModal() 45 46 useComposerKeyboardShortcut() 47 useIntentHandler() 48 49 useEffect(() => { 50 const unsubscribe = navigator.addListener('state', () => { 51 closeAllActiveElements() 52 }) 53 return unsubscribe 54 }, [navigator, closeAllActiveElements]) 55 56 const drawerLayout = useCallback( 57 ({children}: {children: React.ReactNode}) => ( 58 <DrawerLayout>{children}</DrawerLayout> 59 ), 60 [], 61 ) 62 return ( 63 <> 64 <ErrorBoundary> 65 <FlatNavigator layout={drawerLayout} /> 66 </ErrorBoundary> 67 <Composer winHeight={0} /> 68 <ModalsContainer /> 69 <MutedWordsDialog /> 70 <SigninDialog /> 71 <EmailDialog /> 72 <AgeAssuranceRedirectDialog /> 73 <LinkWarningDialog /> 74 <Lightbox /> 75 <NuxDialogs /> 76 77 {welcomeModalControl.isOpen && ( 78 <WelcomeModal control={welcomeModalControl} /> 79 )} 80 81 {/* Until policy update has been completed by the user, don't render anything that is portaled */} 82 {policyUpdateState.completed && ( 83 <> 84 <PortalOutlet /> 85 </> 86 )} 87 88 <PolicyUpdateOverlayPortalOutlet /> 89 </> 90 ) 91} 92 93function DrawerLayout({children}: {children: React.ReactNode}) { 94 const t = useTheme() 95 const isDrawerOpen = useIsDrawerOpen() 96 const setDrawerOpen = useSetDrawerOpen() 97 const {gtTablet} = useBreakpoints() 98 const {_} = useLingui() 99 const showDrawer = !gtTablet && isDrawerOpen 100 const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) 101 102 useLayoutEffect(() => { 103 if (showDrawer !== showDrawerDelayedExit) { 104 if (showDrawer) { 105 setShowDrawerDelayedExit(true) 106 } else { 107 const timeout = setTimeout(() => { 108 setShowDrawerDelayedExit(false) 109 }, 160) 110 return () => clearTimeout(timeout) 111 } 112 } 113 }, [showDrawer, showDrawerDelayedExit]) 114 115 return ( 116 <> 117 {children} 118 {showDrawerDelayedExit && ( 119 <> 120 <RemoveScrollBar /> 121 <TouchableWithoutFeedback 122 onPress={ev => { 123 // Only close if press happens outside of the drawer 124 if (ev.target === ev.currentTarget) { 125 setDrawerOpen(false) 126 } 127 }} 128 accessibilityLabel={_(msg`Close drawer menu`)} 129 accessibilityHint=""> 130 <View 131 style={[ 132 styles.drawerMask, 133 { 134 backgroundColor: showDrawer 135 ? select(t.name, { 136 light: 'rgba(0, 57, 117, 0.1)', 137 dark: 'rgba(1, 82, 168, 0.1)', 138 dim: 'rgba(10, 13, 16, 0.8)', 139 }) 140 : 'transparent', 141 }, 142 a.transition_color, 143 ]}> 144 <View 145 style={[ 146 styles.drawerContainer, 147 showDrawer ? a.slide_in_left : a.slide_out_left, 148 ]}> 149 <DrawerContent /> 150 </View> 151 </View> 152 </TouchableWithoutFeedback> 153 </> 154 )} 155 </> 156 ) 157} 158 159export function Shell() { 160 const t = useTheme() 161 const aa = useAgeAssurance() 162 const {currentAccount} = useSession() 163 return ( 164 <View style={[a.util_screen_outer, t.atoms.bg]}> 165 {currentAccount?.status === 'takendown' ? ( 166 <Takendown /> 167 ) : currentAccount?.status === 'deactivated' ? ( 168 <Deactivated /> 169 ) : ( 170 <> 171 {aa.state.access === aa.Access.None ? ( 172 <NoAccessScreen /> 173 ) : ( 174 <RoutesContainer> 175 <ShellInner /> 176 </RoutesContainer> 177 )} 178 179 <RedirectOverlay /> 180 </> 181 )} 182 </View> 183 ) 184} 185 186const styles = StyleSheet.create({ 187 drawerMask: { 188 ...a.fixed, 189 width: '100%', 190 height: '100%', 191 top: 0, 192 left: 0, 193 }, 194 drawerContainer: { 195 display: 'flex', 196 ...a.fixed, 197 top: 0, 198 left: 0, 199 height: '100%', 200 width: 330, 201 maxWidth: '80%', 202 }, 203})