Move swipe gesture handling upwards
This commit is contained in:
@@ -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<number>
|
||||
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,
|
||||
|
||||
@@ -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<number>
|
||||
dismissSwipePan: PanGesture | null
|
||||
}
|
||||
|
||||
const ImageItem = ({
|
||||
imageSrc,
|
||||
onTap,
|
||||
onZoom,
|
||||
onRequestClose,
|
||||
showControls,
|
||||
dismissSwipeTranslateY,
|
||||
dismissSwipePan,
|
||||
}: Props) => {
|
||||
const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
|
||||
|
||||
@@ -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 (
|
||||
<GestureDetector gesture={composedGesture}>
|
||||
@@ -142,8 +126,9 @@ const ImageItem = ({
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
maximumZoomScale={maxZoomScale}
|
||||
onScroll={scrollHandler}>
|
||||
<Animated.View style={[styles.imageScrollContainer, animatedStyle]}>
|
||||
onScroll={scrollHandler}
|
||||
bounces={scaled}>
|
||||
<View style={styles.imageScrollContainer}>
|
||||
<ActivityIndicator size="small" color="#FFF" style={styles.loading} />
|
||||
<Image
|
||||
contentFit="contain"
|
||||
@@ -156,7 +141,7 @@ const ImageItem = ({
|
||||
enableLiveTextInteraction={showControls && !scaled}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Animated.ScrollView>
|
||||
</GestureDetector>
|
||||
)
|
||||
|
||||
@@ -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<number>
|
||||
dismissSwipePan: PanGesture | null
|
||||
}
|
||||
|
||||
const ImageItem = (_props: Props) => {
|
||||
|
||||
@@ -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) => (
|
||||
<View key={imageSrc.uri}>
|
||||
<ImageItem
|
||||
onTap={onTap}
|
||||
@@ -139,7 +165,9 @@ function ImageViewing({
|
||||
onRequestClose={onRequestClose}
|
||||
isScrollViewBeingDragged={isDragging}
|
||||
showControls={showControls}
|
||||
dismissSwipeTranslateY={dismissSwipeTranslateY}
|
||||
dismissSwipePan={
|
||||
imageIndex === i && !isDragging ? dismissSwipePan : null
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user