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