Bluesky app fork with some witchin' additions 💫

Edge to edge support (#7497)

authored by Mathieu Acthernoene and committed by GitHub a770f563 6e80b340

+5 -5
app.config.js
··· 1 const pkg = require('./package.json') 2 3 - const DARK_SPLASH_ANDROID_BACKGROUND = '#0f141b' 4 - 5 module.exports = function (config) { 6 /** 7 * App version number. Should be incremented as part of a release cycle. ··· 140 }, 141 androidStatusBar: { 142 barStyle: 'light-content', 143 - backgroundColor: '#00000000', 144 }, 145 // Dark nav bar in light mode is better than light nav bar in dark mode 146 androidNavigationBar: { 147 barStyle: 'light-content', 148 - backgroundColor: DARK_SPLASH_ANDROID_BACKGROUND, 149 }, 150 android: { 151 icon: './assets/app-icons/android_icon_default_light.png', ··· 197 plugins: [ 198 'expo-video', 199 'expo-localization', 200 USE_SENTRY && [ 201 '@sentry/react-native/expo', 202 { ··· 240 './plugins/withAndroidManifestPlugin.js', 241 './plugins/withAndroidManifestFCMIconPlugin.js', 242 './plugins/withAndroidStylesAccentColorPlugin.js', 243 - './plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js', 244 './plugins/withAndroidNoJitpackPlugin.js', 245 './plugins/withNoBundleCompression.js', 246 './plugins/shareExtension/withShareExtensions.js',
··· 1 const pkg = require('./package.json') 2 3 module.exports = function (config) { 4 /** 5 * App version number. Should be incremented as part of a release cycle. ··· 138 }, 139 androidStatusBar: { 140 barStyle: 'light-content', 141 }, 142 // Dark nav bar in light mode is better than light nav bar in dark mode 143 androidNavigationBar: { 144 barStyle: 'light-content', 145 }, 146 android: { 147 icon: './assets/app-icons/android_icon_default_light.png', ··· 193 plugins: [ 194 'expo-video', 195 'expo-localization', 196 + [ 197 + 'react-native-edge-to-edge', 198 + {android: {enforceNavigationBarContrast: false}}, 199 + ], 200 USE_SENTRY && [ 201 '@sentry/react-native/expo', 202 { ··· 240 './plugins/withAndroidManifestPlugin.js', 241 './plugins/withAndroidManifestFCMIconPlugin.js', 242 './plugins/withAndroidStylesAccentColorPlugin.js', 243 + './plugins/withAndroidDayNightThemePlugin.js', 244 './plugins/withAndroidNoJitpackPlugin.js', 245 './plugins/withNoBundleCompression.js', 246 './plugins/shareExtension/withShareExtensions.js',
+2 -3
package.json
··· 141 "expo-linking": "~7.0.5", 142 "expo-localization": "~16.0.1", 143 "expo-media-library": "~17.0.6", 144 - "expo-navigation-bar": "~4.0.9", 145 "expo-notifications": "~0.29.14", 146 "expo-screen-orientation": "~8.0.4", 147 "expo-sharing": "~13.0.1", 148 "expo-splash-screen": "~0.29.22", 149 - "expo-status-bar": "~2.0.1", 150 "expo-system-ui": "~4.0.9", 151 "expo-task-manager": "~12.0.6", 152 "expo-updates": "~0.27.4", ··· 178 "react-native-compressor": "1.10.3", 179 "react-native-date-picker": "^5.0.7", 180 "react-native-drawer-layout": "^4.1.1", 181 "react-native-emoji-popup": "^0.1.2", 182 "react-native-gesture-handler": "2.20.2", 183 "react-native-get-random-values": "~1.11.0", 184 "react-native-image-crop-picker": "^0.41.6", 185 "react-native-ios-context-menu": "^1.15.3", 186 - "react-native-keyboard-controller": "^1.14.5", 187 "react-native-mmkv": "^2.12.2", 188 "react-native-pager-view": "6.5.1", 189 "react-native-picker-select": "^9.3.1",
··· 141 "expo-linking": "~7.0.5", 142 "expo-localization": "~16.0.1", 143 "expo-media-library": "~17.0.6", 144 "expo-notifications": "~0.29.14", 145 "expo-screen-orientation": "~8.0.4", 146 "expo-sharing": "~13.0.1", 147 "expo-splash-screen": "~0.29.22", 148 "expo-system-ui": "~4.0.9", 149 "expo-task-manager": "~12.0.6", 150 "expo-updates": "~0.27.4", ··· 176 "react-native-compressor": "1.10.3", 177 "react-native-date-picker": "^5.0.7", 178 "react-native-drawer-layout": "^4.1.1", 179 + "react-native-edge-to-edge": "^1.6.0", 180 "react-native-emoji-popup": "^0.1.2", 181 "react-native-gesture-handler": "2.20.2", 182 "react-native-get-random-values": "~1.11.0", 183 "react-native-image-crop-picker": "^0.41.6", 184 "react-native-ios-context-menu": "^1.15.3", 185 + "react-native-keyboard-controller": "^1.17.1", 186 "react-native-mmkv": "^2.12.2", 187 "react-native-pager-view": "6.5.1", 188 "react-native-picker-select": "^9.3.1",
+27
plugins/withAndroidDayNightThemePlugin.js
···
··· 1 + // Based on https://github.com/expo/expo/pull/33957 2 + // Could be removed once the app has been updated to Expo 53 3 + const {withAndroidStyles} = require('@expo/config-plugins') 4 + 5 + module.exports = function withAndroidDayNightThemePlugin(appConfig) { 6 + const cleanupList = new Set([ 7 + 'colorPrimary', 8 + 'android:editTextBackground', 9 + 'android:textColor', 10 + 'android:editTextStyle', 11 + ]) 12 + 13 + return withAndroidStyles(appConfig, config => { 14 + config.modResults.resources.style = config.modResults.resources.style 15 + ?.map(style => { 16 + if (style.$.name === 'AppTheme' && style.item != null) { 17 + style.item = style.item.filter(item => !cleanupList.has(item.$.name)) 18 + } 19 + return style 20 + }) 21 + .filter(style => { 22 + return style.$.name !== 'ResetEditText' 23 + }) 24 + 25 + return config 26 + }) 27 + }
-28
plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js
··· 1 - const {withStringsXml, AndroidConfig} = require('@expo/config-plugins') 2 - 3 - module.exports = function withAndroidSplashScreenStatusBarTranslucentPlugin( 4 - appConfig, 5 - ) { 6 - return withStringsXml(appConfig, function (decoratedAppConfig) { 7 - try { 8 - decoratedAppConfig.modResults = AndroidConfig.Strings.setStringItem( 9 - [ 10 - { 11 - _: 'true', 12 - $: { 13 - name: 'expo_splash_screen_status_bar_translucent', 14 - translatable: 'false', 15 - }, 16 - }, 17 - ], 18 - decoratedAppConfig.modResults, 19 - ) 20 - } catch (e) { 21 - console.error( 22 - `withAndroidSplashScreenStatusBarTranslucentPlugin failed`, 23 - e, 24 - ) 25 - } 26 - return decoratedAppConfig 27 - }) 28 - }
···
+1 -1
plugins/withAndroidStylesAccentColorPlugin.js
··· 12 decoratedAppConfig.modResults, 13 { 14 add: true, 15 - parent: AndroidConfig.Styles.getAppThemeLightNoActionBarGroup(), 16 name: 'colorAccent', 17 value: '@color/colorPrimary', 18 },
··· 12 decoratedAppConfig.modResults, 13 { 14 add: true, 15 + parent: AndroidConfig.Styles.getAppThemeGroup(), 16 name: 'colorAccent', 17 value: '@color/colorPrimary', 18 },
+2 -5
src/App.native.tsx
··· 46 import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' 47 import { 48 Provider as SessionProvider, 49 - SessionAccount, 50 useSession, 51 useSessionApi, 52 } from '#/state/session' 53 import {readLastActiveAccount} from '#/state/session/util' 54 import {Provider as ShellStateProvider} from '#/state/shell' 55 import {Provider as ComposerProvider} from '#/state/shell/composer' 56 - import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar' 57 import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 58 import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 59 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' ··· 219 <StarterPackProvider> 220 <SafeAreaProvider 221 initialMetrics={initialWindowMetrics}> 222 - <LightStatusBarProvider> 223 - <InnerApp /> 224 - </LightStatusBarProvider> 225 </SafeAreaProvider> 226 </StarterPackProvider> 227 </BottomSheetProvider>
··· 46 import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' 47 import { 48 Provider as SessionProvider, 49 + type SessionAccount, 50 useSession, 51 useSessionApi, 52 } from '#/state/session' 53 import {readLastActiveAccount} from '#/state/session/util' 54 import {Provider as ShellStateProvider} from '#/state/shell' 55 import {Provider as ComposerProvider} from '#/state/shell/composer' 56 import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 57 import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 58 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' ··· 218 <StarterPackProvider> 219 <SafeAreaProvider 220 initialMetrics={initialWindowMetrics}> 221 + <InnerApp /> 222 </SafeAreaProvider> 223 </StarterPackProvider> 224 </BottomSheetProvider>
+2 -5
src/App.web.tsx
··· 35 import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' 36 import { 37 Provider as SessionProvider, 38 - SessionAccount, 39 useSession, 40 useSessionApi, 41 } from '#/state/session' 42 import {readLastActiveAccount} from '#/state/session/util' 43 import {Provider as ShellStateProvider} from '#/state/shell' 44 import {Provider as ComposerProvider} from '#/state/shell/composer' 45 - import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar' 46 import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 47 import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 48 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' ··· 193 <LightboxStateProvider> 194 <PortalProvider> 195 <StarterPackProvider> 196 - <LightStatusBarProvider> 197 - <InnerApp /> 198 - </LightStatusBarProvider> 199 </StarterPackProvider> 200 </PortalProvider> 201 </LightboxStateProvider>
··· 35 import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' 36 import { 37 Provider as SessionProvider, 38 + type SessionAccount, 39 useSession, 40 useSessionApi, 41 } from '#/state/session' 42 import {readLastActiveAccount} from '#/state/session/util' 43 import {Provider as ShellStateProvider} from '#/state/shell' 44 import {Provider as ComposerProvider} from '#/state/shell/composer' 45 import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' 46 import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' 47 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' ··· 192 <LightboxStateProvider> 193 <PortalProvider> 194 <StarterPackProvider> 195 + <InnerApp /> 196 </StarterPackProvider> 197 </PortalProvider> 198 </LightboxStateProvider>
-21
src/alf/util/navigationBar.ts
··· 1 - import * as NavigationBar from 'expo-navigation-bar' 2 - import * as SystemUI from 'expo-system-ui' 3 - 4 - import {isAndroid} from '#/platform/detection' 5 - import {Theme} from '../types' 6 - 7 - export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) { 8 - if (isAndroid) { 9 - if (themeType === 'theme') { 10 - NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 11 - NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor) 12 - NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark') 13 - SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 14 - } else { 15 - NavigationBar.setBackgroundColorAsync('black') 16 - NavigationBar.setBorderColorAsync('black') 17 - NavigationBar.setButtonStyleAsync('light') 18 - SystemUI.setBackgroundColorAsync('black') 19 - } 20 - } 21 - }
···
+14
src/alf/util/systemUI.ts
···
··· 1 + import * as SystemUI from 'expo-system-ui' 2 + 3 + import {isAndroid} from '#/platform/detection' 4 + import {Theme} from '../types' 5 + 6 + export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 7 + if (isAndroid) { 8 + if (themeType === 'theme') { 9 + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 10 + } else { 11 + SystemUI.setBackgroundColorAsync('black') 12 + } 13 + } 14 + }
+1 -1
src/components/ContextMenu/index.tsx
··· 556 // pure vibes based 557 const TOP_INSET = insets.top + 80 558 const BOTTOM_INSET_IOS = insets.bottom + 20 559 - const BOTTOM_INSET_ANDROID = 12 // TODO: revisit when edge-to-edge mode is enabled -sfn 560 561 const {height} = evt.nativeEvent.layout 562 const topPosition =
··· 556 // pure vibes based 557 const TOP_INSET = insets.top + 80 558 const BOTTOM_INSET_IOS = insets.bottom + 20 559 + const BOTTOM_INSET_ANDROID = insets.bottom + 12 560 561 const {height} = evt.nativeEvent.layout 562 const topPosition =
+14 -9
src/components/Dialog/sheet-wrapper.ts
··· 1 import {useCallback} from 'react' 2 3 - import {useDialogStateControlContext} from '#/state/dialogs' 4 5 /** 6 * If we're calling a system API like the image picker that opens a sheet 7 * wrap it in this function to make sure the status bar is the correct color. 8 */ 9 export function useSheetWrapper() { 10 - const {setFullyExpandedCount} = useDialogStateControlContext() 11 - return useCallback( 12 - async <T>(promise: Promise<T>): Promise<T> => { 13 - setFullyExpandedCount(c => c + 1) 14 const res = await promise 15 - setFullyExpandedCount(c => c - 1) 16 return res 17 - }, 18 - [setFullyExpandedCount], 19 - ) 20 }
··· 1 import {useCallback} from 'react' 2 + import {SystemBars} from 'react-native-edge-to-edge' 3 4 + import {isIOS} from '#/platform/detection' 5 6 /** 7 * If we're calling a system API like the image picker that opens a sheet 8 * wrap it in this function to make sure the status bar is the correct color. 9 */ 10 export function useSheetWrapper() { 11 + return useCallback(async <T>(promise: Promise<T>): Promise<T> => { 12 + if (isIOS) { 13 + const entry = SystemBars.pushStackEntry({ 14 + style: { 15 + statusBar: 'light', 16 + }, 17 + }) 18 const res = await promise 19 + SystemBars.popStackEntry(entry) 20 return res 21 + } else { 22 + return await promise 23 + } 24 + }, []) 25 }
+1 -4
src/lib/hooks/useEnableKeyboardController.tsx
··· 26 children: React.ReactNode 27 }) { 28 return ( 29 - <KeyboardProvider 30 - enabled={false} 31 - // I don't think this is necessary, but Chesterton's fence and all that -sfn 32 - statusBarTranslucent={true}> 33 <KeyboardControllerProviderInner> 34 {children} 35 </KeyboardControllerProviderInner>
··· 26 children: React.ReactNode 27 }) { 28 return ( 29 + <KeyboardProvider enabled={false}> 30 <KeyboardControllerProviderInner> 31 {children} 32 </KeyboardControllerProviderInner>
+1 -1
src/screens/Login/index.tsx
··· 8 import {logEvent} from '#/lib/statsig/statsig' 9 import {logger} from '#/logger' 10 import {useServiceQuery} from '#/state/queries/service' 11 - import {SessionAccount, useSession} from '#/state/session' 12 import {useLoggedOutView} from '#/state/shell/logged-out' 13 import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' 14 import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm'
··· 8 import {logEvent} from '#/lib/statsig/statsig' 9 import {logger} from '#/logger' 10 import {useServiceQuery} from '#/state/queries/service' 11 + import {type SessionAccount, useSession} from '#/state/session' 12 import {useLoggedOutView} from '#/state/shell/logged-out' 13 import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' 14 import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm'
+3 -2
src/screens/Messages/components/MessageInput.tsx
··· 24 useMessageDraft, 25 useSaveMessageDraft, 26 } from '#/state/messages/message-drafts' 27 - import {EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web' 28 import * as Toast from '#/view/com/util/Toast' 29 - import {atoms as a, useTheme} from '#/alf' 30 import {useSharedInputStyles} from '#/components/forms/TextField' 31 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 32 import {useExtractEmbedFromFacets} from './MessageInputEmbed' ··· 174 a.text_md, 175 a.px_sm, 176 t.atoms.text, 177 {paddingBottom: isIOS ? 5 : 0}, 178 animatedStyle, 179 ]}
··· 24 useMessageDraft, 25 useSaveMessageDraft, 26 } from '#/state/messages/message-drafts' 27 + import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web' 28 import * as Toast from '#/view/com/util/Toast' 29 + import {android, atoms as a, useTheme} from '#/alf' 30 import {useSharedInputStyles} from '#/components/forms/TextField' 31 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 32 import {useExtractEmbedFromFacets} from './MessageInputEmbed' ··· 174 a.text_md, 175 a.px_sm, 176 t.atoms.text, 177 + android({paddingTop: 0}), 178 {paddingBottom: isIOS ? 5 : 0}, 179 animatedStyle, 180 ]}
+2 -2
src/screens/SignupQueued.tsx
··· 1 import React from 'react' 2 import {Modal, ScrollView, View} from 'react-native' 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 - import {StatusBar} from 'expo-status-bar' 5 import {msg, plural, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 ··· 106 animationType={native('slide')} 107 presentationStyle="formSheet" 108 style={[web(a.util_screen_outer)]}> 109 - {isIOS && <StatusBar style="light" />} 110 <ScrollView 111 style={[a.flex_1, t.atoms.bg]} 112 contentContainerStyle={{borderWidth: 0}}
··· 1 import React from 'react' 2 import {Modal, ScrollView, View} from 'react-native' 3 + import {SystemBars} from 'react-native-edge-to-edge' 4 import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 import {msg, plural, Trans} from '@lingui/macro' 6 import {useLingui} from '@lingui/react' 7 ··· 106 animationType={native('slide')} 107 presentationStyle="formSheet" 108 style={[web(a.util_screen_outer)]}> 109 + {isIOS && <SystemBars style={{statusBar: 'light'}} />} 110 <ScrollView 111 style={[a.flex_1, t.atoms.bg]} 112 contentContainerStyle={{borderWidth: 0}}
+2 -2
src/screens/Takendown.tsx
··· 1 import {useMemo, useState} from 'react' 2 import {Modal, View} from 'react-native' 3 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' 4 import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 - import {StatusBar} from 'expo-status-bar' 6 import {ComAtprotoAdminDefs, ComAtprotoModerationDefs} from '@atproto/api' 7 import {msg, Trans} from '@lingui/macro' 8 import {useLingui} from '@lingui/react' ··· 126 animationType={native('slide')} 127 presentationStyle="formSheet" 128 style={[web(a.util_screen_outer)]}> 129 - {isIOS && <StatusBar style="light" />} 130 <KeyboardAwareScrollView style={[a.flex_1, t.atoms.bg]} centerContent> 131 <View 132 style={[
··· 1 import {useMemo, useState} from 'react' 2 import {Modal, View} from 'react-native' 3 + import {SystemBars} from 'react-native-edge-to-edge' 4 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 6 import {ComAtprotoAdminDefs, ComAtprotoModerationDefs} from '@atproto/api' 7 import {msg, Trans} from '@lingui/macro' 8 import {useLingui} from '@lingui/react' ··· 126 animationType={native('slide')} 127 presentationStyle="formSheet" 128 style={[web(a.util_screen_outer)]}> 129 + {isIOS && <SystemBars style={{statusBar: 'light'}} />} 130 <KeyboardAwareScrollView style={[a.flex_1, t.atoms.bg]} centerContent> 131 <View 132 style={[
+5 -3
src/screens/VideoFeed/index.tsx
··· 8 ViewabilityConfig, 9 ViewToken, 10 } from 'react-native' 11 import { 12 Gesture, 13 GestureDetector, ··· 77 import {UserAvatar} from '#/view/com/util/UserAvatar' 78 import {Header} from '#/screens/VideoFeed/components/Header' 79 import {atoms as a, ios, platform, ThemeProvider, useTheme} from '#/alf' 80 - import {setNavigationBar} from '#/alf/util/navigationBar' 81 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 82 import {Divider} from '#/components/Divider' 83 import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' ··· 126 useFocusEffect( 127 useCallback(() => { 128 setMinShellMode(true) 129 - setNavigationBar('lightbox', t) 130 return () => { 131 setMinShellMode(false) 132 - setNavigationBar('theme', t) 133 } 134 }, [setMinShellMode, t]), 135 ) ··· 140 return ( 141 <ThemeProvider theme="dark"> 142 <Layout.Screen noInsetTop style={{backgroundColor: 'black'}}> 143 <View 144 style={[ 145 a.absolute,
··· 8 ViewabilityConfig, 9 ViewToken, 10 } from 'react-native' 11 + import {SystemBars} from 'react-native-edge-to-edge' 12 import { 13 Gesture, 14 GestureDetector, ··· 78 import {UserAvatar} from '#/view/com/util/UserAvatar' 79 import {Header} from '#/screens/VideoFeed/components/Header' 80 import {atoms as a, ios, platform, ThemeProvider, useTheme} from '#/alf' 81 + import {setSystemUITheme} from '#/alf/util/systemUI' 82 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 83 import {Divider} from '#/components/Divider' 84 import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' ··· 127 useFocusEffect( 128 useCallback(() => { 129 setMinShellMode(true) 130 + setSystemUITheme('lightbox', t) 131 return () => { 132 setMinShellMode(false) 133 + setSystemUITheme('theme', t) 134 } 135 }, [setMinShellMode, t]), 136 ) ··· 141 return ( 142 <ThemeProvider theme="dark"> 143 <Layout.Screen noInsetTop style={{backgroundColor: 'black'}}> 144 + <SystemBars style={{statusBar: 'light', navigationBar: 'light'}} /> 145 <View 146 style={[ 147 a.absolute,
+11 -38
src/state/shell/light-status-bar.tsx
··· 1 - import {createContext, useContext, useEffect, useState} from 'react' 2 - 3 - import {isWeb} from '#/platform/detection' 4 - 5 - const LightStatusBarRefCountContext = createContext<boolean>(false) 6 - const SetLightStatusBarRefCountContext = createContext<React.Dispatch< 7 - React.SetStateAction<number> 8 - > | null>(null) 9 - 10 - export function useLightStatusBar() { 11 - return useContext(LightStatusBarRefCountContext) 12 - } 13 14 export function useSetLightStatusBar(enabled: boolean) { 15 - const setRefCount = useContext(SetLightStatusBarRefCountContext) 16 useEffect(() => { 17 - // noop on web -sfn 18 - if (isWeb) return 19 - 20 - if (!setRefCount) { 21 - if (__DEV__) 22 - console.error( 23 - 'useLightStatusBar was used without a SetLightStatusBarRefCountContext provider', 24 - ) 25 - return 26 - } 27 if (enabled) { 28 - setRefCount(prev => prev + 1) 29 - return () => setRefCount(prev => prev - 1) 30 } 31 - }, [enabled, setRefCount]) 32 - } 33 - 34 - export function Provider({children}: React.PropsWithChildren<{}>) { 35 - const [refCount, setRefCount] = useState(0) 36 - 37 - return ( 38 - <SetLightStatusBarRefCountContext.Provider value={setRefCount}> 39 - <LightStatusBarRefCountContext.Provider value={refCount > 0}> 40 - {children} 41 - </LightStatusBarRefCountContext.Provider> 42 - </SetLightStatusBarRefCountContext.Provider> 43 - ) 44 }
··· 1 + import {useEffect} from 'react' 2 + import {SystemBars} from 'react-native-edge-to-edge' 3 4 export function useSetLightStatusBar(enabled: boolean) { 5 useEffect(() => { 6 if (enabled) { 7 + const entry = SystemBars.pushStackEntry({ 8 + style: { 9 + statusBar: 'light', 10 + }, 11 + }) 12 + return () => { 13 + SystemBars.popStackEntry(entry) 14 + } 15 } 16 + }, [enabled]) 17 }
+1 -2
src/view/com/composer/Composer.tsx
··· 1464 1465 // Android etc 1466 if (!isIOS) { 1467 - // if Android <35 or web, bottom is 0 anyway. if >=35, this is needed to account 1468 - // for the edge-to-edge nav bar 1469 return bottom * -1 1470 } 1471
··· 1464 1465 // Android etc 1466 if (!isIOS) { 1467 + // need to account for the edge-to-edge nav bar 1468 return bottom * -1 1469 } 1470
+21 -35
src/view/com/lightbox/ImageViewing/index.tsx
··· 9 // https://github.com/jobtoday/react-native-image-viewing 10 11 import React, {useCallback, useEffect, useMemo, useState} from 'react' 12 - import { 13 - LayoutAnimation, 14 - PixelRatio, 15 - Platform, 16 - StyleSheet, 17 - View, 18 - } from 'react-native' 19 import {Gesture} from 'react-native-gesture-handler' 20 import PagerView from 'react-native-pager-view' 21 import Animated, { 22 - AnimatedRef, 23 cancelAnimation, 24 interpolate, 25 measure, 26 runOnJS, 27 - SharedValue, 28 useAnimatedReaction, 29 useAnimatedRef, 30 useAnimatedStyle, ··· 32 useSharedValue, 33 withDecay, 34 withSpring, 35 - WithSpringConfig, 36 } from 'react-native-reanimated' 37 import { 38 - Edge, 39 SafeAreaView, 40 useSafeAreaFrame, 41 useSafeAreaInsets, 42 } from 'react-native-safe-area-context' 43 import * as ScreenOrientation from 'expo-screen-orientation' 44 - import {StatusBar} from 'expo-status-bar' 45 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 46 import {Trans} from '@lingui/macro' 47 48 - import {Dimensions} from '#/lib/media/types' 49 import {colors, s} from '#/lib/styles' 50 import {isIOS} from '#/platform/detection' 51 - import {Lightbox} from '#/state/lightbox' 52 import {Button} from '#/view/com/util/forms/Button' 53 import {Text} from '#/view/com/util/text/Text' 54 import {ScrollView} from '#/view/com/util/Views' 55 - import {ios, useTheme} from '#/alf' 56 - import {setNavigationBar} from '#/alf/util/navigationBar' 57 import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' 58 - import {ImageSource, Transform} from './@types' 59 import ImageDefaultHeader from './components/ImageDefaultHeader' 60 import ImageItem from './components/ImageItem/ImageItem' 61 ··· 63 64 const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP 65 const PIXEL_RATIO = PixelRatio.get() 66 - const EDGES = 67 - Platform.OS === 'android' && Platform.Version < 35 68 - ? (['top', 'bottom', 'left', 'right'] satisfies Edge[]) 69 - : ([] satisfies Edge[]) // iOS or Android 15+ bleeds into safe area 70 71 const SLOW_SPRING: WithSpringConfig = { 72 mass: isIOS ? 1.25 : 0.75, ··· 167 168 return ( 169 // Keep it always mounted to avoid flicker on the first frame. 170 - <SafeAreaView 171 style={[styles.screen, !activeLightbox && styles.screenHidden]} 172 - edges={EDGES} 173 aria-modal 174 accessibilityViewIsModal 175 aria-hidden={!activeLightbox}> ··· 197 /> 198 )} 199 </Animated.View> 200 - </SafeAreaView> 201 ) 202 } 203 ··· 325 }, 326 ) 327 328 - // style nav bar on android 329 const t = useTheme() 330 useEffect(() => { 331 - setNavigationBar('lightbox', t) 332 return () => { 333 - setNavigationBar('theme', t) 334 } 335 }, [t]) 336 337 return ( 338 <Animated.View style={[styles.container, containerStyle]}> 339 - <StatusBar 340 - animated 341 - style="light" 342 - hideTransitionAnimation="slide" 343 - backgroundColor="black" 344 - // hiding causes layout shifts on android, 345 - // so avoid until we add edge-to-edge mode 346 - hidden={ios(isScaled || !showControls)} 347 /> 348 <Animated.View 349 style={[styles.backdrop, backdropStyle]}
··· 9 // https://github.com/jobtoday/react-native-image-viewing 10 11 import React, {useCallback, useEffect, useMemo, useState} from 'react' 12 + import {LayoutAnimation, PixelRatio, StyleSheet, View} from 'react-native' 13 + import {SystemBars} from 'react-native-edge-to-edge' 14 import {Gesture} from 'react-native-gesture-handler' 15 import PagerView from 'react-native-pager-view' 16 import Animated, { 17 + type AnimatedRef, 18 cancelAnimation, 19 interpolate, 20 measure, 21 runOnJS, 22 + type SharedValue, 23 useAnimatedReaction, 24 useAnimatedRef, 25 useAnimatedStyle, ··· 27 useSharedValue, 28 withDecay, 29 withSpring, 30 + type WithSpringConfig, 31 } from 'react-native-reanimated' 32 import { 33 SafeAreaView, 34 useSafeAreaFrame, 35 useSafeAreaInsets, 36 } from 'react-native-safe-area-context' 37 import * as ScreenOrientation from 'expo-screen-orientation' 38 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 39 import {Trans} from '@lingui/macro' 40 41 + import {type Dimensions} from '#/lib/media/types' 42 import {colors, s} from '#/lib/styles' 43 import {isIOS} from '#/platform/detection' 44 + import {type Lightbox} from '#/state/lightbox' 45 import {Button} from '#/view/com/util/forms/Button' 46 import {Text} from '#/view/com/util/text/Text' 47 import {ScrollView} from '#/view/com/util/Views' 48 + import {useTheme} from '#/alf' 49 + import {setSystemUITheme} from '#/alf/util/systemUI' 50 import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' 51 + import {type ImageSource, type Transform} from './@types' 52 import ImageDefaultHeader from './components/ImageDefaultHeader' 53 import ImageItem from './components/ImageItem/ImageItem' 54 ··· 56 57 const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP 58 const PIXEL_RATIO = PixelRatio.get() 59 60 const SLOW_SPRING: WithSpringConfig = { 61 mass: isIOS ? 1.25 : 0.75, ··· 156 157 return ( 158 // Keep it always mounted to avoid flicker on the first frame. 159 + <View 160 style={[styles.screen, !activeLightbox && styles.screenHidden]} 161 aria-modal 162 accessibilityViewIsModal 163 aria-hidden={!activeLightbox}> ··· 185 /> 186 )} 187 </Animated.View> 188 + </View> 189 ) 190 } 191 ··· 313 }, 314 ) 315 316 + // style system ui on android 317 const t = useTheme() 318 useEffect(() => { 319 + setSystemUITheme('lightbox', t) 320 return () => { 321 + setSystemUITheme('theme', t) 322 } 323 }, [t]) 324 325 return ( 326 <Animated.View style={[styles.container, containerStyle]}> 327 + <SystemBars 328 + style={{statusBar: 'light', navigationBar: 'light'}} 329 + hidden={{ 330 + statusBar: isScaled || !showControls, 331 + navigationBar: false, 332 + }} 333 /> 334 <Animated.View 335 style={[styles.backdrop, backdropStyle]}
+2 -2
src/view/com/modals/CreateOrEditList.tsx
··· 8 TouchableOpacity, 9 View, 10 } from 'react-native' 11 - import {Image as RNImage} from 'react-native-image-crop-picker' 12 import {LinearGradient} from 'expo-linear-gradient' 13 - import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' 14 import {msg, Trans} from '@lingui/macro' 15 import {useLingui} from '@lingui/react' 16
··· 8 TouchableOpacity, 9 View, 10 } from 'react-native' 11 + import {type Image as RNImage} from 'react-native-image-crop-picker' 12 import {LinearGradient} from 'expo-linear-gradient' 13 + import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' 14 import {msg, Trans} from '@lingui/macro' 15 import {useLingui} from '@lingui/react' 16
+11 -14
src/view/shell/index.tsx
··· 1 import {useCallback, useEffect, useState} from 'react' 2 import {BackHandler, useWindowDimensions, View} from 'react-native' 3 import {Drawer} from 'react-native-drawer-layout' 4 import {Gesture} from 'react-native-gesture-handler' 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 6 - import {StatusBar} from 'expo-status-bar' 7 import {useNavigation, useNavigationState} from '@react-navigation/native' 8 9 import {useDedupe} from '#/lib/hooks/useDedupe' ··· 19 useIsDrawerSwipeDisabled, 20 useSetDrawerOpen, 21 } from '#/state/shell' 22 - import {useLightStatusBar} from '#/state/shell/light-status-bar' 23 import {useCloseAnyActiveElement} from '#/state/util' 24 import {Lightbox} from '#/view/com/lightbox/Lightbox' 25 import {ModalsContainer} from '#/view/com/modals/Modal' 26 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 27 import {atoms as a, select, useTheme} from '#/alf' 28 - import {setNavigationBar} from '#/alf/util/navigationBar' 29 import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 30 import {SigninDialog} from '#/components/dialogs/Signin' 31 import {Outlet as PortalOutlet} from '#/components/Portal' ··· 161 162 export const Shell: React.FC = function ShellImpl() { 163 const {fullyExpandedCount} = useDialogStateControlContext() 164 - const lightStatusBar = useLightStatusBar() 165 const t = useTheme() 166 useIntentHandler() 167 168 useEffect(() => { 169 - setNavigationBar('theme', t) 170 }, [t]) 171 172 return ( 173 <View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}> 174 - <StatusBar 175 - style={ 176 - t.name !== 'light' || 177 - (isIOS && fullyExpandedCount > 0) || 178 - lightStatusBar 179 - ? 'light' 180 - : 'dark' 181 - } 182 - animated 183 /> 184 <RoutesContainer> 185 <ShellInner />
··· 1 import {useCallback, useEffect, useState} from 'react' 2 import {BackHandler, useWindowDimensions, View} from 'react-native' 3 import {Drawer} from 'react-native-drawer-layout' 4 + import {SystemBars} from 'react-native-edge-to-edge' 5 import {Gesture} from 'react-native-gesture-handler' 6 import {useSafeAreaInsets} from 'react-native-safe-area-context' 7 import {useNavigation, useNavigationState} from '@react-navigation/native' 8 9 import {useDedupe} from '#/lib/hooks/useDedupe' ··· 19 useIsDrawerSwipeDisabled, 20 useSetDrawerOpen, 21 } from '#/state/shell' 22 import {useCloseAnyActiveElement} from '#/state/util' 23 import {Lightbox} from '#/view/com/lightbox/Lightbox' 24 import {ModalsContainer} from '#/view/com/modals/Modal' 25 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' 26 import {atoms as a, select, useTheme} from '#/alf' 27 + import {setSystemUITheme} from '#/alf/util/systemUI' 28 import {MutedWordsDialog} from '#/components/dialogs/MutedWords' 29 import {SigninDialog} from '#/components/dialogs/Signin' 30 import {Outlet as PortalOutlet} from '#/components/Portal' ··· 160 161 export const Shell: React.FC = function ShellImpl() { 162 const {fullyExpandedCount} = useDialogStateControlContext() 163 const t = useTheme() 164 useIntentHandler() 165 166 useEffect(() => { 167 + setSystemUITheme('theme', t) 168 }, [t]) 169 170 return ( 171 <View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}> 172 + <SystemBars 173 + style={{ 174 + statusBar: 175 + t.name !== 'light' || (isIOS && fullyExpandedCount > 0) 176 + ? 'light' 177 + : 'dark', 178 + navigationBar: t.name !== 'light' ? 'light' : 'dark', 179 + }} 180 /> 181 <RoutesContainer> 182 <ShellInner />
+9 -17
yarn.lock
··· 11067 dependencies: 11068 invariant "^2.2.4" 11069 11070 - expo-navigation-bar@~4.0.9: 11071 - version "4.0.9" 11072 - resolved "https://registry.yarnpkg.com/expo-navigation-bar/-/expo-navigation-bar-4.0.9.tgz#e0409c2db8f6384d12c87f45c2674effc9fae1b6" 11073 - integrity sha512-dCJ04yPixFOUixJaWlmCZafGeQ1L1g6vWn+oX8rqPnYN9kYCMUz2aRNnhRRoK5MBGFTK/nue2D49TE/AwwWt9w== 11074 - dependencies: 11075 - "@react-native/normalize-colors" "0.76.8" 11076 - debug "^4.3.2" 11077 - 11078 expo-notifications@~0.29.14: 11079 version "0.29.14" 11080 resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.29.14.tgz#77beb6bc74b1b1abfa3adcab77fb6c9ea5d7d1b0" ··· 11114 integrity sha512-f+bPpF06bqiuW1Fbrd3nxeaSsmTVTBEKEYe3epYt4IE6y4Ulli3qEUamMLlRQiDGuIXPU6zQlscpy2mdBUI5cA== 11115 dependencies: 11116 "@expo/prebuild-config" "^8.0.27" 11117 - 11118 - expo-status-bar@~2.0.1: 11119 - version "2.0.1" 11120 - resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.0.1.tgz#fc07726346dc30fbb68aadb0d7890b34fba42eee" 11121 - integrity sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg== 11122 11123 expo-structured-headers@~4.0.0: 11124 version "4.0.0" ··· 16761 dependencies: 16762 use-latest-callback "^0.2.1" 16763 16764 react-native-emoji-popup@^0.1.2: 16765 version "0.1.2" 16766 resolved "https://registry.yarnpkg.com/react-native-emoji-popup/-/react-native-emoji-popup-0.1.2.tgz#7cd3874ba0496031e6f3e24de77e0df895168ce6" ··· 16807 resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902" 16808 integrity sha512-1pHnFTlBahins6UAajXUqeCOHew9l9C2C8tErnpGC3IyLJzvxD+TpYAixnCbrVS52f7+NvMttbiSI290XfwN0w== 16809 16810 - react-native-keyboard-controller@^1.14.5: 16811 - version "1.14.5" 16812 - resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.14.5.tgz#ec1e7d1fb8ee18b69ced4d8ddd6fd99bdaaf14bb" 16813 - integrity sha512-Cx7+SWI/P50i4PKJZN4T43RqoFkJ3GBoxjQ5ysrzZGoImHTF4j3atSwcBQGMmunKCem1yGOOQ84or+Vbcor6wQ== 16814 dependencies: 16815 react-native-is-edge-to-edge "^1.1.6" 16816
··· 11067 dependencies: 11068 invariant "^2.2.4" 11069 11070 expo-notifications@~0.29.14: 11071 version "0.29.14" 11072 resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.29.14.tgz#77beb6bc74b1b1abfa3adcab77fb6c9ea5d7d1b0" ··· 11106 integrity sha512-f+bPpF06bqiuW1Fbrd3nxeaSsmTVTBEKEYe3epYt4IE6y4Ulli3qEUamMLlRQiDGuIXPU6zQlscpy2mdBUI5cA== 11107 dependencies: 11108 "@expo/prebuild-config" "^8.0.27" 11109 11110 expo-structured-headers@~4.0.0: 11111 version "4.0.0" ··· 16748 dependencies: 16749 use-latest-callback "^0.2.1" 16750 16751 + react-native-edge-to-edge@^1.6.0: 16752 + version "1.6.0" 16753 + resolved "https://registry.yarnpkg.com/react-native-edge-to-edge/-/react-native-edge-to-edge-1.6.0.tgz#2ba63b941704a7f713e298185c26cde4d9e4b973" 16754 + integrity sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og== 16755 + 16756 react-native-emoji-popup@^0.1.2: 16757 version "0.1.2" 16758 resolved "https://registry.yarnpkg.com/react-native-emoji-popup/-/react-native-emoji-popup-0.1.2.tgz#7cd3874ba0496031e6f3e24de77e0df895168ce6" ··· 16799 resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902" 16800 integrity sha512-1pHnFTlBahins6UAajXUqeCOHew9l9C2C8tErnpGC3IyLJzvxD+TpYAixnCbrVS52f7+NvMttbiSI290XfwN0w== 16801 16802 + react-native-keyboard-controller@^1.17.1: 16803 + version "1.17.1" 16804 + resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.17.1.tgz#46efe148c1bdd0ee22094dcb2660f70f81e6544e" 16805 + integrity sha512-YM3GYvtkuWimCKkZFURn5hIb1WiKOQqi2DijdwZSF5QSSzGqfqwzEEC3bm1xCN8HGHAEIXAaWIBUsc3Xp5L+Ng== 16806 dependencies: 16807 react-native-is-edge-to-edge "^1.1.6" 16808