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:
+1
-1
@@ -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: {
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user