1- import React , { useMemo , useState } from 'react' ;
1+ import React , { useCallback , useMemo , useState } from 'react' ;
22import { Dimensions , LayoutChangeEvent , StyleSheet , View } from 'react-native' ;
33
44import { Gesture , GestureDetector } from 'react-native-gesture-handler' ;
@@ -12,6 +12,8 @@ import Animated, {
1212 withSpring ,
1313} from 'react-native-reanimated' ;
1414
15+ const AnimatedWrapper = Animated . createAnimatedComponent ( View ) ;
16+
1517import {
1618 MessageContextValue ,
1719 useMessageContext ,
@@ -205,77 +207,104 @@ const MessageSimpleWithContext = <
205207
206208 const translateX = useSharedValue ( 0 ) ;
207209 const touchStart = useSharedValue < { x : number ; y : number } | null > ( null ) ;
210+ const isSwiping = useSharedValue < boolean > ( false ) ;
211+ const [ isBeingSwiped , setIsBeingSwiped ] = useState < boolean > ( false ) ;
208212
209- const onSwipeToReply = ( ) => {
213+ const onSwipeToReply = useCallback ( ( ) => {
210214 clearQuotedMessageState ( ) ;
211215 setQuotedMessageState ( message ) ;
212- } ;
216+ } , [ clearQuotedMessageState , message , setQuotedMessageState ] ) ;
213217
214218 const THRESHOLD = 25 ;
215219
216220 const triggerHaptic = NativeHandlers . triggerHaptic ;
217221
218- const swipeGesture = Gesture . Pan ( )
219- . hitSlop ( messageSwipeToReplyHitSlop )
220- . onBegin ( ( event ) => {
221- touchStart . value = { x : event . x , y : event . y } ;
222- } )
223- . onTouchesMove ( ( event , state ) => {
224- if ( ! touchStart . value || ! event . changedTouches . length ) {
225- state . fail ( ) ;
226- return ;
227- }
228-
229- const xDiff = Math . abs ( event . changedTouches [ 0 ] . x - touchStart . value . x ) ;
230- const yDiff = Math . abs ( event . changedTouches [ 0 ] . y - touchStart . value . y ) ;
231- const isHorizontalPanning = xDiff > yDiff ;
232-
233- if ( isHorizontalPanning ) {
234- state . activate ( ) ;
235- } else {
236- state . fail ( ) ;
237- }
238- } )
239- . onStart ( ( ) => {
240- translateX . value = 0 ;
241- } )
242- . onChange ( ( { translationX } ) => {
243- if ( translationX > 0 ) {
244- translateX . value = translationX ;
245- }
246- } )
247- . onEnd ( ( ) => {
248- if ( translateX . value >= THRESHOLD ) {
249- runOnJS ( onSwipeToReply ) ( ) ;
250- if ( triggerHaptic ) {
251- runOnJS ( triggerHaptic ) ( 'impactMedium' ) ;
252- }
253- }
254- translateX . value = withSpring ( 0 , {
255- dampingRatio : 1 ,
256- duration : 500 ,
257- overshootClamping : true ,
258- stiffness : 1 ,
259- } ) ;
260- } ) ;
261-
262- const messageBubbleAnimatedStyle = useAnimatedStyle ( ( ) => ( {
263- transform : [ { translateX : translateX . value } ] ,
264- } ) ) ;
265-
266- const swipeContentAnimatedStyle = useAnimatedStyle ( ( ) => ( {
267- opacity : interpolate ( translateX . value , [ 0 , THRESHOLD ] , [ 0 , 1 ] ) ,
268- transform : [
269- {
270- translateX : interpolate (
271- translateX . value ,
272- [ 0 , THRESHOLD ] ,
273- [ - THRESHOLD , 0 ] ,
274- Extrapolation . CLAMP ,
275- ) ,
276- } ,
277- ] ,
278- } ) ) ;
222+ const swipeGesture = useMemo (
223+ ( ) =>
224+ Gesture . Pan ( )
225+ . hitSlop ( messageSwipeToReplyHitSlop )
226+ . onBegin ( ( event ) => {
227+ touchStart . value = { x : event . x , y : event . y } ;
228+ } )
229+ . onTouchesMove ( ( event , state ) => {
230+ if ( ! touchStart . value || ! event . changedTouches . length ) {
231+ state . fail ( ) ;
232+ return ;
233+ }
234+
235+ const xDiff = Math . abs ( event . changedTouches [ 0 ] . x - touchStart . value . x ) ;
236+ const yDiff = Math . abs ( event . changedTouches [ 0 ] . y - touchStart . value . y ) ;
237+ const isHorizontalPanning = xDiff > yDiff ;
238+
239+ if ( isHorizontalPanning ) {
240+ state . activate ( ) ;
241+ isSwiping . value = true ;
242+ runOnJS ( setIsBeingSwiped ) ( true ) ;
243+ } else {
244+ state . fail ( ) ;
245+ }
246+ } )
247+ . onStart ( ( ) => {
248+ translateX . value = 0 ;
249+ } )
250+ . onChange ( ( { translationX } ) => {
251+ if ( translationX > 0 ) {
252+ translateX . value = translationX ;
253+ }
254+ } )
255+ . onEnd ( ( ) => {
256+ if ( translateX . value >= THRESHOLD ) {
257+ runOnJS ( onSwipeToReply ) ( ) ;
258+ if ( triggerHaptic ) {
259+ runOnJS ( triggerHaptic ) ( 'impactMedium' ) ;
260+ }
261+ }
262+ translateX . value = withSpring (
263+ 0 ,
264+ {
265+ dampingRatio : 1 ,
266+ duration : 500 ,
267+ overshootClamping : true ,
268+ stiffness : 1 ,
269+ } ,
270+ ( ) => {
271+ isSwiping . value = false ;
272+ runOnJS ( setIsBeingSwiped ) ( false ) ;
273+ } ,
274+ ) ;
275+ } ) ,
276+ [ isSwiping , messageSwipeToReplyHitSlop , onSwipeToReply , touchStart , translateX , triggerHaptic ] ,
277+ ) ;
278+
279+ const messageBubbleAnimatedStyle = useAnimatedStyle (
280+ ( ) =>
281+ isSwiping . value
282+ ? {
283+ transform : [ { translateX : translateX . value } ] ,
284+ }
285+ : { } ,
286+ [ ] ,
287+ ) ;
288+
289+ const swipeContentAnimatedStyle = useAnimatedStyle (
290+ ( ) =>
291+ isSwiping . value
292+ ? {
293+ opacity : interpolate ( translateX . value , [ 0 , THRESHOLD ] , [ 0 , 1 ] ) ,
294+ transform : [
295+ {
296+ translateX : interpolate (
297+ translateX . value ,
298+ [ 0 , THRESHOLD ] ,
299+ [ - THRESHOLD , 0 ] ,
300+ Extrapolation . CLAMP ,
301+ ) ,
302+ } ,
303+ ] ,
304+ }
305+ : { } ,
306+ [ ] ,
307+ ) ;
279308
280309 const renderMessageBubble = useMemo (
281310 ( ) => (
@@ -309,18 +338,31 @@ const MessageSimpleWithContext = <
309338 ( ) => (
310339 < GestureDetector gesture = { swipeGesture } >
311340 < View hitSlop = { messageSwipeToReplyHitSlop } style = { [ styles . contentWrapper , contentWrapper ] } >
312- < Animated . View
313- style = { [ styles . swipeContentContainer , swipeContentAnimatedStyle , swipeContentContainer ] }
314- >
315- { MessageSwipeContent ? < MessageSwipeContent /> : null }
316- </ Animated . View >
317- < Animated . View style = { messageBubbleAnimatedStyle } > { renderMessageBubble } </ Animated . View >
341+ { isBeingSwiped ? (
342+ < >
343+ < AnimatedWrapper
344+ style = { [
345+ styles . swipeContentContainer ,
346+ swipeContentAnimatedStyle ,
347+ swipeContentContainer ,
348+ ] }
349+ >
350+ { MessageSwipeContent ? < MessageSwipeContent /> : null }
351+ </ AnimatedWrapper >
352+ < AnimatedWrapper pointerEvents = 'box-none' style = { messageBubbleAnimatedStyle } >
353+ { renderMessageBubble }
354+ </ AnimatedWrapper >
355+ </ >
356+ ) : (
357+ renderMessageBubble
358+ ) }
318359 </ View >
319360 </ GestureDetector >
320361 ) ,
321362 [
322363 MessageSwipeContent ,
323364 contentWrapper ,
365+ isBeingSwiped ,
324366 messageBubbleAnimatedStyle ,
325367 messageSwipeToReplyHitSlop ,
326368 renderMessageBubble ,
0 commit comments