about summary refs log tree commit diff
path: root/src/view/com/lightbox/ImageViewing/components/ImageItem
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/lightbox/ImageViewing/components/ImageItem')
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx105
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx95
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx9
3 files changed, 91 insertions, 118 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 ed6020000..17c386771 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
@@ -1,6 +1,10 @@
 import React, {useState} from 'react'
-import {ActivityIndicator, StyleSheet, View} from 'react-native'
-import {Gesture, GestureDetector} from 'react-native-gesture-handler'
+import {ActivityIndicator, StyleProp, StyleSheet, View} from 'react-native'
+import {
+  Gesture,
+  GestureDetector,
+  PanGesture,
+} from 'react-native-gesture-handler'
 import Animated, {
   AnimatedRef,
   measure,
@@ -9,12 +13,10 @@ import Animated, {
   useAnimatedRef,
   useAnimatedStyle,
   useSharedValue,
-  withDecay,
   withSpring,
 } from 'react-native-reanimated'
-import {Image} from 'expo-image'
+import {Image, ImageStyle} from 'expo-image'
 
-import {useImageDimensions} from '#/lib/media/image-sizes'
 import type {Dimensions as ImageDimensions, ImageSource} from '../../@types'
 import {
   applyRounding,
@@ -26,6 +28,8 @@ import {
   TransformMatrix,
 } from '../../transforms'
 
+const AnimatedImage = Animated.createAnimatedComponent(Image)
+
 const MIN_SCREEN_ZOOM = 2
 const MAX_ORIGINAL_IMAGE_ZOOM = 2
 
@@ -39,26 +43,28 @@ type Props = {
   isScrollViewBeingDragged: boolean
   showControls: boolean
   safeAreaRef: AnimatedRef<View>
+  imageAspect: number | undefined
+  imageDimensions: ImageDimensions | undefined
+  imageStyle: StyleProp<ImageStyle>
+  dismissSwipePan: PanGesture
 }
 const ImageItem = ({
   imageSrc,
   onTap,
   onZoom,
-  onRequestClose,
   isScrollViewBeingDragged,
   safeAreaRef,
+  imageAspect,
+  imageDimensions,
+  imageStyle,
+  dismissSwipePan,
 }: Props) => {
   const [isScaled, setIsScaled] = useState(false)
-  const [imageAspect, imageDimensions] = useImageDimensions({
-    src: imageSrc.uri,
-    knownDimensions: imageSrc.dimensions,
-  })
   const committedTransform = useSharedValue(initialTransform)
   const panTranslation = useSharedValue({x: 0, y: 0})
   const pinchOrigin = useSharedValue({x: 0, y: 0})
   const pinchScale = useSharedValue(1)
   const pinchTranslation = useSharedValue({x: 0, y: 0})
-  const dismissSwipeTranslateY = useSharedValue(0)
   const containerRef = useAnimatedRef()
 
   // Keep track of when we're entering or leaving scaled rendering.
@@ -97,19 +103,8 @@ const ImageItem = ({
     prependPinch(t, pinchScale.value, pinchOrigin.value, pinchTranslation.value)
     prependTransform(t, committedTransform.value)
     const [translateX, translateY, scale] = readTransform(t)
-
-    const dismissDistance = dismissSwipeTranslateY.value
-    const screenSize = measure(safeAreaRef)
-    const dismissProgress = screenSize
-      ? Math.min(Math.abs(dismissDistance) / (screenSize.height / 2), 1)
-      : 0
     return {
-      opacity: 1 - dismissProgress,
-      transform: [
-        {translateX},
-        {translateY: translateY + dismissDistance},
-        {scale},
-      ],
+      transform: [{translateX}, {translateY: translateY}, {scale}],
     }
   })
 
@@ -307,28 +302,6 @@ const ImageItem = ({
       committedTransform.value = withClampedSpring(finalTransform)
     })
 
-  const dismissSwipePan = Gesture.Pan()
-    .enabled(!isScaled)
-    .activeOffsetY([-10, 10])
-    .failOffsetX([-10, 10])
-    .maxPointers(1)
-    .onUpdate(e => {
-      'worklet'
-      dismissSwipeTranslateY.value = e.translationY
-    })
-    .onEnd(e => {
-      'worklet'
-      if (Math.abs(e.velocityY) > 1000) {
-        dismissSwipeTranslateY.value = withDecay({velocity: e.velocityY})
-        runOnJS(onRequestClose)()
-      } else {
-        dismissSwipeTranslateY.value = withSpring(0, {
-          stiffness: 700,
-          damping: 50,
-        })
-      }
-    })
-
   const composedGesture = isScrollViewBeingDragged
     ? // If the parent is not at rest, provide a no-op gesture.
       Gesture.Manual()
@@ -340,26 +313,28 @@ const ImageItem = ({
       )
 
   return (
-    <Animated.View
-      ref={containerRef}
-      // Necessary to make opacity work for both children together.
-      renderToHardwareTextureAndroid
-      style={[styles.container, animatedStyle]}>
-      <ActivityIndicator size="small" color="#FFF" style={styles.loading} />
-      <GestureDetector gesture={composedGesture}>
-        <Image
-          contentFit="contain"
-          source={{uri: imageSrc.uri}}
-          placeholderContentFit="contain"
-          placeholder={{uri: imageSrc.thumbUri}}
-          style={styles.image}
-          accessibilityLabel={imageSrc.alt}
-          accessibilityHint=""
-          accessibilityIgnoresInvertColors
-          cachePolicy="memory"
-        />
-      </GestureDetector>
-    </Animated.View>
+    <GestureDetector gesture={composedGesture}>
+      <Animated.View style={imageStyle} renderToHardwareTextureAndroid>
+        <Animated.View
+          ref={containerRef}
+          // Necessary to make opacity work for both children together.
+          renderToHardwareTextureAndroid
+          style={[styles.container, animatedStyle]}>
+          <ActivityIndicator size="small" color="#FFF" style={styles.loading} />
+          <AnimatedImage
+            contentFit="contain"
+            source={{uri: imageSrc.uri}}
+            placeholderContentFit="contain"
+            placeholder={{uri: imageSrc.thumbUri}}
+            style={[styles.image]}
+            accessibilityLabel={imageSrc.alt}
+            accessibilityHint=""
+            accessibilityIgnoresInvertColors
+            cachePolicy="memory"
+          />
+        </Animated.View>
+      </Animated.View>
+    </GestureDetector>
   )
 }
 
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
index a17d4fe66..b4bbfb4d5 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
@@ -7,26 +7,27 @@
  */
 
 import React, {useState} from 'react'
-import {ActivityIndicator, StyleSheet, View} from 'react-native'
-import {Gesture, GestureDetector} from 'react-native-gesture-handler'
+import {ActivityIndicator, StyleProp, StyleSheet, View} from 'react-native'
+import {
+  Gesture,
+  GestureDetector,
+  PanGesture,
+} from 'react-native-gesture-handler'
 import Animated, {
   AnimatedRef,
-  interpolate,
   measure,
   runOnJS,
   useAnimatedRef,
   useAnimatedStyle,
-  useSharedValue,
 } from 'react-native-reanimated'
 import {useSafeAreaFrame} from 'react-native-safe-area-context'
-import {Image} from 'expo-image'
+import {Image, ImageStyle} from 'expo-image'
 
 import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
-import {useImageDimensions} from '#/lib/media/image-sizes'
-import {ImageSource} from '../../@types'
+import {Dimensions as ImageDimensions, ImageSource} from '../../@types'
+
+const AnimatedImage = Animated.createAnimatedComponent(Image)
 
-const SWIPE_CLOSE_OFFSET = 75
-const SWIPE_CLOSE_VELOCITY = 1
 const MAX_ORIGINAL_IMAGE_ZOOM = 2
 const MIN_SCREEN_ZOOM = 2
 
@@ -38,24 +39,26 @@ type Props = {
   isScrollViewBeingDragged: boolean
   showControls: boolean
   safeAreaRef: AnimatedRef<View>
+  imageAspect: number | undefined
+  imageDimensions: ImageDimensions | undefined
+  imageStyle: StyleProp<ImageStyle>
+  dismissSwipePan: PanGesture
 }
 
 const ImageItem = ({
   imageSrc,
   onTap,
   onZoom,
-  onRequestClose,
   showControls,
   safeAreaRef,
+  imageAspect,
+  imageDimensions,
+  imageStyle,
+  dismissSwipePan,
 }: Props) => {
   const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
-  const translationY = useSharedValue(0)
   const [scaled, setScaled] = useState(false)
   const screenSizeDelayedForJSThreadOnly = useSafeAreaFrame()
-  const [imageAspect, imageDimensions] = useImageDimensions({
-    src: imageSrc.uri,
-    knownDimensions: imageSrc.dimensions,
-  })
   const maxZoomScale = Math.max(
     MIN_SCREEN_ZOOM,
     imageDimensions
@@ -65,33 +68,21 @@ const ImageItem = ({
   )
 
   const animatedStyle = useAnimatedStyle(() => {
+    const screenSize = measure(safeAreaRef) ?? screenSizeDelayedForJSThreadOnly
     return {
-      flex: 1,
-      opacity: interpolate(
-        translationY.value,
-        [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET],
-        [0.5, 1, 0.5],
-      ),
+      width: screenSize.width,
+      maxHeight: screenSize.height,
+      alignSelf: 'center',
+      aspectRatio: imageAspect,
     }
   })
 
   const scrollHandler = useAnimatedScrollHandler({
     onScroll(e) {
       const nextIsScaled = e.zoomScale > 1
-      translationY.value = nextIsScaled ? 0 : e.contentOffset.y
-      if (scaled !== nextIsScaled) {
-        runOnJS(handleZoom)(nextIsScaled)
-      }
-    },
-    onEndDrag(e) {
-      const velocityY = e.velocity?.y ?? 0
-      const nextIsScaled = e.zoomScale > 1
       if (scaled !== nextIsScaled) {
         runOnJS(handleZoom)(nextIsScaled)
       }
-      if (!nextIsScaled && Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) {
-        runOnJS(onRequestClose)()
-      }
     },
   })
 
@@ -146,7 +137,11 @@ const ImageItem = ({
       runOnJS(zoomTo)(nextZoomRect)
     })
 
-  const composedGesture = Gesture.Exclusive(doubleTap, singleTap)
+  const composedGesture = Gesture.Exclusive(
+    dismissSwipePan,
+    doubleTap,
+    singleTap,
+  )
 
   return (
     <GestureDetector gesture={composedGesture}>
@@ -158,21 +153,22 @@ const ImageItem = ({
         showsVerticalScrollIndicator={false}
         maximumZoomScale={maxZoomScale}
         onScroll={scrollHandler}
-        contentContainerStyle={styles.scrollContainer}>
-        <Animated.View style={animatedStyle}>
-          <ActivityIndicator size="small" color="#FFF" style={styles.loading} />
-          <Image
-            contentFit="contain"
-            source={{uri: imageSrc.uri}}
-            placeholderContentFit="contain"
-            placeholder={{uri: imageSrc.thumbUri}}
-            style={styles.image}
-            accessibilityLabel={imageSrc.alt}
-            accessibilityHint=""
-            enableLiveTextInteraction={showControls && !scaled}
-            accessibilityIgnoresInvertColors
-          />
-        </Animated.View>
+        bounces={scaled}
+        bouncesZoom={true}
+        style={imageStyle}
+        centerContent>
+        <ActivityIndicator size="small" color="#FFF" style={styles.loading} />
+        <AnimatedImage
+          contentFit="contain"
+          source={{uri: imageSrc.uri}}
+          placeholderContentFit="contain"
+          placeholder={{uri: imageSrc.thumbUri}}
+          style={animatedStyle}
+          accessibilityLabel={imageSrc.alt}
+          accessibilityHint=""
+          enableLiveTextInteraction={showControls && !scaled}
+          accessibilityIgnoresInvertColors
+        />
       </Animated.ScrollView>
     </GestureDetector>
   )
@@ -186,9 +182,6 @@ const styles = StyleSheet.create({
     right: 0,
     bottom: 0,
   },
-  scrollContainer: {
-    flex: 1,
-  },
   image: {
     flex: 1,
   },
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
index 383bec995..1cd6b0020 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
@@ -1,10 +1,11 @@
 // default implementation fallback for web
 
 import React from 'react'
-import {View} from 'react-native'
+import {ImageStyle, StyleProp, View} from 'react-native'
+import {PanGesture} from 'react-native-gesture-handler'
 import {AnimatedRef} from 'react-native-reanimated'
 
-import {ImageSource} from '../../@types'
+import {Dimensions as ImageDimensions, ImageSource} from '../../@types'
 
 type Props = {
   imageSrc: ImageSource
@@ -14,6 +15,10 @@ type Props = {
   isScrollViewBeingDragged: boolean
   showControls: boolean
   safeAreaRef: AnimatedRef<View>
+  imageAspect: number | undefined
+  imageDimensions: ImageDimensions | undefined
+  imageStyle: StyleProp<ImageStyle>
+  dismissSwipePan: PanGesture
 }
 
 const ImageItem = (_props: Props) => {