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