forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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})