Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package/src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ const ChannelWithContext = <
const isTypingEvent = event.type === 'typing.start' || event.type === 'typing.stop';
if (isTypingEvent) {
setTyping(channel);
return;
} else {
if (thread?.id) {
const updatedThreadMessages =
Expand Down Expand Up @@ -1758,6 +1759,11 @@ const ChannelWithContext = <
const sendMessageRef =
useRef<InputMessageInputContextValue<StreamChatGenerics>['sendMessage']>(sendMessage);
sendMessageRef.current = sendMessage;
const sendMessageStable = useCallback<
InputMessageInputContextValue<StreamChatGenerics>['sendMessage']
>((...args) => {
return sendMessageRef.current(...args);
}, []);

const inputMessageInputContext = useCreateInputMessageInputContext<StreamChatGenerics>({
additionalTextInputProps,
Expand Down Expand Up @@ -1811,7 +1817,7 @@ const ChannelWithContext = <
quotedMessage,
SendButton,
sendImageAsync,
sendMessage: (...args) => sendMessageRef.current(...args),
sendMessage: sendMessageStable,
SendMessageDisallowedIndicator,
setInputRef,
setQuotedMessageState,
Expand Down
182 changes: 112 additions & 70 deletions package/src/components/Message/MessageSimple/MessageSimple.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { Dimensions, LayoutChangeEvent, StyleSheet, View } from 'react-native';

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
Expand All @@ -12,6 +12,8 @@ import Animated, {
withSpring,
} from 'react-native-reanimated';

const AnimatedWrapper = Animated.createAnimatedComponent(View);

import {
MessageContextValue,
useMessageContext,
Expand Down Expand Up @@ -205,77 +207,104 @@ const MessageSimpleWithContext = <

const translateX = useSharedValue(0);
const touchStart = useSharedValue<{ x: number; y: number } | null>(null);
const isSwiping = useSharedValue<boolean>(false);
const [isBeingSwiped, setIsBeingSwiped] = useState<boolean>(false);

const onSwipeToReply = () => {
const onSwipeToReply = useCallback(() => {
clearQuotedMessageState();
setQuotedMessageState(message);
};
}, [clearQuotedMessageState, message, setQuotedMessageState]);

const THRESHOLD = 25;

const triggerHaptic = NativeHandlers.triggerHaptic;

const swipeGesture = Gesture.Pan()
.hitSlop(messageSwipeToReplyHitSlop)
.onBegin((event) => {
touchStart.value = { x: event.x, y: event.y };
})
.onTouchesMove((event, state) => {
if (!touchStart.value || !event.changedTouches.length) {
state.fail();
return;
}

const xDiff = Math.abs(event.changedTouches[0].x - touchStart.value.x);
const yDiff = Math.abs(event.changedTouches[0].y - touchStart.value.y);
const isHorizontalPanning = xDiff > yDiff;

if (isHorizontalPanning) {
state.activate();
} else {
state.fail();
}
})
.onStart(() => {
translateX.value = 0;
})
.onChange(({ translationX }) => {
if (translationX > 0) {
translateX.value = translationX;
}
})
.onEnd(() => {
if (translateX.value >= THRESHOLD) {
runOnJS(onSwipeToReply)();
if (triggerHaptic) {
runOnJS(triggerHaptic)('impactMedium');
}
}
translateX.value = withSpring(0, {
dampingRatio: 1,
duration: 500,
overshootClamping: true,
stiffness: 1,
});
});

const messageBubbleAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));

const swipeContentAnimatedStyle = useAnimatedStyle(() => ({
opacity: interpolate(translateX.value, [0, THRESHOLD], [0, 1]),
transform: [
{
translateX: interpolate(
translateX.value,
[0, THRESHOLD],
[-THRESHOLD, 0],
Extrapolation.CLAMP,
),
},
],
}));
const swipeGesture = useMemo(
() =>
Gesture.Pan()
.hitSlop(messageSwipeToReplyHitSlop)
.onBegin((event) => {
touchStart.value = { x: event.x, y: event.y };
})
.onTouchesMove((event, state) => {
if (!touchStart.value || !event.changedTouches.length) {
state.fail();
return;
}

const xDiff = Math.abs(event.changedTouches[0].x - touchStart.value.x);
const yDiff = Math.abs(event.changedTouches[0].y - touchStart.value.y);
const isHorizontalPanning = xDiff > yDiff;

if (isHorizontalPanning) {
state.activate();
isSwiping.value = true;
runOnJS(setIsBeingSwiped)(true);
} else {
state.fail();
}
})
.onStart(() => {
translateX.value = 0;
})
.onChange(({ translationX }) => {
if (translationX > 0) {
translateX.value = translationX;
}
})
.onEnd(() => {
if (translateX.value >= THRESHOLD) {
runOnJS(onSwipeToReply)();
if (triggerHaptic) {
runOnJS(triggerHaptic)('impactMedium');
}
}
translateX.value = withSpring(
0,
{
dampingRatio: 1,
duration: 500,
overshootClamping: true,
stiffness: 1,
},
() => {
isSwiping.value = false;
runOnJS(setIsBeingSwiped)(false);
},
);
}),
[isSwiping, messageSwipeToReplyHitSlop, onSwipeToReply, touchStart, translateX, triggerHaptic],
);

const messageBubbleAnimatedStyle = useAnimatedStyle(
() =>
isSwiping.value
? {
transform: [{ translateX: translateX.value }],
}
: {},
[],
);

const swipeContentAnimatedStyle = useAnimatedStyle(
() =>
isSwiping.value
? {
opacity: interpolate(translateX.value, [0, THRESHOLD], [0, 1]),
transform: [
{
translateX: interpolate(
translateX.value,
[0, THRESHOLD],
[-THRESHOLD, 0],
Extrapolation.CLAMP,
),
},
],
}
: {},
[],
);

const renderMessageBubble = useMemo(
() => (
Expand Down Expand Up @@ -309,18 +338,31 @@ const MessageSimpleWithContext = <
() => (
<GestureDetector gesture={swipeGesture}>
<View hitSlop={messageSwipeToReplyHitSlop} style={[styles.contentWrapper, contentWrapper]}>
<Animated.View
style={[styles.swipeContentContainer, swipeContentAnimatedStyle, swipeContentContainer]}
>
{MessageSwipeContent ? <MessageSwipeContent /> : null}
</Animated.View>
<Animated.View style={messageBubbleAnimatedStyle}>{renderMessageBubble}</Animated.View>
{isBeingSwiped ? (
<>
<AnimatedWrapper
style={[
styles.swipeContentContainer,
swipeContentAnimatedStyle,
swipeContentContainer,
]}
>
{MessageSwipeContent ? <MessageSwipeContent /> : null}
</AnimatedWrapper>
<AnimatedWrapper pointerEvents='box-none' style={messageBubbleAnimatedStyle}>
{renderMessageBubble}
</AnimatedWrapper>
</>
) : (
renderMessageBubble
)}
</View>
</GestureDetector>
),
[
MessageSwipeContent,
contentWrapper,
isBeingSwiped,
messageBubbleAnimatedStyle,
messageSwipeToReplyHitSlop,
renderMessageBubble,
Expand Down
23 changes: 15 additions & 8 deletions package/src/components/MessageInput/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ type MessageInputPropsWithContext<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
> = Pick<AttachmentPickerContextValue, 'AttachmentPickerSelectionBar'> &
Pick<ChatContextValue<StreamChatGenerics>, 'isOnline'> &
Pick<ChannelContextValue<StreamChatGenerics>, 'members' | 'threadList' | 'watchers'> &
Pick<ChannelContextValue<StreamChatGenerics>, 'channel' | 'members' | 'threadList' | 'watchers'> &
Pick<
MessageInputContextValue<StreamChatGenerics>,
| 'additionalTextInputProps'
Expand Down Expand Up @@ -198,6 +198,7 @@ const MessageInputWithContext = <
AudioRecordingLockIndicator,
AudioRecordingPreview,
AutoCompleteSuggestionList,
channel,
closeAttachmentPicker,
closePollCreationDialog,
cooldownEndsAt,
Expand Down Expand Up @@ -746,7 +747,6 @@ const MessageInputWithContext = <
})),
};

const { channel } = useChannelContext<StreamChatGenerics>();
const { aiState } = useAIState(channel);

const stopGenerating = useCallback(() => channel?.stopAIResponse(), [channel]);
Expand Down Expand Up @@ -860,18 +860,17 @@ const MessageInputWithContext = <

{shouldDisplayStopAIGeneration ? (
<StopMessageStreamingButton onPress={stopGenerating} />
) : (
isSendingButtonVisible() &&
(cooldownRemainingSeconds ? (
) : isSendingButtonVisible() ? (
cooldownRemainingSeconds ? (
<CooldownTimer seconds={cooldownRemainingSeconds} />
) : (
<View style={[styles.sendButtonContainer, sendButtonContainer]}>
<SendButton
disabled={sending.current || !isValidMessage() || (giphyActive && !isOnline)}
/>
</View>
))
)}
)
) : null}
{audioRecordingEnabled && isAudioRecorderAvailable() && !micLocked && (
<GestureDetector gesture={panGestureMic}>
<Animated.View
Expand Down Expand Up @@ -957,6 +956,7 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
asyncMessagesSlideToCancelDistance: prevAsyncMessagesSlideToCancelDistance,
asyncUploads: prevAsyncUploads,
audioRecordingEnabled: prevAsyncMessagesEnabled,
channel: prevChannel,
closePollCreationDialog: prevClosePollCreationDialog,
editing: prevEditing,
fileUploads: prevFileUploads,
Expand All @@ -982,6 +982,7 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
asyncMessagesSlideToCancelDistance: nextAsyncMessagesSlideToCancelDistance,
asyncUploads: nextAsyncUploads,
audioRecordingEnabled: nextAsyncMessagesEnabled,
channel: nextChannel,
closePollCreationDialog: nextClosePollCreationDialog,
editing: nextEditing,
fileUploads: nextFileUploads,
Expand Down Expand Up @@ -1025,6 +1026,11 @@ const areEqual = <StreamChatGenerics extends DefaultStreamChatGenerics = Default
return false;
}

const channelEqual = prevChannel.cid === nextChannel.cid;
if (!channelEqual) {
return false;
}

const asyncMessagesLockDistanceEqual =
prevAsyncMessagesLockDistance === nextAsyncMessagesLockDistance;
if (!asyncMessagesLockDistanceEqual) {
Expand Down Expand Up @@ -1160,7 +1166,7 @@ export const MessageInput = <
const { isOnline } = useChatContext();
const ownCapabilities = useOwnCapabilitiesContext();

const { members, threadList, watchers } = useChannelContext<StreamChatGenerics>();
const { channel, members, threadList, watchers } = useChannelContext<StreamChatGenerics>();

const {
additionalTextInputProps,
Expand Down Expand Up @@ -1263,6 +1269,7 @@ export const MessageInput = <
AutoCompleteSuggestionHeader,
AutoCompleteSuggestionItem,
AutoCompleteSuggestionList,
channel,
clearEditingState,
clearQuotedMessageState,
closeAttachmentPicker,
Expand Down
1 change: 0 additions & 1 deletion package/src/components/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,6 @@ const MessageListWithContext = <
setTimeout(() => {
channelResyncScrollSet.current = true;
if (channel.countUnread() > 0) {
console.log('marking read');
markRead();
}
}, 500);
Expand Down
Loading