From c860a556e0a9f9834e7990592f37db60358284e7 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 31 Jan 2024 10:42:07 +0530 Subject: [PATCH 01/11] fix: ReactNativeFlipper TS app package import (#2406) --- .../java/com/typescriptmessaging/ReactNativeFlipper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/TypeScriptMessaging/android/app/src/release/java/com/typescriptmessaging/ReactNativeFlipper.java b/examples/TypeScriptMessaging/android/app/src/release/java/com/typescriptmessaging/ReactNativeFlipper.java index 55b604041e..37fc9c5be4 100644 --- a/examples/TypeScriptMessaging/android/app/src/release/java/com/typescriptmessaging/ReactNativeFlipper.java +++ b/examples/TypeScriptMessaging/android/app/src/release/java/com/typescriptmessaging/ReactNativeFlipper.java @@ -4,7 +4,7 @@ *

This source code is licensed under the MIT license found in the LICENSE file in the root * directory of this source tree. */ -package com.rndiffapp; +package com.typescriptmessaging; import android.content.Context; import com.facebook.react.ReactInstanceManager; /** From 965691f3f76d60b12844839291256aa5dc28c5cd Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 31 Jan 2024 12:46:15 +0530 Subject: [PATCH 02/11] fix: keyboard transition issue when switching from attachment picker to keyboard (#2404) * fix: keyboard transition issue when switching from attachment picker to keyboard * fix: remove event listener function --- .../components/AttachmentPicker/AttachmentPicker.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/package/src/components/AttachmentPicker/AttachmentPicker.tsx b/package/src/components/AttachmentPicker/AttachmentPicker.tsx index fb9b76ca21..38ec0771f9 100644 --- a/package/src/components/AttachmentPicker/AttachmentPicker.tsx +++ b/package/src/components/AttachmentPicker/AttachmentPicker.tsx @@ -181,8 +181,14 @@ export const AttachmentPicker = React.forwardRef( }, [selectedPicker, closePicker]); useEffect(() => { + const onKeyboardOpenHandler = () => { + if (selectedPicker) { + setSelectedPicker(undefined); + } + closePicker(); + }; const keyboardShowEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; - const keyboardSubscription = Keyboard.addListener(keyboardShowEvent, closePicker); + const keyboardSubscription = Keyboard.addListener(keyboardShowEvent, onKeyboardOpenHandler); return () => { if (keyboardSubscription?.remove) { @@ -191,9 +197,9 @@ export const AttachmentPicker = React.forwardRef( } // To keep compatibility with older versions of React Native, where `remove()` is not available - Keyboard.removeListener(keyboardShowEvent, closePicker); + Keyboard.removeListener(keyboardShowEvent, onKeyboardOpenHandler); }; - }, [closePicker]); + }, [closePicker, selectedPicker]); useEffect(() => { if (currentIndex < 0) { From 4b6ace014471cd1c6e8aa60bb4b6af4aa028a9f1 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 31 Jan 2024 16:21:43 +0530 Subject: [PATCH 03/11] fix: issues with navigation in SampleApp --- examples/SampleApp/App.tsx | 17 ++++++++++------- .../src/screens/AdvancedUserSelectorScreen.tsx | 2 -- .../src/screens/ChannelPinnedMessagesScreen.tsx | 2 +- .../SampleApp/src/screens/ChannelScreen.tsx | 4 +++- examples/SampleApp/src/screens/ChatScreen.tsx | 4 ++-- .../src/screens/GroupChannelDetailsScreen.tsx | 2 +- .../src/screens/OneOnOneChannelDetailScreen.tsx | 2 +- .../src/screens/SharedGroupsScreen.tsx | 6 +++--- examples/SampleApp/src/types.ts | 2 +- 9 files changed, 22 insertions(+), 19 deletions(-) diff --git a/examples/SampleApp/App.tsx b/examples/SampleApp/App.tsx index f4a1ec71fc..aae87b9686 100644 --- a/examples/SampleApp/App.tsx +++ b/examples/SampleApp/App.tsx @@ -162,11 +162,10 @@ const App = () => { const DrawerNavigator: React.FC = () => ( } - drawerStyle={{ - width: 300, - }} screenOptions={{ - gestureEnabled: true, + drawerStyle: { + width: 300, + }, }} > @@ -204,7 +203,7 @@ const UserSelector = () => ( { return ( - + { }, ]} > - ; export type ChannelPinnedMessagesScreenProps = { diff --git a/examples/SampleApp/src/screens/ChannelScreen.tsx b/examples/SampleApp/src/screens/ChannelScreen.tsx index 0be75d561a..e9b09d16e0 100644 --- a/examples/SampleApp/src/screens/ChannelScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelScreen.tsx @@ -65,7 +65,9 @@ const ChannelHeader: React.FC = ({ channel }) => { if (!navigation.canGoBack()) { // if no previous screen was present in history, go to the list screen // this can happen when opened through push notification - navigation.navigate('ChatScreen'); + navigation.reset({ index: 0, routes: [{ name: 'MessagingScreen' }] }); + } else { + navigation.goBack(); } }} RightContent={() => ( diff --git a/examples/SampleApp/src/screens/ChatScreen.tsx b/examples/SampleApp/src/screens/ChatScreen.tsx index a9fca07206..72ac38745f 100644 --- a/examples/SampleApp/src/screens/ChatScreen.tsx +++ b/examples/SampleApp/src/screens/ChatScreen.tsx @@ -13,8 +13,8 @@ import type { BottomTabNavigatorParamList, StackNavigatorParamList } from '../ty const Tab = createBottomTabNavigator(); -type ChatScreenNavigationProp = StackNavigationProp; -type ChatScreenRouteProp = RouteProp; +type ChatScreenNavigationProp = StackNavigationProp; +type ChatScreenRouteProp = RouteProp; type Props = { navigation: ChatScreenNavigationProp; diff --git a/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx b/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx index 05e67cee37..19ada37cee 100644 --- a/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx +++ b/examples/SampleApp/src/screens/GroupChannelDetailsScreen.tsx @@ -232,7 +232,7 @@ export const GroupChannelDetailsScreen: React.FC = ({ index: 0, routes: [ { - name: 'ChatScreen', + name: 'MessagingScreen', }, ], }); diff --git a/examples/SampleApp/src/screens/OneOnOneChannelDetailScreen.tsx b/examples/SampleApp/src/screens/OneOnOneChannelDetailScreen.tsx index f9ea713981..a79c582f5f 100644 --- a/examples/SampleApp/src/screens/OneOnOneChannelDetailScreen.tsx +++ b/examples/SampleApp/src/screens/OneOnOneChannelDetailScreen.tsx @@ -190,7 +190,7 @@ export const OneOnOneChannelDetailScreen: React.FC = ({ index: 0, routes: [ { - name: 'ChatScreen', + name: 'MessagingScreen', }, ], }); diff --git a/examples/SampleApp/src/screens/SharedGroupsScreen.tsx b/examples/SampleApp/src/screens/SharedGroupsScreen.tsx index 4ac34b9282..0d57413f5a 100644 --- a/examples/SampleApp/src/screens/SharedGroupsScreen.tsx +++ b/examples/SampleApp/src/screens/SharedGroupsScreen.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; -import { RouteProp, useNavigation } from '@react-navigation/native'; +import { NavigationProp, RouteProp, useNavigation } from '@react-navigation/native'; import { Avatar, ChannelList, @@ -59,7 +59,7 @@ type CustomPreviewProps = ChannelPreviewMessengerProps; const CustomPreview: React.FC = ({ channel }) => { const { chatClient } = useAppContext(); const name = useChannelPreviewDisplayName(channel, 30); - const navigation = useNavigation(); + const navigation = useNavigation>(); const { theme: { colors: { black, grey, grey_whisper, white_snow }, @@ -77,7 +77,7 @@ const CustomPreview: React.FC = ({ channel }) => { index: 1, routes: [ { - name: 'ChatScreen', + name: 'MessagingScreen', }, { name: 'ChannelScreen', diff --git a/examples/SampleApp/src/types.ts b/examples/SampleApp/src/types.ts index 0dabec166d..587b5ae500 100644 --- a/examples/SampleApp/src/types.ts +++ b/examples/SampleApp/src/types.ts @@ -47,10 +47,10 @@ export type StackNavigatorParamList = { channelId?: string; messageId?: string; }; - ChatScreen: undefined; GroupChannelDetailsScreen: { channel: Channel; }; + MessagingScreen: undefined; NewDirectMessagingScreen: undefined; NewGroupChannelAddMemberScreen: undefined; NewGroupChannelAssignNameScreen: undefined; From 5d76f6e597be20fc360ec3b179196fa932a6bbac Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Thu, 1 Feb 2024 18:37:05 +0530 Subject: [PATCH 04/11] feat: Moderation: show Blocked messages in SDK (#2408) * feat: Moderation: show Blocked messages in SDK * fix: message status icon --- package/src/components/Message/Message.tsx | 7 +++++-- .../Message/MessageSimple/MessageStatus.tsx | 12 +++++++----- package/src/store/apis/getChannelMessages.ts | 17 ++++++++++------- package/src/utils/utils.ts | 12 ++++++++++++ 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 7eb148b3a9..15d3e2b13d 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -44,7 +44,7 @@ import { import { isVideoPackageAvailable, triggerHaptic } from '../../native'; import type { DefaultStreamChatGenerics } from '../../types/types'; -import { hasOnlyEmojis, MessageStatusTypes } from '../../utils/utils'; +import { hasOnlyEmojis, isBlockedMessage, MessageStatusTypes } from '../../utils/utils'; import { isMessageWithStylesReadByAndDateSeparator, @@ -319,6 +319,9 @@ const MessageWithContext = < } const quotedMessage = message.quoted_message as MessageType; if (error) { + if (isBlockedMessage(message)) { + return; + } showMessageOverlay(false, true); } else if (quotedMessage) { onPressQuotedMessage(quotedMessage); @@ -598,7 +601,7 @@ const MessageWithContext = < }; const onLongPressMessage = - disabled || hasAttachmentActions + disabled || hasAttachmentActions || isBlockedMessage(message) ? () => null : onLongPressMessageProp ? (payload?: TouchableHandlerPayload) => diff --git a/package/src/components/Message/MessageSimple/MessageStatus.tsx b/package/src/components/Message/MessageSimple/MessageStatus.tsx index f118dd4b3a..a6ba28805b 100644 --- a/package/src/components/Message/MessageSimple/MessageStatus.tsx +++ b/package/src/components/Message/MessageSimple/MessageStatus.tsx @@ -69,11 +69,13 @@ const MessageStatusWithContext = < {message.readBy} ) : null} - {typeof message.readBy === 'number' || message.readBy === true ? ( - - ) : ( - - )} + {message.type !== 'error' ? ( + typeof message.readBy === 'number' || message.readBy === true ? ( + + ) : ( + + ) + ) : null} ); } diff --git a/package/src/store/apis/getChannelMessages.ts b/package/src/store/apis/getChannelMessages.ts index 327f6ca1be..ce492d6126 100644 --- a/package/src/store/apis/getChannelMessages.ts +++ b/package/src/store/apis/getChannelMessages.ts @@ -5,6 +5,7 @@ import { selectMessagesForChannels } from './queries/selectMessagesForChannels'; import { selectReactionsForMessages } from './queries/selectReactionsForMessages'; import type { DefaultStreamChatGenerics } from '../../types/types'; +import { isBlockedMessage } from '../../utils/utils'; import { mapStorableToMessage } from '../mappers/mapStorableToMessage'; import type { TableRowJoinedUser } from '../types'; @@ -37,13 +38,15 @@ export const getChannelMessages = < cidVsMessages[m.cid] = []; } - cidVsMessages[m.cid].push( - mapStorableToMessage({ - currentUserId, - messageRow: m, - reactionRows: messageIdVsReactions[m.id], - }), - ); + if (!isBlockedMessage(m)) { + cidVsMessages[m.cid].push( + mapStorableToMessage({ + currentUserId, + messageRow: m, + reactionRows: messageIdVsReactions[m.id], + }), + ); + } }); return cidVsMessages; diff --git a/package/src/utils/utils.ts b/package/src/utils/utils.ts index 4e5bc8469a..51b310a611 100644 --- a/package/src/utils/utils.ts +++ b/package/src/utils/utils.ts @@ -13,6 +13,7 @@ import type { UserResponse, } from 'stream-chat'; +import type { MessageType } from '../components/MessageList/hooks/useMessageList'; import type { MentionAllAppUsersQuery } from '../contexts/messageInputContext/MessageInputContext'; import type { SuggestionCommand, @@ -21,6 +22,7 @@ import type { } from '../contexts/suggestionsContext/SuggestionsContext'; import { compiledEmojis, Emoji } from '../emoji-data/compiled'; import type { IconProps } from '../icons/utils/base'; +import type { TableRowJoinedUser } from '../store/types'; import type { DefaultStreamChatGenerics, ValueOf } from '../types/types'; export type ReactionData = { @@ -82,6 +84,16 @@ export const getIndicatorTypeForFileState = ( return indicatorMap[fileState]; }; +export const isBlockedMessage = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + message: MessageType | TableRowJoinedUser<'messages'>, +) => { + // The only indicator for the blocked message is its message type is error and that the message text contains "Message was blocked by moderation policies". + const pattern = /\bMessage was blocked by moderation policies\b/; + return message.type === 'error' && message.text && pattern.test(message.text); +}; + const defaultAutoCompleteSuggestionsLimit = 10; const defaultMentionAllAppUsersQuery = { filters: {}, From 88238fd0f362e83cb4c44d07186121f2bca44909 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Fri, 2 Feb 2024 14:54:26 +0530 Subject: [PATCH 05/11] fix: scroll to bottom when message is removed from message list (#2411) * fix: scroll to bottom when message is removed from message list * refactor: remove parent_id check --- package/src/components/MessageList/MessageList.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 45189d841c..afa39ac0af 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -489,10 +489,18 @@ const MessageListWithContext = < if (!client || !channel || rawMessageList.length === 0) { return; } + /** + * Condition to check if a message is removed from MessageList. + * Eg: This would happen when giphy search is cancelled, etc. + * If such a case arises, we scroll to bottom. + */ + const isMessageRemovedFromMessageList = + messageListLengthAfterUpdate < messageListLengthBeforeUpdate.current; if ( - topMessageBeforeUpdate.current?.created_at && - topMessageAfterUpdate?.created_at && - topMessageBeforeUpdate.current.created_at < topMessageAfterUpdate.created_at + isMessageRemovedFromMessageList || + (topMessageBeforeUpdate.current?.created_at && + topMessageAfterUpdate?.created_at && + topMessageBeforeUpdate.current.created_at < topMessageAfterUpdate.created_at) ) { channelResyncScrollSet.current = false; setScrollToBottomButtonVisible(false); From 214cb8d214422802eb6dcb3d6a299bf167d7ca85 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Sat, 3 Feb 2024 12:46:04 +0530 Subject: [PATCH 06/11] refactor: remove URL regex usage to check if message text contains url (#2413) --- .../src/contexts/messageInputContext/MessageInputContext.tsx | 5 +++-- package/src/utils/utils.ts | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index 39ec5a5a5a..c7f65e1c22 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -20,6 +20,7 @@ import { import { useCreateMessageInputContext } from './hooks/useCreateMessageInputContext'; import { isEditingBoolean, useMessageDetailsForState } from './hooks/useMessageDetailsForState'; +import { parseLinksFromText } from '../../components/Message/MessageSimple/utils/parseLinks'; import type { AttachButtonProps } from '../../components/MessageInput/AttachButton'; import type { CommandsButtonProps } from '../../components/MessageInput/CommandsButton'; import type { InputEditingStateHeaderProps } from '../../components/MessageInput/components/InputEditingStateHeader'; @@ -53,7 +54,6 @@ import { FileStateValue, generateRandomId, TriggerSettings, - urlRegex, } from '../../utils/utils'; import { useAttachmentPickerContext } from '../attachmentPickerContext/AttachmentPickerContext'; import { ChannelContextValue, useChannelContext } from '../channelContext/ChannelContext'; @@ -689,8 +689,9 @@ export const MessageInputProvider = < if (sending.current) { return; } + const linkInfos = parseLinksFromText(text); - if (!channelCapabities.sendLinks && !!text.match(urlRegex)) { + if (!channelCapabities.sendLinks && linkInfos.length > 0) { Alert.alert(t('Links are disabled'), t('Sending links is not allowed in this conversation')); return; diff --git a/package/src/utils/utils.ts b/package/src/utils/utils.ts index 51b310a611..ac6c5db76e 100644 --- a/package/src/utils/utils.ts +++ b/package/src/utils/utils.ts @@ -555,8 +555,6 @@ export const hasOnlyEmojis = (text: string) => { return false; } }; -export const urlRegex = - /(?:\s|^)((?:https?:\/\/)?(?:[a-z0-9-]+(?:\.[a-z0-9-]+)+)(?::[0-9]+)?(?:\/(?:[^\s]+)?)?)/g; /** * Stringifies a message object From 4d1bf2d8ae495b44df8ebc49d2f7e18fe19c0f5b Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:21:48 +0100 Subject: [PATCH 07/11] fix: outdated unread count on offline support (#2412) * fix: unread count on offline support * fix: make forceload update the channel preview * remove unused --- .../ChannelList/hooks/usePaginatedChannels.ts | 9 ++++++--- .../src/components/ChannelPreview/ChannelPreview.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/package/src/components/ChannelList/hooks/usePaginatedChannels.ts b/package/src/components/ChannelList/hooks/usePaginatedChannels.ts index cd2a46f145..5a47ed9de3 100644 --- a/package/src/components/ChannelList/hooks/usePaginatedChannels.ts +++ b/package/src/components/ChannelList/hooks/usePaginatedChannels.ts @@ -168,7 +168,9 @@ export const usePaginatedChannels = < await queryChannels('refresh'); }; - const reloadList = () => queryChannels('reload'); + const reloadList = async () => { + await queryChannels('reload'); + }; /** * Equality check using stringified filters/sort ensure that we don't make un-necessary queryChannels api calls @@ -219,10 +221,11 @@ export const usePaginatedChannels = < if (enableOfflineSupport) { // Any time DB is synced, we need to update the UI with local DB channels first, // and then call queryChannels to ensure any new channels are added to UI. - listener = DBSyncManager.onSyncStatusChange((syncStatus) => { + listener = DBSyncManager.onSyncStatusChange(async (syncStatus) => { if (syncStatus) { loadOfflineChannels(); - reloadList(); + await reloadList(); + setForceUpdate((u) => u + 1); } }); // On start, load the channels from local db. diff --git a/package/src/components/ChannelPreview/ChannelPreview.tsx b/package/src/components/ChannelPreview/ChannelPreview.tsx index a7cbf66c3d..645f223ef5 100644 --- a/package/src/components/ChannelPreview/ChannelPreview.tsx +++ b/package/src/components/ChannelPreview/ChannelPreview.tsx @@ -16,7 +16,7 @@ import type { DefaultStreamChatGenerics } from '../../types/types'; export type ChannelPreviewPropsWithContext< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'client'> & - Pick, 'Preview'> & { + Pick, 'Preview' | 'forceUpdate'> & { /** * The previewed channel */ @@ -32,7 +32,7 @@ const ChannelPreviewWithContext = < >( props: ChannelPreviewPropsWithContext, ) => { - const { channel, client, Preview } = props; + const { channel, client, forceUpdate: channelListForceUpdate, Preview } = props; const [lastMessage, setLastMessage] = useState< | ReturnType['formatMessage']> @@ -70,7 +70,7 @@ const ChannelPreviewWithContext = < const newUnreadCount = channel.countUnread(); setUnread(newUnreadCount); - }, [channelLastMessageString]); + }, [channelLastMessageString, channelListForceUpdate]); useEffect(() => { const handleNewMessageEvent = (event: Event) => { @@ -126,7 +126,7 @@ export const ChannelPreview = < props: ChannelPreviewProps, ) => { const { client } = useChatContext(); - const { Preview } = useChannelsContext(); + const { forceUpdate, Preview } = useChannelsContext(); - return ; + return ; }; From a2ed1bd83f7605551387d6704fdb689588c6f3e5 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Tue, 6 Feb 2024 15:09:13 +0530 Subject: [PATCH 08/11] feat: add ability to handle bounced message (#2415) * feat: add ability to handle bounced message * feat: add translations * feat: add translations * fix: MessageBounce component * fix: MessageBounce component --- .../message-input-context/editing.mdx | 8 +- package/expo-package/yarn.lock | 8 +- package/native-package/yarn.lock | 8 +- package/package.json | 2 +- package/src/components/Channel/Channel.tsx | 33 +++-- .../Channel/hooks/useCreateMessagesContext.ts | 2 + package/src/components/Message/Message.tsx | 28 ++++- .../Message/MessageSimple/MessageBounce.tsx | 119 ++++++++++++++++++ .../Message/hooks/useMessageActionHandlers.ts | 3 +- .../Message/hooks/useMessageActions.tsx | 2 +- package/src/components/index.ts | 1 + .../MessageInputContext.tsx | 30 ++++- .../__tests__/sendMessage.test.tsx | 16 +-- .../__tests__/updateMessage.test.tsx | 2 +- .../useMessageDetailsForState.test.tsx | 7 +- .../hooks/useMessageDetailsForState.ts | 12 +- .../messagesContext/MessagesContext.tsx | 9 +- package/src/i18n/en.json | 5 +- package/src/i18n/es.json | 5 +- package/src/i18n/fr.json | 5 +- package/src/i18n/he.json | 5 +- package/src/i18n/hi.json | 5 +- package/src/i18n/it.json | 5 +- package/src/i18n/ja.json | 5 +- package/src/i18n/ko.json | 5 +- package/src/i18n/nl.json | 5 +- package/src/i18n/ru.json | 5 +- package/src/i18n/tr.json | 5 +- package/src/utils/removeReservedFields.ts | 7 +- package/src/utils/utils.ts | 16 +++ package/yarn.lock | 8 +- 31 files changed, 307 insertions(+), 69 deletions(-) create mode 100644 package/src/components/Message/MessageSimple/MessageBounce.tsx diff --git a/docusaurus/docs/reactnative/common-content/contexts/message-input-context/editing.mdx b/docusaurus/docs/reactnative/common-content/contexts/message-input-context/editing.mdx index 122204af58..dfce7a338c 100644 --- a/docusaurus/docs/reactnative/common-content/contexts/message-input-context/editing.mdx +++ b/docusaurus/docs/reactnative/common-content/contexts/message-input-context/editing.mdx @@ -1,5 +1,5 @@ -True if the user is editing some message within `MessageInput` component. +Defined with message type if the user is editing some message within `MessageInput` component else its undefined. -| Type | -| ------- | -| boolean | +| Type | +| ------------------------ | +| MessageType \| undefined | diff --git a/package/expo-package/yarn.lock b/package/expo-package/yarn.lock index 9aa9f0693f..f2d0a6052c 100644 --- a/package/expo-package/yarn.lock +++ b/package/expo-package/yarn.lock @@ -3046,10 +3046,10 @@ stream-buffers@2.2.x: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== -stream-chat-react-native-core@5.23.1: - version "5.23.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.23.1.tgz#9bccc2c534762b764cb98703db5b6df1c7d1e394" - integrity sha512-ls+Tvtcbl+lfMgGlFBtHBfvyAKlY94jD9nhFgSeibV2vZzySvl8me9z3pDSZQVFDtqLbdnxCruGa91+hyrfGAg== +stream-chat-react-native-core@5.23.2: + version "5.23.2" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.23.2.tgz#96afac810359f98615906f6c4899fdb9f8c4b609" + integrity sha512-Su94hdFMJfpIlqGRrxvSHy49c1HvJsHMSjc5rEK4K+AzLzMErWxL0/eHCF/QMFA10jkqT1Y2fn9V40byy0fIkg== dependencies: "@babel/runtime" "^7.12.5" "@gorhom/bottom-sheet" "4.4.8" diff --git a/package/native-package/yarn.lock b/package/native-package/yarn.lock index 1f9dc9ce9f..5c9c5aea5b 100644 --- a/package/native-package/yarn.lock +++ b/package/native-package/yarn.lock @@ -4391,10 +4391,10 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@5.23.1: - version "5.23.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.23.1.tgz#9bccc2c534762b764cb98703db5b6df1c7d1e394" - integrity sha512-ls+Tvtcbl+lfMgGlFBtHBfvyAKlY94jD9nhFgSeibV2vZzySvl8me9z3pDSZQVFDtqLbdnxCruGa91+hyrfGAg== +stream-chat-react-native-core@5.23.2: + version "5.23.2" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-5.23.2.tgz#96afac810359f98615906f6c4899fdb9f8c4b609" + integrity sha512-Su94hdFMJfpIlqGRrxvSHy49c1HvJsHMSjc5rEK4K+AzLzMErWxL0/eHCF/QMFA10jkqT1Y2fn9V40byy0fIkg== dependencies: "@babel/runtime" "^7.12.5" "@gorhom/bottom-sheet" "4.4.8" diff --git a/package/package.json b/package/package.json index c22463119e..0c0c117c67 100644 --- a/package/package.json +++ b/package/package.json @@ -80,7 +80,7 @@ "path": "0.12.7", "react-native-markdown-package": "1.8.2", "react-native-url-polyfill": "^1.3.0", - "stream-chat": "8.14.4" + "stream-chat": "8.15.0" }, "peerDependencies": { "react-native-quick-sqlite": ">=5.1.0", diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 6952544a3a..e8a37090e3 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -82,7 +82,14 @@ import { compressedImageURI } from '../../utils/compressImage'; import { DBSyncManager } from '../../utils/DBSyncManager'; import { patchMessageTextCommand } from '../../utils/patchMessageTextCommand'; import { removeReactionFromLocalState } from '../../utils/removeReactionFromLocalState'; -import { generateRandomId, isLocalUrl, MessageStatusTypes, ReactionData } from '../../utils/utils'; +import { removeReservedFields } from '../../utils/removeReservedFields'; +import { + generateRandomId, + isBouncedMessage, + isLocalUrl, + MessageStatusTypes, + ReactionData, +} from '../../utils/utils'; import { Attachment as AttachmentDefault } from '../Attachment/Attachment'; import { AttachmentActions as AttachmentActionsDefault } from '../Attachment/AttachmentActions'; import { AudioAttachment as AudioAttachmentDefault } from '../Attachment/AudioAttachment'; @@ -107,6 +114,7 @@ import { LoadingIndicator as LoadingIndicatorDefault } from '../Indicators/Loadi import { KeyboardCompatibleView as KeyboardCompatibleViewDefault } from '../KeyboardCompatibleView/KeyboardCompatibleView'; import { Message as MessageDefault } from '../Message/Message'; import { MessageAvatar as MessageAvatarDefault } from '../Message/MessageSimple/MessageAvatar'; +import { MessageBounce as MessageBounceDefault } from '../Message/MessageSimple/MessageBounce'; import { MessageContent as MessageContentDefault } from '../Message/MessageSimple/MessageContent'; import { MessageDeleted as MessageDeletedDefault } from '../Message/MessageSimple/MessageDeleted'; import { MessageError as MessageErrorDefault } from '../Message/MessageSimple/MessageError'; @@ -273,6 +281,7 @@ export type ChannelPropsWithContext< | 'Message' | 'messageActions' | 'MessageAvatar' + | 'MessageBounce' | 'MessageContent' | 'messageContentOrder' | 'MessageDeleted' @@ -495,6 +504,7 @@ const ChannelWithContext = < Message = MessageDefault, messageActions, MessageAvatar = MessageAvatarDefault, + MessageBounce = MessageBounceDefault, MessageContent = MessageContentDefault, messageContentOrder = ['quoted_reply', 'gallery', 'files', 'text', 'attachments'], MessageDeleted = MessageDeletedDefault, @@ -564,7 +574,7 @@ const ChannelWithContext = < }, } = useTheme(); const [deleted, setDeleted] = useState(false); - const [editing, setEditing] = useState>(false); + const [editing, setEditing] = useState | undefined>(undefined); const [error, setError] = useState(false); const [hasMore, setHasMore] = useState(true); const [lastRead, setLastRead] = useState['lastRead']>(); @@ -1679,9 +1689,17 @@ const ChannelWithContext = < status: MessageStatusTypes.SENDING, }; - updateMessage(statusPendingMessage); + const messageWithoutReservedFields = removeReservedFields(statusPendingMessage); + + // For bounced messages, we don't need to update the message, instead always send a new message. + if (!isBouncedMessage(message)) { + updateMessage(messageWithoutReservedFields as MessageResponse); + } - await sendMessageRequest(statusPendingMessage, true); + await sendMessageRequest( + messageWithoutReservedFields as MessageResponse, + true, + ); }; // hard limit to prevent you from scrolling faster than 1 page per 2 seconds @@ -1843,10 +1861,10 @@ const ChannelWithContext = < : client.updateMessage(updatedMessage); const setEditingState: MessagesContextValue['setEditingState'] = ( - messageOrBoolean, + message, ) => { clearQuotedMessageState(); - setEditing(messageOrBoolean); + setEditing(message); }; const setQuotedMessageState: MessagesContextValue['setQuotedMessageState'] = ( @@ -1856,7 +1874,7 @@ const ChannelWithContext = < }; const clearEditingState: InputMessageInputContextValue['clearEditingState'] = - () => setEditing(false); + () => setEditing(undefined); const clearQuotedMessageState: InputMessageInputContextValue['clearQuotedMessageState'] = () => setQuotedMessage(false); @@ -2223,6 +2241,7 @@ const ChannelWithContext = < Message, messageActions, MessageAvatar, + MessageBounce, MessageContent, messageContentOrder, MessageDeleted, diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index 1cc8ca3290..aceb116475 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -54,6 +54,7 @@ export const useCreateMessagesContext = < Message, messageActions, MessageAvatar, + MessageBounce, MessageContent, messageContentOrder, MessageDeleted, @@ -149,6 +150,7 @@ export const useCreateMessagesContext = < Message, messageActions, MessageAvatar, + MessageBounce, MessageContent, messageContentOrder, MessageDeleted, diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 15d3e2b13d..936441e844 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { GestureResponderEvent, Keyboard, StyleProp, View, ViewStyle } from 'react-native'; import type { Attachment, UserResponse } from 'stream-chat'; @@ -44,7 +44,12 @@ import { import { isVideoPackageAvailable, triggerHaptic } from '../../native'; import type { DefaultStreamChatGenerics } from '../../types/types'; -import { hasOnlyEmojis, isBlockedMessage, MessageStatusTypes } from '../../utils/utils'; +import { + hasOnlyEmojis, + isBlockedMessage, + isBouncedMessage, + MessageStatusTypes, +} from '../../utils/utils'; import { isMessageWithStylesReadByAndDateSeparator, @@ -141,6 +146,7 @@ export type MessagePropsWithContext< | 'isAttachmentEqual' | 'messageActions' | 'messageContentOrder' + | 'MessageBounce' | 'MessageSimple' | 'onLongPressMessage' | 'onPressInMessage' @@ -217,6 +223,7 @@ const MessageWithContext = < >( props: MessagePropsWithContext, ) => { + const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false); const isMessageTypeDeleted = props.message.type === 'deleted'; const { @@ -249,6 +256,7 @@ const MessageWithContext = < messageActions: messageActionsProp = defaultMessageActions, messageContentOrder: messageContentOrderProp, messagesContext, + MessageBounce, MessageSimple, onLongPress: onLongPressProp, onLongPressMessage: onLongPressMessageProp, @@ -319,9 +327,19 @@ const MessageWithContext = < } const quotedMessage = message.quoted_message as MessageType; if (error) { + /** + * If its a Blocked message, we don't do anything as per specs. + */ if (isBlockedMessage(message)) { return; } + /** + * If its a Bounced message, we open the message bounced options modal. + */ + if (isBouncedMessage(message)) { + setIsBounceDialogOpen(true); + return; + } showMessageOverlay(false, true); } else if (quotedMessage) { onPressQuotedMessage(quotedMessage); @@ -622,6 +640,11 @@ const MessageWithContext = < }) : enableLongPress ? () => { + // If a message is bounced, on long press the message bounce options modal should open. + if (isBouncedMessage(message)) { + setIsBounceDialogOpen(true); + return; + } triggerHaptic('impactMedium'); showMessageOverlay(false); } @@ -730,6 +753,7 @@ const MessageWithContext = < > + {isBounceDialogOpen && } diff --git a/package/src/components/Message/MessageSimple/MessageBounce.tsx b/package/src/components/Message/MessageSimple/MessageBounce.tsx new file mode 100644 index 0000000000..cc00282698 --- /dev/null +++ b/package/src/components/Message/MessageSimple/MessageBounce.tsx @@ -0,0 +1,119 @@ +import React from 'react'; +import { Alert } from 'react-native'; + +import { + MessageContextValue, + useMessageContext, +} from '../../../contexts/messageContext/MessageContext'; +import { + MessagesContextValue, + useMessagesContext, +} from '../../../contexts/messagesContext/MessagesContext'; +import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; + +import type { DefaultStreamChatGenerics } from '../../../types/types'; + +export type MessageBouncePropsWithContext< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Pick< + MessagesContextValue, + 'setEditingState' | 'removeMessage' | 'retrySendMessage' +> & + Pick, 'message'> & { + setIsBounceDialogOpen: React.Dispatch>; + }; + +export const MessageBounceWithContext = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: MessageBouncePropsWithContext, +) => { + const { t } = useTranslationContext(); + const { message, removeMessage, retrySendMessage, setEditingState, setIsBounceDialogOpen } = + props; + + const handleEditMessage = () => { + setEditingState(message); + if (setIsBounceDialogOpen) { + setIsBounceDialogOpen(false); + } + }; + + const handleResend = () => { + retrySendMessage(message); + if (setIsBounceDialogOpen) { + setIsBounceDialogOpen(false); + } + }; + + const handleRemoveMessage = () => { + removeMessage(message); + if (setIsBounceDialogOpen) { + setIsBounceDialogOpen(false); + } + }; + + return ( + <> + {Alert.alert( + t('Are you sure?'), + t( + 'Consider how your comment might make others feel and be sure to follow our Community Guidelines', + ), + [ + { onPress: handleResend, text: t('Send Anyway') }, + { onPress: handleEditMessage, text: t('Edit Message') }, + { onPress: handleRemoveMessage, text: t('Delete Message') }, + ], + { cancelable: true }, + )} + + ); +}; + +const areEqual = ( + prevProps: MessageBouncePropsWithContext, + nextProps: MessageBouncePropsWithContext, +) => { + const { message: prevMessage } = prevProps; + const { message: nextMessage } = nextProps; + const messageEqual = + prevMessage.cid === nextMessage.cid && + prevMessage.type === nextMessage.type && + prevMessage.text === nextMessage.text; + if (!messageEqual) return false; + + return true; +}; + +const MemoizedMessageBounce = React.memo( + MessageBounceWithContext, + areEqual, +) as typeof MessageBounceWithContext; + +export type MessageBounceProps< + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = Partial> & { + setIsBounceDialogOpen: React.Dispatch>; +}; + +export const MessageBounce = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + props: MessageBounceProps, +) => { + const { message } = useMessageContext(); + const { removeMessage, retrySendMessage, setEditingState } = + useMessagesContext(); + return ( + + {...{ + message, + removeMessage, + retrySendMessage, + setEditingState, + }} + {...props} + /> + ); +}; diff --git a/package/src/components/Message/hooks/useMessageActionHandlers.ts b/package/src/components/Message/hooks/useMessageActionHandlers.ts index 169fec8df5..4fc7cb9f08 100644 --- a/package/src/components/Message/hooks/useMessageActionHandlers.ts +++ b/package/src/components/Message/hooks/useMessageActionHandlers.ts @@ -32,8 +32,7 @@ export const useMessageActionHandlers = < Pick, 'channel' | 'enforceUniqueReaction'> & Pick, 'client'> & Pick, 'message'>) => { - const handleResendMessage = () => - retrySendMessage(message as MessageResponse); + const handleResendMessage = () => retrySendMessage(message); const handleQuotedReplyMessage = () => { setQuotedMessageState(message); diff --git a/package/src/components/Message/hooks/useMessageActions.tsx b/package/src/components/Message/hooks/useMessageActions.tsx index b173535e4b..1cc87dac5b 100644 --- a/package/src/components/Message/hooks/useMessageActions.tsx +++ b/package/src/components/Message/hooks/useMessageActions.tsx @@ -339,7 +339,7 @@ export const useMessageActions = < setOverlay('none'); const messageWithoutReservedFields = removeReservedFields(message); if (handleRetry) { - handleRetry(messageWithoutReservedFields); + handleRetry(messageWithoutReservedFields as MessageType); } await handleResendMessage(); diff --git a/package/src/components/index.ts b/package/src/components/index.ts index ebdaf38f83..ebbed257e8 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -96,6 +96,7 @@ export * from './Message/hooks/useMessageActions'; export * from './Message/hooks/useMessageActionHandlers'; export * from './Message/Message'; export * from './Message/MessageSimple/MessageAvatar'; +export * from './Message/MessageSimple/MessageBounce'; export * from './Message/MessageSimple/MessageContent'; export * from './Message/MessageSimple/MessageDeleted'; export * from './Message/MessageSimple/MessageError'; diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index c7f65e1c22..5814a9d3f9 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -18,7 +18,7 @@ import { } from 'stream-chat'; import { useCreateMessageInputContext } from './hooks/useCreateMessageInputContext'; -import { isEditingBoolean, useMessageDetailsForState } from './hooks/useMessageDetailsForState'; +import { useMessageDetailsForState } from './hooks/useMessageDetailsForState'; import { parseLinksFromText } from '../../components/Message/MessageSimple/utils/parseLinks'; import type { AttachButtonProps } from '../../components/MessageInput/AttachButton'; @@ -53,11 +53,13 @@ import { FileState, FileStateValue, generateRandomId, + isBouncedMessage, TriggerSettings, } from '../../utils/utils'; import { useAttachmentPickerContext } from '../attachmentPickerContext/AttachmentPickerContext'; import { ChannelContextValue, useChannelContext } from '../channelContext/ChannelContext'; import { useChatContext } from '../chatContext/ChatContext'; +import { useMessagesContext } from '../messagesContext/MessagesContext'; import { useOwnCapabilitiesContext } from '../ownCapabilitiesContext/OwnCapabilitiesContext'; import { useThreadContext } from '../threadContext/ThreadContext'; import { useTranslationContext } from '../translationContext/TranslationContext'; @@ -226,7 +228,6 @@ export type InputMessageInputContextValue< * **default** [CooldownTimer](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/MessageInput/CooldownTimer.tsx) */ CooldownTimer: React.ComponentType; - editing: boolean | MessageType; editMessage: StreamChat['updateMessage']; /** @@ -331,6 +332,12 @@ export type InputMessageInputContextValue< channel: ChannelContextValue['channel'], ) => Promise; + /** + * Variable that tracks the editing state. + * It is defined with message type if the editing state is true, else its undefined. + */ + editing?: MessageType; + /** Initial value to set on input */ initialValue?: string; /** @@ -398,6 +405,7 @@ export const MessageInputProvider = < const { closePicker, openPicker, selectedPicker, setSelectedPicker } = useAttachmentPickerContext(); const { appSettings, client, enableOfflineSupport } = useChatContext(); + const { removeMessage } = useMessagesContext(); const getFileUploadConfig = () => { const fileConfig = appSettings?.app?.file_upload_config; @@ -625,6 +633,9 @@ export const MessageInputProvider = < (prevNumberOfUploads) => prevNumberOfUploads - (pendingAttachments?.length || 0), ); setText(''); + if (value.editing) { + value.clearEditingState(); + } }; const mapImageUploadToAttachment = (image: ImageUpload): Attachment => { @@ -771,9 +782,10 @@ export const MessageInputProvider = < return; } - if (value.editing && !isEditingBoolean(value.editing)) { + const message = value.editing; + if (message && message.type !== 'error') { const updatedMessage = { - ...value.editing, + ...message, attachments, mentioned_users: mentionedUsers, quoted_message: undefined, @@ -795,6 +807,12 @@ export const MessageInputProvider = < sending.current = false; } else { try { + /** + * If the message is bounced by moderation, we firstly remove the message from message list and then send a new message. + */ + if (message && isBouncedMessage(message as MessageType)) { + removeMessage(message); + } value.sendMessage({ attachments, mentioned_users: uniq(mentionedUsers), @@ -890,7 +908,7 @@ export const MessageInputProvider = < const updateMessage = async () => { try { - if (!isEditingBoolean(value.editing)) { + if (value.editing) { await client.updateMessage({ ...value.editing, quoted_message: undefined, @@ -898,8 +916,8 @@ export const MessageInputProvider = < } as Parameters['updateMessage']>[0]); } - resetInput(); value.clearEditingState(); + resetInput(); } catch (error) { console.log(error); } diff --git a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx index 157e93f774..90e9f5eefa 100644 --- a/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/sendMessage.test.tsx @@ -54,7 +54,7 @@ describe("MessageInputContext's sendMessage", () => { const images = generateImageUploadPreview({ state: FileState.UPLOAD_FAILED }); const { result } = renderHook(() => useMessageInputContext(), { initialProps: { - editing: true, + editing: undefined, }, wrapper: Wrapper, }); @@ -76,7 +76,7 @@ describe("MessageInputContext's sendMessage", () => { const { rerender, result } = renderHook(() => useMessageInputContext(), { initialProps: { - editing: true, + editing: message, sendImageAsync: true, }, wrapper: Wrapper, @@ -92,7 +92,7 @@ describe("MessageInputContext's sendMessage", () => { result.current.sendMessage(); }); - rerender({ editing: false, sendImageAsync: true }); + rerender({ editing: undefined, sendImageAsync: true }); await expect(result.current.asyncIds).toHaveLength(1); await expect(result.current.sending.current).toBeFalsy(); @@ -103,7 +103,7 @@ describe("MessageInputContext's sendMessage", () => { const { result } = renderHook(() => useMessageInputContext(), { initialProps: { - editing: true, + editing: message, }, wrapper: Wrapper, }); @@ -125,7 +125,7 @@ describe("MessageInputContext's sendMessage", () => { const files = generateFileUploadPreview({ state: FileState.UPLOADING }); const { result } = renderHook(() => useMessageInputContext(), { initialProps: { - editing: true, + editing: message, }, wrapper: Wrapper, }); @@ -153,7 +153,7 @@ describe("MessageInputContext's sendMessage", () => { const { result } = renderHook(() => useMessageInputContext(), { initialProps: { clearQuotedMessageState: clearQuotedMessageStateMock, - editing: true, + editing: undefined, quotedMessage: false, sendMessage: sendMessageMock, }, @@ -189,7 +189,7 @@ describe("MessageInputContext's sendMessage", () => { const { result } = renderHook(() => useMessageInputContext(), { initialProps: { clearQuotedMessageState: clearQuotedMessageStateMock, - editing: true, + editing: undefined, quotedMessage: generatedQuotedMessage, setQuotedMessageState: setQuotedMessageStateMock, }, @@ -259,7 +259,7 @@ describe("MessageInputContext's sendMessage", () => { const { result } = renderHook(() => useMessageInputContext(), { initialProps: { clearQuotedMessageState: clearQuotedMessageStateMock, - editing: true, + editing: undefined, quotedMessage: false, sendMessage: sendMessageMock, }, diff --git a/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx b/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx index 023842df8e..a87ad65be1 100644 --- a/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/updateMessage.test.tsx @@ -87,6 +87,6 @@ describe("MessageInputContext's updateMessage", () => { await result.current.updateMessage(); }); - expect(clearEditingStateMock).toHaveBeenCalledTimes(1); + expect(clearEditingStateMock).toHaveBeenCalledTimes(2); }); }); diff --git a/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx b/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx index 26e6d3f379..80e33f6e6d 100644 --- a/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx +++ b/package/src/contexts/messageInputContext/__tests__/useMessageDetailsForState.test.tsx @@ -26,7 +26,12 @@ describe('useMessageDetailsForState', () => { it('showMoreOptions is true when initialValue and text is same', () => { const { result } = renderHook( ({ initialValue, message }) => useMessageDetailsForState(message, initialValue), - { initialProps: { initialValue: 'Dummy text', message: true } }, + { + initialProps: { + initialValue: 'Dummy text', + message: generateMessage({ text: 'Dummy text' }) as MessageType, + }, + }, ); expect(result.current.showMoreOptions).toBe(true); diff --git a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts index c79292fc77..048261a3fd 100644 --- a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts +++ b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts @@ -5,12 +5,6 @@ import { generateRandomId } from '../../../utils/utils'; import type { MessageInputContextValue } from '../MessageInputContext'; -export const isEditingBoolean = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, ->( - editing: MessageInputContextValue['editing'], -): editing is boolean => typeof editing === 'boolean'; - export const useMessageDetailsForState = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( @@ -32,17 +26,17 @@ export const useMessageDetailsForState = < }, [text]); const messageValue = - typeof message === 'boolean' ? '' : `${message.id}${message.text}${message.updated_at}`; + message === undefined ? '' : `${message.id}${message.text}${message.updated_at}`; useEffect(() => { - if (!isEditingBoolean(message) && Array.isArray(message?.mentioned_users)) { + if (message && Array.isArray(message?.mentioned_users)) { const mentionedUsers = message.mentioned_users.map((user) => user.id); setMentionedUsers(mentionedUsers); } }, [messageValue]); useEffect(() => { - if (message && !isEditingBoolean(message)) { + if (message) { setText(message?.text || ''); const newFileUploads: FileUpload[] = []; const newImageUploads: ImageUpload[] = []; diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx index 59cdab88b0..b563dfa72a 100644 --- a/package/src/contexts/messagesContext/MessagesContext.tsx +++ b/package/src/contexts/messagesContext/MessagesContext.tsx @@ -24,6 +24,7 @@ import type { MessageTouchableHandlerPayload, } from '../../components/Message/Message'; import type { MessageAvatarProps } from '../../components/Message/MessageSimple/MessageAvatar'; +import type { MessageBounceProps } from '../../components/Message/MessageSimple/MessageBounce'; import type { MessageContentProps } from '../../components/Message/MessageSimple/MessageContent'; import type { MessageDeletedProps } from '../../components/Message/MessageSimple/MessageDeleted'; import type { MessageErrorProps } from '../../components/Message/MessageSimple/MessageError'; @@ -158,6 +159,10 @@ export type MessagesContextValue< * Defaults to: [MessageAvatar](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageAvatar.tsx) **/ MessageAvatar: React.ComponentType>; + /** + * UI Component for MessageBounce + */ + MessageBounce: React.ComponentType>; /** * UI component for MessageContent * Defaults to: [MessageContent](https://github.com/GetStream/stream-chat-react-native/blob/main/package/src/components/Message/MessageSimple/MessageContent.tsx) @@ -227,14 +232,14 @@ export type MessagesContextValue< /** * Override the api request for retry message functionality. */ - retrySendMessage: (message: MessageResponse) => Promise; + retrySendMessage: (message: MessageType) => Promise; /** * UI component for ScrollToBottomButton * Defaults to: [ScrollToBottomButton](https://getstream.io/chat/docs/sdk/reactnative/ui-components/scroll-to-bottom-button/) */ ScrollToBottomButton: React.ComponentType; sendReaction: (type: string, messageId: string) => Promise; - setEditingState: (message: MessageType | boolean) => void; + setEditingState: (message?: MessageType) => void; setQuotedMessageState: (message: MessageType | boolean) => void; supportedReactions: ReactionData[]; /** diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json index 99fcacd7c7..38d1a8f787 100644 --- a/package/src/i18n/en.json +++ b/package/src/i18n/en.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "How about sending your first message to a friend?", "Allow camera access in device settings": "Allow camera access in device settings", "Device camera is used to take photos or videos.": "Device camera is used to take photos or videos.", - "Open Settings": "Open Settings" + "Open Settings": "Open Settings", + "Send Anyway": "Send Anyway", + "Are you sure?": "Are you sure?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Consider how your comment might make others feel and be sure to follow our Community Guidelines" } diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json index dc099a3f87..a2dd37bffc 100644 --- a/package/src/i18n/es.json +++ b/package/src/i18n/es.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "¿Qué tal enviar tu primer mensaje a un amigo?", "Allow camera access in device settings": "Permitir el acceso a la cámara en la configuración del dispositivo", "Device camera is used to take photos or videos.": "La cámara del dispositivo se utiliza para tomar fotografías o vídeos.", - "Open Settings": "Configuración abierta" + "Open Settings": "Configuración abierta", + "Send Anyway": "Enviar de todos modos", + "Are you sure?": "¿Estás seguro?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Considera cómo tu comentario podría hacer sentir a los demás y asegúrate de seguir nuestras Normas de la Comunidad" } diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json index 22ed265d7b..f572a33f4e 100644 --- a/package/src/i18n/fr.json +++ b/package/src/i18n/fr.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "Et si vous envoyiez votre premier message à un ami ?", "Allow camera access in device settings": "Autoriser l'accès à la caméra dans les paramètres de l'appareil", "Device camera is used to take photos or videos.": "L'appareil photo de l'appareil est utilisé pour prendre des photos ou des vidéos.", - "Open Settings": "Ouvrir les paramètres" + "Open Settings": "Ouvrir les paramètres", + "Send Anyway": "Envoyer quand même", + "Are you sure?": "Es-tu sûr ?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Considérez comment votre commentaire pourrait faire sentir les autres et assurez-vous de suivre nos directives communautaires" } diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json index 7f949c7380..10149aaf49 100644 --- a/package/src/i18n/he.json +++ b/package/src/i18n/he.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "מה דעתך לשלוח את ההודעה הראשונה שלך לחבר?", "Allow camera access in device settings": "אפשר גישה למצלמה בהגדרות המכשיר", "Device camera is used to take photos or videos.": "מצלמת המכשיר משמשת לצילום תמונות או סרטונים.", - "Open Settings": "פתח את ההגדרות" + "Open Settings": "פתח את ההגדרות", + "Send Anyway": "שלח בכל זאת", + "Are you sure?": "האם אתה בטוח?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "שקול איך התגובה שלך עשויה להשפיע על אחרים ווודא שאתה עוקב אחר ההנחיות של הקהילה שלנו" } diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json index 25abc3d34b..da08935f7d 100644 --- a/package/src/i18n/hi.json +++ b/package/src/i18n/hi.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "किसी मित्र को अपना पहला संदेश भेजने के बारे में क्या ख़याल है?", "Allow camera access in device settings": "डिवाइस सेटिंग्स में कैमरा एक्सेस की अनुमति दें", "Device camera is used to take photos or videos.": "डिवाइस कैमरे का उपयोग फ़ोटो या वीडियो लेने के लिए किया जाता है।", - "Open Settings": "सेटिंग्स खोलें" + "Open Settings": "सेटिंग्स खोलें", + "Send Anyway": "फिर भी भेजें", + "Are you sure?": "क्या आप सुनिश्चित हैं?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "ध्यान दें कि आपका संदेश दूसरों को कैसा लगा सकता है और सुनिश्चित हों कि आप हमारी सामुदायिक अनुशासन का पालन कर रहे हैं" } diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json index 43abf8611e..7e358c8030 100644 --- a/package/src/i18n/it.json +++ b/package/src/i18n/it.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "Che ne dici di inviare il tuo primo messaggio ad un amico?", "Allow camera access in device settings": "Consenti l'accesso alla fotocamera nelle impostazioni del dispositivo", "Device camera is used to take photos or videos.": "La fotocamera del dispositivo viene utilizzata per scattare foto o video.", - "Open Settings": "Apri Impostazioni" + "Open Settings": "Apri Impostazioni", + "Send Anyway": "Invia comunque", + "Are you sure?": "Sei sicuro?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Considera come il tuo commento potrebbe far sentire gli altri e assicurati di seguire le nostre Linee guida della community" } diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json index 1b24e20471..8600f014c6 100644 --- a/package/src/i18n/ja.json +++ b/package/src/i18n/ja.json @@ -76,5 +76,8 @@ "How about sending your first message to a friend?": "初めてのメッセージを友達に送ってみてはいかがでしょうか?", "Allow camera access in device settings": "デバイス設定でカメラへのアクセスを許可する", "Device camera is used to take photos or videos.": "デバイスのカメラは写真やビデオの撮影に使用されます。", - "Open Settings": "設定を開く" + "Open Settings": "設定を開く", + "Send Anyway": "とにかく送信", + "Are you sure?": "本当によろしいですか?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "あなたのコメントが他の人にどのように影響するか考え、必ずコミュニティガイドラインに従ってください" } diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json index 7451b0a1bc..d3244142f1 100644 --- a/package/src/i18n/ko.json +++ b/package/src/i18n/ko.json @@ -75,5 +75,8 @@ "How about sending your first message to a friend?": "친구에게 첫 번째 메시지를 보내는 것은 어떻습니까?", "Allow camera access in device settings": "기기 설정에서 카메라 액세스를 허용하세요.", "Device camera is used to take photos or videos.": "기기 카메라는 사진이나 동영상을 촬영하는 데 사용됩니다.", - "Open Settings": "설정 열기" + "Open Settings": "설정 열기", + "Send Anyway": "그래도 보내기", + "Are you sure?": "확실합니까?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "당신의 댓글이 다른 사람들에게 어떤 영향을 줄지 고려하고 반드시 우리의 커뮤니티 가이드라인을 따르십시오" } diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json index 6e21fc3880..95d88fb59f 100644 --- a/package/src/i18n/nl.json +++ b/package/src/i18n/nl.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "Wat dacht je ervan om je eerste bericht naar een vriend te sturen?", "Allow camera access in device settings": "Sta cameratoegang toe in de apparaatinstellingen", "Device camera is used to take photos or videos.": "De camera van het apparaat wordt gebruikt om foto's of video's te maken.", - "Open Settings": "Open instellingen" + "Open Settings": "Open instellingen", + "Send Anyway": "Toch verzenden", + "Are you sure?": "Weet je het zeker?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Denk na over hoe jouw opmerking anderen zou kunnen laten voelen en zorg ervoor dat je onze Community-richtlijnen volgt" } diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json index 0d953978a5..6c91360a46 100644 --- a/package/src/i18n/ru.json +++ b/package/src/i18n/ru.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "Как насчет отправки первого сообщения другу?", "Allow camera access in device settings": "Разрешите доступ к камере в настройках устройства.", "Device camera is used to take photos or videos.": "Камера устройства используется для съемки фотографий или видео.", - "Open Settings": "Открыть настройки" + "Open Settings": "Открыть настройки", + "Send Anyway": "Всё равно отправить", + "Are you sure?": "Вы уверены?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Обдумайте, как ваш комментарий может повлиять на других, и убедитесь, что вы следуете нашим правилам сообщества" } diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json index 37cf6c3694..5a626b0e80 100644 --- a/package/src/i18n/tr.json +++ b/package/src/i18n/tr.json @@ -73,5 +73,8 @@ "How about sending your first message to a friend?": "İlk mesajınızı bir arkadaşınıza göndermeye ne dersiniz?", "Allow camera access in device settings": "Cihaz ayarlarında kamera erişimine izin ver", "Device camera is used to take photos or videos.": "Cihaz kamerası fotoğraf veya video çekmek için kullanılır.", - "Open Settings": "Ayarları aç" + "Open Settings": "Ayarları aç", + "Send Anyway": "Yine de Gönder", + "Are you sure?": "Emin misiniz?", + "Consider how your comment might make others feel and be sure to follow our Community Guidelines": "Yorumunuzun diğerlerini nasıl hissettirebileceğini düşünün ve topluluk kurallarımızı takip ettiğinizden emin olun" } diff --git a/package/src/utils/removeReservedFields.ts b/package/src/utils/removeReservedFields.ts index b548ae38b5..99e0ed37ef 100644 --- a/package/src/utils/removeReservedFields.ts +++ b/package/src/utils/removeReservedFields.ts @@ -1,11 +1,13 @@ +import type { MessageResponse } from 'stream-chat'; + import type { MessageType } from '../components/MessageList/hooks/useMessageList'; import type { DefaultStreamChatGenerics } from '../types/types'; export const removeReservedFields = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( - message: MessageType, -) => { + message: MessageType | MessageResponse, +): MessageType | MessageResponse => { const retryMessage = { ...message }; const reserved = [ 'cid', @@ -21,6 +23,7 @@ export const removeReservedFields = < 'member_count', 'type', 'updated_at', + 'reply_count', ]; reserved.forEach((key) => { delete retryMessage[key]; diff --git a/package/src/utils/utils.ts b/package/src/utils/utils.ts index ac6c5db76e..7f8b63955e 100644 --- a/package/src/utils/utils.ts +++ b/package/src/utils/utils.ts @@ -84,6 +84,11 @@ export const getIndicatorTypeForFileState = ( return indicatorMap[fileState]; }; +/** + * Utility to check if the message is a Blocked message. + * @param message + * @returns boolean + */ export const isBlockedMessage = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( @@ -94,6 +99,17 @@ export const isBlockedMessage = < return message.type === 'error' && message.text && pattern.test(message.text); }; +/** + * Utility to check if the message is a Bounced message. + * @param message + * @returns boolean + */ +export const isBouncedMessage = < + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( + message: MessageType, +) => message.type === 'error' && message.moderation_details !== undefined; + const defaultAutoCompleteSuggestionsLimit = 10; const defaultMentionAllAppUsersQuery = { filters: {}, diff --git a/package/yarn.lock b/package/yarn.lock index 1f86ef0448..8610fdf3de 100644 --- a/package/yarn.lock +++ b/package/yarn.lock @@ -9511,10 +9511,10 @@ stream-buffers@2.2.x: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== -stream-chat@8.14.4: - version "8.14.4" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.14.4.tgz#b035fdafc96cc70e36492e7c11d1f6f51a3d878c" - integrity sha512-V/yTpNPL6+c1FR8u1woKCKjKXRNQ99WaozLt1WuynL1RMjWI8yDtv1VTz4PWrB2efrXCeXzI6SyfMqBQkR0XIw== +stream-chat@8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.15.0.tgz#37d84768b24e32fcea85b29165e05cb2d5cae517" + integrity sha512-6qrV0pL5dBkqPslpoSkBLjrgiMtFaOvUChue7j3VafhogXGLpu4j6ACPWq3Lrj9XJGfJkOwfT7LyqEhwfmajgQ== dependencies: "@babel/runtime" "^7.16.3" "@types/jsonwebtoken" "~9.0.0" From 14ab52f5c3ffdc2076f3a33628d7ff2e2e5c15ee Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Wed, 7 Feb 2024 12:29:16 +0530 Subject: [PATCH 09/11] fix: send button icon theme (#2417) --- package/src/components/MessageInput/SendButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package/src/components/MessageInput/SendButton.tsx b/package/src/components/MessageInput/SendButton.tsx index e2c1b5d067..758de9a3a6 100644 --- a/package/src/components/MessageInput/SendButton.tsx +++ b/package/src/components/MessageInput/SendButton.tsx @@ -40,8 +40,8 @@ const SendButtonWithContext = < testID='send-button' > {giphyActive && } - {!giphyActive && disabled && } - {!giphyActive && !disabled && } + {!giphyActive && disabled && } + {!giphyActive && !disabled && } ); }; From 3589004df4020ad428675b97d6b2d03b0f6b4bb1 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Thu, 8 Feb 2024 06:13:24 +0100 Subject: [PATCH 10/11] fix: my message theme not passed to overlay (#2416) * fix: unread count on offline support * fix: my message theme not passed to overlay * add to memo * isequal check * fix: message overlay theme rerender * fix: myMessageTheme in MessageTextContainer * fix: myMessaTheme update issue with attachment, card and gallery --------- Co-authored-by: Khushal Agarwal --- .../src/components/Attachment/Attachment.tsx | 18 +++++++++++-- package/src/components/Attachment/Card.tsx | 26 ++++++++++++++++--- package/src/components/Attachment/Gallery.tsx | 11 ++++++++ package/src/components/Message/Message.tsx | 9 +++++++ .../Message/MessageSimple/MessageContent.tsx | 9 +++++++ .../Message/MessageSimple/MessageSimple.tsx | 9 +++++++ .../MessageSimple/MessageTextContainer.tsx | 14 ++++++++-- .../Message/hooks/useCreateMessageContext.ts | 4 +++ .../MessageOverlay/MessageOverlay.tsx | 8 ++++++ .../messageContext/MessageContext.tsx | 8 +++++- 10 files changed, 107 insertions(+), 9 deletions(-) diff --git a/package/src/components/Attachment/Attachment.tsx b/package/src/components/Attachment/Attachment.tsx index 0698d30a7a..19467f610e 100644 --- a/package/src/components/Attachment/Attachment.tsx +++ b/package/src/components/Attachment/Attachment.tsx @@ -29,6 +29,7 @@ export type AttachmentPropsWithContext< | 'Giphy' | 'isAttachmentEqual' | 'UrlPreview' + | 'myMessageTheme' > & { /** * The attachment to render @@ -106,8 +107,12 @@ const areEqual = , nextProps: AttachmentPropsWithContext, ) => { - const { attachment: prevAttachment, isAttachmentEqual } = prevProps; - const { attachment: nextAttachment } = nextProps; + const { + attachment: prevAttachment, + isAttachmentEqual, + myMessageTheme: prevMyMessageTheme, + } = prevProps; + const { attachment: nextAttachment, myMessageTheme: nextMyMessageTheme } = nextProps; const attachmentEqual = prevAttachment.actions?.length === nextAttachment.actions?.length && @@ -119,6 +124,10 @@ const areEqual = @@ -160,6 +170,7 @@ export const Attachment = < Gallery: PropGallery, Giphy: PropGiphy, giphyVersion: PropGiphyVersion, + myMessageTheme: PropMyMessageTheme, UrlPreview: PropUrlPreview, } = props; @@ -171,6 +182,7 @@ export const Attachment = < Giphy: ContextGiphy, giphyVersion: ContextGiphyVersion, isAttachmentEqual, + myMessageTheme: ContextMyMessageTheme, UrlPreview: ContextUrlPreview, } = useMessagesContext(); @@ -186,6 +198,7 @@ export const Attachment = < const Giphy = PropGiphy || ContextGiphy || GiphyDefault; const UrlPreview = PropUrlPreview || ContextUrlPreview || CardDefault; const giphyVersion = PropGiphyVersion || ContextGiphyVersion; + const myMessageTheme = PropMyMessageTheme || ContextMyMessageTheme; return ( diff --git a/package/src/components/Attachment/Card.tsx b/package/src/components/Attachment/Card.tsx index 80de80c228..ee7645e007 100644 --- a/package/src/components/Attachment/Card.tsx +++ b/package/src/components/Attachment/Card.tsx @@ -92,7 +92,7 @@ export type CardPropsWithContext< > & Pick< MessagesContextValue, - 'additionalTouchableProps' | 'CardCover' | 'CardFooter' | 'CardHeader' + 'additionalTouchableProps' | 'CardCover' | 'CardFooter' | 'CardHeader' | 'myMessageTheme' > & { channelId: string | undefined; messageId: string | undefined; @@ -287,14 +287,31 @@ const CardWithContext = < ); }; -const MemoizedCard = React.memo(CardWithContext, () => true) as typeof CardWithContext; +const areEqual = ( + prevProps: CardPropsWithContext, + nextProps: CardPropsWithContext, +) => { + const { myMessageTheme: prevMyMessageTheme } = prevProps; + const { myMessageTheme: nextMyMessageTheme } = nextProps; + + const messageThemeEqual = + JSON.stringify(prevMyMessageTheme) === JSON.stringify(nextMyMessageTheme); + if (!messageThemeEqual) return false; + + return true; +}; + +const MemoizedCard = React.memo(CardWithContext, areEqual) as typeof CardWithContext; export type CardProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Attachment & Partial< Pick, 'ImageComponent'> & - Pick, 'onLongPress' | 'onPress' | 'onPressIn'> & + Pick< + MessageContextValue, + 'onLongPress' | 'onPress' | 'onPressIn' | 'myMessageTheme' + > & Pick< MessagesContextValue, 'additionalTouchableProps' | 'CardCover' | 'CardFooter' | 'CardHeader' @@ -312,7 +329,7 @@ export const Card = < const { ImageComponent } = useChatContext(); const { message, onLongPress, onPress, onPressIn, preventPress } = useMessageContext(); - const { additionalTouchableProps, CardCover, CardFooter, CardHeader } = + const { additionalTouchableProps, CardCover, CardFooter, CardHeader, myMessageTheme } = useMessagesContext(); return ( @@ -326,6 +343,7 @@ export const Card = < channelId: message.cid, ImageComponent, messageId: message.id, + myMessageTheme, onLongPress, onPress, onPressIn, diff --git a/package/src/components/Attachment/Gallery.tsx b/package/src/components/Attachment/Gallery.tsx index d7d1ed523e..85b23327ad 100644 --- a/package/src/components/Attachment/Gallery.tsx +++ b/package/src/components/Attachment/Gallery.tsx @@ -57,6 +57,7 @@ export type GalleryPropsWithContext< | 'VideoThumbnail' | 'ImageLoadingIndicator' | 'ImageLoadingFailedIndicator' + | 'myMessageTheme' > & Pick & { channelId: string | undefined; @@ -486,6 +487,7 @@ const areEqual = (); const { setOverlay: contextSetOverlay } = useOverlayContext(); @@ -607,6 +616,7 @@ export const Gallery = < const ImageLoadingFailedIndicator = PropImageLoadingFailedIndicator || ContextImageLoadingFailedIndicator; const ImageLoadingIndicator = PropImageLoadingIndicator || ContextImageLoadingIndicator; + const myMessageTheme = propMyMessageTheme || contextMyMessageTheme; return ( & @@ -417,6 +418,7 @@ const areEqual = (); const { t, tDateTimeParser } = useTranslationContext(); @@ -610,6 +618,7 @@ export const MessageContent = < MessageHeader, MessageReplies, MessageStatus, + myMessageTheme, onLongPress, onlyEmojis, onPress, diff --git a/package/src/components/Message/MessageSimple/MessageSimple.tsx b/package/src/components/Message/MessageSimple/MessageSimple.tsx index c14cbed180..da1ec0b8aa 100644 --- a/package/src/components/Message/MessageSimple/MessageSimple.tsx +++ b/package/src/components/Message/MessageSimple/MessageSimple.tsx @@ -29,6 +29,7 @@ export type MessageSimplePropsWithContext< Pick< MessagesContextValue, | 'enableMessageGroupingByUser' + | 'myMessageTheme' | 'MessageAvatar' | 'MessageContent' | 'MessagePinnedHeader' @@ -104,12 +105,14 @@ const areEqual = (); @@ -212,6 +220,7 @@ export const MessageSimple = < MessageAvatar, MessageContent, MessagePinnedHeader, + myMessageTheme, ReactionList, }} {...props} diff --git a/package/src/components/Message/MessageSimple/MessageTextContainer.tsx b/package/src/components/Message/MessageSimple/MessageTextContainer.tsx index 6e2d6b5816..cd4cc6ed0f 100644 --- a/package/src/components/Message/MessageSimple/MessageTextContainer.tsx +++ b/package/src/components/Message/MessageSimple/MessageTextContainer.tsx @@ -35,7 +35,10 @@ export type MessageTextContainerPropsWithContext< MessageContextValue, 'message' | 'onLongPress' | 'onlyEmojis' | 'onPress' | 'preventPress' > & - Pick, 'markdownRules' | 'MessageText'> & { + Pick< + MessagesContextValue, + 'markdownRules' | 'MessageText' | 'myMessageTheme' + > & { markdownStyles?: MarkdownStyle; messageOverlay?: boolean; messageTextNumberOfLines?: number; @@ -120,11 +123,13 @@ const areEqual = { const { message, onLongPress, onlyEmojis, onPress, preventPress } = useMessageContext(); - const { markdownRules, MessageText } = useMessagesContext(); + const { markdownRules, MessageText, myMessageTheme } = useMessagesContext(); const { messageTextNumberOfLines } = props; return ( @@ -183,6 +192,7 @@ export const MessageTextContainer = < message, MessageText, messageTextNumberOfLines, + myMessageTheme, onLongPress, onlyEmojis, onPress, diff --git a/package/src/components/Message/hooks/useCreateMessageContext.ts b/package/src/components/Message/hooks/useCreateMessageContext.ts index d1b9e25691..b2bfa53723 100644 --- a/package/src/components/Message/hooks/useCreateMessageContext.ts +++ b/package/src/components/Message/hooks/useCreateMessageContext.ts @@ -30,6 +30,7 @@ export const useCreateMessageContext = < members, message, messageContentOrder, + myMessageTheme, onLongPress, onlyEmojis, onOpenThread, @@ -54,6 +55,7 @@ export const useCreateMessageContext = < message.text }${message.reply_count}`; const membersValue = JSON.stringify(members); + const myMessageThemeString = useMemo(() => JSON.stringify(myMessageTheme), [myMessageTheme]); const quotedMessageDeletedValue = message.quoted_message?.deleted_at; @@ -82,6 +84,7 @@ export const useCreateMessageContext = < members, message, messageContentOrder, + myMessageTheme, onLongPress, onlyEmojis, onOpenThread, @@ -108,6 +111,7 @@ export const useCreateMessageContext = < lastReceivedId, membersValue, messageValue, + myMessageThemeString, reactionsValue, showAvatar, showMessageStatus, diff --git a/package/src/components/MessageOverlay/MessageOverlay.tsx b/package/src/components/MessageOverlay/MessageOverlay.tsx index 5da3978a42..da35ffc085 100644 --- a/package/src/components/MessageOverlay/MessageOverlay.tsx +++ b/package/src/components/MessageOverlay/MessageOverlay.tsx @@ -548,11 +548,13 @@ const areEqual = []; reactions: Reactions; - showMessageOverlay: (messageReactions?: boolean) => void; + showMessageOverlay: (messageReactions?: boolean, error?: boolean) => void; showMessageStatus: boolean; /** Whether or not the Message is part of a Thread */ threadList: boolean; @@ -98,6 +100,10 @@ export type MessageContextValue< goToMessage?: (messageId: string) => void; /** Latest message id on current channel */ lastReceivedId?: string; + /** + * Theme provided only to messages that are the current users + */ + myMessageTheme?: DeepPartial; /** Prevent message being pressed for image viewer view */ preventPress?: boolean; /** Whether or not the avatar show show next to Message */ From d12ddadf2118d46a0f1e304c06aee6af2ece73b2 Mon Sep 17 00:00:00 2001 From: Khushal Agarwal Date: Thu, 8 Feb 2024 11:44:27 +0530 Subject: [PATCH 11/11] docs: add message bounce docs (#2418) --- .../channel/props/message_bounce.mdx | 5 ++ .../reactnative/contexts/messages_context.mdx | 5 ++ .../reactnative/core-components/channel.mdx | 5 ++ .../guides/message_customizations.mdx | 1 + .../ui-components/message_bounce.mdx | 46 +++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 docusaurus/docs/reactnative/common-content/core-components/channel/props/message_bounce.mdx create mode 100644 docusaurus/docs/reactnative/ui-components/message_bounce.mdx diff --git a/docusaurus/docs/reactnative/common-content/core-components/channel/props/message_bounce.mdx b/docusaurus/docs/reactnative/common-content/core-components/channel/props/message_bounce.mdx new file mode 100644 index 0000000000..f6dee997d5 --- /dev/null +++ b/docusaurus/docs/reactnative/common-content/core-components/channel/props/message_bounce.mdx @@ -0,0 +1,5 @@ +Component to render bounce action handler on click or long press of a Bounced message, within [`MessageList`](../../../../ui-components/message_list.mdx). + +| Type | Default | +| --------- | --------------------------------------------------------------- | +| component | [`MessageBounce`](../../../../ui-components/message_bounce.mdx) | diff --git a/docusaurus/docs/reactnative/contexts/messages_context.mdx b/docusaurus/docs/reactnative/contexts/messages_context.mdx index a576670752..f83b10ea6e 100644 --- a/docusaurus/docs/reactnative/contexts/messages_context.mdx +++ b/docusaurus/docs/reactnative/contexts/messages_context.mdx @@ -42,6 +42,7 @@ import IsAttachmentEqual from '../common-content/core-components/channel/props/i import LegacyImageViewerSwipeBehaviour from '../common-content/core-components/channel/props/legacy_image_viewer_swipe_behaviour.mdx'; import MarkdownRules from '../common-content/core-components/channel/props/markdown_rules.mdx'; import MessageAvatar from '../common-content/core-components/channel/props/message_avatar.mdx'; +import MessageBounce from '../common-content/core-components/channel/props/message_bounce.mdx'; import MessageContent from '../common-content/core-components/channel/props/message_content.mdx'; import MessageActions from '../common-content/core-components/channel/props/message_actions.mdx'; import MessageContentOrder from '../common-content/core-components/channel/props/message_content_order.mdx'; @@ -324,6 +325,10 @@ Upserts a given message in local channel state. Please note that this function d +###

_forwarded from [Channel](../core-components/channel.mdx#messageavatar)_ props
MessageBounce {#messagebounce} + + + ###
_forwarded from [Channel](../core-components/channel.mdx#messagecontent)_ props
MessageContent {#messagecontent} diff --git a/docusaurus/docs/reactnative/core-components/channel.mdx b/docusaurus/docs/reactnative/core-components/channel.mdx index de4087597e..182f2218f9 100644 --- a/docusaurus/docs/reactnative/core-components/channel.mdx +++ b/docusaurus/docs/reactnative/core-components/channel.mdx @@ -88,6 +88,7 @@ import MaxNumberOfFiles from '../common-content/core-components/channel/props/ma import MentionAllAppUsersEnabled from '../common-content/core-components/channel/props/mention_all_app_users_enabled.mdx'; import MentionAllAppUsersQuery from '../common-content/core-components/channel/props/mention_all_app_users_query.mdx'; import MessageAvatar from '../common-content/core-components/channel/props/message_avatar.mdx'; +import MessageBounce from '../common-content/core-components/channel/props/message_bounce.mdx'; import MessageContent from '../common-content/core-components/channel/props/message_content.mdx'; import MessageActions from '../common-content/core-components/channel/props/message_actions.mdx'; import MessageContentOrder from '../common-content/core-components/channel/props/message_content_order.mdx'; @@ -825,6 +826,10 @@ Component to render full screen error indicator, when channel fails to load. +### MessageBounce + + + ### MessageContent diff --git a/docusaurus/docs/reactnative/guides/message_customizations.mdx b/docusaurus/docs/reactnative/guides/message_customizations.mdx index d468b7c79c..d95a37c463 100644 --- a/docusaurus/docs/reactnative/guides/message_customizations.mdx +++ b/docusaurus/docs/reactnative/guides/message_customizations.mdx @@ -71,6 +71,7 @@ If you want to customize only a specific part of `MessageSimple` component, you - [MessageHeader](../core-components/channel.mdx#messageheader) - [MessageFooter](../core-components/channel.mdx#messagefooter) - [MessageAvatar](../core-components/channel.mdx#messageavatar) +- [MessageBounce](../core-components/channel.mdx#messagebounce) - [MessageStatus](../core-components/channel.mdx#messagestatus) - [MessageText](../core-components/channel.mdx#messagetext) - [MessageSystem](../core-components/channel.mdx#messagesystem) diff --git a/docusaurus/docs/reactnative/ui-components/message_bounce.mdx b/docusaurus/docs/reactnative/ui-components/message_bounce.mdx new file mode 100644 index 0000000000..767782b050 --- /dev/null +++ b/docusaurus/docs/reactnative/ui-components/message_bounce.mdx @@ -0,0 +1,46 @@ +--- +id: message-bounce +title: MessageBounce +--- + +import MessageProp from '../common-content/contexts/message-context/message.mdx'; + +Component to render bounce action handler on click or long press of a Bounced message, within [`MessageList`](./message_list.mdx). + +## Props + +### `setEditingState` + +Enables editing state for given message. + +| Type | +| ------------------- | +| `(message) => void` | + +### `removeMessage` + +Function to remove message from local channel state. Please note that this function is only for updating the local state, it doesn't call the API for deleting message (`channel.deleteMessage`). + +| Type | +| ------------------- | +| `(message) => void` | + +### `retrySendMessage` + +Function to re-attempt sending failed message. + +| Type | +| ------------------- | +| `(message) => void` | + +###
_overrides the value from [MessageContext](../contexts/message_context.mdx#message)_
`message` {#message} + + + +### `setIsBounceDialogOpen` + +React state update function to open/close the message bounce modal. + +| Type | +| ------------------- | +| `(boolean) => void` |