import { CometChat } from "@cometchat/chat-sdk-react-native"; import React, { JSX, useEffect, useMemo, useRef, useState } from "react"; import { Modal, Text, TouchableOpacity, View } from "react-native"; import { MessageCategoryConstants, MessageTypeConstants, } from "../../shared/constants/UIKitConstants"; import { CometChatUIEventHandler } from "../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler"; import { CometChatSoundManager } from "../../shared/resources"; import { CometChatAvatar } from "../../shared/views"; import { CallUIEvents } from "../CallEvents"; import { CallingPackage } from "../CallingPackage"; import { CometChatOngoingCall } from "../CometChatOngoingCall"; import { useTheme } from "../../theme"; import { Icon } from "../../shared/icons/Icon"; import { deepMerge } from "../../shared/helper/helperFunctions"; import { OutgoingCallStyle } from "./styles"; import { DeepPartial } from "../../shared/helper/types"; import { useCometChatTranslation } from "../../shared/resources/CometChatLocalizeNew"; import { SafeAreaView } from "react-native-safe-area-context"; const listenerId = "callListener_" + new Date().getTime(); const CometChatCalls = CallingPackage.CometChatCalls; /** * Props for the CometChatOutgoingCall component. * * @interface CometChatOutgoingCallInterface */ export interface CometChatOutgoingCallInterface { /** * The outgoing call object, can be a CometChat.Call or CometChat.CustomMessage. */ call?: CometChat.Call | CometChat.CustomMessage | any; /** * Action to be performed on click of the cancel/reject button. * Provides the CometChat.Call object as argument. */ onEndCallButtonPressed?: (call: CometChat.Call) => void; /** * Flag to disable sound for the call. */ disableSoundForCalls?: boolean; /** * Custom sound for the call. */ customSoundForCalls?: string; /** * Custom call settings builder instance. */ callSettingsBuilder?: typeof CometChatCalls.CallSettingsBuilder; /** * Custom style overrides for the outgoing call component. */ style?: DeepPartial; /** * Custom view for the title section. */ TitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element; /** * Custom view for the subtitle section. */ SubtitleView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element; /** * Custom view for the avatar section. */ AvatarView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element; /** * Custom view for the end call button. */ EndCallView?: (call: CometChat.Call | CometChat.CustomMessage) => JSX.Element; /** * Callback fired when an error occurs. * @param {CometChat.CometChatException} error - The error object. */ onError?: (error: CometChat.CometChatException) => void; } /** * CometChatOutgoingCall component. * * This component handles the UI for an outgoing call by playing sound, * rendering call details and providing an end call action. It listens to call events * to update the UI based on the call's state. * * @param {CometChatOutgoingCallInterface} props - Component configuration props. * @returns {JSX.Element} The rendered outgoing call UI. */ export const CometChatOutgoingCall = (props: CometChatOutgoingCallInterface): JSX.Element => { const { call, customSoundForCalls, disableSoundForCalls, onEndCallButtonPressed, callSettingsBuilder, style, TitleView, SubtitleView, AvatarView, EndCallView, onError, } = props; // State to track whether the call is connected. const [isCallConnected, setCallConnected] = useState(false); // Controls visibility of this modal. const [isModalVisible, setModalVisible] = useState(true); const ongoingCall = useRef(undefined); const callSessionId = useRef(undefined); const callListener = useRef(null); const callSettings = useRef(null); const isCallEnded = useRef(undefined); const theme = useTheme(); const { t } = useCometChatTranslation(); // Merge default and custom styles for the outgoing call. const outgoingCallStyle = useMemo(() => { return deepMerge(theme.outgoingCallStyle, style ?? {}); }, [theme.outgoingCallStyle, style]); function checkIfDefaultCall(call: CometChat.BaseMessage): boolean { return call.getCategory() === MessageCategoryConstants.call; } /** * Ends the call if required by checking if it is a default call. */ const endCallIfRequired = () => { if (call && checkIfDefaultCall(call)) { CometChat.endCall((call as CometChat.Call).getSessionId()) .then(() => { (call as CometChat.Call).setStatus("ended"); if (!isCallEnded.current) { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call }); } isCallEnded.current = true; }) .catch((err) => { console.log("Error", err); onError && onError(err); }); } }; // Set up listeners, call settings, and sound for the outgoing call. useEffect(() => { if ( call && (call["status"] === "ongoing" || (call.getCategory() === (CometChat.CATEGORY_CUSTOM as CometChat.MessageCategory) && call.getType() === MessageTypeConstants.meeting)) ) { ongoingCall.current = call; if (call.getType() == MessageTypeConstants.meeting) { callSessionId.current = ( (call as CometChat.CustomMessage).getCustomData() as any )?.sessionId; } if (call.getCategory() === MessageCategoryConstants.call) { callSessionId.current = call["sessionId"]; } setCallConnected(true); } if (!disableSoundForCalls && call?.getType() !== MessageTypeConstants.meeting) { if (customSoundForCalls) { CometChatSoundManager.play("outgoingCall", customSoundForCalls); } else { CometChatSoundManager.play("outgoingCall"); } } // Add call listener to handle outgoing call acceptance and rejection. CometChat.addCallListener( listenerId, new CometChat.CallListener({ onOutgoingCallAccepted: (acceptedCall: any) => { CometChatSoundManager.pause(); ongoingCall.current = acceptedCall; callSessionId.current = acceptedCall["sessionId"]; setCallConnected(true); }, onOutgoingCallRejected: () => { CometChatSoundManager.pause(); ongoingCall.current = undefined; callSessionId.current = undefined; setCallConnected(false); }, }) ); // Listen for call failure events. CometChatUIEventHandler.addCallListener(listenerId, { ccCallFailed: (error: CometChat.CometChatException) => { setCallConnected(false); onError && onError(error); }, }); // Create an ongoing call listener to manage call events. callListener.current = new CometChatCalls.OngoingCallListener({ onCallEnded: () => { CometChatCalls.endSession(); if (checkIfDefaultCall(call)) { CometChat.clearActiveCall(); setCallConnected(false); call.setStatus("ended"); if (!isCallEnded.current) { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call }); } isCallEnded.current = true; } }, onCallEndButtonPressed: () => { if (!checkIfDefaultCall(call)) { setCallConnected(false); call.setStatus("ended"); if (!isCallEnded.current) { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call }); } isCallEnded.current = true; } else { endCallIfRequired(); } }, onUserJoined: (user: CometChat.User) => { console.log("user joined:", user); }, onUserLeft: (user: CometChat.User) => { endCallIfRequired(); }, onError: (error: CometChat.CometChatException) => { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, { error }); }, }); // Determine call type (audio or meeting) and initialize call settings. const callType = call?.getType() === MessageTypeConstants.meeting ? call["customData"]?.["callType"] : call.getType(); callSettings.current = callSettingsBuilder?.setCallEventListener(callListener.current) ?? new CometChatCalls.CallSettingsBuilder() .enableDefaultLayout(true) .setCallEventListener(callListener.current) .setIsAudioOnlyCall(callType === "audio"); // Cleanup on unmount. return () => { if (!disableSoundForCalls) { CometChatSoundManager.pause(); } CometChat.removeCallListener(listenerId); }; }, []); /** * Handles closing the modal (via hardware back button on Android or a custom button). * This is where you can call onEndCallButtonPressed or do any other cleanup. */ const handleModalClose = () => { if (onEndCallButtonPressed) { onEndCallButtonPressed(call as CometChat.Call); } // Hide the modal – onDismiss fires after the animation completes setModalVisible(false); }; const callReceiverName = call?.getReceiver?.().getName?.() ?? "Unknown"; return ( {isCallConnected ? ( ) : ( {TitleView ? ( TitleView(call) ) : ( {callReceiverName} )} {SubtitleView ? ( SubtitleView(call) ) : ( {t("CALLING")} )} {AvatarView ? ( AvatarView(call) ) : ( )} {EndCallView ? ( EndCallView(call) ) : ( )} )} ); };