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