UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

1,073 lines 83.8 kB
import { CometChat } from "@cometchat/chat-sdk-react-native"; import Clipboard from "@react-native-clipboard/clipboard"; import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from "react"; import { ActivityIndicator, Dimensions, FlatList, NativeModules, Platform, Pressable, ScrollView, Text, TouchableOpacity, View, } from "react-native"; import { CometChatMessageInformation } from "../CometChatMessageInformation/CometChatMessageInformation"; import { CometChatActionSheet, CometChatBottomSheet, CometChatMentionsFormatter, CometChatSoundManager, CometChatUiKitConstants, localize, SuggestionItem, } from "../shared"; import { CallTypeConstants, CometChatCustomMessageTypes, GroupsConstants, MessageCategoryConstants, MessageOptionConstants, MessageReceipt, MessageStatusConstants, MessageTypeConstants, ReceiverTypeConstants, ViewAlignment, } from "../shared/constants/UIKitConstants"; import { CometChatUIEventHandler } from "../shared/events/CometChatUIEventHandler/CometChatUIEventHandler"; import { MessageEvents } from "../shared/events/messages"; import { ChatConfigurator } from "../shared/framework/ChatConfigurator"; import { deepClone, deepMerge } from "../shared/helper/helperFunctions"; import { Icon } from "../shared/icons/Icon"; import { getUnixTimestamp, messageStatus } from "../shared/utils/CometChatMessageHelper"; import { CommonUtils } from "../shared/utils/CommonUtils"; import { CometChatConfirmDialog, CometChatReceipt } from "../shared/views"; import { CometChatAvatar } from "../shared/views/CometChatAvatar"; import { CometChatBadge } from "../shared/views/CometChatBadge"; import { CometChatDate } from "../shared/views/CometChatDate"; import { CometChatEmojiKeyboard } from "../shared/views/CometChatEmojiKeyboard"; import { CometChatMessageBubble } from "../shared/views/CometChatMessageBubble"; import { CometChatQuickReactions } from "../shared/views/CometChatQuickReactions"; import { CometChatReactionList } from "../shared/views/CometChatReactionList"; import { CometChatReactions } from "../shared/views/CometChatReactions"; import { useTheme } from "../theme"; import { MessageSkeleton } from "./Skeleton"; import { ErrorEmptyView } from "../shared/views/ErrorEmptyView/ErrorEmptyView"; import { ExtensionConstants } from "../extensions"; //@ts-ignore import { getExtensionData } from "../shared/helper/functions"; import { CometChatDateSeparator } from "../shared/views/CometChatDateSeperator"; let _defaultRequestBuilder; export const CometChatMessageList = memo(forwardRef((props, ref) => { const { parentMessageId, user, group, EmptyView, ErrorView, hideError, LoadingView, receiptsVisibility = true, disableSoundForMessages, customSoundForMessages, alignment = "standard", avatarVisibility = true, datePattern, dateSeparatorPattern, templates = [], messageRequestBuilder, scrollToBottomOnNewMessages = false, onThreadRepliesPress, HeaderView, FooterView, onError, onBack, reactionsRequestBuilder, textFormatters, onReactionPress: onReactionPressFromProp, onReactionLongPress: onReactionLongPressFromProp, onReactionListItemPress: onReactionListItemPressProp, style, onAddReactionPress, quickReactionList, addTemplates = [], onLoad, onEmpty, hideReplyInThreadOption = false, hideShareMessageOption = false, hideEditMessageOption = false, hideTranslateMessageOption = false, hideDeleteMessageOption = false, hideReactionOption = false, hideMessagePrivatelyOption = false, hideCopyMessageOption = false, hideMessageInfoOption = false, hideGroupActionMessages = false, hideTimestamp = false, } = props; const callListenerId = "call_" + new Date().getTime(); const groupListenerId = "group_" + new Date().getTime(); const uiEventListener = "uiEvent_" + new Date().getTime(); const callEventListener = "callEvent_" + new Date().getTime(); const uiEventListenerShow = "uiEvent_show_" + new Date().getTime(); const uiEventListenerHide = "uiEvent_hide_" + new Date().getTime(); const connectionListenerId = "connectionListener_" + new Date().getTime(); const messageEventListener = "messageEvent_" + new Date().getTime(); const groupEventListener = "groupEvent_" + new Date().getTime(); const [showDeleteModal, setShowDeleteModal] = useState(false); const deleteItem = useRef(undefined); useLayoutEffect(() => { if (user) { _defaultRequestBuilder = new CometChat.MessagesRequestBuilder() .setLimit(30) .setTags([]) .setUID(user.getUid()); } else if (group) { _defaultRequestBuilder = new CometChat.MessagesRequestBuilder() .setLimit(30) .setTags([]) .setGUID(group.getGuid()); } _defaultRequestBuilder.setTypes(ChatConfigurator.dataSource.getAllMessageTypes()); _defaultRequestBuilder.setCategories(ChatConfigurator.dataSource.getAllMessageCategories()); //updating users request builder let _updatedCustomRequestBuilder = _defaultRequestBuilder; if (messageRequestBuilder) { _updatedCustomRequestBuilder = messageRequestBuilder; if (user) _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setUID(user.getUid()); if (group) _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setGUID(group.getGuid()); } else { _updatedCustomRequestBuilder.hideReplies(true); if (user) _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setUID(user.getUid()); if (group) _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setGUID(group.getGuid()); if (parentMessageId) _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setParentMessageId(parseInt(parentMessageId)); let types = [], categories = []; if (templates.length) { types = templates.map((template) => template.type); categories = templates.map((template) => template.category); } else { types = ChatConfigurator.dataSource.getAllMessageTypes(); categories = ChatConfigurator.dataSource.getAllMessageCategories(); if (addTemplates.length) { types.push(...addTemplates.map((template) => template.type)); categories.push(...addTemplates.map((template) => template.category)); } } if (hideGroupActionMessages) { types = types.filter((type) => type !== MessageTypeConstants.groupMember); } _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setTypes(types); _updatedCustomRequestBuilder = _updatedCustomRequestBuilder.setCategories(categories); } msgRequestBuilder.current = _updatedCustomRequestBuilder; }, [hideGroupActionMessages]); const theme = useTheme(); const mergedTheme = useMemo(() => { const themeClone = deepClone(theme); themeClone.messageListStyles = deepMerge(theme.messageListStyles, style ?? {}); return themeClone; }, [theme, style]); const overridenBubbleStyles = useMemo(() => { const styleCache = new Map(); const outgoingBubbleStyles = mergedTheme.messageListStyles.outgoingMessageBubbleStyles; const incomingBubbleStyles = mergedTheme.messageListStyles.incomingMessageBubbleStyles; styleCache.set(MessageTypeConstants.text, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.textBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.textBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.image, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.imageBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.imageBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.file, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.fileBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.fileBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.audio, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.audioBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.audioBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.messageDeleted, { incoming: deepMerge(incomingBubbleStyles, mergedTheme.deletedBubbleStyles ?? {}, incomingBubbleStyles.deletedBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, mergedTheme.deletedBubbleStyles ?? {}, outgoingBubbleStyles.deletedBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.sticker, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.stickerBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.stickerBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.document, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.collaborativeBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.collaborativeBubbleStyles ?? {}), }); styleCache.set(CometChatCustomMessageTypes.meeting, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.meetCallBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.meetCallBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.whiteboard, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.collaborativeBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.collaborativeBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.video, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.videoBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.videoBubbleStyles ?? {}), }); styleCache.set(MessageTypeConstants.poll, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.pollBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.pollBubbleStyles ?? {}), }); styleCache.set(GroupsConstants.ACTION_TYPE_GROUPMEMBER, { incoming: mergedTheme.messageListStyles.groupActionBubbleStyles, outgoing: mergedTheme.messageListStyles.groupActionBubbleStyles, }); styleCache.set(ExtensionConstants.linkPreview, { incoming: deepMerge(incomingBubbleStyles, incomingBubbleStyles.linkPreviewBubbleStyles ?? {}), outgoing: deepMerge(outgoingBubbleStyles, outgoingBubbleStyles.linkPreviewBubbleStyles ?? {}), }); return styleCache; }, [mergedTheme]); // refs const currentScrollPosition = useRef({ y: null, scrollViewHeight: 0, layoutHeight: 0, contentHeight: 0, }); const messagesLength = useRef(0); const prevMessagesLength = useRef(0); const messageListRef = useRef(null); const loggedInUser = useRef(null); const messageRequest = useRef(null); const messagesContentListRef = useRef([]); const temporaryMessageListRef = useRef([]); const msgRequestBuilder = useRef(undefined); const lastMessageDate = useRef(new Date().getTime()); // states const [messagesList, setMessagesList] = useState([]); const [listState, setListState] = useState("loading"); const [loadingMessages, setLoadingMessages] = useState(false); /** this is required to prevent duplicate api calls. Cannot use state for this since this is being used in scrollHandler **/ const loadingMessagesRef = useRef(false); const [unreadCount, setUnreadCount] = useState(0); const [showMessageOptions, setShowMessageOptions] = useState([]); const [ExtensionsComponent, setExtensionsComponent] = useState(null); const [CustomListHeader, setCustomListHeader] = useState(null); const [messageInfo, setMessageInfo] = useState(false); const [ongoingCallView, setOngoingCallView] = useState(null); const [selectedMessage, setSelectedMessage] = useState(null); const [showEmojiKeyboard, setShowEmojiKeyboard] = useState(false); const [showReactionList, setShowReactionList] = useState(false); const [selectedEmoji, setSelectedEmoji] = useState(undefined); const [hideScrollToBottomButton, setHideScrollToBottomButton] = useState(true); const infoObject = useRef(undefined); const bottomSheetRef = useRef(null); const conversationId = useRef(null); let lastID = useRef(0); let reachedFirstMessage = useRef(false); const markUnreadMessageAsRead = () => { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, { message: messagesContentListRef.current[0], }); CometChat.markAsRead(messagesContentListRef.current[0]); setUnreadCount(0); }; const newMsgIndicatorPressed = useCallback(() => { messagesContentListRef.current = [ ...temporaryMessageListRef.current, ...messagesContentListRef.current, ]; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList((prev) => [...temporaryMessageListRef.current, ...prev]); temporaryMessageListRef.current = []; scrollToBottom(); markUnreadMessageAsRead(); }, [onLoad]); const getPreviousMessages = async () => { if (reachedFirstMessage.current) { return; } if (messagesList.length == 0) setListState("loading"); else { setLoadingMessages(true); loadingMessagesRef.current = true; } // TODO: this condition is applied because somewhere from whiteboard extention group scope is set to undefined. if (group != undefined && group.getGuid() == undefined) { let fetchedGroup = await CometChat.getGroup(group.getGuid()).catch((e) => { console.log("Error: fetching group", e); onError && onError(e); }); group.setScope(fetchedGroup["scope"]); } messageRequest.current ?.fetchPrevious() .then((msgs) => { if (messageRequest.current.getLimit() > msgs.length) { reachedFirstMessage.current = true; } let previousMessagesFetched = [...msgs].reverse(); // Reverse for UI use if (messagesList.length === 0 && msgs?.length > 0) { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccActiveChatChanged, { message: previousMessagesFetched[0], user: user, group: group, theme: mergedTheme, parentMessageId: parentMessageId, }); if (conversationId.current == null) conversationId.current = previousMessagesFetched[0].getConversationId(); } else if (messagesList.length === 0 && !props?.parentMessageId) { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccActiveChatChanged, { message: previousMessagesFetched[0], user: user, group: group, theme: mergedTheme, parentMessageId: parentMessageId, }); } for (let index = 0; index < previousMessagesFetched.length; index++) { const message = previousMessagesFetched[index]; if (message && !message.hasOwnProperty("readAt") && loggedInUser.current?.getUid() != message?.getSender()?.getUid()) { CometChat.markAsRead(message); if (index == 0) CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, { message, }); } else break; } previousMessagesFetched = previousMessagesFetched.map((item, index) => { if (item.getCategory() === MessageCategoryConstants.interactive) { return item; } else { return item; } }); if (previousMessagesFetched.length > 0) { messagesContentListRef.current = [ ...messagesContentListRef.current, ...previousMessagesFetched, ]; setMessagesList((prev) => [...prev, ...previousMessagesFetched]); } if (messagesContentListRef.current.length == 0) { onEmpty && onEmpty(); } else { onLoad && onLoad([...messagesContentListRef.current].reverse()); setLoadingMessages(false); loadingMessagesRef.current = false; } setListState(""); }) .catch((e) => { if (messagesContentListRef.current.length == 0) setListState("error"); if (e?.code === "REQUEST_IN_PROGRESS") return; else { setLoadingMessages(false); loadingMessagesRef.current = false; } onError && onError(e); }); }; const getUpdatedPreviousMessages = () => { let messagesRequest = new CometChat.MessagesRequestBuilder().setLimit(50); if (user) messagesRequest = messagesRequest.setUID(user.getUid()); if (group) messagesRequest = messagesRequest.setGUID(group.getGuid()); messagesRequest.setTypes([MessageCategoryConstants.message]); messagesRequest.setCategories([MessageCategoryConstants.action]); messagesRequest.setMessageId(lastID.current); messagesRequest .build() .fetchNext() .then((updatedMessages) => { let tmpList = [...messagesContentListRef.current]; for (let i = 0; i < updatedMessages.length; i++) { let condition = (msg) => msg.getId() == updatedMessages[i]?.actionOn.getId(); let msgIndex = messagesContentListRef.current.findIndex(condition); if (msgIndex > -1) { tmpList[msgIndex] = updatedMessages[i]?.actionOn; } } // console.log("UPDATES LIST LENGTH", tmpList.length) // setMessagesList(tmpList); getNewMessages(tmpList); }) .catch((e) => console.log("error while fetching updated msgs", e)); }; const getNewMessages = (updatedList) => { let newRequestBuilder = msgRequestBuilder.current; newRequestBuilder?.setMessageId(lastID.current); newRequestBuilder ?.build() .fetchNext() .then((newMessages) => { let cleanUpdatedList = [...updatedList]; let finalOutput = CommonUtils.mergeArrays(newMessages.reverse(), cleanUpdatedList, [ "id", "muid", ]); let tmpList = [...finalOutput]; tmpList = tmpList.map((item, index) => { if (item.getCategory() === MessageCategoryConstants.interactive) { return item; } else { return item; } }); if (isAtBottom() || isNearBottom()) { messagesContentListRef.current = tmpList; setMessagesList(tmpList); onLoad && onLoad([...messagesContentListRef.current].reverse()); for (let i = 0; i < newMessages.length; i++) { if (newMessages[i].getSender().getUid() !== loggedInUser.current.getUid()) { bottomHandler(newMessages[i], true, true); break; } } } else { /***** * If scroll is not at bottom or near bottom then add the messages to temporary list * lastID in onDisconnected will always be messagesList[0].getId() (state) * Example: * - unreadCount is already 2 (meaning not near bottom or at bottom) * - App is now in the background * - Someone sends 2 messages to the user * - User now puts the app in foreground and onConnected handler fetches the messages from lastID onwards * - unreadCount will now be 4 * ****/ const currentUnreadCount = tmpList.length - messagesContentListRef.current.length; temporaryMessageListRef.current = []; const currentUnreadMessages = tmpList.slice(0, currentUnreadCount); temporaryMessageListRef.current = [...currentUnreadMessages]; setUnreadCount(temporaryMessageListRef.current.length); } if (newMessages.length === 30) { getNewMessages(tmpList); } newRequestBuilder?.setMessageId(undefined); }) .catch((e) => console.log("error while fetching updated msgs", e)); }; const templatesMap = useMemo(() => { let _formatters = textFormatters || []; let tempTemplates = templates && templates.length ? templates : [ ...ChatConfigurator.dataSource.getAllMessageTemplates(mergedTheme, { textFormatters: _formatters, hideReplyInThreadOption, hideShareMessageOption, hideEditMessageOption, hideTranslateMessageOption, hideDeleteMessageOption, hideReactionOption, hideMessagePrivatelyOption, hideCopyMessageOption, hideMessageInfoOption, hideGroupActionMessages, }), ...addTemplates, ]; let templatesMapTemp = new Map(); tempTemplates.forEach((template) => { if (hideGroupActionMessages && template.type === MessageTypeConstants.groupMember) return; if (templatesMapTemp.get(`${template.category}_${template.type}`)) return; templatesMapTemp.set(`${template.category}_${template.type}`, template); }); return templatesMapTemp; }, [ mergedTheme, templates, addTemplates, hideReplyInThreadOption, hideShareMessageOption, hideEditMessageOption, hideTranslateMessageOption, hideDeleteMessageOption, hideReactionOption, hideMessagePrivatelyOption, hideCopyMessageOption, hideMessageInfoOption, hideGroupActionMessages, ]); const getPlainString = (underlyingText, messageObject) => { let _plainString = underlyingText; let regexes = []; let users = {}; let edits = []; let allFormatters = [...(textFormatters || [])]; let mentionsTextFormatter = ChatConfigurator.getDataSource().getMentionsFormatter(loggedInUser.current); allFormatters.push(mentionsTextFormatter); allFormatters.forEach((formatter, key) => { regexes.push(formatter.getRegexPattern()); let suggestionUsers = formatter.getSuggestionItems(); if (formatter instanceof CometChatMentionsFormatter) { let mentionUsers = (messageObject?.getMentionedUsers && messageObject?.getMentionedUsers()).map((item) => new SuggestionItem({ id: item.getUid(), name: item.getName(), promptText: "@" + item.getName(), trackingCharacter: "@", underlyingText: `<@uid:${item.getUid()}>`, hideLeadingIcon: false, })) || []; suggestionUsers = [...suggestionUsers, ...mentionUsers]; } suggestionUsers?.length > 0 && suggestionUsers.forEach((item) => (users[item.underlyingText] = item)); }); regexes.forEach((regex) => { let match; while ((match = regex.exec(_plainString)) !== null) { const user = users[match[0]]; if (user) { edits.push({ startIndex: match.index, endIndex: regex.lastIndex, replacement: user.promptText, user, }); } } }); // Sort edits by startIndex to apply them in order edits.sort((a, b) => a.startIndex - b.startIndex); // Get an array of the entries in the map using the spread operator const entries = [...edits].reverse(); // Iterate over the array in reverse order entries.forEach(({ endIndex, replacement, startIndex, user }) => { let pre = _plainString.substring(0, startIndex); let post = _plainString.substring(endIndex); _plainString = pre + replacement + post; }); return _plainString; }; const playNotificationSound = useCallback((message) => { if (disableSoundForMessages) return; if (message?.category === MessageCategoryConstants.message) { if (customSoundForMessages) { CometChatSoundManager.play(loggedInUser.current?.getUid() == message["sender"]["uid"] ? "outgoingMessage" : "incomingMessage", customSoundForMessages); } else { CometChatSoundManager.play( // "incomingMessage" loggedInUser.current?.getUid() == message["sender"]["uid"] ? "outgoingMessage" : "incomingMessage"); } } }, []); const markMessageAsRead = (message) => { if (!message?.readAt) { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageRead, { message }); CometChat.markAsRead(message).catch((error) => { console.log("Error", error); onError && onError(error); // errorHandler(error); }); } }; function checkMessageInSameConversation(message) { return ((message?.getReceiverType() == ReceiverTypeConstants.user && user && user?.getUid() == message.getReceiver()?.["uid"]) || (message?.getReceiverType() == ReceiverTypeConstants.group && message.getReceiverId() && group && group?.getGuid() == message.getReceiverId()) || false); } function messageToSameConversation(message) { return ((message?.getReceiverType() == ReceiverTypeConstants.user && user && user?.getUid() == message.getReceiverId()) || (message?.getReceiverType() == ReceiverTypeConstants.group && message.getReceiverId() && group && group?.getGuid() == message.getReceiverId()) || false); } function checkSameConversation(message) { return (message.getConversationId() == conversationId.current || (message.getSender()?.getUid() === user?.getUid() && message.getReceiverType() == CometChatUiKitConstants.ReceiverTypeConstants.user)); } function isNearBottom() { const { layoutHeight, scrollViewHeight, y } = currentScrollPosition.current; // In an inverted list, scrolling up moves the user towards the top of the scrollable area, // which is actually the "bottom" of the visible chat (since it's inverted). let scrollPos = y; // 'y' is how far the user has scrolled from the top of the full content. // Calculate how far the user is from the top of the scrollable content (which is visually // the "bottom" in an inverted list) as a percentage of the total height. if (!layoutHeight) { return true; } let scrollPosFromTopInPercentage = (scrollPos / layoutHeight) * 100; // If the user has scrolled to within 30% from the top (which is the "bottom" in this case), // we consider them to be near the bottom of the list (since it's inverted). if (scrollPosFromTopInPercentage <= 30) { return true; } return false; } const newMessage = (newMessage, isReceived = true) => { let baseMessage = newMessage; if (baseMessage.getCategory() === MessageCategoryConstants.interactive) { //todo show unsupported bubble } if (checkSameConversation(baseMessage) || checkMessageInSameConversation(baseMessage) || messageToSameConversation(baseMessage)) { CometChat.markAsDelivered(newMessage); //need to add if (!parentMessageId && newMessage.getParentMessageId()) { //increase count let index = messagesList.findIndex((msg) => msg.id === newMessage.parentMessageId); let oldMsg = CommonUtils.clone(messagesList[index]); oldMsg.setReplyCount(oldMsg.getReplyCount() ? oldMsg.getReplyCount() + 1 : 1); let tmpList = [...messagesList]; tmpList[index] = oldMsg; messagesContentListRef.current = tmpList; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList(tmpList); return; } bottomHandler(newMessage, isReceived); } playNotificationSound(newMessage); }; const isAtBottom = () => { //0 or null if (!currentScrollPosition.current.y) { return true; } return false; }; const bottomHandler = (newMessage, isReceived = false, skipAddToList = false) => { if ((newMessage.getSender()?.getUid() || newMessage?.["sender"]?.["uid"]) == loggedInUser.current?.["uid"]) { if (!skipAddToList) { addToMessageList(newMessage); } scrollToBottom(); return; } if (!isReceived) { return; } if ((!parentMessageId && newMessage.getParentMessageId()) || (parentMessageId && !newMessage.getParentMessageId()) || (parentMessageId && newMessage.getParentMessageId() && parentMessageId != newMessage.getParentMessageId())) { return; } if (isAtBottom() || isNearBottom() || scrollToBottomOnNewMessages) { if (!skipAddToList) { addToMessageList(newMessage); } scrollToBottom(); markMessageAsRead(newMessage); } else { temporaryMessageListRef.current = [newMessage, ...temporaryMessageListRef.current]; setUnreadCount(unreadCount + 1); } }; const addToMessageList = (newMessage) => { messagesContentListRef.current = [newMessage, ...messagesContentListRef.current]; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList((prev) => [newMessage, ...prev]); }; const markParentMessageAsRead = (message) => { let condition; condition = (msg) => msg.getId() == message?.["parentMessageId"]; let msgIndex = messagesList.findIndex(condition); if (msgIndex > -1) { let tmpList = [...messagesList]; if (message.getCategory() === MessageCategoryConstants.interactive) { //todo show unsupported bubble } tmpList[msgIndex]?.setUnreadReplyCount(0); messagesContentListRef.current = tmpList; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList(tmpList); } }; const messageEdited = (editedMessage, withMuid = false) => { let condition; if (withMuid) { condition = (msg) => msg["muid"] == editedMessage["muid"]; } else condition = (msg) => msg.getId() == editedMessage.getId(); let msgIndex = messagesContentListRef.current.findIndex(condition); if (msgIndex > -1) { let tmpList = [...messagesContentListRef.current]; if (editedMessage.getCategory() === MessageCategoryConstants.interactive) { //todo show unsupported bubble } tmpList[msgIndex] = CommonUtils.clone(editedMessage); messagesContentListRef.current = tmpList; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList(tmpList); } }; const removeMessage = (message) => { let msgIndex = messagesList.findIndex((msg) => msg.getId() == message.getId()); if (msgIndex == -1) return; let tmpList = [...messagesList]; tmpList.splice(msgIndex, 1); messagesContentListRef.current = tmpList; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList(tmpList); }; const deleteMessage = (message) => { CometChat.deleteMessage(message.getId().toString()) .then((res) => { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageDeleted, { message: res, }); setShowMessageOptions([]); setShowDeleteModal(false); deleteItem.current = undefined; }) .catch((rej) => { deleteItem.current = undefined; setShowDeleteModal(false); console.log(rej); onError && onError(rej); }); }; const createActionMessage = () => { }; const updateMessageReceipt = (receipt) => { if (receipt?.getReceiverType() === ReceiverTypeConstants.group && ![ receipt.RECEIPT_TYPE.DELIVERED_TO_ALL_RECEIPT, receipt.RECEIPT_TYPE.READ_BY_ALL_RECEIPT, ].includes(receipt?.getReceiptType())) { return; } let index = messagesContentListRef.current.findIndex((msg, index) => msg["id"] == receipt["messageId"] || msg["messageId"] == receipt["messageId"]); if (index == -1) return; let tmpList = [...messagesContentListRef.current]; for (let i = index; i < messagesContentListRef.current.length; i++) { if (tmpList[i]?.getReadAt && tmpList[i]?.getReadAt()) break; let tmpMsg = tmpList[i]; if (!Number.isNaN(Number(tmpMsg.getId()))) { if (tmpMsg.getCategory() === MessageCategoryConstants.interactive) { //todo show unsupported bubble } if (receipt.getDeliveredAt()) { tmpMsg.setDeliveredAt(receipt.getDeliveredAt()); } if (receipt.getReadAt()) { tmpMsg.setReadAt(receipt.getReadAt()); } } tmpList[i] = CommonUtils.clone(tmpMsg); } messagesContentListRef.current = tmpList; onLoad && onLoad([...messagesContentListRef.current].reverse()); setMessagesList(tmpList); }; const handlePannel = (item) => { if (item.alignment === ViewAlignment.messageListBottom && user && group && CommonUtils.checkIdBelongsToThisComponent(item.id, user, group, parentMessageId || "")) { if (item.child) setCustomListHeader(() => item.child); else setCustomListHeader(null); } }; useEffect(() => { CometChatUIEventHandler.addUIListener(uiEventListenerShow, { showPanel: (item) => handlePannel(item), // ccMentionClick: (item) => { // // console.log("item", item) // } }); CometChatUIEventHandler.addUIListener(uiEventListenerHide, { hidePanel: (item) => handlePannel(item), }); CometChatUIEventHandler.addUIListener(uiEventListener, { ccToggleBottomSheet: (item) => { bottomSheetRef.current?.togglePanel(); }, }); CometChat.getLoggedinUser() .then((u) => { loggedInUser.current = u; messageRequest.current = msgRequestBuilder.current?.build() || null; getPreviousMessages(); }) .catch((e) => { console.log("Error while getting loggedInUser"); onError && onError(e); loggedInUser.current = null; }); return () => { CometChatUIEventHandler.removeUIListener(uiEventListenerShow); CometChatUIEventHandler.removeUIListener(uiEventListenerHide); CometChatUIEventHandler.removeUIListener(uiEventListener); onBack && onBack(); }; }, []); useEffect(() => { //add listeners let reactionListeners = { onMessageReactionAdded: (reaction) => { updateMessageReaction(reaction, true); }, onMessageReactionRemoved: (reaction) => { updateMessageReaction(reaction, false); }, }; CometChat.addGroupListener(groupListenerId, new CometChat.GroupListener({ onGroupMemberScopeChanged: (message) => { newMessage(message); }, onGroupMemberLeft: (message) => { newMessage(message); }, onGroupMemberKicked: (message) => { newMessage(message); }, onGroupMemberBanned: (message) => { newMessage(message); }, onGroupMemberUnbanned: (message) => { newMessage(message); }, onMemberAddedToGroup: (message) => { newMessage(message); }, onGroupMemberJoined: (message) => { newMessage(message); }, })); CometChatUIEventHandler.addMessageListener(messageEventListener, { ccMessageSent: ({ message, status }) => { if (status == MessageStatusConstants.inprogress) { newMessage(message, false); } if (status == MessageStatusConstants.success) { messageEdited(message, true); } if (status == MessageStatusConstants.error) { messageEdited(message, true); } }, ccMessageEdited: ({ message, status }) => { if (status == messageStatus.success) { messageEdited(message, false); } }, ccMessageDeleted: ({ message }) => { messageEdited(message, false); }, ccMessageRead: ({ message }) => { if (!parentMessageId && message.parentMessageId) { // markParentMessageAsRead(message); //NOTE: uncomment this when want unread count in thread view } }, onTextMessageReceived: (textMessage) => { newMessage(textMessage); }, onMediaMessageReceived: (mediaMessage) => { newMessage(mediaMessage); }, onCustomMessageReceived: (customMessage) => { newMessage(customMessage); }, onMessagesDelivered: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onMessagesRead: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onMessageDeleted: (deletedMessage) => { messageEdited(deletedMessage); }, onMessageEdited: (editedMessage) => { messageEdited(editedMessage); }, onFormMessageReceived: (formMessage) => { newMessage(formMessage); }, onCardMessageReceived: (cardMessage) => { newMessage(cardMessage); }, onSchedulerMessageReceived: (schedulerMessage) => { newMessage(schedulerMessage); }, onCustomInteractiveMessageReceived: (customInteractiveMessage) => { newMessage(customInteractiveMessage); }, onInteractionGoalCompleted: (interactionReceipt) => { //todo show unsupported bubble }, ...reactionListeners, onMessagesDeliveredToAll: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onMessagesReadByAll: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, }); CometChatUIEventHandler.addGroupListener(groupEventListener, { ccGroupMemberUnBanned: ({ message }) => { newMessage(message, false); }, ccGroupMemberBanned: ({ message }) => { newMessage(message, false); }, ccGroupMemberAdded: ({ message, usersAdded, userAddedIn }) => { usersAdded.forEach((user) => { message["message"] = `${loggedInUser.current?.getName()} added ${user.name}`; message["muid"] = String(getUnixTimestamp()); message["sentAt"] = getUnixTimestamp(); message["actionOn"] = user; newMessage(message, false); }); }, ccGroupMemberKicked: ({ message }) => { newMessage(message, false); }, ccGroupMemberScopeChanged: ({ action, updatedUser, scopeChangedTo, scopeChangedFrom, group, }) => { newMessage(action, false); }, ccOwnershipChanged: ({ group, message }) => { // newMessage(message, false); removed after discussion. }, }); CometChat.addCallListener(callListenerId, new CometChat.CallListener({ onIncomingCallReceived: (call) => { newMessage(call); }, onOutgoingCallAccepted: (call) => { newMessage(call); }, onOutgoingCallRejected: (call) => { newMessage(call); }, onIncomingCallCancelled: (call) => { newMessage(call); }, })); CometChatUIEventHandler.addCallListener(callEventListener, { ccCallInitiated: ({ call }) => { if (call["type"] == CallTypeConstants.audio || call["type"] == CallTypeConstants.video) { newMessage(call); } }, ccOutgoingCall: ({ call }) => { if (call["type"] == CallTypeConstants.audio || call["type"] == CallTypeConstants.video) { newMessage(call); } }, ccCallAccepted: ({ call }) => { if (call["type"] == CallTypeConstants.audio || call["type"] == CallTypeConstants.video) { newMessage(call); } }, ccCallRejected: ({ call }) => { if (call["type"] == CallTypeConstants.audio || call["type"] == CallTypeConstants.video) { newMessage(call); } }, ccCallEnded: ({ call }) => { if (call["type"] == CallTypeConstants.audio || call["type"] == CallTypeConstants.video) { newMessage(call); } }, ccShowOngoingCall: (CometChatOngoingComponent) => { //show ongoing call setOngoingCallView(CometChatOngoingComponent?.child); }, }); CometChat.addConnectionListener(connectionListenerId, new CometChat.ConnectionListener({ onConnected: () => { console.log("CONNECTED..."); if (lastID.current) { getUpdatedPreviousMessages(); } }, inConnecting: () => { }, onDisconnected: () => { console.log("DISCONNECTED..."); if (!messagesList[0].id) { for (let i = 0; i < messagesList.length; i++) { if (messagesList[i].id) { lastID.current = messagesList[i].id; break; } } } else { lastID.current = messagesList[0].id; } }, })); return () => { // clean up code like removing listeners CometChatUIEventHandler.removeMessageListener(messageEventListener); CometChatUIEventHandler.removeGroupListener(groupEventListener); CometChatUIEventHandler.removeCallListener(callEventListener); CometChat.removeGroupListener(groupListenerId); CometChat.removeCallListener(callListenerId); CometChat.removeConnectionListener(connectionListenerId); }; }, [messagesList, unreadCount, user, group]); useEffect(() => { prevMessagesLength.current = messagesLength.current || messagesContentListRef.current.length; messagesLength.current = messagesContentListRef.current.length; }, [messagesContentListRef.current]); useEffect(() => { if (selectedEmoji) { setShowReactionList(true); } }, [selectedEmoji]); useImperativeHandle(ref, () => { return { addMessage: newMessage, updateMessage: messageEdited, removeMessage, deleteMessage, scrollToBottom, /// todo: not handeled yet createActionMessage, updateMessageReceipt, }; }); const getMessageById = (messageId) => { const message = messagesList.find((message) => message.getId() === messageId); return message; }; function isReactionOfThisList(receipt) { const receiverId = receipt?.getReceiverId(); const receiverType = receipt?.getReceiverType(); const reactedById = receipt?.getReaction()?.getReactedBy()?.getUid(); const parentMessageId = receipt?.getParentMessageId(); const listParentMessageId = parentMessageId && String(parentMessageId); if (listParentMessageId) { if (parentMessageId === listParentMessageId) { return true; } else { return false; } } else { if (receipt.getParentMessageId()) { return false; } if (user) { if (receiverType === ReceiverTypeConstants.user && (receiverId === user.getUid() || reactedById === user.getUid())) { return true; } } else if (group) { if (receiverType === ReceiverTypeConstants.group && receiverId === group.getGuid()) { return true; } } } return false; } const updateMessageReaction = (message, isAdded) => { let _isReactionOfThisList = isReactionOfThisList(message); if (!_isReactionOfThisList) return; const messageId = message?.getReaction()?.getMessageId(); const messageObject = getMessageById(messageId); if (!messageObject) return; let action; if (isAdded) { action = CometChat.REACTION_ACTION.REACTION_ADDED; } else { action = CometChat.REACTION_ACTION.REACTION_REMOVED; } const modifiedMessage = CometChat.CometChatHelper.updateMessageWithReactionInfo(messageObject, message.getReaction(), action); if (modifiedMessage instanceof CometChat.CometChatException) { onError && onError(modifiedMessage); return; } messageEdited(modifiedMessage, false); }; const getCurrentBubbleStyle = useCallback((item) => { const type = (() => { if (item.getDeletedAt()) { return MessageTypeConstants.messageDeleted; } if (item.getType() === MessageTypeConstants.text) { let linkData = getExtensionData(item, ExtensionConstants.linkPreview); if (linkData && linkData.links.length != 0) { return ExtensionConstants.linkPreview; } } return i