my fork of the bluesky client
1import 'react-native-url-polyfill/auto'
2import '#/lib/sentry' // must be near top
3import '#/view/icons'
4
5import React, {useEffect, useState} from 'react'
6import {GestureHandlerRootView} from 'react-native-gesture-handler'
7import {RootSiblingParent} from 'react-native-root-siblings'
8import {
9 initialWindowMetrics,
10 SafeAreaProvider,
11} from 'react-native-safe-area-context'
12import * as SplashScreen from 'expo-splash-screen'
13import {msg} from '@lingui/macro'
14import {useLingui} from '@lingui/react'
15
16import {QueryProvider} from '#/lib/react-query'
17import {
18 initialize,
19 Provider as StatsigProvider,
20 tryFetchGates,
21} from '#/lib/statsig/statsig'
22import {s} from '#/lib/styles'
23import {ThemeProvider} from '#/lib/ThemeContext'
24import I18nProvider from '#/locale/i18nProvider'
25import {logger} from '#/logger'
26import {Provider as A11yProvider} from '#/state/a11y'
27import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
28import {Provider as DialogStateProvider} from '#/state/dialogs'
29import {listenSessionDropped} from '#/state/events'
30import {
31 beginResolveGeolocation,
32 ensureGeolocationResolved,
33 Provider as GeolocationProvider,
34} from '#/state/geolocation'
35import {Provider as InvitesStateProvider} from '#/state/invites'
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 {
45 Provider as SessionProvider,
46 SessionAccount,
47 useSession,
48 useSessionApi,
49} from '#/state/session'
50import {readLastActiveAccount} from '#/state/session/util'
51import {Provider as ShellStateProvider} from '#/state/shell'
52import {Provider as ComposerProvider} from '#/state/shell/composer'
53import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
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 {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
60import * as Toast from '#/view/com/util/Toast'
61import {Shell} from '#/view/shell'
62import {ThemeProvider as Alf} from '#/alf'
63import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
64import {NuxDialogs} from '#/components/dialogs/nuxs'
65import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
66import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
67import {Provider as PortalProvider} from '#/components/Portal'
68import {Splash} from '#/Splash'
69import {BottomSheetProvider} from '../modules/bottom-sheet'
70import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
71import {AppProfiler} from './AppProfiler'
72import {KeyboardControllerProvider} from './lib/hooks/useEnableKeyboardController'
73
74SplashScreen.preventAutoHideAsync()
75
76/**
77 * Begin geolocation ASAP
78 */
79beginResolveGeolocation()
80
81function InnerApp() {
82 const [isReady, setIsReady] = React.useState(false)
83 const {currentAccount} = useSession()
84 const {resumeSession} = useSessionApi()
85 const theme = useColorModeTheme()
86 const {_} = useLingui()
87
88 const hasCheckedReferrer = useStarterPackEntry()
89
90 // init
91 useEffect(() => {
92 async function onLaunch(account?: SessionAccount) {
93 try {
94 if (account) {
95 await resumeSession(account)
96 } else {
97 await initialize()
98 await tryFetchGates(undefined, 'prefer-fresh-gates')
99 }
100 } catch (e) {
101 logger.error(`session: resume failed`, {message: e})
102 } finally {
103 setIsReady(true)
104 }
105 }
106 const account = readLastActiveAccount()
107 onLaunch(account)
108 }, [resumeSession])
109
110 useEffect(() => {
111 return listenSessionDropped(() => {
112 Toast.show(
113 _(msg`Sorry! Your session expired. Please log in again.`),
114 'info',
115 )
116 })
117 }, [_])
118
119 return (
120 <Alf theme={theme}>
121 <ThemeProvider theme={theme}>
122 <Splash isReady={isReady && hasCheckedReferrer}>
123 <RootSiblingParent>
124 <VideoVolumeProvider>
125 <React.Fragment
126 // Resets the entire tree below when it changes:
127 key={currentAccount?.did}>
128 <QueryProvider currentDid={currentAccount?.did}>
129 <ComposerProvider>
130 <StatsigProvider>
131 <MessagesProvider>
132 {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
133 <LabelDefsProvider>
134 <ModerationOptsProvider>
135 <LoggedOutViewProvider>
136 <SelectedFeedProvider>
137 <HiddenRepliesProvider>
138 <UnreadNotifsProvider>
139 <BackgroundNotificationPreferencesProvider>
140 <MutedThreadsProvider>
141 <ProgressGuideProvider>
142 <GestureHandlerRootView
143 style={s.h100pct}>
144 <TestCtrls />
145 <Shell />
146 <NuxDialogs />
147 </GestureHandlerRootView>
148 </ProgressGuideProvider>
149 </MutedThreadsProvider>
150 </BackgroundNotificationPreferencesProvider>
151 </UnreadNotifsProvider>
152 </HiddenRepliesProvider>
153 </SelectedFeedProvider>
154 </LoggedOutViewProvider>
155 </ModerationOptsProvider>
156 </LabelDefsProvider>
157 </MessagesProvider>
158 </StatsigProvider>
159 </ComposerProvider>
160 </QueryProvider>
161 </React.Fragment>
162 </VideoVolumeProvider>
163 </RootSiblingParent>
164 </Splash>
165 </ThemeProvider>
166 </Alf>
167 )
168}
169
170function App() {
171 const [isReady, setReady] = useState(false)
172
173 React.useEffect(() => {
174 Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() =>
175 setReady(true),
176 )
177 }, [])
178
179 if (!isReady) {
180 return null
181 }
182
183 /*
184 * NOTE: only nothing here can depend on other data or session state, since
185 * that is set up in the InnerApp component above.
186 */
187 return (
188 <AppProfiler>
189 <GeolocationProvider>
190 <A11yProvider>
191 <KeyboardControllerProvider>
192 <SessionProvider>
193 <PrefsStateProvider>
194 <I18nProvider>
195 <ShellStateProvider>
196 <InvitesStateProvider>
197 <ModalStateProvider>
198 <DialogStateProvider>
199 <LightboxStateProvider>
200 <PortalProvider>
201 <BottomSheetProvider>
202 <StarterPackProvider>
203 <SafeAreaProvider
204 initialMetrics={initialWindowMetrics}>
205 <IntentDialogProvider>
206 <InnerApp />
207 </IntentDialogProvider>
208 </SafeAreaProvider>
209 </StarterPackProvider>
210 </BottomSheetProvider>
211 </PortalProvider>
212 </LightboxStateProvider>
213 </DialogStateProvider>
214 </ModalStateProvider>
215 </InvitesStateProvider>
216 </ShellStateProvider>
217 </I18nProvider>
218 </PrefsStateProvider>
219 </SessionProvider>
220 </KeyboardControllerProvider>
221 </A11yProvider>
222 </GeolocationProvider>
223 </AppProfiler>
224 )
225}
226
227export default App