UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

236 lines (232 loc) 9.79 kB
import { CometChat } from "@cometchat/chat-sdk-react-native"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { Text, TouchableOpacity, View, SafeAreaView, StyleSheet, } from "react-native"; import { CometChatAvatar, CometChatSoundManager, localize } from "../../shared"; import { CallTypeConstants, MessageCategoryConstants, MessageTypeConstants, } from "../../shared/constants/UIKitConstants"; import { CometChatUIEventHandler } from "../../shared/events/CometChatUIEventHandler/CometChatUIEventHandler"; import { CallUIEvents } from "../CallEvents"; import { CallingPackage } from "../CallingPackage"; import { CometChatOngoingCall } from "../CometChatOngoingCall"; import { Icon } from "../../shared/icons/Icon"; import { useTheme } from "../../theme"; import { deepMerge } from "../../shared/helper/helperFunctions"; const listnerID = "CALL_LISTENER_" + new Date().getTime(); const CometChatCalls = CallingPackage.CometChatCalls; /** * CometChatIncomingCall component. * * This component handles incoming calls by playing a sound, offering accept/decline buttons, * and showing an ongoing call screen if accepted. Custom views for various parts of the call UI * can be provided via props. * * @param {CometChatIncomingCallInterface} props - Component configuration props. * @returns {JSX.Element} The rendered incoming call UI. */ export const CometChatIncomingCall = (props) => { const { onAccept, onDecline, customSoundForCalls, disableSoundForCalls, ItemView, TitleView, SubtitleView, LeadingView, TrailingView, call, onError, callSettingsBuilder, style, } = props; const theme = useTheme(); const [showCallScreen, setShowCallScreen] = useState(false); const acceptedCall = useRef(undefined); /** Reference to the call listener */ const callListener = useRef(undefined); /** Reference to the call settings builder instance */ const callSettings = useRef(undefined); // Merge the default and custom styles for incoming calls. const incomingCallStyle = useMemo(() => { return deepMerge(theme.incomingCallStyle, style ?? {}); }, [theme.incomingCallStyle, style]); /** * Ends the call by rejecting it and emitting a call rejected event. */ const endCall = () => { CometChat.rejectCall(call["sessionId"], CometChat.CALL_STATUS.REJECTED).then((rejectedCall) => { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallRejected, { call: rejectedCall, }); // Notify parent so it can unmount this component. onDecline && onDecline(rejectedCall); CometChatSoundManager.pause(); }, (err) => { onError && onError(err); }); }; /** * Accepts the incoming call. * * If a custom onAccept callback is provided, it is used instead of the default behavior. */ const acceptCall = () => { CometChatSoundManager.pause(); if (onAccept) { onAccept(call); return; } CometChat.acceptCall(call["sessionId"]).then((accepted) => { acceptedCall.current = accepted; setShowCallScreen(true); CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallAccepted, { call: accepted, }); }, (err) => { onError && onError(err); }); }; /** * Checks if the provided call is a default CometChat call (not a custom meeting call). * * @param {CometChat.BaseMessage} ccCall - The call to check. * @returns {Boolean} True if it's a default call; otherwise, false. */ function isDefaultCall(ccCall) { return ccCall.getCategory() === MessageCategoryConstants.call; } // Set up listeners and call settings on component mount. useEffect(() => { if (call && !disableSoundForCalls && call.getType() !== MessageTypeConstants.meeting) { // Play a custom or default incoming call ringtone. if (customSoundForCalls) { CometChatSoundManager.play("incomingCall", customSoundForCalls); } else { CometChatSoundManager.play("incomingCall"); } } // Add a call listener for call cancellation. CometChat.addCallListener(listnerID, new CometChat.CallListener({ onIncomingCallCancelled: () => { CometChatSoundManager.pause(); }, })); // Create an ongoing call listener for managing call events. callListener.current = new CometChatCalls.OngoingCallListener({ onCallEnded: () => { CometChatCalls.endSession(); CometChat.clearActiveCall(); CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, {}); setShowCallScreen(false); acceptedCall.current = undefined; }, onCallEndButtonPressed: () => { if (isDefaultCall(call)) { CometChat.endCall(call.getSessionId()).then((endedCall) => { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call: endedCall, }); }); } }, onUserJoined: (user) => { console.log("user joined:", user); }, onUserLeft: (user) => { if (isDefaultCall(call)) { CometChat.endCall(call.getSessionId()) .then((endedCall2) => { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallEnded, { call: endedCall2, }); }) .catch((err) => { console.log("Error on userLeft:", err); }); } }, onError: (error) => { CometChatUIEventHandler.emitCallEvent(CallUIEvents.ccCallFailed, { error }); onError && onError(error); }, }); // Initialize call settings using the provided builder or default values. callSettings.current = callSettingsBuilder?.setCallEventListener(callListener.current) ?? new CometChatCalls.CallSettingsBuilder() .enableDefaultLayout(true) .setCallEventListener(callListener.current) .setIsAudioOnlyCall(call["type"] === "audio"); // Cleanup listeners and pause any sounds on unmount. return () => { CometChatUIEventHandler.removeCallListener(listnerID); CometChat.removeCallListener(listnerID); CometChatSoundManager.pause(); }; }, []); /** * If the call is accepted, render the ongoing call screen. */ if (showCallScreen) { return (<CometChatOngoingCall sessionID={acceptedCall.current?.getSessionId()} onError={onError} callSettingsBuilder={callSettings.current}/>); } /** * Render a custom ItemView if provided. */ if (ItemView) { return ItemView(call); } /** * Render the default incoming call overlay with header and action buttons. */ return (<SafeAreaView style={styles.overlay}> <View style={[ incomingCallStyle.containerStyle, { width: "100%" }, ]}> {/* Top row: LeadingView, Title/Subtitle, TrailingView */} <View style={styles.topRow}> {LeadingView && LeadingView(call)} <View> {TitleView ? (TitleView(call)) : (<Text style={incomingCallStyle.titleTextStyle}> {call["sender"]?.["name"] ?? localize("INCOMING_CALL")} </Text>)} {SubtitleView ? (SubtitleView(call)) : (<View style={styles.rowInline}> <Icon name="call-fill" size={16} containerStyle={{ marginTop: 4, marginRight: 4 }}/> <Text style={incomingCallStyle.subtitleTextStyle}> {call?.["type"] === CallTypeConstants.audio ? localize("INCOMING_AUDIO_CALL") : localize("INCOMING_VIDEO_CALL")} </Text> </View>)} </View> {TrailingView ? (TrailingView(call)) : (<CometChatAvatar name={call?.["sender"]?.["name"]} image={{ uri: call?.["sender"]?.["avatar"] }} style={incomingCallStyle.avatarStyle}/>)} </View> {/* Buttons row */} <View style={[styles.bottomRow, { marginTop: 16 }]}> <TouchableOpacity onPress={endCall} style={incomingCallStyle.declineCallButtonStyle}> <Text style={incomingCallStyle.declineCallTextStyle}> {localize("DECLINE")} </Text> </TouchableOpacity> <TouchableOpacity onPress={acceptCall} style={incomingCallStyle.acceptCallButtonStyle}> <Text style={incomingCallStyle.acceptCallTextStyle}> {localize("ACCEPT")} </Text> </TouchableOpacity> </View> </View> </SafeAreaView>); }; const styles = StyleSheet.create({ overlay: { position: "absolute", top: 0, left: 10, right: 10, bottom: 0, zIndex: 99999, }, topRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", }, bottomRow: { flexDirection: "row", gap: 10, justifyContent: "space-between", alignItems: "center", }, rowInline: { flexDirection: "row", alignItems: "center", }, }); //# sourceMappingURL=CometChatIncomingCall.js.map