about summary refs log tree commit diff
path: root/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-11-06 00:21:35 +0000
committerGitHub <noreply@github.com>2024-11-06 00:21:35 +0000
commit206df2ab801d211a412f9ce3694d90bdd053caaa (patch)
treee185c0694eba262c48bd08fc0f430dd0fb203a47 /src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
parent6b826fb88dd33fe594fd7bb631a90d1a1713d0df (diff)
downloadvoidsky-206df2ab801d211a412f9ce3694d90bdd053caaa.tar.zst
Remove SCREEN from lightbox layout (#6124)
* Assign an ID to lightbox and use it as a key

* Consolidate lightbox props into an object

* Remove unused prop

* Move SafeAreaView declaration

* Keep SafeAreaView always mounted

When exploring Android animation, I noticed its content jumps on the first frame. I think this should help prevent that.

* Pass safe area down for measurement

* Remove dependency on SCREEN in Android event handlers

* Remove dependency on SCREEN in iOS event handlers

* Remove dependency on SCREEN on iOS

* Remove dependency on SCREEN on Android

* Remove dependency on JS calc in controls

* Use flex for iOS layout
Diffstat (limited to 'src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx')
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx83
1 files changed, 48 insertions, 35 deletions
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
index 487acf931..ea77ec273 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
@@ -1,7 +1,9 @@
 import React, {useState} from 'react'
-import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native'
+import {ActivityIndicator, StyleSheet, View} from 'react-native'
 import {Gesture, GestureDetector} from 'react-native-gesture-handler'
 import Animated, {
+  AnimatedRef,
+  measure,
   runOnJS,
   useAnimatedReaction,
   useAnimatedRef,
@@ -24,13 +26,6 @@ import {
   TransformMatrix,
 } from '../../transforms'
 
-const windowDim = Dimensions.get('window')
-const screenDim = Dimensions.get('screen')
-const statusBarHeight = windowDim.height - screenDim.height
-const SCREEN = {
-  width: windowDim.width,
-  height: windowDim.height + statusBarHeight,
-}
 const MIN_DOUBLE_TAP_SCALE = 2
 const MAX_ORIGINAL_IMAGE_ZOOM = 2
 
@@ -43,6 +38,7 @@ type Props = {
   onZoom: (isZoomed: boolean) => void
   isScrollViewBeingDragged: boolean
   showControls: boolean
+  safeAreaRef: AnimatedRef<View>
 }
 const ImageItem = ({
   imageSrc,
@@ -50,6 +46,7 @@ const ImageItem = ({
   onZoom,
   onRequestClose,
   isScrollViewBeingDragged,
+  safeAreaRef,
 }: Props) => {
   const [isScaled, setIsScaled] = useState(false)
   const [imageAspect, imageDimensions] = useImageDimensions({
@@ -102,10 +99,10 @@ const ImageItem = ({
     const [translateX, translateY, scale] = readTransform(t)
 
     const dismissDistance = dismissSwipeTranslateY.value
-    const dismissProgress = Math.min(
-      Math.abs(dismissDistance) / (SCREEN.height / 2),
-      1,
-    )
+    const screenSize = measure(safeAreaRef)
+    const dismissProgress = screenSize
+      ? Math.min(Math.abs(dismissDistance) / (screenSize.height / 2), 1)
+      : 0
     return {
       opacity: 1 - dismissProgress,
       transform: [
@@ -120,6 +117,7 @@ const ImageItem = ({
   // If the user tried to pan too hard, this function will provide the negative panning to stay in bounds.
   function getExtraTranslationToStayInBounds(
     candidateTransform: TransformMatrix,
+    screenSize: {width: number; height: number},
   ) {
     'worklet'
     if (!imageAspect) {
@@ -127,16 +125,20 @@ const ImageItem = ({
     }
     const [nextTranslateX, nextTranslateY, nextScale] =
       readTransform(candidateTransform)
-    const scaledDimensions = getScaledDimensions(imageAspect, nextScale)
+    const scaledDimensions = getScaledDimensions(
+      imageAspect,
+      nextScale,
+      screenSize,
+    )
     const clampedTranslateX = clampTranslation(
       nextTranslateX,
       scaledDimensions.width,
-      SCREEN.width,
+      screenSize.width,
     )
     const clampedTranslateY = clampTranslation(
       nextTranslateY,
       scaledDimensions.height,
-      SCREEN.height,
+      screenSize.height,
     )
     const dx = clampedTranslateX - nextTranslateX
     const dy = clampedTranslateY - nextTranslateY
@@ -146,21 +148,26 @@ const ImageItem = ({
   const pinch = Gesture.Pinch()
     .onStart(e => {
       'worklet'
+      const screenSize = measure(safeAreaRef)
+      if (!screenSize) {
+        return
+      }
       pinchOrigin.value = {
-        x: e.focalX - SCREEN.width / 2,
-        y: e.focalY - SCREEN.height / 2,
+        x: e.focalX - screenSize.width / 2,
+        y: e.focalY - screenSize.height / 2,
       }
     })
     .onChange(e => {
       'worklet'
-      if (!imageDimensions) {
+      const screenSize = measure(safeAreaRef)
+      if (!imageDimensions || !screenSize) {
         return
       }
       // Don't let the picture zoom in so close that it gets blurry.
       // Also, like in stock Android apps, don't let the user zoom out further than 1:1.
       const [, , committedScale] = readTransform(committedTransform.value)
       const maxCommittedScale =
-        (imageDimensions.width / SCREEN.width) * MAX_ORIGINAL_IMAGE_ZOOM
+        (imageDimensions.width / screenSize.width) * MAX_ORIGINAL_IMAGE_ZOOM
       const minPinchScale = 1 / committedScale
       const maxPinchScale = maxCommittedScale / committedScale
       const nextPinchScale = Math.min(
@@ -175,7 +182,7 @@ const ImageItem = ({
       prependPan(t, panTranslation.value)
       prependPinch(t, nextPinchScale, pinchOrigin.value, pinchTranslation.value)
       prependTransform(t, committedTransform.value)
-      const [dx, dy] = getExtraTranslationToStayInBounds(t)
+      const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
       if (dx !== 0 || dy !== 0) {
         pinchTranslation.value = {
           x: pinchTranslation.value.x + dx,
@@ -209,9 +216,11 @@ const ImageItem = ({
     .minPointers(isScaled ? 1 : 2)
     .onChange(e => {
       'worklet'
-      if (!imageDimensions) {
+      const screenSize = measure(safeAreaRef)
+      if (!imageDimensions || !screenSize) {
         return
       }
+
       const nextPanTranslation = {x: e.translationX, y: e.translationY}
       let t = createTransform()
       prependPan(t, nextPanTranslation)
@@ -224,7 +233,7 @@ const ImageItem = ({
       prependTransform(t, committedTransform.value)
 
       // Prevent panning from going out of bounds.
-      const [dx, dy] = getExtraTranslationToStayInBounds(t)
+      const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
       nextPanTranslation.x += dx
       nextPanTranslation.y += dy
       panTranslation.value = nextPanTranslation
@@ -251,7 +260,8 @@ const ImageItem = ({
     .numberOfTaps(2)
     .onEnd(e => {
       'worklet'
-      if (!imageDimensions || !imageAspect) {
+      const screenSize = measure(safeAreaRef)
+      if (!imageDimensions || !imageAspect || !screenSize) {
         return
       }
       const [, , committedScale] = readTransform(committedTransform.value)
@@ -263,7 +273,7 @@ const ImageItem = ({
       }
 
       // Try to zoom in so that we get rid of the black bars (whatever the orientation was).
-      const screenAspect = SCREEN.width / SCREEN.height
+      const screenAspect = screenSize.width / screenSize.height
       const candidateScale = Math.max(
         imageAspect / screenAspect,
         screenAspect / imageAspect,
@@ -271,20 +281,23 @@ const ImageItem = ({
       )
       // But don't zoom in so close that the picture gets blurry.
       const maxScale =
-        (imageDimensions.width / SCREEN.width) * MAX_ORIGINAL_IMAGE_ZOOM
+        (imageDimensions.width / screenSize.width) * MAX_ORIGINAL_IMAGE_ZOOM
       const scale = Math.min(candidateScale, maxScale)
 
       // Calculate where we would be if the user pinched into the double tapped point.
       // We won't use this transform directly because it may go out of bounds.
       const candidateTransform = createTransform()
       const origin = {
-        x: e.absoluteX - SCREEN.width / 2,
-        y: e.absoluteY - SCREEN.height / 2,
+        x: e.absoluteX - screenSize.width / 2,
+        y: e.absoluteY - screenSize.height / 2,
       }
       prependPinch(candidateTransform, scale, origin, {x: 0, y: 0})
 
       // Now we know how much we went out of bounds, so we can shoot correctly.
-      const [dx, dy] = getExtraTranslationToStayInBounds(candidateTransform)
+      const [dx, dy] = getExtraTranslationToStayInBounds(
+        candidateTransform,
+        screenSize,
+      )
       const finalTransform = createTransform()
       prependPinch(finalTransform, scale, origin, {x: dx, y: dy})
       committedTransform.value = withClampedSpring(finalTransform)
@@ -348,8 +361,7 @@ const ImageItem = ({
 
 const styles = StyleSheet.create({
   container: {
-    width: SCREEN.width,
-    height: SCREEN.height,
+    height: '100%',
     overflow: 'hidden',
   },
   image: {
@@ -367,19 +379,20 @@ const styles = StyleSheet.create({
 function getScaledDimensions(
   imageAspect: number,
   scale: number,
+  screenSize: {width: number; height: number},
 ): ImageDimensions {
   'worklet'
-  const screenAspect = SCREEN.width / SCREEN.height
+  const screenAspect = screenSize.width / screenSize.height
   const isLandscape = imageAspect > screenAspect
   if (isLandscape) {
     return {
-      width: scale * SCREEN.width,
-      height: (scale * SCREEN.width) / imageAspect,
+      width: scale * screenSize.width,
+      height: (scale * screenSize.width) / imageAspect,
     }
   } else {
     return {
-      width: scale * SCREEN.height * imageAspect,
-      height: scale * SCREEN.height,
+      width: scale * screenSize.height * imageAspect,
+      height: scale * screenSize.height,
     }
   }
 }