Skip to content

Commit 477553d

Browse files
committed
perf: message simple perf improvements for swipe to reply
1 parent 59fcdc5 commit 477553d

File tree

2 files changed

+112
-70
lines changed

2 files changed

+112
-70
lines changed

need.tsx

Whitespace-only changes.

package/src/components/Message/MessageSimple/MessageSimple.tsx

Lines changed: 112 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo, useState } from 'react';
1+
import React, { useCallback, useMemo, useState } from 'react';
22
import { Dimensions, LayoutChangeEvent, StyleSheet, View } from 'react-native';
33

44
import { 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+
1517
import {
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

Comments
 (0)