Edge to edge support (#7497)

This commit is contained in:
Mathieu Acthernoene
2025-04-22 18:16:50 +02:00
committed by GitHub
parent 6e80b340c8
commit a770f5635b
23 changed files with 137 additions and 200 deletions
+5 -5
View File
@@ -1,7 +1,5 @@
const pkg = require('./package.json') const pkg = require('./package.json')
const DARK_SPLASH_ANDROID_BACKGROUND = '#0f141b'
module.exports = function (config) { module.exports = function (config) {
/** /**
* App version number. Should be incremented as part of a release cycle. * App version number. Should be incremented as part of a release cycle.
@@ -140,12 +138,10 @@ module.exports = function (config) {
}, },
androidStatusBar: { androidStatusBar: {
barStyle: 'light-content', barStyle: 'light-content',
backgroundColor: '#00000000',
}, },
// Dark nav bar in light mode is better than light nav bar in dark mode // Dark nav bar in light mode is better than light nav bar in dark mode
androidNavigationBar: { androidNavigationBar: {
barStyle: 'light-content', barStyle: 'light-content',
backgroundColor: DARK_SPLASH_ANDROID_BACKGROUND,
}, },
android: { android: {
icon: './assets/app-icons/android_icon_default_light.png', icon: './assets/app-icons/android_icon_default_light.png',
@@ -197,6 +193,10 @@ module.exports = function (config) {
plugins: [ plugins: [
'expo-video', 'expo-video',
'expo-localization', 'expo-localization',
[
'react-native-edge-to-edge',
{android: {enforceNavigationBarContrast: false}},
],
USE_SENTRY && [ USE_SENTRY && [
'@sentry/react-native/expo', '@sentry/react-native/expo',
{ {
@@ -240,7 +240,7 @@ module.exports = function (config) {
'./plugins/withAndroidManifestPlugin.js', './plugins/withAndroidManifestPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js', './plugins/withAndroidManifestFCMIconPlugin.js',
'./plugins/withAndroidStylesAccentColorPlugin.js', './plugins/withAndroidStylesAccentColorPlugin.js',
'./plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js', './plugins/withAndroidDayNightThemePlugin.js',
'./plugins/withAndroidNoJitpackPlugin.js', './plugins/withAndroidNoJitpackPlugin.js',
'./plugins/withNoBundleCompression.js', './plugins/withNoBundleCompression.js',
'./plugins/shareExtension/withShareExtensions.js', './plugins/shareExtension/withShareExtensions.js',
+2 -3
View File
@@ -141,12 +141,10 @@
"expo-linking": "~7.0.5", "expo-linking": "~7.0.5",
"expo-localization": "~16.0.1", "expo-localization": "~16.0.1",
"expo-media-library": "~17.0.6", "expo-media-library": "~17.0.6",
"expo-navigation-bar": "~4.0.9",
"expo-notifications": "~0.29.14", "expo-notifications": "~0.29.14",
"expo-screen-orientation": "~8.0.4", "expo-screen-orientation": "~8.0.4",
"expo-sharing": "~13.0.1", "expo-sharing": "~13.0.1",
"expo-splash-screen": "~0.29.22", "expo-splash-screen": "~0.29.22",
"expo-status-bar": "~2.0.1",
"expo-system-ui": "~4.0.9", "expo-system-ui": "~4.0.9",
"expo-task-manager": "~12.0.6", "expo-task-manager": "~12.0.6",
"expo-updates": "~0.27.4", "expo-updates": "~0.27.4",
@@ -178,12 +176,13 @@
"react-native-compressor": "1.10.3", "react-native-compressor": "1.10.3",
"react-native-date-picker": "^5.0.7", "react-native-date-picker": "^5.0.7",
"react-native-drawer-layout": "^4.1.1", "react-native-drawer-layout": "^4.1.1",
"react-native-edge-to-edge": "^1.6.0",
"react-native-emoji-popup": "^0.1.2", "react-native-emoji-popup": "^0.1.2",
"react-native-gesture-handler": "2.20.2", "react-native-gesture-handler": "2.20.2",
"react-native-get-random-values": "~1.11.0", "react-native-get-random-values": "~1.11.0",
"react-native-image-crop-picker": "^0.41.6", "react-native-image-crop-picker": "^0.41.6",
"react-native-ios-context-menu": "^1.15.3", "react-native-ios-context-menu": "^1.15.3",
"react-native-keyboard-controller": "^1.14.5", "react-native-keyboard-controller": "^1.17.1",
"react-native-mmkv": "^2.12.2", "react-native-mmkv": "^2.12.2",
"react-native-pager-view": "6.5.1", "react-native-pager-view": "6.5.1",
"react-native-picker-select": "^9.3.1", "react-native-picker-select": "^9.3.1",
+27
View File
@@ -0,0 +1,27 @@
// Based on https://github.com/expo/expo/pull/33957
// Could be removed once the app has been updated to Expo 53
const {withAndroidStyles} = require('@expo/config-plugins')
module.exports = function withAndroidDayNightThemePlugin(appConfig) {
const cleanupList = new Set([
'colorPrimary',
'android:editTextBackground',
'android:textColor',
'android:editTextStyle',
])
return withAndroidStyles(appConfig, config => {
config.modResults.resources.style = config.modResults.resources.style
?.map(style => {
if (style.$.name === 'AppTheme' && style.item != null) {
style.item = style.item.filter(item => !cleanupList.has(item.$.name))
}
return style
})
.filter(style => {
return style.$.name !== 'ResetEditText'
})
return config
})
}
@@ -1,28 +0,0 @@
const {withStringsXml, AndroidConfig} = require('@expo/config-plugins')
module.exports = function withAndroidSplashScreenStatusBarTranslucentPlugin(
appConfig,
) {
return withStringsXml(appConfig, function (decoratedAppConfig) {
try {
decoratedAppConfig.modResults = AndroidConfig.Strings.setStringItem(
[
{
_: 'true',
$: {
name: 'expo_splash_screen_status_bar_translucent',
translatable: 'false',
},
},
],
decoratedAppConfig.modResults,
)
} catch (e) {
console.error(
`withAndroidSplashScreenStatusBarTranslucentPlugin failed`,
e,
)
}
return decoratedAppConfig
})
}
@@ -12,7 +12,7 @@ module.exports = function withAndroidStylesAccentColorPlugin(appConfig) {
decoratedAppConfig.modResults, decoratedAppConfig.modResults,
{ {
add: true, add: true,
parent: AndroidConfig.Styles.getAppThemeLightNoActionBarGroup(), parent: AndroidConfig.Styles.getAppThemeGroup(),
name: 'colorAccent', name: 'colorAccent',
value: '@color/colorPrimary', value: '@color/colorPrimary',
}, },
+2 -5
View File
@@ -46,14 +46,13 @@ import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation
import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
import { import {
Provider as SessionProvider, Provider as SessionProvider,
SessionAccount, type SessionAccount,
useSession, useSession,
useSessionApi, useSessionApi,
} from '#/state/session' } from '#/state/session'
import {readLastActiveAccount} from '#/state/session/util' import {readLastActiveAccount} from '#/state/session/util'
import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as ShellStateProvider} from '#/state/shell'
import {Provider as ComposerProvider} from '#/state/shell/composer' import {Provider as ComposerProvider} from '#/state/shell/composer'
import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar'
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
@@ -219,9 +218,7 @@ function App() {
<StarterPackProvider> <StarterPackProvider>
<SafeAreaProvider <SafeAreaProvider
initialMetrics={initialWindowMetrics}> initialMetrics={initialWindowMetrics}>
<LightStatusBarProvider> <InnerApp />
<InnerApp />
</LightStatusBarProvider>
</SafeAreaProvider> </SafeAreaProvider>
</StarterPackProvider> </StarterPackProvider>
</BottomSheetProvider> </BottomSheetProvider>
+2 -5
View File
@@ -35,14 +35,13 @@ import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation
import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread'
import { import {
Provider as SessionProvider, Provider as SessionProvider,
SessionAccount, type SessionAccount,
useSession, useSession,
useSessionApi, useSessionApi,
} from '#/state/session' } from '#/state/session'
import {readLastActiveAccount} from '#/state/session/util' import {readLastActiveAccount} from '#/state/session/util'
import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as ShellStateProvider} from '#/state/shell'
import {Provider as ComposerProvider} from '#/state/shell/composer' import {Provider as ComposerProvider} from '#/state/shell/composer'
import {Provider as LightStatusBarProvider} from '#/state/shell/light-status-bar'
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
@@ -193,9 +192,7 @@ function App() {
<LightboxStateProvider> <LightboxStateProvider>
<PortalProvider> <PortalProvider>
<StarterPackProvider> <StarterPackProvider>
<LightStatusBarProvider> <InnerApp />
<InnerApp />
</LightStatusBarProvider>
</StarterPackProvider> </StarterPackProvider>
</PortalProvider> </PortalProvider>
</LightboxStateProvider> </LightboxStateProvider>
-21
View File
@@ -1,21 +0,0 @@
import * as NavigationBar from 'expo-navigation-bar'
import * as SystemUI from 'expo-system-ui'
import {isAndroid} from '#/platform/detection'
import {Theme} from '../types'
export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) {
if (isAndroid) {
if (themeType === 'theme') {
NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark')
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
} else {
NavigationBar.setBackgroundColorAsync('black')
NavigationBar.setBorderColorAsync('black')
NavigationBar.setButtonStyleAsync('light')
SystemUI.setBackgroundColorAsync('black')
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import * as SystemUI from 'expo-system-ui'
import {isAndroid} from '#/platform/detection'
import {Theme} from '../types'
export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
if (isAndroid) {
if (themeType === 'theme') {
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
} else {
SystemUI.setBackgroundColorAsync('black')
}
}
}
+1 -1
View File
@@ -556,7 +556,7 @@ export function Outer({
// pure vibes based // pure vibes based
const TOP_INSET = insets.top + 80 const TOP_INSET = insets.top + 80
const BOTTOM_INSET_IOS = insets.bottom + 20 const BOTTOM_INSET_IOS = insets.bottom + 20
const BOTTOM_INSET_ANDROID = 12 // TODO: revisit when edge-to-edge mode is enabled -sfn const BOTTOM_INSET_ANDROID = insets.bottom + 12
const {height} = evt.nativeEvent.layout const {height} = evt.nativeEvent.layout
const topPosition = const topPosition =
+14 -9
View File
@@ -1,20 +1,25 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {SystemBars} from 'react-native-edge-to-edge'
import {useDialogStateControlContext} from '#/state/dialogs' import {isIOS} from '#/platform/detection'
/** /**
* If we're calling a system API like the image picker that opens a sheet * If we're calling a system API like the image picker that opens a sheet
* wrap it in this function to make sure the status bar is the correct color. * wrap it in this function to make sure the status bar is the correct color.
*/ */
export function useSheetWrapper() { export function useSheetWrapper() {
const {setFullyExpandedCount} = useDialogStateControlContext() return useCallback(async <T>(promise: Promise<T>): Promise<T> => {
return useCallback( if (isIOS) {
async <T>(promise: Promise<T>): Promise<T> => { const entry = SystemBars.pushStackEntry({
setFullyExpandedCount(c => c + 1) style: {
statusBar: 'light',
},
})
const res = await promise const res = await promise
setFullyExpandedCount(c => c - 1) SystemBars.popStackEntry(entry)
return res return res
}, } else {
[setFullyExpandedCount], return await promise
) }
}, [])
} }
@@ -26,10 +26,7 @@ export function KeyboardControllerProvider({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<KeyboardProvider <KeyboardProvider enabled={false}>
enabled={false}
// I don't think this is necessary, but Chesterton's fence and all that -sfn
statusBarTranslucent={true}>
<KeyboardControllerProviderInner> <KeyboardControllerProviderInner>
{children} {children}
</KeyboardControllerProviderInner> </KeyboardControllerProviderInner>
+1 -1
View File
@@ -8,7 +8,7 @@ import {DEFAULT_SERVICE} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useServiceQuery} from '#/state/queries/service' import {useServiceQuery} from '#/state/queries/service'
import {SessionAccount, useSession} from '#/state/session' import {type SessionAccount, useSession} from '#/state/session'
import {useLoggedOutView} from '#/state/shell/logged-out' import {useLoggedOutView} from '#/state/shell/logged-out'
import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout'
import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm' import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm'
@@ -24,9 +24,9 @@ import {
useMessageDraft, useMessageDraft,
useSaveMessageDraft, useSaveMessageDraft,
} from '#/state/messages/message-drafts' } from '#/state/messages/message-drafts'
import {EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web' import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf' import {android, atoms as a, useTheme} from '#/alf'
import {useSharedInputStyles} from '#/components/forms/TextField' import {useSharedInputStyles} from '#/components/forms/TextField'
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
import {useExtractEmbedFromFacets} from './MessageInputEmbed' import {useExtractEmbedFromFacets} from './MessageInputEmbed'
@@ -174,6 +174,7 @@ export function MessageInput({
a.text_md, a.text_md,
a.px_sm, a.px_sm,
t.atoms.text, t.atoms.text,
android({paddingTop: 0}),
{paddingBottom: isIOS ? 5 : 0}, {paddingBottom: isIOS ? 5 : 0},
animatedStyle, animatedStyle,
]} ]}
+2 -2
View File
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import {Modal, ScrollView, View} from 'react-native' import {Modal, ScrollView, View} from 'react-native'
import {SystemBars} from 'react-native-edge-to-edge'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {StatusBar} from 'expo-status-bar'
import {msg, plural, Trans} from '@lingui/macro' import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -106,7 +106,7 @@ export function SignupQueued() {
animationType={native('slide')} animationType={native('slide')}
presentationStyle="formSheet" presentationStyle="formSheet"
style={[web(a.util_screen_outer)]}> style={[web(a.util_screen_outer)]}>
{isIOS && <StatusBar style="light" />} {isIOS && <SystemBars style={{statusBar: 'light'}} />}
<ScrollView <ScrollView
style={[a.flex_1, t.atoms.bg]} style={[a.flex_1, t.atoms.bg]}
contentContainerStyle={{borderWidth: 0}} contentContainerStyle={{borderWidth: 0}}
+2 -2
View File
@@ -1,8 +1,8 @@
import {useMemo, useState} from 'react' import {useMemo, useState} from 'react'
import {Modal, View} from 'react-native' import {Modal, View} from 'react-native'
import {SystemBars} from 'react-native-edge-to-edge'
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {StatusBar} from 'expo-status-bar'
import {ComAtprotoAdminDefs, ComAtprotoModerationDefs} from '@atproto/api' import {ComAtprotoAdminDefs, ComAtprotoModerationDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -126,7 +126,7 @@ export function Takendown() {
animationType={native('slide')} animationType={native('slide')}
presentationStyle="formSheet" presentationStyle="formSheet"
style={[web(a.util_screen_outer)]}> style={[web(a.util_screen_outer)]}>
{isIOS && <StatusBar style="light" />} {isIOS && <SystemBars style={{statusBar: 'light'}} />}
<KeyboardAwareScrollView style={[a.flex_1, t.atoms.bg]} centerContent> <KeyboardAwareScrollView style={[a.flex_1, t.atoms.bg]} centerContent>
<View <View
style={[ style={[
+5 -3
View File
@@ -8,6 +8,7 @@ import {
ViewabilityConfig, ViewabilityConfig,
ViewToken, ViewToken,
} from 'react-native' } from 'react-native'
import {SystemBars} from 'react-native-edge-to-edge'
import { import {
Gesture, Gesture,
GestureDetector, GestureDetector,
@@ -77,7 +78,7 @@ import {PostCtrls} from '#/view/com/util/post-ctrls/PostCtrls'
import {UserAvatar} from '#/view/com/util/UserAvatar' import {UserAvatar} from '#/view/com/util/UserAvatar'
import {Header} from '#/screens/VideoFeed/components/Header' import {Header} from '#/screens/VideoFeed/components/Header'
import {atoms as a, ios, platform, ThemeProvider, useTheme} from '#/alf' import {atoms as a, ios, platform, ThemeProvider, useTheme} from '#/alf'
import {setNavigationBar} from '#/alf/util/navigationBar' import {setSystemUITheme} from '#/alf/util/systemUI'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider' import {Divider} from '#/components/Divider'
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow'
@@ -126,10 +127,10 @@ export function VideoFeed({}: NativeStackScreenProps<
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
setMinShellMode(true) setMinShellMode(true)
setNavigationBar('lightbox', t) setSystemUITheme('lightbox', t)
return () => { return () => {
setMinShellMode(false) setMinShellMode(false)
setNavigationBar('theme', t) setSystemUITheme('theme', t)
} }
}, [setMinShellMode, t]), }, [setMinShellMode, t]),
) )
@@ -140,6 +141,7 @@ export function VideoFeed({}: NativeStackScreenProps<
return ( return (
<ThemeProvider theme="dark"> <ThemeProvider theme="dark">
<Layout.Screen noInsetTop style={{backgroundColor: 'black'}}> <Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
<SystemBars style={{statusBar: 'light', navigationBar: 'light'}} />
<View <View
style={[ style={[
a.absolute, a.absolute,
+11 -38
View File
@@ -1,44 +1,17 @@
import {createContext, useContext, useEffect, useState} from 'react' import {useEffect} from 'react'
import {SystemBars} from 'react-native-edge-to-edge'
import {isWeb} from '#/platform/detection'
const LightStatusBarRefCountContext = createContext<boolean>(false)
const SetLightStatusBarRefCountContext = createContext<React.Dispatch<
React.SetStateAction<number>
> | null>(null)
export function useLightStatusBar() {
return useContext(LightStatusBarRefCountContext)
}
export function useSetLightStatusBar(enabled: boolean) { export function useSetLightStatusBar(enabled: boolean) {
const setRefCount = useContext(SetLightStatusBarRefCountContext)
useEffect(() => { useEffect(() => {
// noop on web -sfn
if (isWeb) return
if (!setRefCount) {
if (__DEV__)
console.error(
'useLightStatusBar was used without a SetLightStatusBarRefCountContext provider',
)
return
}
if (enabled) { if (enabled) {
setRefCount(prev => prev + 1) const entry = SystemBars.pushStackEntry({
return () => setRefCount(prev => prev - 1) style: {
statusBar: 'light',
},
})
return () => {
SystemBars.popStackEntry(entry)
}
} }
}, [enabled, setRefCount]) }, [enabled])
}
export function Provider({children}: React.PropsWithChildren<{}>) {
const [refCount, setRefCount] = useState(0)
return (
<SetLightStatusBarRefCountContext.Provider value={setRefCount}>
<LightStatusBarRefCountContext.Provider value={refCount > 0}>
{children}
</LightStatusBarRefCountContext.Provider>
</SetLightStatusBarRefCountContext.Provider>
)
} }
+1 -2
View File
@@ -1464,8 +1464,7 @@ function useKeyboardVerticalOffset() {
// Android etc // Android etc
if (!isIOS) { if (!isIOS) {
// if Android <35 or web, bottom is 0 anyway. if >=35, this is needed to account // need to account for the edge-to-edge nav bar
// for the edge-to-edge nav bar
return bottom * -1 return bottom * -1
} }
+21 -35
View File
@@ -9,22 +9,17 @@
// https://github.com/jobtoday/react-native-image-viewing // https://github.com/jobtoday/react-native-image-viewing
import React, {useCallback, useEffect, useMemo, useState} from 'react' import React, {useCallback, useEffect, useMemo, useState} from 'react'
import { import {LayoutAnimation, PixelRatio, StyleSheet, View} from 'react-native'
LayoutAnimation, import {SystemBars} from 'react-native-edge-to-edge'
PixelRatio,
Platform,
StyleSheet,
View,
} from 'react-native'
import {Gesture} from 'react-native-gesture-handler' import {Gesture} from 'react-native-gesture-handler'
import PagerView from 'react-native-pager-view' import PagerView from 'react-native-pager-view'
import Animated, { import Animated, {
AnimatedRef, type AnimatedRef,
cancelAnimation, cancelAnimation,
interpolate, interpolate,
measure, measure,
runOnJS, runOnJS,
SharedValue, type SharedValue,
useAnimatedReaction, useAnimatedReaction,
useAnimatedRef, useAnimatedRef,
useAnimatedStyle, useAnimatedStyle,
@@ -32,30 +27,28 @@ import Animated, {
useSharedValue, useSharedValue,
withDecay, withDecay,
withSpring, withSpring,
WithSpringConfig, type WithSpringConfig,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import { import {
Edge,
SafeAreaView, SafeAreaView,
useSafeAreaFrame, useSafeAreaFrame,
useSafeAreaInsets, useSafeAreaInsets,
} from 'react-native-safe-area-context' } from 'react-native-safe-area-context'
import * as ScreenOrientation from 'expo-screen-orientation' import * as ScreenOrientation from 'expo-screen-orientation'
import {StatusBar} from 'expo-status-bar'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {Dimensions} from '#/lib/media/types' import {type Dimensions} from '#/lib/media/types'
import {colors, s} from '#/lib/styles' import {colors, s} from '#/lib/styles'
import {isIOS} from '#/platform/detection' import {isIOS} from '#/platform/detection'
import {Lightbox} from '#/state/lightbox' import {type Lightbox} from '#/state/lightbox'
import {Button} from '#/view/com/util/forms/Button' import {Button} from '#/view/com/util/forms/Button'
import {Text} from '#/view/com/util/text/Text' import {Text} from '#/view/com/util/text/Text'
import {ScrollView} from '#/view/com/util/Views' import {ScrollView} from '#/view/com/util/Views'
import {ios, useTheme} from '#/alf' import {useTheme} from '#/alf'
import {setNavigationBar} from '#/alf/util/navigationBar' import {setSystemUITheme} from '#/alf/util/systemUI'
import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
import {ImageSource, Transform} from './@types' import {type ImageSource, type Transform} from './@types'
import ImageDefaultHeader from './components/ImageDefaultHeader' import ImageDefaultHeader from './components/ImageDefaultHeader'
import ImageItem from './components/ImageItem/ImageItem' import ImageItem from './components/ImageItem/ImageItem'
@@ -63,10 +56,6 @@ type Rect = {x: number; y: number; width: number; height: number}
const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP
const PIXEL_RATIO = PixelRatio.get() const PIXEL_RATIO = PixelRatio.get()
const EDGES =
Platform.OS === 'android' && Platform.Version < 35
? (['top', 'bottom', 'left', 'right'] satisfies Edge[])
: ([] satisfies Edge[]) // iOS or Android 15+ bleeds into safe area
const SLOW_SPRING: WithSpringConfig = { const SLOW_SPRING: WithSpringConfig = {
mass: isIOS ? 1.25 : 0.75, mass: isIOS ? 1.25 : 0.75,
@@ -167,9 +156,8 @@ export default function ImageViewRoot({
return ( return (
// Keep it always mounted to avoid flicker on the first frame. // Keep it always mounted to avoid flicker on the first frame.
<SafeAreaView <View
style={[styles.screen, !activeLightbox && styles.screenHidden]} style={[styles.screen, !activeLightbox && styles.screenHidden]}
edges={EDGES}
aria-modal aria-modal
accessibilityViewIsModal accessibilityViewIsModal
aria-hidden={!activeLightbox}> aria-hidden={!activeLightbox}>
@@ -197,7 +185,7 @@ export default function ImageViewRoot({
/> />
)} )}
</Animated.View> </Animated.View>
</SafeAreaView> </View>
) )
} }
@@ -325,25 +313,23 @@ function ImageView({
}, },
) )
// style nav bar on android // style system ui on android
const t = useTheme() const t = useTheme()
useEffect(() => { useEffect(() => {
setNavigationBar('lightbox', t) setSystemUITheme('lightbox', t)
return () => { return () => {
setNavigationBar('theme', t) setSystemUITheme('theme', t)
} }
}, [t]) }, [t])
return ( return (
<Animated.View style={[styles.container, containerStyle]}> <Animated.View style={[styles.container, containerStyle]}>
<StatusBar <SystemBars
animated style={{statusBar: 'light', navigationBar: 'light'}}
style="light" hidden={{
hideTransitionAnimation="slide" statusBar: isScaled || !showControls,
backgroundColor="black" navigationBar: false,
// hiding causes layout shifts on android, }}
// so avoid until we add edge-to-edge mode
hidden={ios(isScaled || !showControls)}
/> />
<Animated.View <Animated.View
style={[styles.backdrop, backdropStyle]} style={[styles.backdrop, backdropStyle]}
+2 -2
View File
@@ -8,9 +8,9 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import {Image as RNImage} from 'react-native-image-crop-picker' import {type Image as RNImage} from 'react-native-image-crop-picker'
import {LinearGradient} from 'expo-linear-gradient' import {LinearGradient} from 'expo-linear-gradient'
import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
+11 -14
View File
@@ -1,9 +1,9 @@
import {useCallback, useEffect, useState} from 'react' import {useCallback, useEffect, useState} from 'react'
import {BackHandler, useWindowDimensions, View} from 'react-native' import {BackHandler, useWindowDimensions, View} from 'react-native'
import {Drawer} from 'react-native-drawer-layout' import {Drawer} from 'react-native-drawer-layout'
import {SystemBars} from 'react-native-edge-to-edge'
import {Gesture} from 'react-native-gesture-handler' import {Gesture} from 'react-native-gesture-handler'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {StatusBar} from 'expo-status-bar'
import {useNavigation, useNavigationState} from '@react-navigation/native' import {useNavigation, useNavigationState} from '@react-navigation/native'
import {useDedupe} from '#/lib/hooks/useDedupe' import {useDedupe} from '#/lib/hooks/useDedupe'
@@ -19,13 +19,12 @@ import {
useIsDrawerSwipeDisabled, useIsDrawerSwipeDisabled,
useSetDrawerOpen, useSetDrawerOpen,
} from '#/state/shell' } from '#/state/shell'
import {useLightStatusBar} from '#/state/shell/light-status-bar'
import {useCloseAnyActiveElement} from '#/state/util' import {useCloseAnyActiveElement} from '#/state/util'
import {Lightbox} from '#/view/com/lightbox/Lightbox' import {Lightbox} from '#/view/com/lightbox/Lightbox'
import {ModalsContainer} from '#/view/com/modals/Modal' import {ModalsContainer} from '#/view/com/modals/Modal'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, select, useTheme} from '#/alf' import {atoms as a, select, useTheme} from '#/alf'
import {setNavigationBar} from '#/alf/util/navigationBar' import {setSystemUITheme} from '#/alf/util/systemUI'
import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {MutedWordsDialog} from '#/components/dialogs/MutedWords'
import {SigninDialog} from '#/components/dialogs/Signin' import {SigninDialog} from '#/components/dialogs/Signin'
import {Outlet as PortalOutlet} from '#/components/Portal' import {Outlet as PortalOutlet} from '#/components/Portal'
@@ -161,25 +160,23 @@ function ShellInner() {
export const Shell: React.FC = function ShellImpl() { export const Shell: React.FC = function ShellImpl() {
const {fullyExpandedCount} = useDialogStateControlContext() const {fullyExpandedCount} = useDialogStateControlContext()
const lightStatusBar = useLightStatusBar()
const t = useTheme() const t = useTheme()
useIntentHandler() useIntentHandler()
useEffect(() => { useEffect(() => {
setNavigationBar('theme', t) setSystemUITheme('theme', t)
}, [t]) }, [t])
return ( return (
<View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}> <View testID="mobileShellView" style={[a.h_full, t.atoms.bg]}>
<StatusBar <SystemBars
style={ style={{
t.name !== 'light' || statusBar:
(isIOS && fullyExpandedCount > 0) || t.name !== 'light' || (isIOS && fullyExpandedCount > 0)
lightStatusBar ? 'light'
? 'light' : 'dark',
: 'dark' navigationBar: t.name !== 'light' ? 'light' : 'dark',
} }}
animated
/> />
<RoutesContainer> <RoutesContainer>
<ShellInner /> <ShellInner />
+9 -17
View File
@@ -11067,14 +11067,6 @@ expo-modules-core@^2.1.1:
dependencies: dependencies:
invariant "^2.2.4" invariant "^2.2.4"
expo-navigation-bar@~4.0.9:
version "4.0.9"
resolved "https://registry.yarnpkg.com/expo-navigation-bar/-/expo-navigation-bar-4.0.9.tgz#e0409c2db8f6384d12c87f45c2674effc9fae1b6"
integrity sha512-dCJ04yPixFOUixJaWlmCZafGeQ1L1g6vWn+oX8rqPnYN9kYCMUz2aRNnhRRoK5MBGFTK/nue2D49TE/AwwWt9w==
dependencies:
"@react-native/normalize-colors" "0.76.8"
debug "^4.3.2"
expo-notifications@~0.29.14: expo-notifications@~0.29.14:
version "0.29.14" version "0.29.14"
resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.29.14.tgz#77beb6bc74b1b1abfa3adcab77fb6c9ea5d7d1b0" resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.29.14.tgz#77beb6bc74b1b1abfa3adcab77fb6c9ea5d7d1b0"
@@ -11115,11 +11107,6 @@ expo-splash-screen@~0.29.22:
dependencies: dependencies:
"@expo/prebuild-config" "^8.0.27" "@expo/prebuild-config" "^8.0.27"
expo-status-bar@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/expo-status-bar/-/expo-status-bar-2.0.1.tgz#fc07726346dc30fbb68aadb0d7890b34fba42eee"
integrity sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg==
expo-structured-headers@~4.0.0: expo-structured-headers@~4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/expo-structured-headers/-/expo-structured-headers-4.0.0.tgz#85537ae6daec61ebfb214ede4107c8841c6e16d0" resolved "https://registry.yarnpkg.com/expo-structured-headers/-/expo-structured-headers-4.0.0.tgz#85537ae6daec61ebfb214ede4107c8841c6e16d0"
@@ -16761,6 +16748,11 @@ react-native-drawer-layout@^4.1.1:
dependencies: dependencies:
use-latest-callback "^0.2.1" use-latest-callback "^0.2.1"
react-native-edge-to-edge@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/react-native-edge-to-edge/-/react-native-edge-to-edge-1.6.0.tgz#2ba63b941704a7f713e298185c26cde4d9e4b973"
integrity sha512-2WCNdE3Qd6Fwg9+4BpbATUxCLcouF6YRY7K+J36KJ4l3y+tWN6XCqAC4DuoGblAAbb2sLkhEDp4FOlbOIot2Og==
react-native-emoji-popup@^0.1.2: react-native-emoji-popup@^0.1.2:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/react-native-emoji-popup/-/react-native-emoji-popup-0.1.2.tgz#7cd3874ba0496031e6f3e24de77e0df895168ce6" resolved "https://registry.yarnpkg.com/react-native-emoji-popup/-/react-native-emoji-popup-0.1.2.tgz#7cd3874ba0496031e6f3e24de77e0df895168ce6"
@@ -16807,10 +16799,10 @@ react-native-is-edge-to-edge@^1.1.6:
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902" resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902"
integrity sha512-1pHnFTlBahins6UAajXUqeCOHew9l9C2C8tErnpGC3IyLJzvxD+TpYAixnCbrVS52f7+NvMttbiSI290XfwN0w== integrity sha512-1pHnFTlBahins6UAajXUqeCOHew9l9C2C8tErnpGC3IyLJzvxD+TpYAixnCbrVS52f7+NvMttbiSI290XfwN0w==
react-native-keyboard-controller@^1.14.5: react-native-keyboard-controller@^1.17.1:
version "1.14.5" version "1.17.1"
resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.14.5.tgz#ec1e7d1fb8ee18b69ced4d8ddd6fd99bdaaf14bb" resolved "https://registry.yarnpkg.com/react-native-keyboard-controller/-/react-native-keyboard-controller-1.17.1.tgz#46efe148c1bdd0ee22094dcb2660f70f81e6544e"
integrity sha512-Cx7+SWI/P50i4PKJZN4T43RqoFkJ3GBoxjQ5ysrzZGoImHTF4j3atSwcBQGMmunKCem1yGOOQ84or+Vbcor6wQ== integrity sha512-YM3GYvtkuWimCKkZFURn5hIb1WiKOQqi2DijdwZSF5QSSzGqfqwzEEC3bm1xCN8HGHAEIXAaWIBUsc3Xp5L+Ng==
dependencies: dependencies:
react-native-is-edge-to-edge "^1.1.6" react-native-is-edge-to-edge "^1.1.6"