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 ,
@@ -199,77 +201,104 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
199201
200202 const translateX = useSharedValue ( 0 ) ;
201203 const touchStart = useSharedValue < { x : number ; y : number } | null > ( null ) ;
204+ const isSwiping = useSharedValue < boolean > ( false ) ;
205+ const [ isBeingSwiped , setIsBeingSwiped ] = useState < boolean > ( false ) ;
202206
203- const onSwipeToReply = ( ) => {
207+ const onSwipeToReply = useCallback ( ( ) => {
204208 clearQuotedMessageState ( ) ;
205209 setQuotedMessageState ( message ) ;
206- } ;
210+ } , [ clearQuotedMessageState , message , setQuotedMessageState ] ) ;
207211
208212 const THRESHOLD = 25 ;
209213
210214 const triggerHaptic = NativeHandlers . triggerHaptic ;
211215
212- const swipeGesture = Gesture . Pan ( )
213- . hitSlop ( messageSwipeToReplyHitSlop )
214- . onBegin ( ( event ) => {
215- touchStart . value = { x : event . x , y : event . y } ;
216- } )
217- . onTouchesMove ( ( event , state ) => {
218- if ( ! touchStart . value || ! event . changedTouches . length ) {
219- state . fail ( ) ;
220- return ;
221- }
222-
223- const xDiff = Math . abs ( event . changedTouches [ 0 ] . x - touchStart . value . x ) ;
224- const yDiff = Math . abs ( event . changedTouches [ 0 ] . y - touchStart . value . y ) ;
225- const isHorizontalPanning = xDiff > yDiff ;
226-
227- if ( isHorizontalPanning ) {
228- state . activate ( ) ;
229- } else {
230- state . fail ( ) ;
231- }
232- } )
233- . onStart ( ( ) => {
234- translateX . value = 0 ;
235- } )
236- . onChange ( ( { translationX } ) => {
237- if ( translationX > 0 ) {
238- translateX . value = translationX ;
239- }
240- } )
241- . onEnd ( ( ) => {
242- if ( translateX . value >= THRESHOLD ) {
243- runOnJS ( onSwipeToReply ) ( ) ;
244- if ( triggerHaptic ) {
245- runOnJS ( triggerHaptic ) ( 'impactMedium' ) ;
246- }
247- }
248- translateX . value = withSpring ( 0 , {
249- dampingRatio : 1 ,
250- duration : 500 ,
251- overshootClamping : true ,
252- stiffness : 1 ,
253- } ) ;
254- } ) ;
255-
256- const messageBubbleAnimatedStyle = useAnimatedStyle ( ( ) => ( {
257- transform : [ { translateX : translateX . value } ] ,
258- } ) ) ;
259-
260- const swipeContentAnimatedStyle = useAnimatedStyle ( ( ) => ( {
261- opacity : interpolate ( translateX . value , [ 0 , THRESHOLD ] , [ 0 , 1 ] ) ,
262- transform : [
263- {
264- translateX : interpolate (
265- translateX . value ,
266- [ 0 , THRESHOLD ] ,
267- [ - THRESHOLD , 0 ] ,
268- Extrapolation . CLAMP ,
269- ) ,
270- } ,
271- ] ,
272- } ) ) ;
216+ const swipeGesture = useMemo (
217+ ( ) =>
218+ 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+ isSwiping . value = true ;
236+ runOnJS ( setIsBeingSwiped ) ( true ) ;
237+ } else {
238+ state . fail ( ) ;
239+ }
240+ } )
241+ . onStart ( ( ) => {
242+ translateX . value = 0 ;
243+ } )
244+ . onChange ( ( { translationX } ) => {
245+ if ( translationX > 0 ) {
246+ translateX . value = translationX ;
247+ }
248+ } )
249+ . onEnd ( ( ) => {
250+ if ( translateX . value >= THRESHOLD ) {
251+ runOnJS ( onSwipeToReply ) ( ) ;
252+ if ( triggerHaptic ) {
253+ runOnJS ( triggerHaptic ) ( 'impactMedium' ) ;
254+ }
255+ }
256+ translateX . value = withSpring (
257+ 0 ,
258+ {
259+ dampingRatio : 1 ,
260+ duration : 500 ,
261+ overshootClamping : true ,
262+ stiffness : 1 ,
263+ } ,
264+ ( ) => {
265+ isSwiping . value = false ;
266+ runOnJS ( setIsBeingSwiped ) ( false ) ;
267+ } ,
268+ ) ;
269+ } ) ,
270+ [ isSwiping , messageSwipeToReplyHitSlop , onSwipeToReply , touchStart , translateX , triggerHaptic ] ,
271+ ) ;
272+
273+ const messageBubbleAnimatedStyle = useAnimatedStyle (
274+ ( ) =>
275+ isSwiping . value
276+ ? {
277+ transform : [ { translateX : translateX . value } ] ,
278+ }
279+ : { } ,
280+ [ ] ,
281+ ) ;
282+
283+ const swipeContentAnimatedStyle = useAnimatedStyle (
284+ ( ) =>
285+ isSwiping . value
286+ ? {
287+ opacity : interpolate ( translateX . value , [ 0 , THRESHOLD ] , [ 0 , 1 ] ) ,
288+ transform : [
289+ {
290+ translateX : interpolate (
291+ translateX . value ,
292+ [ 0 , THRESHOLD ] ,
293+ [ - THRESHOLD , 0 ] ,
294+ Extrapolation . CLAMP ,
295+ ) ,
296+ } ,
297+ ] ,
298+ }
299+ : { } ,
300+ [ ] ,
301+ ) ;
273302
274303 const renderMessageBubble = useMemo (
275304 ( ) => (
@@ -303,18 +332,31 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
303332 ( ) => (
304333 < GestureDetector gesture = { swipeGesture } >
305334 < View hitSlop = { messageSwipeToReplyHitSlop } style = { [ styles . contentWrapper , contentWrapper ] } >
306- < Animated . View
307- style = { [ styles . swipeContentContainer , swipeContentAnimatedStyle , swipeContentContainer ] }
308- >
309- { MessageSwipeContent ? < MessageSwipeContent /> : null }
310- </ Animated . View >
311- < Animated . View style = { messageBubbleAnimatedStyle } > { renderMessageBubble } </ Animated . View >
335+ { isBeingSwiped ? (
336+ < >
337+ < AnimatedWrapper
338+ style = { [
339+ styles . swipeContentContainer ,
340+ swipeContentAnimatedStyle ,
341+ swipeContentContainer ,
342+ ] }
343+ >
344+ { MessageSwipeContent ? < MessageSwipeContent /> : null }
345+ </ AnimatedWrapper >
346+ < AnimatedWrapper pointerEvents = 'box-none' style = { messageBubbleAnimatedStyle } >
347+ { renderMessageBubble }
348+ </ AnimatedWrapper >
349+ </ >
350+ ) : (
351+ renderMessageBubble
352+ ) }
312353 </ View >
313354 </ GestureDetector >
314355 ) ,
315356 [
316357 MessageSwipeContent ,
317358 contentWrapper ,
359+ isBeingSwiped ,
318360 messageBubbleAnimatedStyle ,
319361 messageSwipeToReplyHitSlop ,
320362 renderMessageBubble ,
0 commit comments