about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx7
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx117
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx1
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx19
4 files changed, 81 insertions, 63 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 513524864..7c7ad0616 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
@@ -34,11 +34,13 @@ const initialTransform = createTransform()
 type Props = {
   imageSrc: ImageSource
   onRequestClose: () => void
+  onTap: () => void
   onZoom: (isZoomed: boolean) => void
   isScrollViewBeingDragged: boolean
 }
 const ImageItem = ({
   imageSrc,
+  onTap,
   onZoom,
   onRequestClose,
   isScrollViewBeingDragged,
@@ -227,6 +229,10 @@ const ImageItem = ({
       panTranslation.value = {x: 0, y: 0}
     })
 
+  const singleTap = Gesture.Tap().onEnd(() => {
+    runOnJS(onTap)()
+  })
+
   const doubleTap = Gesture.Tap()
     .numberOfTaps(2)
     .onEnd(e => {
@@ -297,6 +303,7 @@ const ImageItem = ({
         dismissSwipePan,
         Gesture.Simultaneous(pinch, pan),
         doubleTap,
+        singleTap,
       )
 
   const isLoading = !isLoaded || !imageDimensions
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 cd550670c..f73f355ac 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
@@ -6,16 +6,9 @@
  *
  */
 
-import React, {useCallback, useState} from 'react'
+import React, {useState} from 'react'
 
-import {
-  Dimensions,
-  StyleSheet,
-  View,
-  NativeSyntheticEvent,
-  NativeTouchEvent,
-  TouchableWithoutFeedback,
-} from 'react-native'
+import {Dimensions, StyleSheet} from 'react-native'
 import {Image} from 'expo-image'
 import Animated, {
   interpolate,
@@ -25,13 +18,13 @@ import Animated, {
   useAnimatedStyle,
   useSharedValue,
 } from 'react-native-reanimated'
+import {Gesture, GestureDetector} from 'react-native-gesture-handler'
 
 import useImageDimensions from '../../hooks/useImageDimensions'
 
 import {ImageSource, Dimensions as ImageDimensions} from '../../@types'
 import {ImageLoading} from './ImageLoading'
 
-const DOUBLE_TAP_DELAY = 300
 const SWIPE_CLOSE_OFFSET = 75
 const SWIPE_CLOSE_VELOCITY = 1
 const SCREEN = Dimensions.get('screen')
@@ -41,15 +34,14 @@ const MIN_DOUBLE_TAP_SCALE = 2
 type Props = {
   imageSrc: ImageSource
   onRequestClose: () => void
+  onTap: () => void
   onZoom: (scaled: boolean) => void
   isScrollViewBeingDragged: boolean
 }
 
 const AnimatedImage = Animated.createAnimatedComponent(Image)
 
-let lastTapTS: number | null = null
-
-const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
+const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
   const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
   const translationY = useSharedValue(0)
   const [loaded, setLoaded] = useState(false)
@@ -71,12 +63,18 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
 
   const scrollHandler = useAnimatedScrollHandler({
     onScroll(e) {
-      translationY.value = e.zoomScale > 1 ? 0 : e.contentOffset.y
+      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
-      runOnJS(handleZoom)(nextIsScaled)
+      if (scaled !== nextIsScaled) {
+        runOnJS(handleZoom)(nextIsScaled)
+      }
       if (!nextIsScaled && Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) {
         runOnJS(onRequestClose)()
       }
@@ -88,43 +86,46 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
     setScaled(nextIsScaled)
   }
 
-  const handleDoubleTap = useCallback(
-    (event: NativeSyntheticEvent<NativeTouchEvent>) => {
-      const nowTS = new Date().getTime()
-      const scrollResponderRef = scrollViewRef?.current?.getScrollResponder()
+  function handleDoubleTap(absoluteX: number, absoluteY: number) {
+    const scrollResponderRef = scrollViewRef?.current?.getScrollResponder()
+    let nextZoomRect = {
+      x: 0,
+      y: 0,
+      width: SCREEN.width,
+      height: SCREEN.height,
+    }
 
-      if (lastTapTS && nowTS - lastTapTS < DOUBLE_TAP_DELAY) {
-        let nextZoomRect = {
-          x: 0,
-          y: 0,
-          width: SCREEN.width,
-          height: SCREEN.height,
-        }
+    const willZoom = !scaled
+    if (willZoom) {
+      nextZoomRect = getZoomRectAfterDoubleTap(
+        imageDimensions,
+        absoluteX,
+        absoluteY,
+      )
+    }
 
-        const willZoom = !scaled
-        if (willZoom) {
-          const {pageX, pageY} = event.nativeEvent
-          nextZoomRect = getZoomRectAfterDoubleTap(
-            imageDimensions,
-            pageX,
-            pageY,
-          )
-        }
+    // @ts-ignore
+    scrollResponderRef?.scrollResponderZoomTo({
+      ...nextZoomRect, // This rect is in screen coordinates
+      animated: true,
+    })
+  }
 
-        // @ts-ignore
-        scrollResponderRef?.scrollResponderZoomTo({
-          ...nextZoomRect, // This rect is in screen coordinates
-          animated: true,
-        })
-      } else {
-        lastTapTS = nowTS
-      }
-    },
-    [imageDimensions, scaled, scrollViewRef],
-  )
+  const singleTap = Gesture.Tap().onEnd(() => {
+    runOnJS(onTap)()
+  })
+
+  const doubleTap = Gesture.Tap()
+    .numberOfTaps(2)
+    .onEnd(e => {
+      const {absoluteX, absoluteY} = e
+      runOnJS(handleDoubleTap)(absoluteX, absoluteY)
+    })
+
+  const composedGesture = Gesture.Exclusive(doubleTap, singleTap)
 
   return (
-    <View>
+    <GestureDetector gesture={composedGesture}>
       <Animated.ScrollView
         // @ts-ignore Something's up with the types here
         ref={scrollViewRef}
@@ -136,21 +137,17 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
         contentContainerStyle={styles.imageScrollContainer}
         onScroll={scrollHandler}>
         {(!loaded || !imageDimensions) && <ImageLoading />}
-        <TouchableWithoutFeedback
-          onPress={handleDoubleTap}
-          accessibilityRole="image"
+        <AnimatedImage
+          contentFit="contain"
+          // NOTE: Don't pass imageSrc={imageSrc} or MobX will break.
+          source={{uri: imageSrc.uri}}
+          style={[styles.image, animatedStyle]}
           accessibilityLabel={imageSrc.alt}
-          accessibilityHint="">
-          <AnimatedImage
-            contentFit="contain"
-            // NOTE: Don't pass imageSrc={imageSrc} or MobX will break.
-            source={{uri: imageSrc.uri}}
-            style={[styles.image, animatedStyle]}
-            onLoad={() => setLoaded(true)}
-          />
-        </TouchableWithoutFeedback>
+          accessibilityHint=""
+          onLoad={() => setLoaded(true)}
+        />
       </Animated.ScrollView>
-    </View>
+    </GestureDetector>
   )
 }
 
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
index 35be96e46..16688b820 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
@@ -7,6 +7,7 @@ import {ImageSource} from '../../@types'
 type Props = {
   imageSrc: ImageSource
   onRequestClose: () => void
+  onTap: () => void
   onZoom: (scaled: boolean) => void
   isScrollViewBeingDragged: boolean
 }
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index 78d16f8a6..b6835793d 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -43,24 +43,36 @@ function ImageViewing({
   const [isScaled, setIsScaled] = useState(false)
   const [isDragging, setIsDragging] = useState(false)
   const [imageIndex, setImageIndex] = useState(initialImageIndex)
+  const [showControls, setShowControls] = useState(true)
 
   const animatedHeaderStyle = useAnimatedStyle(() => ({
+    pointerEvents: showControls ? 'auto' : 'none',
+    opacity: withClampedSpring(showControls ? 1 : 0),
     transform: [
       {
-        translateY: withClampedSpring(isScaled ? -300 : 0),
+        translateY: withClampedSpring(showControls ? 0 : -30),
       },
     ],
   }))
   const animatedFooterStyle = useAnimatedStyle(() => ({
+    pointerEvents: showControls ? 'auto' : 'none',
+    opacity: withClampedSpring(showControls ? 1 : 0),
     transform: [
       {
-        translateY: withClampedSpring(isScaled ? 300 : 0),
+        translateY: withClampedSpring(showControls ? 0 : 30),
       },
     ],
   }))
 
+  const onTap = useCallback(() => {
+    setShowControls(show => !show)
+  }, [])
+
   const onZoom = useCallback((nextIsScaled: boolean) => {
     setIsScaled(nextIsScaled)
+    if (nextIsScaled) {
+      setShowControls(false)
+    }
   }, [])
 
   const edges = useMemo(() => {
@@ -105,6 +117,7 @@ function ImageViewing({
           {images.map(imageSrc => (
             <View key={imageSrc.uri}>
               <ImageItem
+                onTap={onTap}
                 onZoom={onZoom}
                 imageSrc={imageSrc}
                 onRequestClose={onRequestClose}
@@ -161,7 +174,7 @@ const EnhancedImageViewing = (props: Props) => (
 
 function withClampedSpring(value: any) {
   'worklet'
-  return withSpring(value, {overshootClamping: true})
+  return withSpring(value, {overshootClamping: true, stiffness: 300})
 }
 
 export default EnhancedImageViewing