@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
1,018 lines (1,017 loc) • 53.5 kB
JavaScript
/*
* 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,