@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
296 lines • 13.8 kB
JavaScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import { ChatConfigurator, localize } from "../shared";
import { listners } from "./listners";
import { CometChat } from "@cometchat/chat-sdk-react-native";
import { GroupTypeConstants, UserStatusConstants } from "../shared/constants/UIKitConstants";
import { CometChatUIEventHandler } from "../shared/events/CometChatUIEventHandler/CometChatUIEventHandler";
import { deepMerge } from "../shared/helper/helperFunctions";
import { Icon } from "../shared/icons/Icon";
import { CometChatAvatar } from "../shared/views/CometChatAvatar";
import { CometChatStatusIndicator } from "../shared/views/CometChatStatusIndicator";
import { useTheme } from "../theme";
import { CommonUtils } from "../shared/utils/CommonUtils";
/** CometChatMessageHeader renders the header for a conversation. */
export const CometChatMessageHeader = (props) => {
const userStatusListenerId = "user_status_" + new Date().getTime();
const msgTypingListenerId = "message_typing_" + new Date().getTime();
const groupListenerId = "head_group_" + new Date().getTime();
const theme = useTheme();
const { TitleView, SubtitleView = null, AuxiliaryButtonView, user, group, showBackButton = false, onBack, style = {}, ItemView, LeadingView, TrailingView, onError, hideVoiceCallButton = false, hideVideoCallButton = false, usersStatusVisibility = true, } = props;
const [groupObj, setGroupObj] = useState(group);
const [userObj, setUserObj] = useState(user);
const [userStatus, setUserStatus] = useState(user && user.getStatus ? user.getStatus() : "");
const [typingText, setTypingText] = useState("");
const receiverTypeRef = useRef(user ? CometChat.RECEIVER_TYPE.USER : group ? CometChat.RECEIVER_TYPE.GROUP : null);
useEffect(() => {
setGroupObj(group);
}, [group]);
useEffect(() => {
setUserStatus(userObj ? userObj.getStatus() : "");
}, [userObj]);
const messageHeaderStyles = useMemo(() => {
return deepMerge(theme.messageHeaderStyles, style);
}, [theme.messageHeaderStyles, style]);
const translations = {
lastSeen: "Last seen",
minutesAgo: (minutes) => `${minutes} minute${minutes === 1 ? "" : "s"} ago`,
hoursAgo: (hours) => `${hours} hour${hours === 1 ? "" : "s"} ago`,
};
/**
* Returns a formatted last seen string.
*/
function getLastSeenTime(timestamp, translations) {
try {
if (timestamp === null)
return `${translations.lastSeen} Unknown`;
if (String(timestamp).length === 10)
timestamp *= 1000;
const now = new Date();
const lastSeen = new Date(timestamp);
const diffInMillis = now.getTime() - lastSeen.getTime();
const diffInMinutes = Math.floor(diffInMillis / (1000 * 60));
const diffInHours = Math.floor(diffInMillis / (1000 * 60 * 60));
if (diffInMinutes === 0)
return `${translations.lastSeen} ${translations.minutesAgo(1)}`;
if (diffInMinutes < 60)
return `${translations.lastSeen} ${translations.minutesAgo(diffInMinutes)}`;
if (diffInHours < 24)
return `${translations.lastSeen} ${translations.hoursAgo(diffInHours)}`;
const isSameYear = lastSeen.getFullYear() === now.getFullYear();
const dateOptions = {
day: "2-digit",
month: "short",
...(isSameYear ? {} : { year: "numeric" }),
};
const timeOptions = {
hour: "2-digit",
minute: "2-digit",
hour12: true,
};
const formattedDate = lastSeen.toLocaleDateString(undefined, dateOptions);
const formattedTime = lastSeen.toLocaleTimeString(undefined, timeOptions);
if (formattedDate === "Invalid Date" || formattedTime === "Invalid Date")
return "Offline";
return `${translations.lastSeen} ${formattedDate} at ${formattedTime}`;
}
catch (e) {
errorHandler(e);
return "";
}
}
/**
* Renders the back button.
*/
const BackButton = useCallback(() => (<TouchableOpacity style={[messageHeaderStyles.backButtonStyle]} onPress={onBack}>
<Icon name='arrow-back-fill' size={messageHeaderStyles.backButtonIconStyle.width} height={messageHeaderStyles.backButtonIconStyle.height} width={messageHeaderStyles.backButtonIconStyle.width} color={messageHeaderStyles.backButtonIconStyle.tintColor} icon={messageHeaderStyles.backButtonIcon} imageStyle={messageHeaderStyles.backButtonIconStyle}/>
</TouchableOpacity>), [onBack, messageHeaderStyles]);
const statusIndicatorType = useMemo(() => {
if (groupObj?.getType() === GroupTypeConstants.password)
return "protected";
if (groupObj?.getType() === GroupTypeConstants.private)
return "private";
if (userStatus === "online")
return "online";
return "offline";
}, [userStatus, groupObj]);
/**
* Renders the avatar with a status indicator.
*/
const AvatarWithStatusView = useCallback(() => {
try {
return (<View>
<CometChatAvatar style={messageHeaderStyles.avatarStyle} image={userObj
? userObj.getAvatar()
? { uri: userObj.getAvatar() }
: undefined
: groupObj
? groupObj.getIcon()
? { uri: groupObj.getIcon() }
: undefined
: undefined} name={(userObj?.getName() ?? groupObj?.getName())}/>
{groupObj ? (<CometChatStatusIndicator type={statusIndicatorType}/>) : (usersStatusVisibility &&
!(userObj?.getBlockedByMe() || userObj?.getHasBlockedMe()) && (<CometChatStatusIndicator type={statusIndicatorType}/>))}
</View>);
}
catch (e) {
errorHandler(e);
return <></>;
}
}, [userObj, groupObj, statusIndicatorType, messageHeaderStyles]);
/**
* Renders subtitle view content.
*/
const SubtitleViewFnc = useCallback(() => {
try {
if (typingText !== "")
return <Text style={[messageHeaderStyles.typingIndicatorTextStyle]}>{typingText}</Text>;
let subtitle = "";
if (groupObj) {
const count = groupObj?.["membersCount"];
if (count || count === 0) {
subtitle = `${count} ${localize(count === 1 ? "MEMBER" : "MEMBERS")}`;
}
}
if (userObj &&
!(userObj.getBlockedByMe() || userObj.getHasBlockedMe()) &&
usersStatusVisibility &&
userStatus) {
subtitle =
userStatus === UserStatusConstants.online
? localize("ONLINE")
: getLastSeenTime(userObj.getLastActiveAt(), translations);
}
if (subtitle) {
return <Text style={[messageHeaderStyles.subtitleTextStyle]}>{subtitle}</Text>;
}
return <></>;
}
catch (error) {
errorHandler(error);
return <></>;
}
}, [userObj, groupObj, messageHeaderStyles, usersStatusVisibility, userStatus]);
/**
* Error handler to call onError with a proper CometChatException.
*/
const errorHandler = (error) => {
if (error instanceof CometChat.CometChatException) {
onError && onError(error);
}
else if (error instanceof Error) {
onError &&
onError(new CometChat.CometChatException({
code: "ERR_SYSTEM",
details: error.stack,
message: error.message,
}));
}
};
const handleUserStatus = (userDetails) => {
if (userDetails.getUid() === userObj?.getUid())
setUserStatus(userDetails.getStatus());
};
const msgTypingIndicator = (typist, status) => {
if (receiverTypeRef.current === CometChat.RECEIVER_TYPE.GROUP &&
receiverTypeRef.current === typist.getReceiverType() &&
groupObj?.getGuid() === typist.getReceiverId()) {
setTypingText(status === "typing" ? `${typist.getSender().getName()}: ${localize("IS_TYPING")}` : "");
}
else if (receiverTypeRef.current === CometChat.RECEIVER_TYPE.USER &&
receiverTypeRef.current === typist.getReceiverType() &&
userObj?.getUid() === typist.getSender().getUid()
&& !(userObj.getBlockedByMe() || userObj.getHasBlockedMe())) {
setTypingText(status === "typing" ? localize("TYPING") : "");
}
};
const handleGroupListener = (groupDetails) => {
if (groupDetails?.getGuid() === groupObj?.getGuid() && groupDetails.getMembersCount()) {
setGroupObj(CommonUtils.clone(groupDetails));
}
};
useEffect(() => {
try {
if (userObj) {
listners.addListener.userListener({ userStatusListenerId, handleUserStatus });
receiverTypeRef.current = CometChat.RECEIVER_TYPE.USER;
CometChatUIEventHandler.addUserListener(userStatusListenerId, {
ccUserBlocked: (item) => handleccUserBlocked(item),
ccUserUnBlocked: (item) => handleccUserUnBlocked(item),
});
}
if (groupObj) {
listners.addListener.groupListener({ groupListenerId, handleGroupListener });
receiverTypeRef.current = CometChat.RECEIVER_TYPE.GROUP;
}
listners.addListener.messageListener({ msgTypingListenerId, msgTypingIndicator });
}
catch (error) {
errorHandler(error);
}
return () => {
try {
if (groupObj)
listners.removeListner.removeGroupListener({ groupListenerId });
if (userObj) {
listners.removeListner.removeUserListener({ userStatusListenerId });
CometChatUIEventHandler.removeUserListener(userStatusListenerId);
}
listners.removeListner.removeMessageListener({ msgTypingListenerId });
}
catch (cleanupError) {
errorHandler(cleanupError);
}
};
}, [userObj]);
const handleccUserBlocked = ({ user: blockedUser }) => {
if (userObj && userObj.getUid() === blockedUser.getUid()) {
const tempUser = CommonUtils.clone(userObj);
tempUser.setBlockedByMe(true);
setUserObj(tempUser);
}
};
const handleccUserUnBlocked = ({ user: unBlockedUser }) => {
if (userObj && userObj.getUid() === unBlockedUser.getUid()) {
setUserObj(unBlockedUser);
}
};
const handleGroupMemberKicked = ({ kickedFrom }) => {
setGroupObj(CommonUtils.clone(kickedFrom));
};
const handleGroupMemberBanned = ({ kickedFrom }) => {
setGroupObj(CommonUtils.clone(kickedFrom));
};
const handleGroupMemberAdded = ({ userAddedIn }) => {
setGroupObj(CommonUtils.clone(userAddedIn));
};
const handleOwnershipChanged = ({ group }) => {
setGroupObj(CommonUtils.clone(group));
};
useEffect(() => {
try {
CometChatUIEventHandler.addGroupListener(groupListenerId, {
ccGroupMemberKicked: (item) => handleGroupMemberKicked(item),
ccGroupMemberBanned: (item) => handleGroupMemberBanned(item),
ccGroupMemberAdded: (item) => handleGroupMemberAdded(item),
ccOwnershipChanged: (item) => handleOwnershipChanged(item),
});
}
catch (e) {
errorHandler(e);
}
return () => {
try {
CometChatUIEventHandler.removeGroupListener(groupListenerId);
}
catch (e) {
errorHandler(e);
}
};
}, []);
return (<>
{ItemView ? (ItemView({ user: userObj, group })) : (<View style={[messageHeaderStyles.containerStyle]}>
{showBackButton === true && <BackButton />}
{LeadingView ? LeadingView({ user: userObj, group }) : <AvatarWithStatusView />}
<View style={{ flex: 1, justifyContent: "center" }}>
{TitleView ? (TitleView({ user: userObj, group })) : (<Text numberOfLines={1} ellipsizeMode='tail' style={[messageHeaderStyles.titleTextStyle]}>
{userObj ? userObj.getName() : groupObj ? groupObj.getName() : ""}
</Text>)}
{SubtitleView ? SubtitleView({ user: userObj, group }) : <SubtitleViewFnc />}
</View>
<View style={{ flex: 1, flexDirection: "row" }}>
<View style={{ marginLeft: "auto", flexDirection: "row" }}>
{AuxiliaryButtonView
? AuxiliaryButtonView({ user: userObj, group })
: ChatConfigurator.getDataSource().getAuxiliaryHeaderAppbarOptions(userObj, group, {
callButtonStyle: messageHeaderStyles.callButtonStyle,
hideVideoCallButton,
hideVoiceCallButton,
})}
{TrailingView && <>{TrailingView({ user: userObj, group })}</>}
</View>
</View>
</View>)}
</>);
};
//# sourceMappingURL=CometChatMessageHeader.js.map