Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 233 lines 7.0 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 {useCloseAllActiveElements} from '#/state/util' 13import {Lightbox} from '#/view/com/lightbox/Lightbox' 14import {ModalsContainer} from '#/view/com/modals/Modal' 15import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 16import {Deactivated} from '#/screens/Deactivated' 17import {Takendown} from '#/screens/Takendown' 18import {atoms as a, select, useBreakpoints, useTheme} from '#/alf' 19import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog' 20import {EmailDialog} from '#/components/dialogs/EmailDialog' 21import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' 22import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 23import {NuxDialogs} from '#/components/dialogs/nuxs' 24import {SigninDialog} from '#/components/dialogs/Signin' 25import {useWelcomeModal} from '#/components/hooks/useWelcomeModal' 26import {GlobalReportDialog} from '#/components/moderation/ReportDialog' 27import { 28 Outlet as PolicyUpdateOverlayPortalOutlet, 29 usePolicyUpdateContext, 30} from '#/components/PolicyUpdateOverlay' 31import {Outlet as PortalOutlet} from '#/components/Portal' 32import {WelcomeModal} from '#/components/WelcomeModal' 33import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' 34import {PassiveAnalytics} from '#/analytics/PassiveAnalytics' 35import {FlatNavigator, RoutesContainer} from '#/Navigation' 36import {Composer} from './Composer' 37import {DrawerContent} from './Drawer' 38 39function ShellInner() { 40 const t = useTheme() 41 const navigator = useNavigation<NavigationProp>() 42 const closeAllActiveElements = useCloseAllActiveElements() 43 const {state: policyUpdateState} = usePolicyUpdateContext() 44 const welcomeModalControl = useWelcomeModal() 45 46 useIntentHandler() 47 48 useLayoutEffect(() => { 49 const rootElement = document.documentElement 50 rootElement.className = `html` 51 rootElement.style.setProperty( 52 'background', 53 `${t.atoms.bg.backgroundColor}`, 54 'important', 55 ) 56 }, [t.atoms.bg.backgroundColor, t.name]) 57 58 useLayoutEffect(() => { 59 const color = t.palette.primary_500 60 61 const styleId = 'prosemirror-mention-color' 62 let style = document.getElementById(styleId) as HTMLStyleElement | null 63 64 if (!style) { 65 style = document.createElement('style') 66 style.id = styleId 67 document.head.appendChild(style) 68 } 69 70 style.innerHTML = ` 71 .ProseMirror .mention { 72 color: ${color} !important; 73 } 74 .ProseMirror a, 75 .ProseMirror .autolink { 76 color: ${color} !important; 77 } 78 ` 79 }, [t.palette.primary_500]) 80 81 useEffect(() => { 82 const unsubscribe = navigator.addListener('state', () => { 83 closeAllActiveElements() 84 }) 85 return unsubscribe 86 }, [navigator, closeAllActiveElements]) 87 88 const drawerLayout = useCallback( 89 ({children}: {children: React.ReactNode}) => ( 90 <DrawerLayout>{children}</DrawerLayout> 91 ), 92 [], 93 ) 94 return ( 95 <> 96 <ErrorBoundary> 97 <FlatNavigator layout={drawerLayout} /> 98 </ErrorBoundary> 99 <Composer winHeight={0} /> 100 <ModalsContainer /> 101 <MutedWordsDialog /> 102 <SigninDialog /> 103 <EmailDialog /> 104 <AgeAssuranceRedirectDialog /> 105 <LinkWarningDialog /> 106 <Lightbox /> 107 <NuxDialogs /> 108 <GlobalReportDialog /> 109 110 {welcomeModalControl.isOpen && ( 111 <WelcomeModal control={welcomeModalControl} /> 112 )} 113 114 {/* Until policy update has been completed by the user, don't render anything that is portaled */} 115 {policyUpdateState.completed && ( 116 <> 117 <PortalOutlet /> 118 </> 119 )} 120 121 <PolicyUpdateOverlayPortalOutlet /> 122 </> 123 ) 124} 125 126function DrawerLayout({children}: {children: React.ReactNode}) { 127 const t = useTheme() 128 const isDrawerOpen = useIsDrawerOpen() 129 const setDrawerOpen = useSetDrawerOpen() 130 const {gtTablet} = useBreakpoints() 131 const {_} = useLingui() 132 const showDrawer = !gtTablet && isDrawerOpen 133 const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) 134 135 useLayoutEffect(() => { 136 if (showDrawer !== showDrawerDelayedExit) { 137 if (showDrawer) { 138 setShowDrawerDelayedExit(true) 139 } else { 140 const timeout = setTimeout(() => { 141 setShowDrawerDelayedExit(false) 142 }, 160) 143 return () => clearTimeout(timeout) 144 } 145 } 146 }, [showDrawer, showDrawerDelayedExit]) 147 148 return ( 149 <> 150 {children} 151 {showDrawerDelayedExit && ( 152 <> 153 <RemoveScrollBar /> 154 <TouchableWithoutFeedback 155 onPress={ev => { 156 // Only close if press happens outside of the drawer 157 if (ev.target === ev.currentTarget) { 158 setDrawerOpen(false) 159 } 160 }} 161 accessibilityLabel={_(msg`Close drawer menu`)} 162 accessibilityHint=""> 163 <View 164 style={[ 165 styles.drawerMask, 166 { 167 backgroundColor: showDrawer 168 ? select(t.name, { 169 light: 'rgba(0, 57, 117, 0.1)', 170 dark: 'rgba(1, 82, 168, 0.1)', 171 dim: 'rgba(10, 13, 16, 0.8)', 172 }) 173 : 'transparent', 174 }, 175 a.transition_color, 176 ]}> 177 <View 178 style={[ 179 styles.drawerContainer, 180 showDrawer ? a.slide_in_left : a.slide_out_left, 181 ]}> 182 <DrawerContent /> 183 </View> 184 </View> 185 </TouchableWithoutFeedback> 186 </> 187 )} 188 </> 189 ) 190} 191 192export function Shell() { 193 const t = useTheme() 194 const {currentAccount} = useSession() 195 return ( 196 <View style={[a.util_screen_outer, t.atoms.bg]}> 197 {currentAccount?.status === 'takendown' ? ( 198 <Takendown /> 199 ) : currentAccount?.status === 'deactivated' ? ( 200 <Deactivated /> 201 ) : ( 202 <> 203 <RoutesContainer> 204 <ShellInner /> 205 </RoutesContainer> 206 207 <RedirectOverlay /> 208 </> 209 )} 210 211 <PassiveAnalytics /> 212 </View> 213 ) 214} 215 216const styles = StyleSheet.create({ 217 drawerMask: { 218 ...a.fixed, 219 width: '100%', 220 height: '100%', 221 top: 0, 222 left: 0, 223 }, 224 drawerContainer: { 225 display: 'flex', 226 ...a.fixed, 227 top: 0, 228 left: 0, 229 height: '100%', 230 width: 330, 231 maxWidth: '80%', 232 }, 233})