about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2024-12-24 20:05:54 +0000
committerGitHub <noreply@github.com>2024-12-24 20:05:54 +0000
commit6c9e1d4837e9e385da5ca0c89c28000ad25c70d8 (patch)
tree8de94bf059fc4065cf228b63f50f23840ceda4d8
parentf05962a4928e5102e6abe773e938ae8ab941a2c4 (diff)
downloadvoidsky-6c9e1d4837e9e385da5ca0c89c28000ad25c70d8.tar.zst
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>
-rw-r--r--app.config.js2
-rw-r--r--package.json1
-rw-r--r--src/App.native.tsx7
-rw-r--r--src/alf/util/navigationBar.ts3
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx39
-rw-r--r--yarn.lock5
6 files changed, 51 insertions, 6 deletions
diff --git a/app.config.js b/app.config.js
index 404f02dde..88a71a08e 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 5e1394c2c..8eafdd6a1 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 780295ddc..d1d6b7213 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 face86983..cb315f70a 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 96f78a709..7018d753a 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<View>()
   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}>
-      <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 && (
           <ImageView
-            key={activeLightbox.id}
+            key={activeLightbox.id + '-' + orientation}
             lightbox={activeLightbox}
+            orientation={orientation}
             onRequestClose={onRequestClose}
             onPressSave={onPressSave}
             onPressShare={onPressShare}
@@ -174,6 +203,7 @@ export default function ImageViewRoot({
 
 function ImageView({
   lightbox,
+  orientation,
   onRequestClose,
   onPressSave,
   onPressShare,
@@ -182,6 +212,7 @@ function ImageView({
   openProgress,
 }: {
   lightbox: Lightbox
+  orientation: 'portrait' | 'landscape'
   onRequestClose: () => 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 df8572a28..27c3b4fad 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"