import {useCallback, useEffect, useLayoutEffect, useState} from 'react' import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {RemoveScrollBar} from 'react-remove-scroll-bar' import {useIntentHandler} from '#/lib/hooks/useIntentHandler' import {type NavigationProp} from '#/lib/routes/types' import {useSession} from '#/state/session' import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useCloseAllActiveElements} from '#/state/util' import {Lightbox} from '#/view/com/lightbox/Lightbox' import {ModalsContainer} from '#/view/com/modals/Modal' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {LOGO_PATH, LOGO_VIEW_BOX} from '#/view/icons/Logo' import {Deactivated} from '#/screens/Deactivated' import {Takendown} from '#/screens/Takendown' import {atoms as a, select, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceRedirectDialog} from '#/components/ageAssurance/AgeAssuranceRedirectDialog' import {EmailDialog} from '#/components/dialogs/EmailDialog' import {LinkWarningDialog} from '#/components/dialogs/LinkWarning' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {NuxDialogs} from '#/components/dialogs/nuxs' import {SigninDialog} from '#/components/dialogs/Signin' import {useWelcomeModal} from '#/components/hooks/useWelcomeModal' import {GlobalReportDialog} from '#/components/moderation/ReportDialog' import { Outlet as PolicyUpdateOverlayPortalOutlet, usePolicyUpdateContext, } from '#/components/PolicyUpdateOverlay' import {Outlet as PortalOutlet} from '#/components/Portal' import {WelcomeModal} from '#/components/WelcomeModal' import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' import {PassiveAnalytics} from '#/analytics/PassiveAnalytics' import {FlatNavigator, RoutesContainer} from '#/Navigation' import {Composer} from './Composer' import {DrawerContent} from './Drawer' function createFaviconDataUrl(color: string) { const svg = `` return `data:image/svg+xml,${encodeURIComponent(svg)}` } function upsertHeadLink({ rel, href, type, sizes, }: { rel: string href: string type?: string sizes?: string }) { let link = document.head.querySelector(`link[rel="${rel}"]`) if (!link) { link = document.createElement('link') link.rel = rel document.head.appendChild(link) } if (type) link.type = type if (sizes) link.sizes = sizes link.href = href } function ShellInner() { const t = useTheme() const navigator = useNavigation() const closeAllActiveElements = useCloseAllActiveElements() const {state: policyUpdateState} = usePolicyUpdateContext() const welcomeModalControl = useWelcomeModal() useIntentHandler() useLayoutEffect(() => { const rootElement = document.documentElement rootElement.className = `html` rootElement.style.setProperty( 'background', `${t.atoms.bg.backgroundColor}`, 'important', ) }, [t.atoms.bg.backgroundColor, t.name]) useLayoutEffect(() => { const color = t.palette.primary_500 const styleId = 'prosemirror-mention-color' let style = document.getElementById(styleId) as HTMLStyleElement | null if (!style) { style = document.createElement('style') style.id = styleId document.head.appendChild(style) } style.innerHTML = ` .ProseMirror .mention { color: ${color} !important; } .ProseMirror a, .ProseMirror .autolink { color: ${color} !important; } ` }, [t.palette.primary_500]) useLayoutEffect(() => { const faviconHref = createFaviconDataUrl(t.palette.primary_500) upsertHeadLink({ rel: 'icon', href: faviconHref, type: 'image/svg+xml', sizes: 'any', }) upsertHeadLink({ rel: 'shortcut icon', href: faviconHref, type: 'image/svg+xml', }) }, [t.palette.primary_500]) useEffect(() => { const unsubscribe = navigator.addListener('state', () => { closeAllActiveElements() }) return unsubscribe }, [navigator, closeAllActiveElements]) const drawerLayout = useCallback( ({children}: {children: React.ReactNode}) => ( {children} ), [], ) return ( <> {welcomeModalControl.isOpen && ( )} {/* Until policy update has been completed by the user, don't render anything that is portaled */} {policyUpdateState.completed && ( <> )} {/* workaround for a WebKit compositing bug. After a dialog (which uses Portal + fixed elements + CSS animations) closes, WebKit can skip painting subsequent view transitions. A persistent zero-size fixed element keeps the compositing tree from entering this broken state. */}
) } function DrawerLayout({children}: {children: React.ReactNode}) { const t = useTheme() const isDrawerOpen = useIsDrawerOpen() const setDrawerOpen = useSetDrawerOpen() const {gtTablet} = useBreakpoints() const {_} = useLingui() const showDrawer = !gtTablet && isDrawerOpen const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer) useLayoutEffect(() => { if (showDrawer !== showDrawerDelayedExit) { if (showDrawer) { setShowDrawerDelayedExit(true) } else { const timeout = setTimeout(() => { setShowDrawerDelayedExit(false) }, 160) return () => clearTimeout(timeout) } } }, [showDrawer, showDrawerDelayedExit]) return ( <> {children} {showDrawerDelayedExit && ( <> { // Only close if press happens outside of the drawer if (ev.target === ev.currentTarget) { setDrawerOpen(false) } }} accessibilityLabel={_(msg`Close drawer menu`)} accessibilityHint=""> )} ) } export function Shell() { const t = useTheme() const {currentAccount} = useSession() return ( {currentAccount?.status === 'takendown' ? ( ) : currentAccount?.status === 'deactivated' ? ( ) : ( <> )} ) } const styles = StyleSheet.create({ drawerMask: { ...a.fixed, width: '100%', height: '100%', top: 0, left: 0, }, drawerContainer: { display: 'flex', ...a.fixed, top: 0, left: 0, height: '100%', width: 330, maxWidth: '80%', }, })