UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

1,018 lines (1,017 loc) 53.5 kB
/* * CometChatConversations.tsx * --------------------------------------------------------------------------- * CometChatConversations is a React Native component that displays a list of conversations * It provides features such as message receipt visibility, custom sound notifications, * date formatting, and selection modes (none, single, multiple). * It also allows for custom rendering of conversation items, error handling, and loading states. * The component supports user and group events, message events, and call events. * It also provides options for customizing the appearance of the conversation list. * --------------------------------------------------------------------------- */ import { CometChat } from "@cometchat/chat-sdk-react-native"; import React, { useCallback, useMemo } from "react"; import { Text, View } from "react-native"; import { ChatConfigurator, CometChatAvatar, CometChatConversationEvents, CometChatList, CometChatSoundManager, CometChatStatusIndicator, CometChatUIKit, CometChatUiKitConstants, localize, } from "../shared"; import { ConversationTypeConstants, GroupTypeConstants, MentionsTargetElement, MessageCategoryConstants, MessageReceipt, MessageStatusConstants, ReceiverTypeConstants, } from "../shared/constants/UIKitConstants"; import { CometChatUIEventHandler } from "../shared/events/CometChatUIEventHandler/CometChatUIEventHandler"; import { Icon } from "../shared/icons/Icon"; import { CommonUtils } from "../shared/utils/CommonUtils"; import { getMessagePreviewInternal } from "../shared/utils/MessageUtils"; import { CometChatBadge } from "../shared/views/CometChatBadge"; import { CometChatConfirmDialog } from "../shared/views/CometChatConfirmDialog"; import { CometChatDate } from "../shared/views/CometChatDate"; import { CometChatReceipt } from "../shared/views/CometChatReceipt"; import { CometChatTooltipMenu } from "../shared/views/CometChatTooltipMenu"; import { ErrorEmptyView } from "../shared/views/ErrorEmptyView/ErrorEmptyView"; import { useTheme } from "../theme"; import { Skeleton } from "./Skeleton"; import { Style } from "./style"; import { deepMerge } from "../shared/helper/helperFunctions"; import Delete from "../shared/icons/components/delete"; // Unique listener IDs for conversation, user, group, message and call events. const conversationListenerId = "chatlist_" + new Date().getTime(); const userListenerId = "chatlist_user_" + new Date().getTime(); const groupListenerId = "chatlist_group_" + new Date().getTime(); const messageListenerId = "chatlist_message_" + new Date().getTime(); const callListenerId = "call_" + new Date().getTime(); /** * CometChatConversations is a container component that wraps and formats the conversation list. * It handles events such as new messages, typing indicators, call events, and group events. */ export const CometChatConversations = (props) => { const { receiptsVisibility = true, disableSoundForMessages = false, hideHeader = false, customSoundForMessages, datePattern, ItemView, AppBarOptions, hideSubmitButton = false, hideBackButton = true, selectionMode = "none", onSelection, onSubmit, EmptyView, ErrorView, LoadingView, conversationsRequestBuilder, LeadingView, TitleView, SubtitleView, TrailingView, hideError = false, onItemPress, onItemLongPress, onError, onBack, textFormatters, style, onEmpty, onLoad, options, addOptions, usersStatusVisibility = true, groupTypeVisibility = true, deleteConversationOptionVisibility = true, } = props; // Reference for accessing CometChatList methods const conversationListRef = React.useRef(null); // Store the logged in user for comparison and event handling. const loggedInUser = React.useRef(undefined); // State to control the confirmation dialog for deleting a conversation. const [confirmDelete, setConfirmDelete] = React.useState(undefined); // State to control selection mode for conversation items. const [selecting, setSelecting] = React.useState(selectionMode === "none" ? false : true); const [selectedConversation, setSelectedConversations] = React.useState([]); // Timer for debouncing member-added events. const onMemberAddedToGroupDebounceTimer = React.useRef(null); // Reference to store long press identifier. const longPressId = React.useRef(undefined); const longPressedConversation = React.useRef(undefined); // Reference to store tooltip position for long press events. const tooltipPositon = React.useRef({ pageX: 0, pageY: 0, }); const [tooltipVisible, setTooltipVisible] = React.useState(false); // Merge theme styles with provided style overrides. const theme = useTheme(); const mergedStyles = useMemo(() => { return deepMerge(theme.conversationStyles, style ?? {}); }, [theme.conversationStyles, style]); /** * ErrorStateView renders a view to show when an error occurs. */ const ErrorStateView = useCallback(() => { return (<ErrorEmptyView title={localize("OOPS")} subTitle={localize("SOMETHING_WENT_WRONG")} tertiaryTitle={localize("WRONG_TEXT_TRY_AGAIN")} Icon={<Icon name='error-state' size={theme.spacing.margin.m15 << 1} containerStyle={{ marginBottom: theme.spacing.margin.m5, ...mergedStyles?.errorStateStyle?.iconContainerStyle, }} icon={mergedStyles?.errorStateStyle?.icon} imageStyle={mergedStyles?.errorStateStyle?.iconStyle}/>} containerStyle={mergedStyles?.errorStateStyle?.containerStyle} titleStyle={mergedStyles?.errorStateStyle?.titleStyle} subTitleStyle={mergedStyles?.errorStateStyle?.subTitleStyle}/>); }, [theme, mergedStyles]); /** * EmptyStateView renders a view when no conversations are available. */ const EmptyStateView = useCallback(() => { return (<ErrorEmptyView title='No Conversations Yet' subTitle='Start a new chat or invite others to join the conversation.' Icon={<Icon name='empty-state' size={theme.spacing.spacing.s15 << 1} containerStyle={{ marginBottom: theme.spacing.spacing.s5, ...mergedStyles?.emptyStateStyle?.iconContainerStyle, }} icon={mergedStyles?.emptyStateStyle?.icon} imageStyle={mergedStyles?.emptyStateStyle?.iconStyle}/>} containerStyle={mergedStyles?.emptyStateStyle?.containerStyle} titleStyle={mergedStyles?.emptyStateStyle?.titleStyle} subTitleStyle={mergedStyles?.emptyStateStyle?.subTitleStyle}/>); }, [theme, mergedStyles]); /** * Handler for user online/offline events. Finds the corresponding conversation and updates it. */ const userEventHandler = (...args) => { const { uid } = args[0]; let item = conversationListRef.current?.getListItem(`${uid}_user_${loggedInUser.current?.getUid()}`) || conversationListRef.current?.getListItem(`${loggedInUser.current?.getUid()}_user_${uid}`); const user = item.getConversationWith(); if (user.getBlockedByMe() || user.getHasBlockedMe()) return; if (item) { let updatedConversation = CommonUtils.clone(item); updatedConversation.setConversationWith(args[0]); conversationListRef.current?.updateList(updatedConversation); } }; /** * Returns a conversation that matches a typing indicator. */ const getConversationRefFromTypingIndicator = (typingIndicator) => { let list = conversationListRef.current?.getAllListItems(); return list?.find((item) => { return ((typingIndicator.getReceiverType() == ReceiverTypeConstants.user && item.getConversationType() == ReceiverTypeConstants.user && item.getConversationWith().getUid() == typingIndicator.getSender().getUid() && !(item.getConversationWith()?.getBlockedByMe() || item.getConversationWith()?.getHasBlockedMe())) || (typingIndicator.getReceiverType() == ReceiverTypeConstants.group && item.getConversationType() == ReceiverTypeConstants.group && item.getConversationWith().getGuid() == typingIndicator.getReceiverId())); }); }; /** * Handler for typing events in conversations. * Toggle the *live typing…* indicator on a conversation row. */ const typingEventHandler = (...args) => { let conversation = CommonUtils.clone(getConversationRefFromTypingIndicator(args[0])); if (conversation) { let isTyping = args[1]; let newConversation = conversation; if (isTyping && !newConversation?.["lastMessage"]?.["typing"]) { newConversation["lastMessage"]["typing"] = args[0]?.receiverType === "group" ? `${args[0].sender.name} ${localize("IS_TYPING")}` : localize("IS_TYPING"); } else { delete newConversation["lastMessage"]["typing"]; } conversationListRef.current.updateList(newConversation); } }; /** * Checks and updates the last message in a conversation if it matches the provided message. * @param newMessage - The new message object. */ const checkAndUpdateLastMessage = (newMessage) => { CometChat.CometChatHelper.getConversationFromMessage(newMessage).then((conversation) => { let conver = conversationListRef.current.getListItem(conversation.getConversationId()); if (!conver) return; let lastMessageId = conver.getLastMessage().getId(); if (lastMessageId == newMessage.getId()) { conversationListRef.current.updateList(CommonUtils.clone(conversation)); } }); }; /** * Determines whether the last message and unread count should be updated. * @param message - The message to check. * @returns True if an update is needed. */ const shouldUpdateLastMessageAndUnreadCount = (message) => { // Do not update for threaded messages if not enabled. if (message.getParentMessageId() && !CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnMessageReplies()) { return false; } // Do not update for custom messages if not allowed. if (message.getCategory() == CometChatUiKitConstants.MessageCategoryConstants.custom) { let customMessage = message; if (!customMessage.willUpdateConversation() && !(customMessage.getMetadata() && customMessage.getMetadata()["incrementUnreadCount"]) && !CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCustomMessages()) { return false; } } // Check for group actions. if (message.getCategory() == CometChatUiKitConstants.MessageCategoryConstants.action && message.getReceiverType() == CometChatUiKitConstants.ReceiverTypeConstants.group) { return CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnGroupActions(); } // Check for call activities. if (message.getCategory() == CometChatUiKitConstants.MessageCategoryConstants.call && !CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return false; } return true; }; /** * Updates the conversation with a new message and moves it to the top of the list. * @param newMessage - The new message to update. */ const updateLastMessage = (newMessage) => { CometChat.CometChatHelper.getConversationFromMessage(newMessage) .then((conversation) => { if (newMessage.getCategory() === MessageCategoryConstants.interactive) { // TODO: Show unsupported message view. } const oldConversation = conversationListRef.current.getListItem(conversation.getConversationId()); if (oldConversation == undefined) { // If conversation not found, add it. CometChat.CometChatHelper.getConversationFromMessage(newMessage) .then((newConversation) => { if (newConversation?.getLastMessage().getSender().getUid() !== loggedInUser.current?.getUid()) newConversation.setUnreadMessageCount(1); conversationListRef.current.addItemToList(newConversation, 0); }) .catch((err) => onError && onError(err)); return; } // Update last message and unread count. oldConversation.setLastMessage(newMessage); if (newMessage.getSender().getUid() != loggedInUser.current?.getUid()) oldConversation.setUnreadMessageCount(oldConversation.getUnreadMessageCount() + 1); conversationListRef.current.updateAndMoveToFirst(CommonUtils.clone(oldConversation)); }) .catch((err) => { console.log("Error", err); }); }; /** * Plays the notification sound for incoming messages. */ const playNotificationSound = () => { if (disableSoundForMessages) return; CometChatSoundManager.play(customSoundForMessages || CometChatSoundManager.SoundOutput.incomingMessageFromOther); }; /** * Determines if a message should be marked as delivered. * @param message - The message object. * @returns True if the message does not have a "deliveredAt" property. */ const shouldMarkAsDelivered = (message) => { return !message.hasOwnProperty("deliveredAt"); }; /** * Marks a message as delivered and plays notification sound if applicable. * @param message - The message to mark as delivered. */ const markMessageAsDelivered = (message) => { if (message.hasOwnProperty("deletedAt")) return; if (shouldMarkAsDelivered(message)) { CometChat.markAsDelivered(message); playNotificationSound(); } }; /** * Updates message receipt for the conversation. * @param receipt - The message receipt. */ const updateMessageReceipt = (receipt) => { const conv = receipt?.getReceiverType() === ReceiverTypeConstants.user ? conversationListRef.current?.getListItem(`${receipt?.getReceiver()}_user_${receipt?.getSender().getUid()}`) || conversationListRef.current?.getListItem(`${receipt?.getSender()?.getUid()}_user_${receipt?.getReceiver()}`) : [ receipt.RECEIPT_TYPE.DELIVERED_TO_ALL_RECEIPT, receipt.RECEIPT_TYPE.READ_BY_ALL_RECEIPT, ].includes(receipt?.getReceiptType()) && conversationListRef.current?.getListItem(`group_${receipt?.getReceiver()}`); if (conv && conv.getConversationType() == ConversationTypeConstants.group && conv.getLastMessage().getSender().getUid() !== loggedInUser.current.getUid()) { return; } if (conv && conv?.getLastMessage && (conv.getLastMessage().id == receipt.getMessageId() || conv.getLastMessage().messageId == receipt.getMessageId())) { let newConversation = CommonUtils.clone(conv); if (receipt.getReadAt()) { newConversation.getLastMessage().setReadAt(receipt.getReadAt()); } if (receipt.getDeliveredAt()) { newConversation.getLastMessage().setDeliveredAt(receipt.getDeliveredAt()); } conversationListRef.current?.updateList(newConversation); } }; /** * Handler for when a message (text/media/custom) is received. * Marks the message as delivered and updates the conversation. * @param args - Contains the new message. */ const messageEventHandler = (...args) => { let message = args[0]; markMessageAsDelivered(message); updateLastMessage(message); }; /** * Handler for various group actions such as member kicked, banned, left, or scope change. * @param message - The action message. * @param otherDetails - Additional details about the action. */ const groupHandler = (message, otherDetails = {}) => { let conversation = conversationListRef.current.getListItem(message.getConversationId()); let { action, actionOn, actionBy, group, newScope, oldScope } = otherDetails; if (conversation) { if (action == "scopeChange" && actionOn?.getUid() !== loggedInUser.current.getUid()) { oldScope = undefined; newScope = undefined; } const oldScopeLocal = oldScope ?? conversation.getConversationWith().getScope(); if (action && ["kicked", "banned", "left"].includes(action) && actionOn && actionOn.getUid() == loggedInUser.current.getUid()) { conversationListRef.current.removeItemFromList(message.getConversationId()); return; } else { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnGroupActions()) { return; } conversation.setLastMessage(message); if (group) { !group.getScope() && group.setScope(newScope ?? oldScopeLocal); conversation.setConversationWith(group); } conversationListRef.current.updateList(conversation); } } else { CometChat.CometChatHelper.getConversationFromMessage(message).then((newConversation) => { const conversation = conversationListRef.current.getListItem(message.getConversationId()); if (conversation) { groupHandler(message); } else { conversationListRef.current.addItemToList(newConversation, 0); } }); } }; /** * Handles the conversation click event. * If an onItemPress callback is provided, it is invoked. * Else, toggles selection of the conversation. * @param conversation - The conversation object that was clicked. */ const conversationClicked = (conversation) => { if (onItemPress) { onItemPress(conversation); return; } if (!selecting) { // Fire event if not selecting. return; } if (selectionMode == "none") return; let index = selectedConversation.findIndex((tmpConver) => tmpConver.getConversationId() == conversation.getConversationId()); if (index < 0) { if (selectionMode == "single") setSelectedConversations([conversation]); if (selectionMode == "multiple") setSelectedConversations([...selectedConversation, conversation]); } else { selectedConversation.splice(index, 1); setSelectedConversations([...selectedConversation]); } }; /** * Removes a conversation from the selection list. * @param id - The conversation ID. */ const removeItemFromSelectionList = (id) => { if (selecting) { let index = selectedConversation.findIndex((member) => member.getConversationId() == id); if (index > -1) { let tmpSelectedConversations = [...selectedConversation]; tmpSelectedConversations.splice(index, 1); setSelectedConversations(tmpSelectedConversations); } } }; /** * Removes a conversation from the list by calling the delete API and then updating the UI. * @param id - The conversation ID to remove. */ const removeConversation = (id) => { let conversation = conversationListRef.current.getListItem(id); const { conversationWith, conversationType } = conversation; let conversationWithId = conversationType == "group" ? conversationWith.guid : conversationWith.uid; CometChat.deleteConversation(conversationWithId, conversationType) .then((success) => { CometChatUIEventHandler.emitConversationEvent(CometChatConversationEvents.ccConversationDeleted, { conversation: conversation }); conversationListRef.current.removeItemFromList(id); removeItemFromSelectionList(id); }) .catch((err) => console.log(err)); }; /** * Returns a formatted preview for the last message in a conversation. * @param conversations - The conversation object. * @param theme - The theme object. * @returns A JSX.Element containing the preview. */ const getMessagePreview = (conversations, theme) => { const loggedInUserId = CometChatUIKit.loggedInUser.getUid(); let lastMessage = conversations?.getLastMessage && conversations.getLastMessage(); if (!lastMessage) return null; let messageText = ""; messageText = ChatConfigurator.getDataSource().getLastConversationMessage(conversations, theme); if (lastMessage && typeof messageText === "string") { messageText = getFormattedText(lastMessage, messageText?.trim()); if (lastMessage instanceof CometChat.TextMessage && lastMessage.getCategory() === "message" && lastMessage .getText() .substr(0, 50) .match(/http[s]{0,1}:\/\//)) { messageText = getMessagePreviewInternal("link-fill", localize("LINK"), { theme }); } else { // Ensure ellipsis is applied if the text is too long. messageText = (<Text style={[mergedStyles.itemStyle.subtitleStyle, { flexShrink: 2 }]} numberOfLines={1} ellipsizeMode='tail'> {messageText} </Text>); } } let groupText = ""; if (!(lastMessage instanceof CometChat.Action)) { if (lastMessage.getReceiverType() == ReceiverTypeConstants.group) { if (lastMessage.getSender().getUid() == loggedInUserId) { groupText = localize("YOU") + ": "; } else { groupText = lastMessage.getSender().getName() + ": "; } } } return (<> {groupText && (<Text style={[mergedStyles.itemStyle.subtitleStyle, { flexShrink: 1 }]} numberOfLines={1} ellipsizeMode='middle'> {groupText} </Text>)} {messageText} </>); }; /** * Applies text formatters to the message text. * @param message - The message object. * @param subtitle - The raw text to format. * @returns The formatted text. */ function getFormattedText(message, subtitle) { let messageTextTmp = subtitle; let allFormatters = [...(textFormatters || [])]; if (message.getMentionedUsers().length) { let mentionsFormatter = ChatConfigurator.getDataSource().getMentionsFormatter(); mentionsFormatter.setLoggedInUser(CometChatUIKit.loggedInUser); mentionsFormatter.setMentionsStyle(mergedStyles.mentionsStyles); mentionsFormatter.setTargetElement(MentionsTargetElement.conversation); mentionsFormatter.setMessage(message); allFormatters.push(mentionsFormatter); } if (message instanceof CometChat.TextMessage && message.getCategory() === "message" && message .getText() .substr(0, 50) .match(/http[s]{0,1}:\/\//)) { // For link messages, simply return the text. return messageTextTmp; } if (allFormatters && allFormatters.length) { for (let i = 0; i < allFormatters.length; i++) { let suggestionUsers = allFormatters[i].getSuggestionItems(); allFormatters[i].setMessage(message); suggestionUsers.length > 0 && allFormatters[i].setSuggestionItems(suggestionUsers); let _formatter = CommonUtils.clone(allFormatters[i]); messageTextTmp = _formatter.getFormattedText(messageTextTmp, mergedStyles.itemStyle.subtitleStyle); } } return messageTextTmp; } /** * Component to render the last message view for a conversation item. * @param params - Contains conversation and typing indicator text. * @returns A JSX.Element rendering the last message. */ const LastMessageView = (params) => { const lastMessage = params.conversations.getLastMessage(); if (!lastMessage) return (<Text style={[mergedStyles.itemStyle.subtitleStyle]} numberOfLines={1} ellipsizeMode={"tail"}> {localize("TAP_TO_START_CONVERSATION")} </Text>); let readReceipt; if (params.typingText) { return (<View style={Style.row}> <Text numberOfLines={1} ellipsizeMode={"tail"} style={[mergedStyles.typingIndicatorStyle]}> {params.typingText} </Text> </View>); } if (lastMessage && lastMessage.getSender().getUid() == loggedInUser.current.getUid() && !lastMessage.getDeletedAt()) { let status = MessageReceipt.ERROR; if (lastMessage?.hasOwnProperty("readAt")) status = MessageReceipt.READ; else if (lastMessage?.hasOwnProperty("deliveredAt")) status = MessageReceipt.DELIVERED; else if (lastMessage?.hasOwnProperty("sentAt")) status = MessageReceipt.SENT; readReceipt = !receiptsVisibility ? null : (<CometChatReceipt receipt={status} style={mergedStyles.itemStyle.receiptStyles}/>); } let threadView = null; if (lastMessage?.getParentMessageId()) { threadView = (<> <Icon name='subdirectory-arrow-right-fill' size={theme.spacing.spacing.s4} color={mergedStyles.itemStyle.subtitleStyle.color}/> {/* Optional: Add text for thread indicator */} </>); } return (<View style={[Style.row, { gap: 2, alignItems: "center" }]}> {threadView} <View style={[Style.row, { gap: 2, alignItems: "center" }]}> {!["call", "action"].includes(params["conversations"].getLastMessage().getCategory()) ? readReceipt : null} {getMessagePreview(params["conversations"], theme)} </View> </View>); }; /** * Returns the trailing view (date and badge) for a conversation item. * @param conversation - The conversation object. * @returns A JSX.Element for the trailing view. */ const getTrailingView = useCallback((conversation) => { const customPattern = () => datePattern?.(conversation); const timestamp = conversation.getLastMessage()?.getSentAt(); if (!timestamp) return <></>; return (<View style={[ { marginHorizontal: 6, justifyContent: "center", alignItems: "flex-end" }, mergedStyles.itemStyle.trailingViewContainerStyle, ]}> <CometChatDate timeStamp={timestamp * 1000} customDateString={customPattern && customPattern()} pattern={"dayWeekDayDateTimeFormat"} style={mergedStyles?.itemStyle?.dateStyle}/> <CometChatBadge count={conversation.getUnreadMessageCount()} style={mergedStyles?.itemStyle?.badgeStyle}/> </View>); }, [mergedStyles, datePattern]); /** * Updates the conversation's last message for a group conversation. * @param message - The new message. * @param group - The group the conversation belongs to. */ const updateConversationLastMessage = (message, group) => { try { let conversation = conversationListRef.current?.getListItem(message.getConversationId()); if (conversation) { conversation = CommonUtils.clone(conversation); conversation.setLastMessage(message); conversation.setConversationWith(group); conversationListRef.current?.updateAndMoveToFirst(conversation); } else { CometChat.CometChatHelper.getConversationFromMessage(message) .then((newConversation) => { if (newConversation?.getLastMessage().getSender().getUid() !== loggedInUser.current?.getUid()) newConversation.setUnreadMessageCount(1); conversationListRef.current.addItemToList(newConversation, 0); }) .catch((err) => onError && onError(err)); } } catch (error) { onError && onError(error); } }; /** * Increments the unread message count for a conversation. * @param conversation - The conversation to update. * @returns The updated conversation. */ const updateUnreadMessageCount = (conversation) => { const oldConversation = conversationListRef.current.getListItem(conversation["conversationId"]); if (oldConversation == undefined) { conversation.setUnreadMessageCount(1); return conversation; } oldConversation.setUnreadMessageCount(oldConversation.getUnreadMessageCount() + 1); return oldConversation; }; // Set up event listeners for user, call, group and message events. React.useEffect(() => { // Get logged in user. CometChat.getLoggedinUser() .then((u) => { loggedInUser.current = u; }) .catch((err) => console.log(err)); // Listen for user online/offline changes. CometChat.addUserListener(userListenerId, new CometChat.UserListener({ onUserOnline: (onlineUser) => { userEventHandler(onlineUser); }, onUserOffline: (offlineUser) => { userEventHandler(offlineUser); }, })); // Listen for call events. CometChat.addCallListener(callListenerId, new CometChat.CallListener({ onIncomingCallReceived: (call) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversation.setLastMessage(call); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, onOutgoingCallAccepted: (call) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversation.setLastMessage(call); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, onOutgoingCallRejected: (call) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversation.setLastMessage(call); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, onIncomingCallCancelled: (call) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversation.setLastMessage(call); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, })); // Listen for group events. CometChat.addGroupListener(groupListenerId, new CometChat.GroupListener({ onGroupMemberScopeChanged: (message, changedUser, newScope, oldScope, changedGroup) => { groupHandler(message, { action: "scopeChange", actionOn: changedUser, newScope: newScope, oldScope: oldScope, group: changedGroup, }); }, onGroupMemberKicked: (message, kickedUser, kickedBy, kickedFrom) => { groupHandler(message, { action: "kicked", actionOn: kickedUser, actionBy: kickedBy, group: kickedFrom, }); }, onGroupMemberLeft: (message, leavingUser, group) => { groupHandler(message, { action: "left", actionOn: leavingUser, group }); }, onGroupMemberUnbanned: (message) => { groupHandler(message); }, onGroupMemberBanned: (message, bannedUser, bannedBy, bannedFrom) => { groupHandler(message, { action: "banned", actionOn: bannedUser, actionBy: bannedBy, group: bannedFrom, }); }, onMemberAddedToGroup: (message, userAdded, userAddedBy, userAddedIn) => { if (onMemberAddedToGroupDebounceTimer.current) { clearTimeout(onMemberAddedToGroupDebounceTimer.current); } onMemberAddedToGroupDebounceTimer.current = setTimeout(() => { groupHandler(message, { action: "joined", actionOn: userAdded, actionBy: userAddedBy, group: userAddedIn, }); }, 50); }, onGroupMemberJoined: (message) => { groupHandler(message); }, })); // Listen for conversation deletion events. CometChatUIEventHandler.addConversationListener(conversationListenerId, { ccConversationDeleted: ({ conversation }) => { conversationListRef.current.removeItemFromList(conversation.getConversationId()); removeItemFromSelectionList(conversation.getConversationId()); }, }); // Listen for message events. CometChatUIEventHandler.addMessageListener(messageListenerId, { ccMessageSent: ({ message, status }) => { if (status == MessageStatusConstants.success) { if (!shouldUpdateLastMessageAndUnreadCount(message)) { return; } updateLastMessage(message); } }, ccMessageRead: ({ message }) => { checkAndUpdateLastMessage(message); }, ccMessageDeleted: ({ message }) => { checkAndUpdateLastMessage(message); }, ccMessageEdited: ({ message }) => { checkAndUpdateLastMessage(message); }, onTextMessageReceived: (textMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(textMessage)) { return; } messageEventHandler(textMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, onMediaMessageReceived: (mediaMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(mediaMessage)) { return; } messageEventHandler(mediaMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, onCustomMessageReceived: (customMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(customMessage)) { return; } messageEventHandler(customMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, onMessageDeleted: (deletedMessage) => { checkAndUpdateLastMessage(deletedMessage); }, onMessageEdited: (editedMessage) => { checkAndUpdateLastMessage(editedMessage); }, onMessagesRead: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onMessagesDelivered: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onMessagesDeliveredToAll: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onMessagesReadByAll: (messageReceipt) => { updateMessageReceipt(messageReceipt); }, onTypingStarted: (typingIndicator) => { typingEventHandler(typingIndicator, true); }, onTypingEnded: (typingIndicator) => { typingEventHandler(typingIndicator, false); }, onFormMessageReceived: (formMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(formMessage)) { return; } messageEventHandler(formMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, onCardMessageReceived: (cardMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(cardMessage)) { return; } messageEventHandler(cardMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, onSchedulerMessageReceived: (schedulerMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(schedulerMessage)) { return; } messageEventHandler(schedulerMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, onCustomInteractiveMessageReceived: (customInteractiveMessage) => { if (!shouldUpdateLastMessageAndUnreadCount(customInteractiveMessage)) { return; } messageEventHandler(customInteractiveMessage); !disableSoundForMessages && CometChatSoundManager.play("incomingMessage"); }, }); // Listen for additional group events. CometChatUIEventHandler.addGroupListener(groupListenerId, { ccGroupCreated: ({ group }) => { CometChat.getConversation(group.getGuid(), CometChatUiKitConstants.ConversationTypeConstants.group).then((conversation) => { conversationListRef.current?.addItemToList(conversation, 0); }); }, ccGroupDeleted: ({ group }) => { CometChat.getConversation(group.getGuid(), CometChatUiKitConstants.ConversationTypeConstants.group).then((conversation) => { conversationListRef.current?.removeItemFromList(conversation.getConversationId()); removeItemFromSelectionList(conversation.getConversationId()); }); }, ccGroupLeft: ({ leftGroup }) => { const foundConversation = conversationListRef.current?.getAllListItems().find((conv) => { const convWith = conv.getConversationWith(); return convWith instanceof CometChat.Group && convWith.getGuid() === leftGroup.getGuid(); }); if (foundConversation) { conversationListRef.current?.removeItemFromList(foundConversation.getConversationId()); removeItemFromSelectionList(foundConversation.getConversationId()); } }, ccGroupMemberKicked: ({ message, kickedFrom, }) => { if (!shouldUpdateLastMessageAndUnreadCount(message)) { return; } updateConversationLastMessage(message, kickedFrom); }, ccGroupMemberBanned: ({ message }) => { if (!shouldUpdateLastMessageAndUnreadCount(message)) { return; } groupHandler(message); }, ccGroupMemberUnBanned: ({ message }) => { if (!shouldUpdateLastMessageAndUnreadCount(message)) { return; } groupHandler(message); }, ccOwnershipChanged: ({ message }) => { if (!shouldUpdateLastMessageAndUnreadCount(message)) { return; } CometChat.CometChatHelper.getConversationFromMessage(message) .then((conversation) => { conversationListRef.current?.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, ccGroupMemberAdded: ({ message, userAddedIn, }) => { if (!shouldUpdateLastMessageAndUnreadCount(message)) { return; } updateConversationLastMessage(message, userAddedIn); }, }); // Listen for user block events. CometChatUIEventHandler.addUserListener(userListenerId, { ccUserBlocked: ({ user }) => { const uid = user.getUid(); let item = conversationListRef.current?.getListItem(`${uid}_user_${loggedInUser.current?.getUid()}`) || conversationListRef.current?.getListItem(`${loggedInUser.current?.getUid()}_user_${uid}`); if (conversationsRequestBuilder && conversationsRequestBuilder.build().isIncludeBlockedUsers()) { if (item) { let updatedConversation = CommonUtils.clone(item); updatedConversation.setConversationWith(user); conversationListRef.current?.updateList(updatedConversation); } return; } conversationListRef?.current?.removeItemFromList(item.getConversationId()); removeItemFromSelectionList(item.getConversationId()); }, ccUserUnBlocked: ({ user }) => { /**unblocked handling is required to enable user presence listener for the user**/ const uid = user.getUid(); let item = conversationListRef.current?.getListItem(`${uid}_user_${loggedInUser.current?.getUid()}`) || conversationListRef.current?.getListItem(`${loggedInUser.current?.getUid()}_user_${uid}`); if (item) { let updatedConversation = CommonUtils.clone(item); updatedConversation.setConversationWith(user); conversationListRef.current?.updateList(updatedConversation); } }, }); // Listen for call events via UI event handler. CometChatUIEventHandler.addCallListener(callListenerId, { ccOutgoingCall: ({ call }) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, ccCallAccepted: ({ call }) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, ccCallRejected: ({ call }) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, ccCallEnded: ({ call }) => { CometChat.CometChatHelper.getConversationFromMessage(call) .then((conversation) => { if (!CometChatUIKit.getConversationUpdateSettings().shouldUpdateOnCallActivities()) { return; } conversation = updateUnreadMessageCount(conversation); conversationListRef.current.updateList(conversation); }) .catch((e) => { onError && onError(e); }); }, }); // Cleanup all listeners on unmount. return () => { CometChat.removeUserListener(userListenerId); CometChat.removeCallListener(callListenerId); CometChat.removeGroupListener(groupListenerId); CometChatUIEventHandler.removeMessageListener(messageListenerId); CometChatUIEventHandler.removeConversationListener(conversationListenerId); CometChatUIEventHandler.removeGroupListener(groupListenerId); CometChatUIEventHandler.removeUserListener(userListenerId); }; }, []); const getStatusIndicator = (conv) => { const withObj = conv.getConversationWith(); if (groupTypeVisibility) { if (withObj instanceof CometChat.Group) { if (withObj.getType() === GroupTypeConstants.password) return "protected"; if (withObj.getType() === GroupTypeConstants.private) return "private"; } } else { return undefined; } if (usersStatusVisibility) { if (withObj instanceof CometChat.User && withObj.getStatus() === CometChatUiKitConstants.UserStatusConstants.online && !withObj.getHasBlockedMe() && !withObj.getBlockedByMe()) { return "online"; } return "offline"; } else { return undefined; } }; const LeadingViewRaw = useCallback((conv) => { const withObj = conv.getConversationWith(); const avatarURL = withObj instanceof CometChat.User ? withObj.getAvatar() : withObj.getIcon(); const name = withObj.getName(); return (<> <CometChatAvatar image={{ uri: avatarURL }} name={name} style={mergedStyles.itemStyle.avatarStyle}/> <CometChatStatusIndicator type={getStatusIndicator(conv)} style={mergedStyles?.itemStyle?.statusIndicatorStyle}/> </>); }, [mergedStyles]); const TitleViewRaw = useCallback((conv) => (<Text numberOfLines={1} ellipsizeMode='tail' style={mergedStyles.itemStyle.titleStyle}> {conv.getConversationWith().getName()} </Text>), [mergedStyles]); const SubtitleViewRaw = (conv) => (<LastMessageView conversations={conv} typingText={conv?.["lastMessage"]?.["typing"]}/>); const TrailingViewRaw = useCallback((conv) => getTrailingView(conv), []); return (<View style={mergedStyles.containerStyle}> <CometChatTooltipMenu visible={tooltipVisible} onClose={() => { setTooltipVisible(false); }} event={{ nativeEvent: tooltipPositon.current,