@@ -90,7 +90,16 @@ export type MessageSimplePropsWithContext = Pick<
9090 | 'reactionListPosition'
9191 | 'ReactionListTop'
9292 | 'setQuotedMessageState'
93- > ;
93+ > & {
94+ /**
95+ * Will determine whether the swipeable wrapper is always rendered for each
96+ * message. If set to false, the animated wrapper will be rendered only when
97+ * a swiping gesture is active and not otherwise.
98+ * Since stateful components would lose their state if we remount them while
99+ * an animation is happening, this should always be set to true in those instances.
100+ */
101+ shouldRenderSwipeableWrapper : boolean ;
102+ } ;
94103
95104const MessageSimpleWithContext = ( props : MessageSimplePropsWithContext ) => {
96105 const [ messageContentWidth , setMessageContentWidth ] = useState ( 0 ) ;
@@ -123,6 +132,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
123132 ReactionListTop,
124133 setQuotedMessageState,
125134 showMessageStatus,
135+ shouldRenderSwipeableWrapper,
126136 } = props ;
127137
128138 const {
@@ -202,7 +212,9 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
202212 const translateX = useSharedValue ( 0 ) ;
203213 const touchStart = useSharedValue < { x : number ; y : number } | null > ( null ) ;
204214 const isSwiping = useSharedValue < boolean > ( false ) ;
205- const [ isBeingSwiped , setIsBeingSwiped ] = useState < boolean > ( false ) ;
215+ const [ shouldRenderAnimatedWrapper , setShouldRenderAnimatedWrapper ] = useState < boolean > (
216+ shouldRenderSwipeableWrapper ,
217+ ) ;
206218
207219 const onSwipeToReply = useCallback ( ( ) => {
208220 clearQuotedMessageState ( ) ;
@@ -233,7 +245,9 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
233245 if ( isHorizontalPanning ) {
234246 state . activate ( ) ;
235247 isSwiping . value = true ;
236- runOnJS ( setIsBeingSwiped ) ( true ) ;
248+ if ( ! shouldRenderSwipeableWrapper ) {
249+ runOnJS ( setShouldRenderAnimatedWrapper ) ( isSwiping . value ) ;
250+ }
237251 } else {
238252 state . fail ( ) ;
239253 }
@@ -253,6 +267,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
253267 runOnJS ( triggerHaptic ) ( 'impactMedium' ) ;
254268 }
255269 }
270+ isSwiping . value = false ;
256271 translateX . value = withSpring (
257272 0 ,
258273 {
@@ -262,41 +277,44 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
262277 stiffness : 1 ,
263278 } ,
264279 ( ) => {
265- isSwiping . value = false ;
266- runOnJS ( setIsBeingSwiped ) ( false ) ;
280+ if ( ! shouldRenderSwipeableWrapper ) {
281+ runOnJS ( setShouldRenderAnimatedWrapper ) ( isSwiping . value ) ;
282+ }
267283 } ,
268284 ) ;
269285 } ) ,
270- [ isSwiping , messageSwipeToReplyHitSlop , onSwipeToReply , touchStart , translateX , triggerHaptic ] ,
286+ [
287+ isSwiping ,
288+ messageSwipeToReplyHitSlop ,
289+ onSwipeToReply ,
290+ touchStart ,
291+ translateX ,
292+ triggerHaptic ,
293+ shouldRenderSwipeableWrapper ,
294+ ] ,
271295 ) ;
272296
273297 const messageBubbleAnimatedStyle = useAnimatedStyle (
274- ( ) =>
275- isSwiping . value
276- ? {
277- transform : [ { translateX : translateX . value } ] ,
278- }
279- : { } ,
298+ ( ) => ( {
299+ transform : [ { translateX : translateX . value } ] ,
300+ } ) ,
280301 [ ] ,
281302 ) ;
282303
283304 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- : { } ,
305+ ( ) => ( {
306+ opacity : interpolate ( translateX . value , [ 0 , THRESHOLD ] , [ 0 , 1 ] ) ,
307+ transform : [
308+ {
309+ translateX : interpolate (
310+ translateX . value ,
311+ [ 0 , THRESHOLD ] ,
312+ [ - THRESHOLD , 0 ] ,
313+ Extrapolation . CLAMP ,
314+ ) ,
315+ } ,
316+ ] ,
317+ } ) ,
300318 [ ] ,
301319 ) ;
302320
@@ -332,7 +350,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
332350 ( ) => (
333351 < GestureDetector gesture = { swipeGesture } >
334352 < View hitSlop = { messageSwipeToReplyHitSlop } style = { [ styles . contentWrapper , contentWrapper ] } >
335- { isBeingSwiped ? (
353+ { shouldRenderAnimatedWrapper ? (
336354 < >
337355 < AnimatedWrapper
338356 style = { [
@@ -356,7 +374,7 @@ const MessageSimpleWithContext = (props: MessageSimplePropsWithContext) => {
356374 [
357375 MessageSwipeContent ,
358376 contentWrapper ,
359- isBeingSwiped ,
377+ shouldRenderAnimatedWrapper ,
360378 messageBubbleAnimatedStyle ,
361379 messageSwipeToReplyHitSlop ,
362380 renderMessageBubble ,
@@ -592,6 +610,7 @@ export const MessageSimple = (props: MessageSimpleProps) => {
592610 onlyEmojis,
593611 otherAttachments,
594612 showMessageStatus,
613+ isMessageAIGenerated,
595614 } = useMessageContext ( ) ;
596615 const {
597616 clearQuotedMessageState,
@@ -613,6 +632,11 @@ export const MessageSimple = (props: MessageSimpleProps) => {
613632 ReactionListTop,
614633 setQuotedMessageState,
615634 } = useMessagesContext ( ) ;
635+ const isAIGenerated = useMemo (
636+ ( ) => isMessageAIGenerated ( message ) ,
637+ [ message , isMessageAIGenerated ] ,
638+ ) ;
639+ const shouldRenderSwipeableWrapper = ( message ?. attachments || [ ] ) . length > 0 || isAIGenerated ;
616640
617641 return (
618642 < MemoizedMessageSimple
@@ -645,6 +669,7 @@ export const MessageSimple = (props: MessageSimpleProps) => {
645669 reactionListPosition,
646670 ReactionListTop,
647671 setQuotedMessageState,
672+ shouldRenderSwipeableWrapper,
648673 showMessageStatus,
649674 } }
650675 { ...props }
0 commit comments