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