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 <dan.abramov@gmail.com>
This commit is contained in:
Samuel Newman
2024-12-24 20:05:54 +00:00
committed by GitHub
parent f05962a492
commit 6c9e1d4837
6 changed files with 51 additions and 6 deletions
+1 -1
View File
@@ -50,7 +50,6 @@ module.exports = function (config) {
runtimeVersion: { runtimeVersion: {
policy: 'appVersion', policy: 'appVersion',
}, },
orientation: 'portrait',
icon: './assets/app-icons/ios_icon_default_light.png', icon: './assets/app-icons/ios_icon_default_light.png',
userInterfaceStyle: 'automatic', userInterfaceStyle: 'automatic',
primaryColor: '#1083fe', primaryColor: '#1083fe',
@@ -346,6 +345,7 @@ module.exports = function (config) {
}, },
}, },
], ],
['expo-screen-orientation', {initialOrientation: 'PORTRAIT_UP'}],
].filter(Boolean), ].filter(Boolean),
extra: { extra: {
eas: { eas: {
+1
View File
@@ -138,6 +138,7 @@
"expo-media-library": "~17.0.3", "expo-media-library": "~17.0.3",
"expo-navigation-bar": "~4.0.4", "expo-navigation-bar": "~4.0.4",
"expo-notifications": "~0.29.11", "expo-notifications": "~0.29.11",
"expo-screen-orientation": "^8.0.2",
"expo-sharing": "^13.0.0", "expo-sharing": "^13.0.0",
"expo-splash-screen": "~0.29.18", "expo-splash-screen": "~0.29.18",
"expo-status-bar": "~2.0.0", "expo-status-bar": "~2.0.0",
+6 -1
View File
@@ -10,6 +10,7 @@ import {
initialWindowMetrics, initialWindowMetrics,
SafeAreaProvider, SafeAreaProvider,
} from 'react-native-safe-area-context' } from 'react-native-safe-area-context'
import * as ScreenOrientation from 'expo-screen-orientation'
import * as SplashScreen from 'expo-splash-screen' import * as SplashScreen from 'expo-splash-screen'
import * as SystemUI from 'expo-system-ui' import * as SystemUI from 'expo-system-ui'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
@@ -22,7 +23,7 @@ import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext' import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider' import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger' 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 A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs' import {Provider as DialogStateProvider} from '#/state/dialogs'
@@ -77,6 +78,10 @@ SplashScreen.preventAutoHideAsync()
if (isIOS) { if (isIOS) {
SystemUI.setBackgroundColorAsync('black') SystemUI.setBackgroundColorAsync('black')
} }
if (isAndroid) {
// iOS is handled by the config plugin -sfn
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP)
}
/** /**
* Begin geolocation ASAP * Begin geolocation ASAP
+3
View File
@@ -1,4 +1,5 @@
import * as NavigationBar from 'expo-navigation-bar' import * as NavigationBar from 'expo-navigation-bar'
import * as SystemUI from 'expo-system-ui'
import {isAndroid} from '#/platform/detection' import {isAndroid} from '#/platform/detection'
import {Theme} from '../types' import {Theme} from '../types'
@@ -9,10 +10,12 @@ export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) {
NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor) NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor) NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark') NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark')
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
} else { } else {
NavigationBar.setBackgroundColorAsync('black') NavigationBar.setBackgroundColorAsync('black')
NavigationBar.setBorderColorAsync('black') NavigationBar.setBorderColorAsync('black')
NavigationBar.setButtonStyleAsync('light') NavigationBar.setButtonStyleAsync('light')
SystemUI.setBackgroundColorAsync('black')
} }
} }
} }
+35 -4
View File
@@ -40,6 +40,7 @@ import {
useSafeAreaFrame, useSafeAreaFrame,
useSafeAreaInsets, useSafeAreaInsets,
} from 'react-native-safe-area-context' } from 'react-native-safe-area-context'
import * as ScreenOrientation from 'expo-screen-orientation'
import {StatusBar} from 'expo-status-bar' 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'
@@ -60,11 +61,12 @@ import ImageItem from './components/ImageItem/ImageItem'
type Rect = {x: number; y: number; width: number; height: number} type Rect = {x: number; y: number; width: number; height: number}
const PORTRAIT_UP = ScreenOrientation.OrientationLock.PORTRAIT_UP
const PIXEL_RATIO = PixelRatio.get() const PIXEL_RATIO = PixelRatio.get()
const EDGES = const EDGES =
Platform.OS === 'android' && Platform.Version < 35 Platform.OS === 'android' && Platform.Version < 35
? (['top', 'bottom', 'left', 'right'] satisfies Edge[]) ? (['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 = { const SLOW_SPRING: WithSpringConfig = {
mass: isIOS ? 1.25 : 0.75, mass: isIOS ? 1.25 : 0.75,
@@ -102,6 +104,9 @@ export default function ImageViewRoot({
'use no memo' 'use no memo'
const ref = useAnimatedRef<View>() const ref = useAnimatedRef<View>()
const [activeLightbox, setActiveLightbox] = useState(nextLightbox) const [activeLightbox, setActiveLightbox] = useState(nextLightbox)
const [orientation, setOrientation] = useState<'portrait' | 'landscape'>(
'portrait',
)
const openProgress = useSharedValue(0) const openProgress = useSharedValue(0)
if (!activeLightbox && nextLightbox) { 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(() => { const onFlyAway = React.useCallback(() => {
'worklet' 'worklet'
openProgress.set(0) openProgress.set(0)
@@ -154,11 +173,21 @@ export default function ImageViewRoot({
aria-modal aria-modal
accessibilityViewIsModal accessibilityViewIsModal
aria-hidden={!activeLightbox}> aria-hidden={!activeLightbox}>
<Animated.View ref={ref} style={{flex: 1}} collapsable={false}> <Animated.View
ref={ref}
style={{flex: 1}}
collapsable={false}
onLayout={e => {
const layout = e.nativeEvent.layout
setOrientation(
layout.height > layout.width ? 'portrait' : 'landscape',
)
}}>
{activeLightbox && ( {activeLightbox && (
<ImageView <ImageView
key={activeLightbox.id} key={activeLightbox.id + '-' + orientation}
lightbox={activeLightbox} lightbox={activeLightbox}
orientation={orientation}
onRequestClose={onRequestClose} onRequestClose={onRequestClose}
onPressSave={onPressSave} onPressSave={onPressSave}
onPressShare={onPressShare} onPressShare={onPressShare}
@@ -174,6 +203,7 @@ export default function ImageViewRoot({
function ImageView({ function ImageView({
lightbox, lightbox,
orientation,
onRequestClose, onRequestClose,
onPressSave, onPressSave,
onPressShare, onPressShare,
@@ -182,6 +212,7 @@ function ImageView({
openProgress, openProgress,
}: { }: {
lightbox: Lightbox lightbox: Lightbox
orientation: 'portrait' | 'landscape'
onRequestClose: () => void onRequestClose: () => void
onPressSave: (uri: string) => void onPressSave: (uri: string) => void
onPressShare: (uri: string) => void onPressShare: (uri: string) => void
@@ -221,7 +252,7 @@ function ImageView({
const openProgressValue = openProgress.get() const openProgressValue = openProgress.get()
if (openProgressValue < 1) { if (openProgressValue < 1) {
opacity = Math.sqrt(openProgressValue) opacity = Math.sqrt(openProgressValue)
} else if (screenSize) { } else if (screenSize && orientation === 'portrait') {
const dragProgress = Math.min( const dragProgress = Math.min(
Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2), Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2),
1, 1,
+5
View File
@@ -10621,6 +10621,11 @@ expo-pwa@0.0.127:
commander "2.20.0" commander "2.20.0"
update-check "1.5.3" 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: expo-sharing@^13.0.0:
version "13.0.0" version "13.0.0"
resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f" resolved "https://registry.yarnpkg.com/expo-sharing/-/expo-sharing-13.0.0.tgz#fbc46f4afdaa265a2811fe88c2a589aae2d2de0f"