@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
1,073 lines • 83.8 kB
JavaScript
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