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