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/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})