import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";
import { Keyboard } from "react-native";
import {
ColorValue,
Dimensions,
ImageSourcePropType,
ImageStyle,
InteractionManager,
KeyboardAvoidingView,
KeyboardAvoidingViewProps,
Modal,
NativeModules,
Platform,
Text,
TouchableOpacity,
TouchableWithoutFeedbackProps,
View,
ViewStyle,
} from "react-native";
import { startStreamingForRunId, stopStreamingForRunId, streamingState$ } from "../shared/services/stream-message.service";
import {
CometChatActionSheet,
CometChatBottomSheet,
CometChatMentionsFormatter,
CometChatMessageInput,
CometChatMessagePreview,
CometChatSuggestionList,
CometChatTextFormatter,
CometChatUIKit,
CometChatUrlsFormatter,
SuggestionItem,
} from "../shared";
import { Style } from "./styles";
//@ts-ignore
import { CheckPropertyExists } from "../shared/helper/functions";
import { CometChat } from "@cometchat/chat-sdk-react-native";
import { ChatConfigurator, CometChatSoundManager } from "../shared";
import { commonVars } from "../shared/base/vars";
import {
ConversationOptionConstants,
MentionsTargetElement,
MentionsVisibility,
MessageTypeConstants,
ReceiverTypeConstants,
ViewAlignment,
} from "../shared/constants/UIKitConstants";
import { MessageEvents } from "../shared/events";
import { CometChatUIEventHandler } from "../shared/events/CometChatUIEventHandler/CometChatUIEventHandler";
import { CometChatMessageEvents } from "../shared/events/CometChatMessageEvents";
import { CometChatMessageComposerAction, DeepPartial } from "../shared/helper/types";
import { Icon } from "../shared/icons/Icon";
import { CometChatSendButtonView } from "../shared/views/CometChatSendButtonView/CometChatSendButtonView";
import {
getUnixTimestampInMilliseconds,
messageStatus,
} from "../shared/utils/CometChatMessageHelper";
import { CommonUtils } from "../shared/utils/CommonUtils";
import { isCursorWithinMentionRange, getMentionRangeAtCursor } from "../shared/utils/MentionUtils";
import { permissionUtil } from "../shared/utils/PermissionUtil";
import { CometChatMediaRecorder } from "../shared/views/CometChatMediaRecorder";
import { useTheme } from "../theme";
import { ICONS } from "./resources";
import { CometChatTheme } from "../theme/type";
import { deepMerge } from "../shared/helper/helperFunctions";
import { JSX } from "react";
import { useCometChatTranslation } from "../shared/resources/CometChatLocalizeNew";
type MentionOverlap = {
key: string;
value: SuggestionItem;
start: number;
end: number;
};
const { FileManager, CommonUtil } = NativeModules;
const uiEventListenerShow = "uiEvent_show_" + new Date().getTime();
const uiEventListenerHide = "uiEvent_hide_" + new Date().getTime();
const AttachIconButton = (props: {
onPress: TouchableWithoutFeedbackProps["onPress"];
icon: ImageSourcePropType | JSX.Element;
iconStyle: ImageStyle;
}) => {
return (
);
};
const ActionSheetBoard = (props: any) => {
const { shouldShow = false, onClose = () => {}, options = [], sheetRef, style } = props;
return (
);
};
const RecordAudio = (props: any) => {
const {
shouldShow = false,
onClose = () => {},
options = [],
cometChatBottomSheetStyle = {},
sheetRef,
onPause = () => {},
onPlay = () => {},
onSend = (recordedFile: String) => {},
onStop = (recordedFile: String) => {},
onStart = () => {},
mediaRecorderStyle,
...otherProps
} = props;
return (
);
};
type Enumerate = Acc["length"] extends N
? Acc[number]
: Enumerate;
type IntRange = Exclude, Enumerate>;
/**
* Properties for the CometChat message composer component.
*/
export interface CometChatMessageComposerInterface {
/**
* Message composer identifier.
*
* @type {string | number}
*/
id?: string | number;
/**
* CometChat SDK's user object.
*
* @type {CometChat.User}
*/
user?: CometChat.User;
/**
* CometChat SDK's group object.
*
* @type {CometChat.Group}
*/
group?: CometChat.Group;
/**
* Flag to turn off sound for outgoing messages.
*
* @type {boolean}
*/
disableSoundForOutgoingMessages?: boolean;
/**
* Custom audio sound to be played while sending messages.
*
* @type {*}
*/
customSoundForOutgoingMessage?: any;
/**
* Flag to disable typing events.
*
* @type {boolean}
*/
disableTypingEvents?: boolean;
/**
* Initial text to be displayed in the composer.
*
* @type {string}
*/
initialComposertext?: string;
/**
* Renders a preview section at the top of the composer.
*
* @param {Object} props - The component properties.
* @param {CometChat.User} [props.user] - The user object.
* @param {CometChat.Group} [props.group] - The group object.
* @returns {JSX.Element} The header view element.
*/
HeaderView?: ({ user, group }: { user?: CometChat.User; group?: CometChat.Group }) => JSX.Element;
/**
* Callback triggered when the input text changes.
*
* @param {string} text - The updated text.
*/
onTextChange?: (text: string) => void;
/**
* Returns the attachment options for the composer.
*
* @param {Object} props - The function properties.
* @param {CometChat.User} [props.user] - The user object.
* @param {CometChat.Group} [props.group] - The group object.
* @param {Map} props.composerId - The composer identifier as a Map.
* @returns {CometChatMessageComposerAction[]} An array of composer actions.
*/
attachmentOptions?: ({
user,
group,
composerId,
}: {
user?: CometChat.User;
group?: CometChat.Group;
composerId: Map;
}) => CometChatMessageComposerAction[];
/**
* Replaces the default Auxiliary Button.
*
* @param {Object} props - The function properties.
* @param {CometChat.User} [props.user] - The user object.
* @param {CometChat.Group} [props.group] - The group object.
* @param {string | number} props.composerId - The composer identifier.
* @returns {JSX.Element} A custom auxiliary button component.
*/
AuxiliaryButtonView?: ({
user,
group,
composerId,
}: {
user?: CometChat.User;
group?: CometChat.Group;
composerId: string | number;
}) => JSX.Element;
/**
* Replaces the default Send Button.
*
* @param {Object} props - The function properties.
* @param {CometChat.User} [props.user] - The user object.
* @param {CometChat.Group} [props.group] - The group object.
* @param {string | number} props.composerId - The composer identifier.
* @returns {JSX.Element} A custom send button component.
*/
SendButtonView?: ({
user,
group,
composerId,
}: {
user?: CometChat.User;
group?: CometChat.Group;
composerId: string | number;
}) => JSX.Element;
/**
* Message id required for threaded messages.
*
* @type {string | number}
*/
parentMessageId?: string | number;
/**
* Custom styles for the message composer component.
*/
style?: DeepPartial;
/**
* Flag to hide the voice recording button.
*
* @type {boolean}
*/
hideVoiceRecordingButton?: boolean;
/**
* Callback triggered when the send button is pressed.
*
* @param {CometChat.BaseMessage} message - The base message object.
*/
onSendButtonPress?: (message: CometChat.BaseMessage) => void;
/**
* Callback triggered when an error occurs.
*
* @param {CometChat.CometChatException} error - The error object.
*/
onError?: (error: CometChat.CometChatException) => void;
/**
* Override properties for the KeyboardAvoidingView.
*/
keyboardAvoidingViewProps?: KeyboardAvoidingViewProps;
/**
* Collection of text formatter classes to apply custom formatting.
*
* @type {Array}
*/
textFormatters?: Array<
CometChatMentionsFormatter | CometChatUrlsFormatter | CometChatTextFormatter
>;
/**
* Flag to disable mention functionality.
*/
disableMentions?: boolean;
/**
* Flag to disable the special group mention (@all / @channel etc.).
* When true, the alias suggestion will not appear.
* @default false
*/
disableMentionAll?: boolean;
/**
* Custom alias label for the group-wide mention.
* Rendered as @Alias in composer and bubbles.
* Internally stored as <@all:Alias>.
* @default "all"
*/
mentionAllLabel?: string;
/**
* Controls image quality when taking pictures from the camera.
* A value of 100 means no compression.
*
* @default 20
* @type {IntRange<1, 100>}
*/
imageQuality?: IntRange<1, 100>;
/**
* If true, hides the camera option from the attachment options.
*/
hideCameraOption?: boolean;
/**
* If true, hides the image attachment option from the attachment options.
*/
hideImageAttachmentOption?: boolean;
/**
* If true, hides the video attachment option from the attachment options.
*/
hideVideoAttachmentOption?: boolean;
/**
* If true, hides the audio attachment option from the attachment options.
*/
hideAudioAttachmentOption?: boolean;
/**
* If true, hides the file/document attachment option from the attachment options.
*/
hideFileAttachmentOption?: boolean;
/**
* If true, hides the polls option from the attachment options.
*/
hidePollsAttachmentOption?: boolean;
/**
* If true, hides the collaborative document option (e.g., shared document editing).
*/
hideCollaborativeDocumentOption?: boolean;
/**
* If true, hides the collaborative whiteboard option.
*/
hideCollaborativeWhiteboardOption?: boolean;
/**
* If true, hides the entire attachment button from the composer.
*/
hideAttachmentButton?: boolean;
/**
* If true, hides the stickers button from the composer.
*/
hideStickersButton?: boolean;
/**
* If true, hides the send button from the composer.
*/
hideSendButton?: boolean;
/**
* If true, hides all auxiliary buttons (such as voice input, GIFs, or other plugin buttons).
*/
hideAuxiliaryButtons?: boolean;
/**
* Returns the attachment options for the composer.
*
* @param {Object} props - The function properties.
* @param {CometChat.User} [props.user] - The user object.
* @param {CometChat.Group} [props.group] - The group object.
* @param {Map} props.composerId - The composer identifier as a Map.
* @returns {CometChatMessageComposerAction[]} An array of composer actions.
*/
addAttachmentOptions?: ({
user,
group,
composerId,
}: {
user?: CometChat.User;
group?: CometChat.Group;
composerId: Map;
}) => CometChatMessageComposerAction[];
/**
* Determines the alignment of auxiliary buttons (e.g., sticker button).
* Can be either "left" or "right".
*
* @default "left"
*/
auxiliaryButtonsAlignment?: "left" | "right";
/**
* Custom send button view for AI agents (only applies to @agentic users)
*/
AgentSendButtonView?: React.ComponentType<{
isButtonDisabled: boolean;
composerRef: any;
}>;
}
export const CometChatMessageComposer = React.forwardRef(
(props: CometChatMessageComposerInterface, ref) => {
const editMessageListenerID = "editMessageListener_" + new Date().getTime();
const replyMessageListenerID = "replyMessageListener_" + new Date().getTime();
const UiEventListenerID = "UiEventListener_" + new Date().getTime();
const theme = useTheme();
const {t} = useCometChatTranslation()
const {
id,
user,
group,
disableSoundForOutgoingMessages = true,
customSoundForOutgoingMessage,
disableTypingEvents: propDisableTypingEvents,
initialComposertext,
HeaderView,
onTextChange,
attachmentOptions,
AuxiliaryButtonView,
SendButtonView,
parentMessageId,
style = {},
onSendButtonPress,
onError,
hideVoiceRecordingButton: propHideVoiceRecordingButton,
keyboardAvoidingViewProps,
textFormatters,
disableMentions: propDisableMentions,
disableMentionAll = false,
mentionAllLabel = "all",
imageQuality = 20,
hideCameraOption = false,
hideImageAttachmentOption = false,
hideVideoAttachmentOption = false,
hideAudioAttachmentOption = false,
hideFileAttachmentOption = false,
hidePollsAttachmentOption = false,
hideCollaborativeDocumentOption = false,
hideCollaborativeWhiteboardOption = false,
hideAttachmentButton: propHideAttachmentButton = false,
hideStickersButton: propHideStickersButton = false,
hideSendButton = false,
hideAuxiliaryButtons = false,
addAttachmentOptions,
auxiliaryButtonsAlignment = "left",
AgentSendButtonView,
} = props;
// Helper function to check if user is agentic
const isAgenticUser = useCallback(() => {
return user?.getRole?.() === '@agentic';
}, [user]);
// Apply automatic hiding for @agentic users
const disableTypingEvents = isAgenticUser() ? true : propDisableTypingEvents;
const disableMentions = isAgenticUser() ? true : propDisableMentions;
const hideAttachmentButton = isAgenticUser() ? true : propHideAttachmentButton;
const hideStickersButton = isAgenticUser() ? true : propHideStickersButton;
const hideVoiceRecordingButton = isAgenticUser() ? true : propHideVoiceRecordingButton;
const composerIdMap = new Map().set("parentMessageId", parentMessageId);
const parentMessageIdRef = useRef(null);
const mergedComposerStyle: CometChatTheme["messageComposerStyles"] = useMemo(() => {
const mergedStyle = deepMerge(theme.messageComposerStyles, style);
if (isAgenticUser()) {
return {
...mergedStyle,
messageInputStyles: {
...mergedStyle.messageInputStyles,
dividerStyle: {
display: 'none'
}
}
};
}
return mergedStyle;
}, [theme, style, isAgenticUser]);
const loggedInUser = React.useRef({});
const chatWith = React.useRef(null);
const chatWithId = React.useRef(null);
const messageInputRef = React.useRef(null);
const chatRef = React.useRef(chatWith);
const inputValueRef = React.useRef(null);
const plainTextInput = React.useRef(initialComposertext || "");
let mentionMap = React.useRef