From fe7d97b0bf9d1844496490df68071fef4cbc19ca Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 30 Oct 2024 23:43:45 +0000 Subject: [PATCH] Move swipe gesture handling upwards --- .../ImageItem/ImageItem.android.tsx | 50 ++++-------------- .../components/ImageItem/ImageItem.ios.tsx | 51 +++++++------------ .../components/ImageItem/ImageItem.tsx | 4 +- src/view/com/lightbox/ImageViewing/index.tsx | 32 +++++++++++- 4 files changed, 59 insertions(+), 78 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 fee4d3a89c..ad30067d46 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx @@ -1,14 +1,16 @@ import React, {useState} from 'react' import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native' -import {Gesture, GestureDetector} from 'react-native-gesture-handler' +import { + Gesture, + GestureDetector, + PanGesture, +} from 'react-native-gesture-handler' import Animated, { runOnJS, - SharedValue, useAnimatedReaction, useAnimatedRef, useAnimatedStyle, useSharedValue, - withDecay, withSpring, } from 'react-native-reanimated' import {Image} from 'expo-image' @@ -44,15 +46,14 @@ type Props = { onZoom: (isZoomed: boolean) => void isScrollViewBeingDragged: boolean showControls: boolean - dismissSwipeTranslateY: SharedValue + dismissSwipePan: PanGesture } const ImageItem = ({ imageSrc, onTap, onZoom, - onRequestClose, isScrollViewBeingDragged, - dismissSwipeTranslateY, + dismissSwipePan, }: Props) => { const [isScaled, setIsScaled] = useState(false) const [imageAspect, imageDimensions] = useImageDimensions({ @@ -102,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 dismissProgress = Math.min( - Math.abs(dismissDistance) / (SCREEN.height / 2), - 1, - ) return { - opacity: 1 - dismissProgress, - transform: [ - {translateX}, - {translateY: translateY + dismissDistance}, - {scale}, - ], + transform: [{translateX}, {translateY: translateY}, {scale}], } }) @@ -292,33 +282,11 @@ 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() : Gesture.Exclusive( - dismissSwipePan, + dismissSwipePan ?? Gesture.Manual(), Gesture.Simultaneous(pinch, pan), doubleTap, singleTap, 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 1dd06f512f..d98078f792 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -7,23 +7,19 @@ */ import React, {useState} from 'react' -import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native' -import {Gesture, GestureDetector} from 'react-native-gesture-handler' -import Animated, { - interpolate, - runOnJS, - SharedValue, - useAnimatedRef, - useAnimatedStyle, -} from 'react-native-reanimated' +import {ActivityIndicator, Dimensions, StyleSheet, View} from 'react-native' +import { + Gesture, + GestureDetector, + PanGesture, +} from 'react-native-gesture-handler' +import Animated, {runOnJS, useAnimatedRef} from 'react-native-reanimated' import {Image} from 'expo-image' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useImageDimensions} from '#/lib/media/image-sizes' import {ImageSource} from '../../@types' -const SWIPE_CLOSE_OFFSET = 75 -const SWIPE_CLOSE_VELOCITY = 1 const SCREEN = Dimensions.get('screen') const MAX_ORIGINAL_IMAGE_ZOOM = 2 const MIN_DOUBLE_TAP_SCALE = 2 @@ -35,16 +31,15 @@ type Props = { onZoom: (scaled: boolean) => void isScrollViewBeingDragged: boolean showControls: boolean - dismissSwipeTranslateY: SharedValue + dismissSwipePan: PanGesture | null } const ImageItem = ({ imageSrc, onTap, onZoom, - onRequestClose, showControls, - dismissSwipeTranslateY, + dismissSwipePan, }: Props) => { const scrollViewRef = useAnimatedRef() @@ -57,33 +52,18 @@ const ImageItem = ({ ? (imageDimensions.width / SCREEN.width) * MAX_ORIGINAL_IMAGE_ZOOM : 1 - const animatedStyle = useAnimatedStyle(() => { - return { - opacity: interpolate( - dismissSwipeTranslateY.value, - [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], - [0.5, 1, 0.5], - ), - } - }) - const scrollHandler = useAnimatedScrollHandler({ onScroll(e) { const nextIsScaled = e.zoomScale > 1 - dismissSwipeTranslateY.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)() - } }, }) @@ -130,7 +110,11 @@ const ImageItem = ({ runOnJS(handleDoubleTap)(absoluteX, absoluteY) }) - const composedGesture = Gesture.Exclusive(doubleTap, singleTap) + const composedGesture = Gesture.Exclusive( + dismissSwipePan ?? Gesture.Manual(), + doubleTap, + singleTap, + ) return ( @@ -142,8 +126,9 @@ const ImageItem = ({ showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} maximumZoomScale={maxZoomScale} - onScroll={scrollHandler}> - + onScroll={scrollHandler} + bounces={scaled}> + - + ) diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx index a351b40867..a08abc18b4 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx @@ -2,7 +2,7 @@ import React from 'react' import {View} from 'react-native' -import {SharedValue} from 'react-native-reanimated' +import {PanGesture} from 'react-native-gesture-handler' import {ImageSource} from '../../@types' @@ -13,7 +13,7 @@ type Props = { onZoom: (scaled: boolean) => void isScrollViewBeingDragged: boolean showControls: boolean - dismissSwipeTranslateY: SharedValue + dismissSwipePan: PanGesture | null } const ImageItem = (_props: Props) => { diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 2600dd6924..e6d68c733c 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -16,11 +16,14 @@ import { StyleSheet, View, } from 'react-native' +import {Gesture} from 'react-native-gesture-handler' import PagerView from 'react-native-pager-view' import {MeasuredDimensions} from 'react-native-reanimated' import Animated, { + runOnJS, useAnimatedStyle, useSharedValue, + withDecay, withSpring, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' @@ -86,6 +89,29 @@ function ImageViewing({ ], })) + const dismissSwipePan = Gesture.Pan() + .enabled(!isScaled) + .activeOffsetY([-10, 10]) + .failOffsetX([-10, 10]) + .maxPointers(1) + .onUpdate(e => { + 'worklet' + dismissSwipeTranslateY.value = e.translationY + console.log(dismissSwipeTranslateY.value) + }) + .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 onTap = useCallback(() => { setShowControls(show => !show) }, []) @@ -130,7 +156,7 @@ function ImageViewing({ }} overdrag={true} style={styles.pager}> - {images.map(imageSrc => ( + {images.map((imageSrc, i) => ( ))}