@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
236 lines (232 loc) • 9.79 kB
JavaScript
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