import { CometChat } from "@cometchat/chat-sdk-react-native"; import React, { JSX } from "react"; import { ColorValue, PixelRatio, Text, View } from "react-native"; import { MessageBubbleAlignmentType } from "../base"; import { CometChatCustomMessageTypes, MessageCategoryConstants, MentionsTargetElement, MessageReceipt, MessageTypeConstants, } from "../constants/UIKitConstants"; import { ChatConfigurator } from "../framework"; import { Icon, IconName } from "../icons/Icon"; import { CometChatMessageTemplate } from "../modals"; import { CometChatMessageBubble } from "../views/CometChatMessageBubble"; import { BubbleStyles, CometChatTheme, OutgoingBubbleStyles } from "../../theme/type"; import { CometChatUIKit } from "../CometChatUiKit/CometChatUIKit"; import { SuggestionItem } from "../views/CometChatSuggestionList/SuggestionItem"; import { CommonUtils } from "./CommonUtils"; import { deepMerge } from "../helper/helperFunctions"; import { CometChatDate } from "../views/CometChatDate"; import { CometChatAvatar, CometChatReceipt } from "../views"; import { useCometChatTranslation } from "../resources/CometChatLocalizeNew"; import { useTheme } from "../../theme"; type MessageViewParamsType = { message: CometChat.BaseMessage; templates?: CometChatMessageTemplate[]; alignment?: MessageBubbleAlignmentType; theme: CometChatTheme; isThreaded?: boolean; datePattern?: (message: CometChat.BaseMessage) => string; receiptsVisibility?: boolean; avatarVisibility?: boolean; }; const getOverridenBubbleStyles = (theme: CometChatTheme) => { const styleCache = new Map(); const outgoingBubbleStyles = theme.messageListStyles.outgoingMessageBubbleStyles; const incomingBubbleStyles = theme.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, theme.deletedBubbleStyles ?? {}, incomingBubbleStyles.deletedBubbleStyles ?? {} ), outgoing: deepMerge( outgoingBubbleStyles, theme.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 ?? {}), }); return styleCache; }; const getTemplatesMap = (templates: CometChatMessageTemplate[]) => { let templatesMap = new Map(); templates.forEach((template) => { if (templatesMap.get(`${template.category}_${template.type}`)) return; templatesMap.set(`${template.category}_${template.type}`, template); }); return templatesMap; }; const MessageContentView = (props: { message: CometChat.BaseMessage; alignment: MessageBubbleAlignmentType; theme: CometChatTheme; }): JSX.Element | null => { const { message, alignment, theme } = props; switch (message.getType()) { case MessageTypeConstants.audio: return ChatConfigurator.dataSource.getAudioMessageContentView(message, alignment, theme); case MessageTypeConstants.video: return ChatConfigurator.dataSource.getVideoMessageContentView(message, alignment, theme); case MessageTypeConstants.file: return ChatConfigurator.dataSource.getFileMessageContentView(message, alignment, theme); case MessageTypeConstants.text: return ChatConfigurator.dataSource.getTextMessageContentView(message, alignment, theme); case MessageTypeConstants.image: return ChatConfigurator.dataSource.getImageMessageContentView(message, alignment, theme); } return null; }; const getLeadingView = ( item: CometChat.BaseMessage, theme: CometChatTheme, avatarVisibility = true ): JSX.Element | undefined => { if (!avatarVisibility) return undefined; let _style = getBubbleStyle(item, theme); if ( item.getSender()?.getUid() !== CometChatUIKit.loggedInUser?.getUid() && item.getCategory() != MessageCategoryConstants.action ) { return ( ); } return undefined; }; const getHeaderView = ( item: CometChat.BaseMessage | any, theme: CometChatTheme ): JSX.Element | undefined => { const _style = getBubbleStyle(item, theme); if ( item.getSender()?.getUid() != CometChatUIKit.loggedInUser?.getUid() && ![MessageCategoryConstants.action, MessageCategoryConstants.call].includes(item.getCategory()) ) { const senderName = (item.getSender()?.getName() || "").trim(); return ( {Boolean(senderName) && ( {senderName} )} ); } return undefined; }; const getBubbleStyle = (item: CometChat.BaseMessage, theme: CometChatTheme): BubbleStyles => { const loggedInUser = CometChatUIKit.loggedInUser!; const type = (() => { if (item.getDeletedAt()) { return MessageTypeConstants.messageDeleted; } return item.getType(); })(); if (item.getSender().getUid() != loggedInUser.getUid()) { return ( getOverridenBubbleStyles(theme).get(type)?.incoming ?? theme.messageListStyles.incomingMessageBubbleStyles ); } return ( getOverridenBubbleStyles(theme).get(type)?.outgoing ?? theme.messageListStyles.outgoingMessageBubbleStyles ); }; const getSentAtTimestamp = (item: any) => { return item.getSentAt() ? item.getSentAt() * 1000 : Date.now(); }; const getStatusInfoView = ( item: | CometChat.TextMessage | CometChat.MediaMessage | CometChat.CustomMessage | CometChat.InteractiveMessage | CometChat.BaseMessage | any, theme: CometChatTheme, receiptsVisibility: boolean = true, datePattern?: (message: CometChat.BaseMessage) => string ): JSX.Element | undefined => { const loggedInUser = CometChatUIKit.loggedInUser!; let isOutgoingMessage = item.getSender()?.getUid() == loggedInUser.getUid(); let _style = getBubbleStyle(item, theme); let messageState; if (item.getReadAt()) messageState = "READ"; else if (item.getDeliveredAt()) messageState = "DELIVERED"; else if (item.getSentAt()) messageState = "SENT"; else if (item?.getData()?.metaData?.error) messageState = "ERROR"; else if (isOutgoingMessage) messageState = "WAIT"; else messageState = "ERROR"; return ( {receiptsVisibility && isOutgoingMessage ? ( /* ToDoM Use Icon From Incoming/Outgoing bubble styles */ ) : null} ); }; export const MessageUtils = { getMessageView: (params: MessageViewParamsType) => { const { message, templates, alignment, theme, datePattern, receiptsVisibility, avatarVisibility, } = params; const templatesMap = getTemplatesMap(templates!); const baseStyle = getBubbleStyle(message, theme); let hasTemplate = templatesMap.get(`${message.getCategory()}_${message.getType()}`); if (templates!.length > 0) { let customTemplate = templates!.find( (template) => template.type == message.getType() && template.category == message.getCategory() ); if (customTemplate) hasTemplate = customTemplate; } // Only show moderation bottom view for outgoing disapproved messages const moderationStatus = getModerationStatus(message); const isOutgoing = message.getSender()?.getUid() === CometChatUIKit.loggedInUser?.getUid(); const styleForThisMessage = isOutgoing && moderationStatus === "disapproved" ? deepMerge(baseStyle, { containerStyle: { borderBottomLeftRadius: 0, borderBottomRightRadius: 0, overflow: "hidden", }, }) : baseStyle; const DefaultModerationBottomView = isOutgoing && moderationStatus === "disapproved" ? ( ) : undefined; return ( ); }, }; export const getMessagePreviewInternal = ( iconName: IconName, text: string, { iconColor, theme }: { iconColor?: ColorValue; theme?: CometChatTheme } ) => { const fontScale = PixelRatio.getFontScale(); const iconSize = (theme?.spacing?.spacing?.s4 ?? 16) * fontScale; return ( <> {iconName && ( )} {text} ); }; /** * Applies mentions formatting to message content. * Shared helper used by CometChatConversations subtitle and CometChatMessagePreview * to ensure consistent @mention styling across the UI. * * @param message - The message containing mentioned users * @param content - The current formatted content (string or JSX) * @param rawText - The raw message text (for detecting @all alias tokens) * @param mentionsStyle - Optional style overrides for mentions * @returns The content with styled mentions applied */ export const applyMentionsFormatting = ( message: CometChat.BaseMessage, content: string | JSX.Element, rawText: string, mentionsStyle?: any, ): string | JSX.Element => { const containsAllAlias = /<@all:(.*?)>/.test(rawText); if (!message.getMentionedUsers?.().length && !containsAllAlias) { return content; } try { let mentionsFormatter = ChatConfigurator.getDataSource().getMentionsFormatter(); mentionsFormatter.setContext("conversation"); mentionsFormatter.setLoggedInUser(CometChatUIKit.loggedInUser!); mentionsFormatter.setTargetElement(MentionsTargetElement.conversation); mentionsFormatter.setMessage(message); if (mentionsStyle) { mentionsFormatter.setMentionsStyle(mentionsStyle); } if (containsAllAlias) { const match = rawText.match(/<@all:(.*?)>/); const aliasLabel = match && match[1] ? match[1] : "all"; let existing = mentionsFormatter.getSuggestionItems(); const underlyingText = `<@all:${aliasLabel}>`; const already = existing.find((it: any) => it.underlyingText === underlyingText); if (!already) { const aliasItem = new SuggestionItem({ id: aliasLabel, name: aliasLabel, promptText: aliasLabel, trackingCharacter: "@", underlyingText, hideLeadingIcon: true, }); mentionsFormatter.setSuggestionItems([...existing, aliasItem]); } } let suggestionUsers = mentionsFormatter.getSuggestionItems(); mentionsFormatter.setMessage(message); if (suggestionUsers.length > 0) mentionsFormatter.setSuggestionItems(suggestionUsers); let _formatter = CommonUtils.clone(mentionsFormatter); return _formatter.getFormattedText(content, mentionsStyle); } catch (e) { console.warn("Error applying mentions formatting:", e); return content; } }; export const getModerationStatus = (message: CometChat.BaseMessage | any): string => { if (message?.getModerationStatus) { try { return message.getModerationStatus(); } catch (e) { console.log("🚀 ~ getModerationStatus ~ e:", e); } } const rawStatus = message?.getData?.()?.moderation?.status || message?.getData?.()?.metaData?.moderation?.status || message?.data?.moderation?.status || message?.data?.metaData?.moderation?.status || message?.moderationStatus; if (!rawStatus) return "unmoderated"; return String(rawStatus); }; export const ModerationBottomView = ({ status, moderationStyle, }: { status: string; moderationStyle?: OutgoingBubbleStyles["moderationStyle"]; }) => { const { t } = useCometChatTranslation(); const theme = useTheme(); if (status !== "disapproved") return null; const bubblePadH = theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingHorizontal ?? theme.spacing?.spacing?.s3 ?? 12; const bubblePadB = theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingBottom ?? 0; const bubbleRadius = theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.borderRadius ?? 8; const themeModerationStyle = theme.messageListStyles.outgoingMessageBubbleStyles.moderationStyle || {}; const effective = deepMerge(themeModerationStyle, moderationStyle || {}); return ( {t("BLOCKED_MODERATION")} ); }; export const MimeErrorBottomView = ({ moderationStyle, }: { moderationStyle?: OutgoingBubbleStyles["moderationStyle"]; }) => { const { t } = useCometChatTranslation(); const theme = useTheme(); const bubblePadH = theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingHorizontal ?? theme.spacing?.spacing?.s3 ?? 12; const bubblePadB = theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.paddingBottom ?? 0; const bubbleRadius = theme.messageListStyles.outgoingMessageBubbleStyles?.containerStyle?.borderRadius ?? 8; const themeModerationStyle = theme.messageListStyles.outgoingMessageBubbleStyles.moderationStyle || {}; const effective = deepMerge(themeModerationStyle, moderationStyle || {}); return ( {t("FILE_TYPE_NOT_ALLOWED")} ); };