forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import '#/logger/sentry/setup'
2import '#/view/icons'
3
4import React, {useEffect, useState} from 'react'
5import {GestureHandlerRootView} from 'react-native-gesture-handler'
6import {
7 initialWindowMetrics,
8 SafeAreaProvider,
9} from 'react-native-safe-area-context'
10import * as ScreenOrientation from 'expo-screen-orientation'
11import * as SplashScreen from 'expo-splash-screen'
12import * as SystemUI from 'expo-system-ui'
13import {msg} from '@lingui/macro'
14import {useLingui} from '@lingui/react'
15import * as Sentry from '@sentry/react-native'
16
17import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController'
18import {Provider as HideBottomBarBorderProvider} from '#/lib/hooks/useHideBottomBarBorder'
19import {QueryProvider} from '#/lib/react-query'
20import {Provider as StatsigProvider, tryFetchGates} from '#/lib/statsig/statsig'
21import {s} from '#/lib/styles'
22import {ThemeProvider} from '#/lib/ThemeContext'
23import I18nProvider from '#/locale/i18nProvider'
24import {logger} from '#/logger'
25import {Provider as A11yProvider} from '#/state/a11y'
26import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
27import {Provider as DialogStateProvider} from '#/state/dialogs'
28import {Provider as EmailVerificationProvider} from '#/state/email-verification'
29import {listenSessionDropped} from '#/state/events'
30import {GlobalGestureEventsProvider} from '#/state/global-gesture-events'
31import {Provider as HomeBadgeProvider} from '#/state/home-badge'
32import {Provider as LightboxStateProvider} from '#/state/lightbox'
33import {MessagesProvider} from '#/state/messages'
34import {Provider as ModalStateProvider} from '#/state/modals'
35import {init as initPersistedState} from '#/state/persisted'
36import {Provider as PrefsStateProvider} from '#/state/preferences'
37import {Provider as LabelDefsProvider} from '#/state/preferences/label-defs'
38import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation-opts'
39import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
40import {Provider as ServiceAccountManager} from '#/state/service-config'
41import {
42 Provider as SessionProvider,
43 type SessionAccount,
44 useSession,
45 useSessionApi,
46} from '#/state/session'
47import {readLastActiveAccount} from '#/state/session/util'
48import {Provider as ShellStateProvider} from '#/state/shell'
49import {Provider as ComposerProvider} from '#/state/shell/composer'
50import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
51import {Provider as OnboardingProvider} from '#/state/shell/onboarding'
52import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
53import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
54import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
55import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
56import {TestCtrls} from '#/view/com/testing/TestCtrls'
57import * as Toast from '#/view/com/util/Toast'
58import {Shell} from '#/view/shell'
59import {ThemeProvider as Alf} from '#/alf'
60import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
61import {Provider as ContextMenuProvider} from '#/components/ContextMenu'
62import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
63import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
64import {Provider as PolicyUpdateOverlayProvider} from '#/components/PolicyUpdateOverlay'
65import {Provider as PortalProvider} from '#/components/Portal'
66import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
67import {ToastOutlet} from '#/components/Toast'
68import {
69 prefetchAgeAssuranceConfig,
70 Provider as AgeAssuranceV2Provider,
71} from '#/ageAssurance'
72import {IS_ANDROID, IS_IOS} from '#/env'
73import {
74 prefetchLiveEvents,
75 Provider as LiveEventsProvider,
76} from '#/features/liveEvents/context'
77import * as Geo from '#/geolocation'
78import {Splash} from '#/Splash'
79import {BottomSheetProvider} from '../modules/bottom-sheet'
80import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
81
82SplashScreen.preventAutoHideAsync()
83if (IS_IOS) {
84 SystemUI.setBackgroundColorAsync('black')
85}
86if (IS_ANDROID) {
87 // iOS is handled by the config plugin -sfn
88 ScreenOrientation.lockAsync(
89 ScreenOrientation.OrientationLock.PORTRAIT_UP,
90 ).catch(error =>
91 logger.debug('Could not lock orientation', {safeMessage: error}),
92 )
93}
94
95/**
96 * Begin geolocation ASAP
97 */
98Geo.resolve()
99prefetchAgeAssuranceConfig()
100prefetchLiveEvents()
101
102function InnerApp() {
103 const [isReady, setIsReady] = React.useState(false)
104 const {currentAccount} = useSession()
105 const {resumeSession} = useSessionApi()
106 const theme = useColorModeTheme()
107 const {_} = useLingui()
108 const hasCheckedReferrer = useStarterPackEntry()
109
110 // init
111 useEffect(() => {
112 async function onLaunch(account?: SessionAccount) {
113 try {
114 if (account) {
115 await resumeSession(account)
116 } else {
117 await tryFetchGates(undefined, 'prefer-fresh-gates')
118 }
119 } catch (e) {
120 logger.error(`session: resume failed`, {message: e})
121 } finally {
122 setIsReady(true)
123 }
124 }
125 const account = readLastActiveAccount()
126 onLaunch(account)
127 }, [resumeSession])
128
129 useEffect(() => {
130 return listenSessionDropped(() => {
131 Toast.show(
132 _(msg`Sorry! Your session expired. Please sign in again.`),
133 'info',
134 )
135 })
136 }, [_])
137
138 return (
139 <Alf theme={theme}>
140 <ThemeProvider theme={theme}>
141 <ContextMenuProvider>
142 <Splash isReady={isReady && hasCheckedReferrer}>
143 <VideoVolumeProvider>
144 <React.Fragment
145 // Resets the entire tree below when it changes:
146 key={currentAccount?.did}>
147 <QueryProvider currentDid={currentAccount?.did}>
148 <PolicyUpdateOverlayProvider>
149 <StatsigProvider>
150 <LiveEventsProvider>
151 <AgeAssuranceV2Provider>
152 <ComposerProvider>
153 <MessagesProvider>
154 {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
155 <LabelDefsProvider>
156 <ModerationOptsProvider>
157 <LoggedOutViewProvider>
158 <SelectedFeedProvider>
159 <HiddenRepliesProvider>
160 <HomeBadgeProvider>
161 <UnreadNotifsProvider>
162 <BackgroundNotificationPreferencesProvider>
163 <MutedThreadsProvider>
164 <ProgressGuideProvider>
165 <ServiceAccountManager>
166 <EmailVerificationProvider>
167 <HideBottomBarBorderProvider>
168 <GestureHandlerRootView
169 style={s.h100pct}>
170 <GlobalGestureEventsProvider>
171 <IntentDialogProvider>
172 <TestCtrls />
173 <Shell />
174 <ToastOutlet />
175 </IntentDialogProvider>
176 </GlobalGestureEventsProvider>
177 </GestureHandlerRootView>
178 </HideBottomBarBorderProvider>
179 </EmailVerificationProvider>
180 </ServiceAccountManager>
181 </ProgressGuideProvider>
182 </MutedThreadsProvider>
183 </BackgroundNotificationPreferencesProvider>
184 </UnreadNotifsProvider>
185 </HomeBadgeProvider>
186 </HiddenRepliesProvider>
187 </SelectedFeedProvider>
188 </LoggedOutViewProvider>
189 </ModerationOptsProvider>
190 </LabelDefsProvider>
191 </MessagesProvider>
192 </ComposerProvider>
193 </AgeAssuranceV2Provider>
194 </LiveEventsProvider>
195 </StatsigProvider>
196 </PolicyUpdateOverlayProvider>
197 </QueryProvider>
198 </React.Fragment>
199 </VideoVolumeProvider>
200 </Splash>
201 </ContextMenuProvider>
202 </ThemeProvider>
203 </Alf>
204 )
205}
206
207function App() {
208 const [isReady, setReady] = useState(false)
209
210 React.useEffect(() => {
211 Promise.all([initPersistedState(), Geo.resolve()]).then(() =>
212 setReady(true),
213 )
214 }, [])
215
216 if (!isReady) {
217 return null
218 }
219
220 /*
221 * NOTE: only nothing here can depend on other data or session state, since
222 * that is set up in the InnerApp component above.
223 */
224 return (
225 <Geo.Provider>
226 <A11yProvider>
227 <KeyboardControllerProvider>
228 <OnboardingProvider>
229 <SessionProvider>
230 <PrefsStateProvider>
231 <I18nProvider>
232 <ShellStateProvider>
233 <ModalStateProvider>
234 <DialogStateProvider>
235 <LightboxStateProvider>
236 <PortalProvider>
237 <BottomSheetProvider>
238 <StarterPackProvider>
239 <SafeAreaProvider
240 initialMetrics={initialWindowMetrics}>
241 <InnerApp />
242 </SafeAreaProvider>
243 </StarterPackProvider>
244 </BottomSheetProvider>
245 </PortalProvider>
246 </LightboxStateProvider>
247 </DialogStateProvider>
248 </ModalStateProvider>
249 </ShellStateProvider>
250 </I18nProvider>
251 </PrefsStateProvider>
252 </SessionProvider>
253 </OnboardingProvider>
254 </KeyboardControllerProvider>
255 </A11yProvider>
256 </Geo.Provider>
257 )
258}
259
260export default Sentry.wrap(App)