diff --git a/native/chat/swipeable-message.react.js b/native/chat/swipeable-message.react.js --- a/native/chat/swipeable-message.react.js +++ b/native/chat/swipeable-message.react.js @@ -4,17 +4,16 @@ import * as React from 'react'; import { View } from 'react-native'; import { - PanGestureHandler, + Gesture, + GestureDetector, type PanGestureEvent, } from 'react-native-gesture-handler'; import Animated, { - useAnimatedGestureHandler, useSharedValue, useAnimatedStyle, runOnJS, withSpring, interpolate, - cancelAnimation, Extrapolate, type SharedValue, type WithSpringConfig, @@ -176,6 +175,7 @@ }; function SwipeableMessage(props: Props): React.Node { const { isViewer, triggerReply, triggerSidebar } = props; + const gestureEnabled = !!(triggerReply || triggerSidebar); const secondaryActionExists = triggerReply && triggerSidebar; const onPassPrimaryThreshold = React.useCallback(() => { @@ -204,63 +204,72 @@ }, [triggerReply, triggerSidebar]); const translateX = useSharedValue(0); - const swipeEvent = useAnimatedGestureHandler( - { - onStart: (event: Partial, ctx: { [string]: mixed }) => { - ctx.translationAtStart = translateX.value; - cancelAnimation(translateX); - }, - - onActive: (event: Partial, ctx: { [string]: mixed }) => { - const { translationAtStart } = ctx; - if (typeof translationAtStart !== 'number') { - throw new Error('translationAtStart should be number'); - } - const translationX = translationAtStart + (event.translationX ?? 0); - const baseActiveTranslation = isViewer - ? Math.min(translationX, 0) - : Math.max(translationX, 0); - translateX.value = dividePastDistance( - baseActiveTranslation, - primaryThreshold, - 2, - ); - - const absValue = Math.abs(translateX.value); - const pastPrimaryThreshold = absValue >= primaryThreshold; - if (pastPrimaryThreshold && !ctx.prevPastPrimaryThreshold) { - runOnJS(onPassPrimaryThreshold)(); - } - ctx.prevPastPrimaryThreshold = pastPrimaryThreshold; - - const pastSecondaryThreshold = absValue >= secondaryThreshold; - if (pastSecondaryThreshold && !ctx.prevPastSecondaryThreshold) { - runOnJS(onPassSecondaryThreshold)(); - } - ctx.prevPastSecondaryThreshold = pastSecondaryThreshold; - }, - - onEnd: (event: Partial) => { - const absValue = Math.abs(translateX.value); - if (absValue >= secondaryThreshold && secondaryActionExists) { - runOnJS(secondaryAction)(); - } else if (absValue >= primaryThreshold) { - runOnJS(primaryAction)(); - } - - translateX.value = withSpring( - 0, - makeSpringConfig(event.velocityX ?? 0), - ); - }, - }, + const translationAtStart = useSharedValue(0); + const prevPastPrimaryThreshold = useSharedValue(false); + const prevPastSecondaryThreshold = useSharedValue(false); + const panGesture = React.useMemo( + () => + Gesture.Pan() + .enabled(gestureEnabled) + .maxPointers(1) + .activeOffsetX(panGestureHandlerActiveOffsetX) + .failOffsetX(isViewer ? 5 : -5) + .failOffsetY(panGestureHandlerFailOffsetY) + .onStart(() => { + translationAtStart.value = translateX.value; + prevPastPrimaryThreshold.value = false; + prevPastSecondaryThreshold.value = false; + }) + .onChange((event: PanGestureEvent) => { + const translationX = + translationAtStart.value + (event.translationX ?? 0); + const baseActiveTranslation = isViewer + ? Math.min(translationX, 0) + : Math.max(translationX, 0); + translateX.value = dividePastDistance( + baseActiveTranslation, + primaryThreshold, + 2, + ); + + const absValue = Math.abs(translateX.value); + const pastPrimaryThreshold = absValue >= primaryThreshold; + if (pastPrimaryThreshold && !prevPastPrimaryThreshold.value) { + runOnJS(onPassPrimaryThreshold)(); + } + prevPastPrimaryThreshold.value = pastPrimaryThreshold; + + const pastSecondaryThreshold = absValue >= secondaryThreshold; + if (pastSecondaryThreshold && !prevPastSecondaryThreshold.value) { + runOnJS(onPassSecondaryThreshold)(); + } + prevPastSecondaryThreshold.value = pastSecondaryThreshold; + }) + .onEnd((event: PanGestureEvent) => { + const absValue = Math.abs(translateX.value); + if (absValue >= secondaryThreshold && secondaryActionExists) { + runOnJS(secondaryAction)(); + } else if (absValue >= primaryThreshold) { + runOnJS(primaryAction)(); + } + + const velocityX = Number.isFinite(event.velocityX) + ? event.velocityX + : 0; + translateX.value = withSpring(0, makeSpringConfig(velocityX)); + }), [ isViewer, + gestureEnabled, onPassPrimaryThreshold, onPassSecondaryThreshold, primaryAction, secondaryAction, secondaryActionExists, + translateX, + translationAtStart, + prevPastPrimaryThreshold, + prevPastSecondaryThreshold, ], ); @@ -274,8 +283,9 @@ const { contentStyle, children } = props; const panGestureHandlerStyle = React.useMemo( - () => [contentStyle, transformContentStyle], - [contentStyle, transformContentStyle], + () => + gestureEnabled ? [contentStyle, transformContentStyle] : contentStyle, + [gestureEnabled, contentStyle, transformContentStyle], ); const threadColor = `#${props.threadColor}`; @@ -340,30 +350,16 @@ [isViewer, threadColor, translateX], ); - const panGestureHandler = React.useMemo( + const gestureContent = React.useMemo( () => ( - + {children} - + ), - [children, isViewer, panGestureHandlerStyle, swipeEvent], + [panGesture, panGestureHandlerStyle, children], ); const swipeableMessage = React.useMemo(() => { - if (!triggerReply && !triggerSidebar) { - return ( - - {children} - - ); - } const snakes: Array = []; if (triggerReply) { snakes.push(replySwipeSnake); @@ -373,12 +369,10 @@ } else if (triggerSidebar) { snakes.push(sidebarSwipeSnakeWithoutReplySwipeSnake); } - snakes.push(panGestureHandler); + snakes.push(gestureContent); return snakes; }, [ - children, - contentStyle, - panGestureHandler, + gestureContent, replySwipeSnake, sidebarSwipeSnakeWithReplySwipeSnake, sidebarSwipeSnakeWithoutReplySwipeSnake, diff --git a/native/flow-typed/npm/react-native-gesture-handler_v2.x.x.js b/native/flow-typed/npm/react-native-gesture-handler_v2.x.x.js --- a/native/flow-typed/npm/react-native-gesture-handler_v2.x.x.js +++ b/native/flow-typed/npm/react-native-gesture-handler_v2.x.x.js @@ -663,6 +663,7 @@ HandlerStateChangeEventPayload & GestureStateChangeEventPayloadT; declare class BaseGesture { + enabled(isEnabled: boolean): this; onBegin( callback: (event: GestureStateChangeEvent) => void, ): this;