···4646import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
4747import {
4848 Provider as SessionProvider,
4949- SessionAccount,
4949+ type SessionAccount,
5050 useSession,
5151 useSessionApi,
5252} from '#/state/session'
5353import {readLastActiveAccount} from '#/state/session/util'
5454import {Provider as ShellStateProvider} from '#/state/shell'
5555import {Provider as ComposerProvider} from '#/state/shell/composer'
5656-import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar'
5756import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
5857import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
5958import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
···219218 <StarterPackProvider>
220219 <SafeAreaProvider
221220 initialMetrics={initialWindowMetrics}>
222222- <LightStatusBarProvider>
223223- <InnerApp />
224224- </LightStatusBarProvider>
221221+ <InnerApp />
225222 </SafeAreaProvider>
226223 </StarterPackProvider>
227224 </BottomSheetProvider>
+2-5
src/App.web.tsx
···3535import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
3636import {
3737 Provider as SessionProvider,
3838- SessionAccount,
3838+ type SessionAccount,
3939 useSession,
4040 useSessionApi,
4141} from '#/state/session'
4242import {readLastActiveAccount} from '#/state/session/util'
4343import {Provider as ShellStateProvider} from '#/state/shell'
4444import {Provider as ComposerProvider} from '#/state/shell/composer'
4545-import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar'
4645import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
4746import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
4847import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
···193192 <LightboxStateProvider>
194193 <PortalProvider>
195194 <StarterPackProvider>
196196- <LightStatusBarProvider>
197197- <InnerApp />
198198- </LightStatusBarProvider>
195195+ <InnerApp />
199196 </StarterPackProvider>
200197 </PortalProvider>
201198 </LightboxStateProvider>
-21
src/alf/util/navigationBar.ts
···11-import * as NavigationBar from 'expo-navigation-bar'
22-import * as SystemUI from 'expo-system-ui'
33-44-import {isAndroid} from '#/platform/detection'
55-import {Theme} from '../types'
66-77-export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) {
88- if (isAndroid) {
99- if (themeType === 'theme') {
1010- NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
1111- NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor)
1212- NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark')
1313- SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
1414- } else {
1515- NavigationBar.setBackgroundColorAsync('black')
1616- NavigationBar.setBorderColorAsync('black')
1717- NavigationBar.setButtonStyleAsync('light')
1818- SystemUI.setBackgroundColorAsync('black')
1919- }
2020- }
2121-}
+14
src/alf/util/systemUI.ts
···11+import * as SystemUI from 'expo-system-ui'
22+33+import {isAndroid} from '#/platform/detection'
44+import {Theme} from '../types'
55+66+export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
77+ if (isAndroid) {
88+ if (themeType === 'theme') {
99+ SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
1010+ } else {
1111+ SystemUI.setBackgroundColorAsync('black')
1212+ }
1313+ }
1414+}
···11import {useCallback} from 'react'
22+import {SystemBars} from 'react-native-edge-to-edge'
2333-import {useDialogStateControlContext} from '#/state/dialogs'
44+import {isIOS} from '#/platform/detection'
4556/**
67 * If we're calling a system API like the image picker that opens a sheet
78 * wrap it in this function to make sure the status bar is the correct color.
89 */
910export function useSheetWrapper() {
1010- const {setFullyExpandedCount} = useDialogStateControlContext()
1111- return useCallback(
1212- async <T>(promise: Promise<T>): Promise<T> => {
1313- setFullyExpandedCount(c => c + 1)
1111+ return useCallback(async <T>(promise: Promise<T>): Promise<T> => {
1212+ if (isIOS) {
1313+ const entry = SystemBars.pushStackEntry({
1414+ style: {
1515+ statusBar: 'light',
1616+ },
1717+ })
1418 const res = await promise
1515- setFullyExpandedCount(c => c - 1)
1919+ SystemBars.popStackEntry(entry)
1620 return res
1717- },
1818- [setFullyExpandedCount],
1919- )
2121+ } else {
2222+ return await promise
2323+ }
2424+ }, [])
2025}
+1-4
src/lib/hooks/useEnableKeyboardController.tsx
···2626 children: React.ReactNode
2727}) {
2828 return (
2929- <KeyboardProvider
3030- enabled={false}
3131- // I don't think this is necessary, but Chesterton's fence and all that -sfn
3232- statusBarTranslucent={true}>
2929+ <KeyboardProvider enabled={false}>
3330 <KeyboardControllerProviderInner>
3431 {children}
3532 </KeyboardControllerProviderInner>
+1-1
src/screens/Login/index.tsx
···88import {logEvent} from '#/lib/statsig/statsig'
99import {logger} from '#/logger'
1010import {useServiceQuery} from '#/state/queries/service'
1111-import {SessionAccount, useSession} from '#/state/session'
1111+import {type SessionAccount, useSession} from '#/state/session'
1212import {useLoggedOutView} from '#/state/shell/logged-out'
1313import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout'
1414import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm'
+3-2
src/screens/Messages/components/MessageInput.tsx
···2424 useMessageDraft,
2525 useSaveMessageDraft,
2626} from '#/state/messages/message-drafts'
2727-import {EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web'
2727+import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web'
2828import * as Toast from '#/view/com/util/Toast'
2929-import {atoms as a, useTheme} from '#/alf'
2929+import {android, atoms as a, useTheme} from '#/alf'
3030import {useSharedInputStyles} from '#/components/forms/TextField'
3131import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
3232import {useExtractEmbedFromFacets} from './MessageInputEmbed'
···174174 a.text_md,
175175 a.px_sm,
176176 t.atoms.text,
177177+ android({paddingTop: 0}),
177178 {paddingBottom: isIOS ? 5 : 0},
178179 animatedStyle,
179180 ]}
+2-2
src/screens/SignupQueued.tsx
···11import React from 'react'
22import {Modal, ScrollView, View} from 'react-native'
33+import {SystemBars} from 'react-native-edge-to-edge'
34import {useSafeAreaInsets} from 'react-native-safe-area-context'
44-import {StatusBar} from 'expo-status-bar'
55import {msg, plural, Trans} from '@lingui/macro'
66import {useLingui} from '@lingui/react'
77···106106 animationType={native('slide')}
107107 presentationStyle="formSheet"
108108 style={[web(a.util_screen_outer)]}>
109109- {isIOS && <StatusBar style="light" />}
109109+ {isIOS && <SystemBars style={{statusBar: 'light'}} />}
110110 <ScrollView
111111 style={[a.flex_1, t.atoms.bg]}
112112 contentContainerStyle={{borderWidth: 0}}
+2-2
src/screens/Takendown.tsx
···11import {useMemo, useState} from 'react'
22import {Modal, View} from 'react-native'
33+import {SystemBars} from 'react-native-edge-to-edge'
34import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
45import {useSafeAreaInsets} from 'react-native-safe-area-context'
55-import {StatusBar} from 'expo-status-bar'
66import {ComAtprotoAdminDefs, ComAtprotoModerationDefs} from '@atproto/api'
77import {msg, Trans} from '@lingui/macro'
88import {useLingui} from '@lingui/react'
···126126 animationType={native('slide')}
127127 presentationStyle="formSheet"
128128 style={[web(a.util_screen_outer)]}>
129129- {isIOS && <StatusBar style="light" />}
129129+ {isIOS && <SystemBars style={{statusBar: 'light'}} />}
130130 <KeyboardAwareScrollView style={[a.flex_1, t.atoms.bg]} centerContent>
131131 <View
132132 style={[
+5-3
src/screens/VideoFeed/index.tsx
···88 ViewabilityConfig,
99 ViewToken,
1010} from 'react-native'
1111+import {SystemBars} from 'react-native-edge-to-edge'
1112import {
1213 Gesture,
1314 GestureDetector,
···7778import {UserAvatar} from '#/view/com/util/UserAvatar'
7879import {Header} from '#/screens/VideoFeed/components/Header'
7980import {atoms as a, ios, platform, ThemeProvider, useTheme} from '#/alf'
8080-import {setNavigationBar} from '#/alf/util/navigationBar'
8181+import {setSystemUITheme} from '#/alf/util/systemUI'
8182import {Button, ButtonIcon, ButtonText} from '#/components/Button'
8283import {Divider} from '#/components/Divider'
8384import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow'
···126127 useFocusEffect(
127128 useCallback(() => {
128129 setMinShellMode(true)
129129- setNavigationBar('lightbox', t)
130130+ setSystemUITheme('lightbox', t)
130131 return () => {
131132 setMinShellMode(false)
132132- setNavigationBar('theme', t)
133133+ setSystemUITheme('theme', t)
133134 }
134135 }, [setMinShellMode, t]),
135136 )
···140141 return (
141142 <ThemeProvider theme="dark">
142143 <Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
144144+ <SystemBars style={{statusBar: 'light', navigationBar: 'light'}} />
143145 <View
144146 style={[
145147 a.absolute,
···1464146414651465 // Android etc
14661466 if (!isIOS) {
14671467- // if Android <35 or web, bottom is 0 anyway. if >=35, this is needed to account
14681468- // for the edge-to-edge nav bar
14671467+ // need to account for the edge-to-edge nav bar
14691468 return bottom * -1
14701469 }
14711470
+21-35
src/view/com/lightbox/ImageViewing/index.tsx
···99// https://github.com/jobtoday/react-native-image-viewing
10101111import React, {useCallback, useEffect, useMemo, useState} from 'react'
1212-import {
1313- LayoutAnimation,
1414- PixelRatio,
1515- Platform,
1616- StyleSheet,
1717- View,
1818-} from 'react-native'
1212+import {LayoutAnimation, PixelRatio, StyleSheet, View} from 'react-native'
1313+import {SystemBars} from 'react-native-edge-to-edge'
1914import {Gesture} from 'react-native-gesture-handler'
2015import PagerView from 'react-native-pager-view'
2116import Animated, {
2222- AnimatedRef,
1717+ type AnimatedRef,
2318 cancelAnimation,
2419 interpolate,
2520 measure,
2621 runOnJS,
2727- SharedValue,
2222+ type SharedValue,
2823 useAnimatedReaction,
2924 useAnimatedRef,
3025 useAnimatedStyle,
···3227 useSharedValue,
3328 withDecay,
3429 withSpring,
3535- WithSpringConfig,
3030+ type WithSpringConfig,
3631} from 'react-native-reanimated'
3732import {
3838- Edge,
3933 SafeAreaView,
4034 useSafeAreaFrame,
4135 useSafeAreaInsets,
4236} from 'react-native-safe-area-context'
4337import * as ScreenOrientation from 'expo-screen-orientation'
4444-import {StatusBar} from 'expo-status-bar'
4538import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
4639import {Trans} from '@lingui/macro'
47404848-import {Dimensions} from '#/lib/media/types'
4141+import {type Dimensions} from '#/lib/media/types'
4942import {colors, s} from '#/lib/styles'
5043import {isIOS} from '#/platform/detection'
5151-import {Lightbox} from '#/state/lightbox'
4444+import {type Lightbox} from '#/state/lightbox'
5245import {Button} from '#/view/com/util/forms/Button'
5346import {Text} from '#/view/com/util/text/Text'
5447import {ScrollView} from '#/view/com/util/Views'
5555-import {ios, useTheme} from '#/alf'
5656-import {setNavigationBar} from '#/alf/util/navigationBar'
4848+import {useTheme} from '#/alf'
4949+import {setSystemUITheme} from '#/alf/util/systemUI'
5750import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
5858-import {ImageSource, Transform} from './@types'
5151+import {type ImageSource, type Transform} from './@types'
5952import ImageDefaultHeader from './components/ImageDefaultHeader'
6053import ImageItem from './components/ImageItem/ImageItem'
6154···63566457const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP
6558const PIXEL_RATIO = PixelRatio.get()
6666-const EDGES =
6767- Platform.OS === 'android' && Platform.Version < 35
6868- ? (['top', 'bottom', 'left', 'right'] satisfies Edge[])
6969- : ([] satisfies Edge[]) // iOS or Android 15+ bleeds into safe area
70597160const SLOW_SPRING: WithSpringConfig = {
7261 mass: isIOS ? 1.25 : 0.75,
···167156168157 return (
169158 // Keep it always mounted to avoid flicker on the first frame.
170170- <SafeAreaView
159159+ <View
171160 style={[styles.screen, !activeLightbox && styles.screenHidden]}
172172- edges={EDGES}
173161 aria-modal
174162 accessibilityViewIsModal
175163 aria-hidden={!activeLightbox}>
···197185 />
198186 )}
199187 </Animated.View>
200200- </SafeAreaView>
188188+ </View>
201189 )
202190}
203191···325313 },
326314 )
327315328328- // style nav bar on android
316316+ // style system ui on android
329317 const t = useTheme()
330318 useEffect(() => {
331331- setNavigationBar('lightbox', t)
319319+ setSystemUITheme('lightbox', t)
332320 return () => {
333333- setNavigationBar('theme', t)
321321+ setSystemUITheme('theme', t)
334322 }
335323 }, [t])
336324337325 return (
338326 <Animated.View style={[styles.container, containerStyle]}>
339339- <StatusBar
340340- animated
341341- style="light"
342342- hideTransitionAnimation="slide"
343343- backgroundColor="black"
344344- // hiding causes layout shifts on android,
345345- // so avoid until we add edge-to-edge mode
346346- hidden={ios(isScaled || !showControls)}
327327+ <SystemBars
328328+ style={{statusBar: 'light', navigationBar: 'light'}}
329329+ hidden={{
330330+ statusBar: isScaled || !showControls,
331331+ navigationBar: false,
332332+ }}
347333 />
348334 <Animated.View
349335 style={[styles.backdrop, backdropStyle]}
+2-2
src/view/com/modals/CreateOrEditList.tsx
···88 TouchableOpacity,
99 View,
1010} from 'react-native'
1111-import {Image as RNImage} from 'react-native-image-crop-picker'
1111+import {type Image as RNImage} from 'react-native-image-crop-picker'
1212import {LinearGradient} from 'expo-linear-gradient'
1313-import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
1313+import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
1414import {msg, Trans} from '@lingui/macro'
1515import {useLingui} from '@lingui/react'
1616
+11-14
src/view/shell/index.tsx
···11import {useCallback, useEffect, useState} from 'react'
22import {BackHandler, useWindowDimensions, View} from 'react-native'
33import {Drawer} from 'react-native-drawer-layout'
44+import {SystemBars} from 'react-native-edge-to-edge'
45import {Gesture} from 'react-native-gesture-handler'
56import {useSafeAreaInsets} from 'react-native-safe-area-context'
66-import {StatusBar} from 'expo-status-bar'
77import {useNavigation, useNavigationState} from '@react-navigation/native'
8899import {useDedupe} from '#/lib/hooks/useDedupe'
···1919 useIsDrawerSwipeDisabled,
2020 useSetDrawerOpen,
2121} from '#/state/shell'
2222-import {useLightStatusBar} from '#/state/shell/light-status-bar'
2322import {useCloseAnyActiveElement} from '#/state/util'
2423import {Lightbox} from '#/view/com/lightbox/Lightbox'
2524import {ModalsContainer} from '#/view/com/modals/Modal'
2625import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
2726import {atoms as a, select, useTheme} from '#/alf'
2828-import {setNavigationBar} from '#/alf/util/navigationBar'
2727+import {setSystemUITheme} from '#/alf/util/systemUI'
2928import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
3029import {SigninDialog} from '#/components/dialogs/Signin'
3130import {Outlet as PortalOutlet} from '#/components/Portal'
···161160162161export const Shell: React.FC = function ShellImpl() {
163162 const {fullyExpandedCount} = useDialogStateControlContext()
164164- const lightStatusBar = useLightStatusBar()
165163 const t = useTheme()
166164 useIntentHandler()
167165168166 useEffect(() => {
169169- setNavigationBar('theme', t)
167167+ setSystemUITheme('theme', t)
170168 }, [t])
171169172170 return (
173171 <View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}>
174174- <StatusBar
175175- style={
176176- t.name !== 'light' ||
177177- (isIOS && fullyExpandedCount > 0) ||
178178- lightStatusBar
179179- ? 'light'
180180- : 'dark'
181181- }
182182- animated
172172+ <SystemBars
173173+ style={{
174174+ statusBar:
175175+ t.name !== 'light' || (isIOS && fullyExpandedCount > 0)
176176+ ? 'light'
177177+ : 'dark',
178178+ navigationBar: t.name !== 'light' ? 'light' : 'dark',
179179+ }}
183180 />
184181 <RoutesContainer>
185182 <ShellInner />