diff options
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%', |