From 6c9e1d4837e9e385da5ca0c89c28000ad25c70d8 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 24 Dec 2024 20:05:54 +0000 Subject: [PATCH] Unlock orientation when lightbox is open (#7257) * unlock orientation when lightbox is open * rm outer safe area view, make sure alt text is safe * restore safe area view for android 14 and below * lock orientation on launch for android * set system ui background to black when lightbox is open * reset state on relayout * catch async functions with noops * rm superfluous catches * Delay unlock until after animation * Simplify how key is determined * Make landscape backdrop opaque --------- Co-authored-by: Dan Abramov --- app.config.js | 2 +- package.json | 1 + src/App.native.tsx | 7 +++- src/alf/util/navigationBar.ts | 3 ++ src/view/com/lightbox/ImageViewing/index.tsx | 39 ++++++++++++++++++-- yarn.lock | 5 +++ 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app.config.js b/app.config.js index 404f02ddef..88a71a08e1 100644 --- a/app.config.js +++ b/app.config.js @@ -50,7 +50,6 @@ module.exports = function (config) { runtimeVersion: { policy: 'appVersion', }, - orientation: 'portrait', icon: './assets/app-icons/ios_icon_default_light.png', userInterfaceStyle: 'automatic', primaryColor: '#1083fe', @@ -346,6 +345,7 @@ module.exports = function (config) { }, }, ], + ['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}], ].filter(Boolean), extra: { eas: { diff --git a/package.json b/package.json index 5e1394c2cf..8eafdd6a19 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "expo-media-library": "~17.0.3", "expo-navigation-bar": "~4.0.4", "expo-notifications": "~0.29.11", + "expo-screen-orientation": "^8.0.2", "expo-sharing": "^13.0.0", "expo-splash-screen": "~0.29.18", "expo-status-bar": "~2.0.0", diff --git a/src/App.native.tsx b/src/App.native.tsx index 780295ddce..d1d6b7213e 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -10,6 +10,7 @@ import { initialWindowMetrics, SafeAreaProvider, } from 'react-native-safe-area-context' +import * as ScreenOrientation from 'expo-screen-orientation' import * as SplashScreen from 'expo-splash-screen' import * as SystemUI from 'expo-system-ui' import {msg} from '@lingui/macro' @@ -22,7 +23,7 @@ import {s} from '#/lib/styles' import {ThemeProvider} from '#/lib/ThemeContext' import I18nProvider from '#/locale/i18nProvider' import {logger} from '#/logger' -import {isIOS} from '#/platform/detection' +import {isAndroid, isIOS} from '#/platform/detection' import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' @@ -77,6 +78,10 @@ SplashScreen.preventAutoHideAsync() if (isIOS) { SystemUI.setBackgroundColorAsync('black') } +if (isAndroid) { + // iOS is handled by the config plugin -sfn + ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) +} /** * Begin geolocation ASAP diff --git a/src/alf/util/navigationBar.ts b/src/alf/util/navigationBar.ts index face869837..cb315f70a2 100644 --- a/src/alf/util/navigationBar.ts +++ b/src/alf/util/navigationBar.ts @@ -1,4 +1,5 @@ import * as NavigationBar from 'expo-navigation-bar' +import * as SystemUI from 'expo-system-ui' import {isAndroid} from '#/platform/detection' import {Theme} from '../types' @@ -9,10 +10,12 @@ export function setNavigationBar(themeType: 'theme' | 'lightbox', t: 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/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 96f78a709e..7018d753a2 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -40,6 +40,7 @@ import { 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' @@ -60,11 +61,12 @@ import ImageItem from './components/ImageItem/ImageItem' 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[]) - : (['left', 'right'] satisfies Edge[]) // iOS or Android 15+, so no top/bottom safe area + : ([] satisfies Edge[]) // iOS or Android 15+ bleeds into safe area const SLOW_SPRING: WithSpringConfig = { mass: isIOS ? 1.25 : 0.75, @@ -102,6 +104,9 @@ export default function ImageViewRoot({ 'use no memo' const ref = useAnimatedRef() const [activeLightbox, setActiveLightbox] = useState(nextLightbox) + const [orientation, setOrientation] = useState<'portrait' | 'landscape'>( + 'portrait', + ) const openProgress = useSharedValue(0) if (!activeLightbox && nextLightbox) { @@ -140,6 +145,20 @@ export default function ImageViewRoot({ }, ) + // Delay the unlock until after we've finished the scale up animation. + // It's complicated to do the same for locking it back so we don't attempt that. + useAnimatedReaction( + () => openProgress.get() === 1, + (isOpen, wasOpen) => { + if (isOpen && !wasOpen) { + runOnJS(ScreenOrientation.unlockAsync)() + } else if (!isOpen && wasOpen) { + // default is PORTRAIT_UP - set via config plugin in app.config.js -sfn + runOnJS(ScreenOrientation.lockAsync)(PORTRAIT_UP) + } + }, + ) + const onFlyAway = React.useCallback(() => { 'worklet' openProgress.set(0) @@ -154,11 +173,21 @@ export default function ImageViewRoot({ aria-modal accessibilityViewIsModal aria-hidden={!activeLightbox}> - + { + const layout = e.nativeEvent.layout + setOrientation( + layout.height > layout.width ? 'portrait' : 'landscape', + ) + }}> {activeLightbox && ( void onPressSave: (uri: string) => void onPressShare: (uri: string) => void @@ -221,7 +252,7 @@ function ImageView({ const openProgressValue = openProgress.get() if (openProgressValue < 1) { opacity = Math.sqrt(openProgressValue) - } else if (screenSize) { + } else if (screenSize && orientation === 'portrait') { const dragProgress = Math.min( Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2), 1, diff --git a/yarn.lock b/yarn.lock index df8572a289..27c3b4fad6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10621,6 +10621,11 @@ expo-pwa@0.0.127: commander "2.20.0" update-check "1.5.3" +expo-screen-orientation@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/expo-screen-orientation/-/expo-screen-orientation-8.0.2.tgz#69139a1967557a331188d36b4dd615a0e220c2a0" + integrity sha512-YsY7Oumlv1WsHLQVgl1f+vAiMZfzUPGyVF2xc7jxmHKtM+jMzIflDl2qKLGfoB0S9Pgg6Z8V4+c+A8wlCURw4A== + expo-sharing@^13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f"