From a770f5635b549f2a87ffeaedd031dfe8e37b58c8 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 22 Apr 2025 18:16:50 +0200 Subject: Edge to edge support (#7497) --- src/App.native.tsx | 7 +-- src/App.web.tsx | 7 +-- src/alf/util/navigationBar.ts | 21 --------- src/alf/util/systemUI.ts | 14 ++++++ src/components/ContextMenu/index.tsx | 2 +- src/components/Dialog/sheet-wrapper.ts | 23 ++++++---- src/lib/hooks/useEnableKeyboardController.tsx | 5 +-- src/screens/Login/index.tsx | 2 +- src/screens/Messages/components/MessageInput.tsx | 5 ++- src/screens/SignupQueued.tsx | 4 +- src/screens/Takendown.tsx | 4 +- src/screens/VideoFeed/index.tsx | 8 ++-- src/state/shell/light-status-bar.tsx | 49 +++++---------------- src/view/com/composer/Composer.tsx | 3 +- src/view/com/lightbox/ImageViewing/index.tsx | 56 +++++++++--------------- src/view/com/modals/CreateOrEditList.tsx | 4 +- src/view/shell/index.tsx | 25 +++++------ 17 files changed, 93 insertions(+), 146 deletions(-) delete mode 100644 src/alf/util/navigationBar.ts create mode 100644 src/alf/util/systemUI.ts (limited to 'src') diff --git a/src/App.native.tsx b/src/App.native.tsx index ac985e560..5023c48bb 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -46,14 +46,13 @@ import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' import { Provider as SessionProvider, - SessionAccount, + type SessionAccount, useSession, useSessionApi, } from '#/state/session' import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' 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 ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' @@ -219,9 +218,7 @@ function App() { - - - + diff --git a/src/App.web.tsx b/src/App.web.tsx index af39bee47..bbe23e5a5 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -35,14 +35,13 @@ import {Provider as ModerationOptsProvider} from '#/state/preferences/moderation import {Provider as UnreadNotifsProvider} from '#/state/queries/notifications/unread' import { Provider as SessionProvider, - SessionAccount, + type SessionAccount, useSession, useSessionApi, } from '#/state/session' import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' 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 ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' @@ -193,9 +192,7 @@ function App() { - - - + diff --git a/src/alf/util/navigationBar.ts b/src/alf/util/navigationBar.ts deleted file mode 100644 index cb315f70a..000000000 --- a/src/alf/util/navigationBar.ts +++ /dev/null @@ -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') - } - } -} diff --git a/src/alf/util/systemUI.ts b/src/alf/util/systemUI.ts new file mode 100644 index 000000000..c973e10ea --- /dev/null +++ b/src/alf/util/systemUI.ts @@ -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') + } + } +} diff --git a/src/components/ContextMenu/index.tsx b/src/components/ContextMenu/index.tsx index aebed6419..4a0814dfe 100644 --- a/src/components/ContextMenu/index.tsx +++ b/src/components/ContextMenu/index.tsx @@ -556,7 +556,7 @@ export function Outer({ // pure vibes based const TOP_INSET = insets.top + 80 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 topPosition = diff --git a/src/components/Dialog/sheet-wrapper.ts b/src/components/Dialog/sheet-wrapper.ts index 37c663383..b655dde00 100644 --- a/src/components/Dialog/sheet-wrapper.ts +++ b/src/components/Dialog/sheet-wrapper.ts @@ -1,20 +1,25 @@ 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 * wrap it in this function to make sure the status bar is the correct color. */ export function useSheetWrapper() { - const {setFullyExpandedCount} = useDialogStateControlContext() - return useCallback( - async (promise: Promise): Promise => { - setFullyExpandedCount(c => c + 1) + return useCallback(async (promise: Promise): Promise => { + if (isIOS) { + const entry = SystemBars.pushStackEntry({ + style: { + statusBar: 'light', + }, + }) const res = await promise - setFullyExpandedCount(c => c - 1) + SystemBars.popStackEntry(entry) return res - }, - [setFullyExpandedCount], - ) + } else { + return await promise + } + }, []) } diff --git a/src/lib/hooks/useEnableKeyboardController.tsx b/src/lib/hooks/useEnableKeyboardController.tsx index 366791c0c..858f6943a 100644 --- a/src/lib/hooks/useEnableKeyboardController.tsx +++ b/src/lib/hooks/useEnableKeyboardController.tsx @@ -26,10 +26,7 @@ export function KeyboardControllerProvider({ children: React.ReactNode }) { return ( - + {children} diff --git a/src/screens/Login/index.tsx b/src/screens/Login/index.tsx index 8ed8d2da8..e4e2f43f0 100644 --- a/src/screens/Login/index.tsx +++ b/src/screens/Login/index.tsx @@ -8,7 +8,7 @@ import {DEFAULT_SERVICE} from '#/lib/constants' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' 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 {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' import {ForgotPasswordForm} from '#/screens/Login/ForgotPasswordForm' diff --git a/src/screens/Messages/components/MessageInput.tsx b/src/screens/Messages/components/MessageInput.tsx index ac0f7969f..69cba07f7 100644 --- a/src/screens/Messages/components/MessageInput.tsx +++ b/src/screens/Messages/components/MessageInput.tsx @@ -24,9 +24,9 @@ import { useMessageDraft, useSaveMessageDraft, } 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 {atoms as a, useTheme} from '#/alf' +import {android, atoms as a, useTheme} from '#/alf' import {useSharedInputStyles} from '#/components/forms/TextField' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' import {useExtractEmbedFromFacets} from './MessageInputEmbed' @@ -174,6 +174,7 @@ export function MessageInput({ a.text_md, a.px_sm, t.atoms.text, + android({paddingTop: 0}), {paddingBottom: isIOS ? 5 : 0}, animatedStyle, ]} diff --git a/src/screens/SignupQueued.tsx b/src/screens/SignupQueued.tsx index 823ed0784..6a2c5bbc7 100644 --- a/src/screens/SignupQueued.tsx +++ b/src/screens/SignupQueued.tsx @@ -1,7 +1,7 @@ import React from 'react' import {Modal, ScrollView, View} from 'react-native' +import {SystemBars} from 'react-native-edge-to-edge' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {StatusBar} from 'expo-status-bar' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -106,7 +106,7 @@ export function SignupQueued() { animationType={native('slide')} presentationStyle="formSheet" style={[web(a.util_screen_outer)]}> - {isIOS && } + {isIOS && } - {isIOS && } + {isIOS && } { setMinShellMode(true) - setNavigationBar('lightbox', t) + setSystemUITheme('lightbox', t) return () => { setMinShellMode(false) - setNavigationBar('theme', t) + setSystemUITheme('theme', t) } }, [setMinShellMode, t]), ) @@ -140,6 +141,7 @@ export function VideoFeed({}: NativeStackScreenProps< return ( + (false) -const SetLightStatusBarRefCountContext = createContext -> | null>(null) - -export function useLightStatusBar() { - return useContext(LightStatusBarRefCountContext) -} +import {useEffect} from 'react' +import {SystemBars} from 'react-native-edge-to-edge' export function useSetLightStatusBar(enabled: boolean) { - const setRefCount = useContext(SetLightStatusBarRefCountContext) useEffect(() => { - // noop on web -sfn - if (isWeb) return - - if (!setRefCount) { - if (__DEV__) - console.error( - 'useLightStatusBar was used without a SetLightStatusBarRefCountContext provider', - ) - return - } if (enabled) { - setRefCount(prev => prev + 1) - return () => setRefCount(prev => prev - 1) + const entry = SystemBars.pushStackEntry({ + style: { + statusBar: 'light', + }, + }) + return () => { + SystemBars.popStackEntry(entry) + } } - }, [enabled, setRefCount]) -} - -export function Provider({children}: React.PropsWithChildren<{}>) { - const [refCount, setRefCount] = useState(0) - - return ( - - 0}> - {children} - - - ) + }, [enabled]) } diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 4384783dc..aa27adb3d 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1464,8 +1464,7 @@ function useKeyboardVerticalOffset() { // Android etc if (!isIOS) { - // if Android <35 or web, bottom is 0 anyway. if >=35, this is needed to account - // for the edge-to-edge nav bar + // need to account for the edge-to-edge nav bar return bottom * -1 } diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 7018d753a..41a54eba6 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -9,22 +9,17 @@ // https://github.com/jobtoday/react-native-image-viewing import React, {useCallback, useEffect, useMemo, useState} from 'react' -import { - LayoutAnimation, - PixelRatio, - Platform, - StyleSheet, - View, -} from 'react-native' +import {LayoutAnimation, PixelRatio, StyleSheet, View} from 'react-native' +import {SystemBars} from 'react-native-edge-to-edge' import {Gesture} from 'react-native-gesture-handler' import PagerView from 'react-native-pager-view' import Animated, { - AnimatedRef, + type AnimatedRef, cancelAnimation, interpolate, measure, runOnJS, - SharedValue, + type SharedValue, useAnimatedReaction, useAnimatedRef, useAnimatedStyle, @@ -32,30 +27,28 @@ import Animated, { useSharedValue, withDecay, withSpring, - WithSpringConfig, + type WithSpringConfig, } from 'react-native-reanimated' import { - Edge, SafeAreaView, useSafeAreaFrame, useSafeAreaInsets, } from 'react-native-safe-area-context' import * as ScreenOrientation from 'expo-screen-orientation' -import {StatusBar} from 'expo-status-bar' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 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 {isIOS} from '#/platform/detection' -import {Lightbox} from '#/state/lightbox' +import {type Lightbox} from '#/state/lightbox' import {Button} from '#/view/com/util/forms/Button' import {Text} from '#/view/com/util/text/Text' import {ScrollView} from '#/view/com/util/Views' -import {ios, useTheme} from '#/alf' -import {setNavigationBar} from '#/alf/util/navigationBar' +import {useTheme} from '#/alf' +import {setSystemUITheme} from '#/alf/util/systemUI' 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 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 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 = { mass: isIOS ? 1.25 : 0.75, @@ -167,9 +156,8 @@ export default function ImageViewRoot({ return ( // Keep it always mounted to avoid flicker on the first frame. - @@ -197,7 +185,7 @@ export default function ImageViewRoot({ /> )} - + ) } @@ -325,25 +313,23 @@ function ImageView({ }, ) - // style nav bar on android + // style system ui on android const t = useTheme() useEffect(() => { - setNavigationBar('lightbox', t) + setSystemUITheme('lightbox', t) return () => { - setNavigationBar('theme', t) + setSystemUITheme('theme', t) } }, [t]) return ( -