about summary refs log tree commit diff
path: root/src/view/com/lightbox/ImageViewing
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/lightbox/ImageViewing')
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx49
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx6
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx4
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx128
4 files changed, 46 insertions, 141 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 553a4a2e7..6fe7854bc 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
@@ -1,9 +1,8 @@
-import React, {MutableRefObject, useState} from 'react'
+import React, {useState} from 'react'
 
 import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native'
 import {Image} from 'expo-image'
 import Animated, {
-  measure,
   runOnJS,
   useAnimatedRef,
   useAnimatedStyle,
@@ -12,11 +11,7 @@ import Animated, {
   withDecay,
   withSpring,
 } from 'react-native-reanimated'
-import {
-  GestureDetector,
-  Gesture,
-  GestureType,
-} from 'react-native-gesture-handler'
+import {GestureDetector, Gesture} from 'react-native-gesture-handler'
 import useImageDimensions from '../../hooks/useImageDimensions'
 import {
   createTransform,
@@ -40,7 +35,6 @@ type Props = {
   imageSrc: ImageSource
   onRequestClose: () => void
   onZoom: (isZoomed: boolean) => void
-  pinchGestureRef: MutableRefObject<GestureType | undefined>
   isScrollViewBeingDragged: boolean
 }
 const ImageItem = ({
@@ -48,7 +42,6 @@ const ImageItem = ({
   onZoom,
   onRequestClose,
   isScrollViewBeingDragged,
-  pinchGestureRef,
 }: Props) => {
   const [isScaled, setIsScaled] = useState(false)
   const [isLoaded, setIsLoaded] = useState(false)
@@ -140,28 +133,7 @@ const ImageItem = ({
     return [dx, dy]
   }
 
-  // This is a hack.
-  // We need to disallow any gestures (and let the native parent scroll view scroll) while you're scrolling it.
-  // However, there is no great reliable way to coordinate this yet in RGNH.
-  // This "fake" manual gesture handler whenever you're trying to touch something while the parent scrollview is not at rest.
-  const consumeHScroll = Gesture.Manual().onTouchesDown((e, manager) => {
-    if (isScrollViewBeingDragged) {
-      // Steal the gesture (and do nothing, so native ScrollView does its thing).
-      manager.activate()
-      return
-    }
-    const measurement = measure(containerRef)
-    if (!measurement || measurement.pageX !== 0) {
-      // Steal the gesture (and do nothing, so native ScrollView does its thing).
-      manager.activate()
-      return
-    }
-    // Fail this "fake" gesture so that the gestures after it can proceed.
-    manager.fail()
-  })
-
   const pinch = Gesture.Pinch()
-    .withRef(pinchGestureRef)
     .onStart(e => {
       pinchOrigin.value = {
         x: e.focalX - SCREEN.width / 2,
@@ -318,19 +290,22 @@ const ImageItem = ({
       }
     })
 
+  const composedGesture = isScrollViewBeingDragged
+    ? // If the parent is not at rest, provide a no-op gesture.
+      Gesture.Manual()
+    : Gesture.Exclusive(
+        dismissSwipePan,
+        Gesture.Simultaneous(pinch, pan),
+        doubleTap,
+      )
+
   const isLoading = !isLoaded || !imageDimensions
   return (
     <Animated.View ref={containerRef} style={styles.container}>
       {isLoading && (
         <ActivityIndicator size="small" color="#FFF" style={styles.loading} />
       )}
-      <GestureDetector
-        gesture={Gesture.Exclusive(
-          consumeHScroll,
-          dismissSwipePan,
-          Gesture.Simultaneous(pinch, pan),
-          doubleTap,
-        )}>
+      <GestureDetector gesture={composedGesture}>
         <AnimatedImage
           source={imageSrc}
           contentFit="contain"
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 75e8b0e82..1bd24a841 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
@@ -6,7 +6,7 @@
  *
  */
 
-import React, {MutableRefObject, useCallback, useState} from 'react'
+import React, {useCallback, useState} from 'react'
 
 import {
   Dimensions,
@@ -25,7 +25,6 @@ import Animated, {
   useAnimatedStyle,
   useSharedValue,
 } from 'react-native-reanimated'
-import {GestureType} from 'react-native-gesture-handler'
 
 import useImageDimensions from '../../hooks/useImageDimensions'
 
@@ -43,7 +42,6 @@ type Props = {
   imageSrc: ImageSource
   onRequestClose: () => void
   onZoom: (scaled: boolean) => void
-  pinchGestureRef: MutableRefObject<GestureType>
   isScrollViewBeingDragged: boolean
 }
 
@@ -145,7 +143,7 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
           accessibilityHint="">
           <AnimatedImage
             contentFit="contain"
-            source={imageSrc}
+            source={{uri: imageSrc.uri}}
             style={[styles.image, animatedStyle]}
             onLoad={() => setLoaded(true)}
           />
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
index 898b00c78..35be96e46 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
@@ -1,15 +1,13 @@
 // default implementation fallback for web
 
-import React, {MutableRefObject} from 'react'
+import React from 'react'
 import {View} from 'react-native'
-import {GestureType} from 'react-native-gesture-handler'
 import {ImageSource} from '../../@types'
 
 type Props = {
   imageSrc: ImageSource
   onRequestClose: () => void
   onZoom: (scaled: boolean) => void
-  pinchGestureRef: MutableRefObject<GestureType | undefined>
   isScrollViewBeingDragged: boolean
 }
 
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index 7d3f80b49..631a6ca0c 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -8,32 +8,15 @@
 // Original code copied and simplified from the link below as the codebase is currently not maintained:
 // https://github.com/jobtoday/react-native-image-viewing
 
-import React, {
-  ComponentType,
-  createRef,
-  useCallback,
-  useRef,
-  useMemo,
-  useState,
-} from 'react'
-import {
-  Animated,
-  Dimensions,
-  NativeSyntheticEvent,
-  NativeScrollEvent,
-  StyleSheet,
-  View,
-  VirtualizedList,
-  ModalProps,
-  Platform,
-} from 'react-native'
+import React, {ComponentType, useMemo, useState} from 'react'
+import {Animated, StyleSheet, View, ModalProps, Platform} from 'react-native'
 
 import ImageItem from './components/ImageItem/ImageItem'
 import ImageDefaultHeader from './components/ImageDefaultHeader'
 
 import {ImageSource} from './@types'
-import {ScrollView, GestureType} from 'react-native-gesture-handler'
 import {Edge, SafeAreaView} from 'react-native-safe-area-context'
+import PagerView from 'react-native-pager-view'
 
 type Props = {
   images: ImageSource[]
@@ -48,8 +31,6 @@ type Props = {
 }
 
 const DEFAULT_BG_COLOR = '#000'
-const SCREEN = Dimensions.get('screen')
-const SCREEN_WIDTH = SCREEN.width
 const INITIAL_POSITION = {x: 0, y: 0}
 const ANIMATION_CONFIG = {
   duration: 200,
@@ -65,7 +46,6 @@ function ImageViewing({
   HeaderComponent,
   FooterComponent,
 }: Props) {
-  const imageList = useRef<VirtualizedList<ImageSource>>(null)
   const [isScaled, setIsScaled] = useState(false)
   const [isDragging, setIsDragging] = useState(false)
   const [imageIndex, setImageIndex] = useState(initialImageIndex)
@@ -96,19 +76,6 @@ function ImageViewing({
     }
   }
 
-  const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
-    const {
-      nativeEvent: {
-        contentOffset: {x: scrollX},
-      },
-    } = event
-
-    if (SCREEN.width) {
-      const nextIndex = Math.round(scrollX / SCREEN.width)
-      setImageIndex(nextIndex < 0 ? 0 : nextIndex)
-    }
-  }
-
   const onZoom = (nextIsScaled: boolean) => {
     toggleBarsVisible(!nextIsScaled)
     setIsScaled(false)
@@ -121,26 +88,6 @@ function ImageViewing({
     return ['left', 'right'] satisfies Edge[] // iOS, so no top/bottom safe area
   }, [])
 
-  const onLayout = useCallback(() => {
-    if (initialImageIndex) {
-      imageList.current?.scrollToIndex({
-        index: initialImageIndex,
-        animated: false,
-      })
-    }
-  }, [imageList, initialImageIndex])
-
-  // This is a hack.
-  // RNGH doesn't have an easy way to express that pinch of individual items
-  // should "steal" all pinches from the scroll view. So we're keeping a ref
-  // to all pinch gestures so that we may give them to <ScrollView waitFor={...}>.
-  const [pinchGestureRefs] = useState(new Map())
-  for (let imageSrc of images) {
-    if (!pinchGestureRefs.get(imageSrc)) {
-      pinchGestureRefs.set(imageSrc, createRef<GestureType | undefined>())
-    }
-  }
-
   if (!visible) {
     return null
   }
@@ -150,7 +97,6 @@ function ImageViewing({
   return (
     <SafeAreaView
       style={styles.screen}
-      onLayout={onLayout}
       edges={edges}
       aria-modal
       accessibilityViewIsModal>
@@ -164,48 +110,29 @@ function ImageViewing({
             <ImageDefaultHeader onRequestClose={onRequestClose} />
           )}
         </Animated.View>
-        <VirtualizedList
-          ref={imageList}
-          data={images}
-          horizontal
-          pagingEnabled
-          scrollEnabled={!isScaled || isDragging}
-          showsHorizontalScrollIndicator={false}
-          showsVerticalScrollIndicator={false}
-          getItem={(_, index) => images[index]}
-          getItemCount={() => images.length}
-          getItemLayout={(_, index) => ({
-            length: SCREEN_WIDTH,
-            offset: SCREEN_WIDTH * index,
-            index,
-          })}
-          renderItem={({item: imageSrc}) => (
-            <ImageItem
-              onZoom={onZoom}
-              imageSrc={imageSrc}
-              onRequestClose={onRequestClose}
-              pinchGestureRef={pinchGestureRefs.get(imageSrc)}
-              isScrollViewBeingDragged={isDragging}
-            />
-          )}
-          renderScrollComponent={props => (
-            <ScrollView
-              {...props}
-              waitFor={Array.from(pinchGestureRefs.values())}
-            />
-          )}
-          onScrollBeginDrag={() => {
-            setIsDragging(true)
-          }}
-          onScrollEndDrag={() => {
-            setIsDragging(false)
-          }}
-          onMomentumScrollEnd={e => {
+        <PagerView
+          scrollEnabled={!isScaled}
+          initialPage={initialImageIndex}
+          onPageSelected={e => {
+            setImageIndex(e.nativeEvent.position)
             setIsScaled(false)
-            onScroll(e)
           }}
-          keyExtractor={imageSrc => imageSrc.uri}
-        />
+          onPageScrollStateChanged={e => {
+            setIsDragging(e.nativeEvent.pageScrollState !== 'idle')
+          }}
+          overdrag={true}
+          style={styles.pager}>
+          {images.map(imageSrc => (
+            <View key={imageSrc.uri}>
+              <ImageItem
+                onZoom={onZoom}
+                imageSrc={imageSrc}
+                onRequestClose={onRequestClose}
+                isScrollViewBeingDragged={isDragging}
+              />
+            </View>
+          ))}
+        </PagerView>
         {typeof FooterComponent !== 'undefined' && (
           <Animated.View style={[styles.footer, {transform: footerTransform}]}>
             {React.createElement(FooterComponent, {
@@ -221,11 +148,18 @@ function ImageViewing({
 const styles = StyleSheet.create({
   screen: {
     position: 'absolute',
+    top: 0,
+    left: 0,
+    bottom: 0,
+    right: 0,
   },
   container: {
     flex: 1,
     backgroundColor: '#000',
   },
+  pager: {
+    flex: 1,
+  },
   header: {
     position: 'absolute',
     width: '100%',