diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index d9f1c6d4b..9641e17b8 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -141,6 +141,7 @@ import { MessageReplies as MessageRepliesDefault } from '../Message/MessageSimpl import { MessageRepliesAvatars as MessageRepliesAvatarsDefault } from '../Message/MessageSimple/MessageRepliesAvatars'; import { MessageSimple as MessageSimpleDefault } from '../Message/MessageSimple/MessageSimple'; import { MessageStatus as MessageStatusDefault } from '../Message/MessageSimple/MessageStatus'; +import { MessageSwipeLeftContent as MessageSwipeLeftContentDefault } from '../Message/MessageSimple/MessageSwipeLeftContent'; import { MessageTimestamp as MessageTimestampDefault } from '../Message/MessageSimple/MessageTimestamp'; import { ReactionListBottom as ReactionListBottomDefault } from '../Message/MessageSimple/ReactionList/ReactionListBottom'; import { ReactionListTop as ReactionListTopDefault } from '../Message/MessageSimple/ReactionList/ReactionListTop'; @@ -295,6 +296,7 @@ export type ChannelPropsWithContext< | 'deletedMessagesVisibilityType' | 'disableTypingIndicator' | 'dismissKeyboardOnMessageTouch' + | 'enableSwipeToReply' | 'FileAttachment' | 'FileAttachmentIcon' | 'FileAttachmentGroup' @@ -348,6 +350,7 @@ export type ChannelPropsWithContext< | 'messageTextNumberOfLines' | 'MessageTimestamp' | 'MessageUserReactions' + | 'MessageSwipeLeftContent' | 'myMessageTheme' | 'onLongPressMessage' | 'onPressInMessage' @@ -528,6 +531,7 @@ const ChannelWithContext = < EmptyStateIndicator = EmptyStateIndicatorDefault, enableMessageGroupingByUser = true, enableOfflineSupport, + enableSwipeToReply = true, enforceUniqueReaction = false, FileAttachment = FileAttachmentDefault, FileAttachmentGroup = FileAttachmentGroupDefault, @@ -620,6 +624,7 @@ const ChannelWithContext = < MessageRepliesAvatars = MessageRepliesAvatarsDefault, MessageSimple = MessageSimpleDefault, MessageStatus = MessageStatusDefault, + MessageSwipeLeftContent = MessageSwipeLeftContentDefault, MessageSystem = MessageSystemDefault, MessageText, messageTextNumberOfLines, @@ -1802,6 +1807,7 @@ const ChannelWithContext = < CardFooter, CardHeader, channelId, + clearQuotedMessageState, DateHeader, deletedMessagesVisibilityType, deleteMessage, @@ -1809,6 +1815,7 @@ const ChannelWithContext = < disableTypingIndicator, dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, + enableSwipeToReply, FileAttachment, FileAttachmentGroup, FileAttachmentIcon, @@ -1862,6 +1869,7 @@ const ChannelWithContext = < MessageRepliesAvatars, MessageSimple, MessageStatus, + MessageSwipeLeftContent, MessageSystem, MessageText, messageTextNumberOfLines, diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index 7849cd85b..2707f4404 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -15,6 +15,7 @@ export const useCreateMessagesContext = < CardFooter, CardHeader, channelId, + clearQuotedMessageState, DateHeader, deletedMessagesVisibilityType, deleteMessage, @@ -22,6 +23,7 @@ export const useCreateMessagesContext = < disableTypingIndicator, dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, + enableSwipeToReply, FileAttachment, FileAttachmentGroup, FileAttachmentIcon, @@ -74,6 +76,7 @@ export const useCreateMessagesContext = < MessageRepliesAvatars, MessageSimple, MessageStatus, + MessageSwipeLeftContent, MessageSystem, MessageText, messageTextNumberOfLines, @@ -128,6 +131,7 @@ export const useCreateMessagesContext = < CardCover, CardFooter, CardHeader, + clearQuotedMessageState, DateHeader, deletedMessagesVisibilityType, deleteMessage, @@ -135,6 +139,7 @@ export const useCreateMessagesContext = < disableTypingIndicator, dismissKeyboardOnMessageTouch, enableMessageGroupingByUser, + enableSwipeToReply, FileAttachment, FileAttachmentGroup, FileAttachmentIcon, @@ -187,6 +192,7 @@ export const useCreateMessagesContext = < MessageRepliesAvatars, MessageSimple, MessageStatus, + MessageSwipeLeftContent, MessageSystem, MessageText, messageTextNumberOfLines, diff --git a/package/src/components/Message/MessageSimple/MessageSimple.tsx b/package/src/components/Message/MessageSimple/MessageSimple.tsx index 7f922f76a..29b80afdc 100644 --- a/package/src/components/Message/MessageSimple/MessageSimple.tsx +++ b/package/src/components/Message/MessageSimple/MessageSimple.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; import { LayoutChangeEvent, StyleSheet, View } from 'react-native'; +import { Swipeable } from 'react-native-gesture-handler'; import { MessageContextValue, @@ -22,7 +23,9 @@ const styles = StyleSheet.create({ contentContainer: {}, contentWrapper: { flexDirection: 'row', + overflow: 'visible', }, + lastMessageContainer: { marginBottom: 12, }, @@ -56,7 +59,9 @@ export type MessageSimplePropsWithContext< > & Pick< MessagesContextValue, + | 'clearQuotedMessageState' | 'enableMessageGroupingByUser' + | 'enableSwipeToReply' | 'myMessageTheme' | 'MessageAvatar' | 'MessageContent' @@ -66,9 +71,11 @@ export type MessageSimplePropsWithContext< | 'MessagePinnedHeader' | 'MessageReplies' | 'MessageStatus' + | 'MessageSwipeLeftContent' | 'ReactionListBottom' | 'reactionListPosition' | 'ReactionListTop' + | 'setQuotedMessageState' >; const MessageSimpleWithContext = < @@ -77,9 +84,13 @@ const MessageSimpleWithContext = < props: MessageSimplePropsWithContext, ) => { const [messageContentWidth, setMessageContentWidth] = useState(0); + const swipeableRef = useRef(null); + const { alignment, + clearQuotedMessageState, enableMessageGroupingByUser, + enableSwipeToReply, groupStyles, hasReactions, isMyMessage, @@ -94,11 +105,13 @@ const MessageSimpleWithContext = < MessagePinnedHeader, MessageReplies, MessageStatus, + MessageSwipeLeftContent, onlyEmojis, otherAttachments, ReactionListBottom, reactionListPosition, ReactionListTop, + setQuotedMessageState, showMessageStatus, } = props; @@ -234,7 +247,25 @@ const MessageSimpleWithContext = < {message.pinned ? : null} - + { + if (!swipeableRef.current) return; + clearQuotedMessageState(); + setQuotedMessageState(message); + swipeableRef.current.close(); + }} + ref={swipeableRef} + renderLeftActions={() => { + if (alignment === 'left' && enableSwipeToReply) { + return MessageSwipeLeftContent ? : null; + } else { + return null; + } + }} + > ) : null} - + {reactionListPosition === 'bottom' && ReactionListBottom ? : null} @@ -394,7 +425,9 @@ export const MessageSimple = < showMessageStatus, } = useMessageContext(); const { + clearQuotedMessageState, enableMessageGroupingByUser, + enableSwipeToReply, MessageAvatar, MessageContent, MessageDeleted, @@ -403,10 +436,12 @@ export const MessageSimple = < MessagePinnedHeader, MessageReplies, MessageStatus, + MessageSwipeLeftContent, myMessageTheme, ReactionListBottom, reactionListPosition, ReactionListTop, + setQuotedMessageState, } = useMessagesContext(); return ( @@ -414,7 +449,9 @@ export const MessageSimple = < {...{ alignment, channel, + clearQuotedMessageState, enableMessageGroupingByUser, + enableSwipeToReply, groupStyles, hasReactions, isMyMessage, @@ -429,12 +466,14 @@ export const MessageSimple = < MessagePinnedHeader, MessageReplies, MessageStatus, + MessageSwipeLeftContent, myMessageTheme, onlyEmojis, otherAttachments, ReactionListBottom, reactionListPosition, ReactionListTop, + setQuotedMessageState, showMessageStatus, }} {...props} diff --git a/package/src/components/Message/MessageSimple/MessageSwipeLeftContent.tsx b/package/src/components/Message/MessageSimple/MessageSwipeLeftContent.tsx new file mode 100644 index 000000000..1537bd903 --- /dev/null +++ b/package/src/components/Message/MessageSimple/MessageSwipeLeftContent.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; + +import { useTheme } from '../../../contexts/themeContext/ThemeContext'; +import { CurveLineLeftUp } from '../../../icons'; + +export const MessageSwipeLeftContent = () => { + const { + theme: { + colors: { grey }, + messageSimple: { + swipeLeftContent: { container }, + }, + }, + } = useTheme(); + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + paddingHorizontal: 16, + }, +}); diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index af64d4223..1582c74fd 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -384,120 +384,264 @@ exports[`Thread should match thread snapshot 1`] = ` } /> - + + + + + + + + + + + + - - Message6 + + Message6 + - + @@ -739,120 +883,264 @@ exports[`Thread should match thread snapshot 1`] = ` } /> - + + + + + + + + + + + + - - Message5 + + Message5 + - + @@ -1132,120 +1420,264 @@ exports[`Thread should match thread snapshot 1`] = ` } /> + - - + + + + + + + + + + + + - - Message4 + + Message4 + - + @@ -1480,120 +1912,264 @@ exports[`Thread should match thread snapshot 1`] = ` } /> - + + + + + + + + + + + + - - Message3 + + Message3 + - + diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx index 5aba809e3..a81d00019 100644 --- a/package/src/contexts/messagesContext/MessagesContext.tsx +++ b/package/src/contexts/messagesContext/MessagesContext.tsx @@ -100,6 +100,10 @@ export type MessagesContextValue< * Defaults to: [Card](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Attachment/Card.tsx) */ Card: React.ComponentType>; + /** + * Handler to clear the quoted state of the message. + */ + clearQuotedMessageState: () => void; /** * UI component for DateHeader * Defaults to: [DateHeader](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageList/DateHeader.tsx) @@ -358,6 +362,10 @@ export type MessagesContextValue< deletedMessagesVisibilityType?: DeletedMessagesVisibilityType; disableTypingIndicator?: boolean; + /** + * Enable swipe to reply on messages. + */ + enableSwipeToReply?: boolean; /** * Whether messages should be aligned to right or left part of screen. * By default, messages will be received messages will be aligned to left and @@ -463,6 +471,7 @@ export type MessagesContextValue< * Custom message header component */ MessageHeader?: React.ComponentType>; + MessageSwipeLeftContent?: React.ComponentType; /** Custom UI component for message text */ MessageText?: React.ComponentType>; /** diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index fe663bf60..a9eddca5b 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -654,6 +654,9 @@ export type Theme = { statusContainer: ViewStyle; timeIcon: IconProps; }; + swipeLeftContent: { + container: ViewStyle; + }; targetedMessageContainer: ViewStyle; videoThumbnail: { container: ViewStyle; @@ -1434,6 +1437,9 @@ export const defaultTheme: Theme = { width: DEFAULT_STATUS_ICON_SIZE, }, }, + swipeLeftContent: { + container: {}, + }, targetedMessageContainer: {}, unreadUnderlayColor: Colors.bg_gradient_start, videoThumbnail: {