@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
321 lines (319 loc) • 16.3 kB
JavaScript
import { CometChat } from "@cometchat/chat-sdk-react-native";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, FlatList, Platform, ScrollView, Text, TouchableOpacity, View, } from "react-native";
import { localize } from "../../resources";
import { CommonUtils } from "../../utils/CommonUtils";
import { CometChatListItem } from "../CometChatListItem";
import { useTheme } from "../../../theme";
import { Skeleton } from "../../../CometChatConversations";
import { ErrorEmptyView } from "../ErrorEmptyView/ErrorEmptyView";
import { Icon } from "../../icons/Icon";
import { CometChatUIEventHandler, MessageEvents } from "../../events";
import { messageStatus } from "../../utils/CometChatMessageHelper";
export const CometChatReactionList = (props) => {
const { message, onPress, reactionsRequestBuilder, selectedReaction, ErrorStateView, errorStateText, LoadingStateView, } = props;
const theme = useTheme();
const reactionListStyleFromTheme = theme.reactionListStyles.reactionListItemStyle;
const tabStyleFromTheme = theme.reactionListStyles.tabStyle;
const [messageReactions, setMessageReactions] = useState(message?.getReactions() || []);
const [currentSelectedReaction, setCurrentSelectedReaction] = useState(selectedReaction || "All");
const [reactionList, setReactionList] = useState();
const [state, setState] = useState("loading");
const loggedInUser = useRef(undefined);
const newMessageObj = useRef(CommonUtils.clone(message));
const requestBuilderMap = useRef({});
const reactionListMap = useRef({});
const listRef = useRef(null);
const [messageObject, setMessageObject] = useState(message);
useEffect(() => {
CometChat.getLoggedinUser()
.then((user) => (loggedInUser.current = user))
.catch((rej) => {
loggedInUser.current = null;
// onError && onError(rej);
});
showReactions(true);
let newMessageReactions = messageObject?.getReactions() || [];
_setAllReactions(newMessageReactions);
}, []);
useEffect(() => {
showReactions();
}, [currentSelectedReaction]);
const _setAllReactions = (_messageReactions) => {
let totalCount = _messageReactions.reduce((acc, curr) => acc + curr.count, 0);
const allReactionCountObject = new CometChat.ReactionCount("All", totalCount, false);
setMessageReactions([allReactionCountObject, ..._messageReactions]);
};
const showReactions = async (firstFetch) => {
const requestBuilder = getRequestBuilder(currentSelectedReaction);
const list = await getReactionList(requestBuilder, currentSelectedReaction);
if (firstFetch && currentSelectedReaction !== "All") {
await getReactionList(getRequestBuilder("All"), "All");
}
setReactionList(list ?? []);
};
const getRequestBuilder = (reaction) => {
let requestBuilder;
if (requestBuilderMap.current[reaction]) {
return requestBuilderMap.current[reaction];
}
requestBuilder = reactionsRequestBuilder || new CometChat.ReactionsRequestBuilder().setLimit(10);
requestBuilder.setMessageId(messageObject?.getId());
if (reaction !== "All") {
requestBuilder.setReaction(reaction);
}
const request = requestBuilder.build();
requestBuilderMap.current[reaction] = request;
return request;
};
const getReactionList = async (requestBuilder, reaction) => {
setState("loading");
if (reactionListMap.current[reaction]) {
setState("done");
let list = reactionListMap.current[reaction];
return list;
}
try {
const list = await requestBuilder.fetchNext();
reactionListMap.current[reaction] = list;
setState("done");
return list;
}
catch (error) {
//console.log("error while fetching reactions", error);
if (error?.code === "REQUEST_IN_PROGRESS")
return null;
setState("error");
return [];
}
};
const fetchNext = async () => {
try {
const requestBuilder = getRequestBuilder(currentSelectedReaction);
if (reactionListMap.current[currentSelectedReaction]?.length === 0) {
return;
}
else {
const newList = await requestBuilder.fetchNext();
reactionListMap.current[currentSelectedReaction] = [
...(reactionListMap.current?.[currentSelectedReaction] || []),
...newList,
];
setReactionList(reactionListMap.current[currentSelectedReaction]);
}
setState("done");
}
catch (error) {
//console.log("error while fetching next reactions", error);
if (error?.code === "REQUEST_IN_PROGRESS")
return;
setState("error");
}
};
const reactToMessage = useCallback((emoji) => {
const msgObj = CommonUtils.clone(messageObject);
const messageId = msgObj?.getId();
const reactions = msgObj?.getReactions() || [];
const emojiObject = reactions?.find((reaction) => {
return reaction?.reaction == emoji;
});
if (emojiObject && emojiObject?.getReactedByMe()) {
const updatedReactions = [];
reactions.forEach((reaction) => {
if (reaction?.getReaction() == emoji) {
if (reaction?.getCount() === 1) {
return;
}
else {
reaction.setCount(reaction?.getCount() - 1);
reaction.setReactedByMe(false);
updatedReactions.push(reaction);
}
}
else {
updatedReactions.push(reaction);
}
});
const newMessageObj = CommonUtils.clone(msgObj);
newMessageObj.setReactions(updatedReactions);
CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {
message: newMessageObj,
status: messageStatus.success,
});
CometChat.removeReaction(messageId, emoji)
.then((message) => { })
.catch((error) => {
CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {
message: msgObj,
status: messageStatus.success,
});
//console.log(error);
});
}
else {
const updatedReactions = [];
const reactionAvailable = reactions.find((reaction) => {
return reaction?.getReaction() == emoji;
});
reactions.forEach((reaction) => {
if (reaction?.getReaction() == emoji) {
reaction.setCount(reaction?.getCount() + 1);
reaction.setReactedByMe(true);
updatedReactions.push(reaction);
}
else {
updatedReactions.push(reaction);
}
});
if (!reactionAvailable) {
const react = new CometChat.ReactionCount(emoji, 1, true);
updatedReactions.push(react);
}
const newMessageObj = CommonUtils.clone(msgObj);
newMessageObj.setReactions(updatedReactions);
CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {
message: newMessageObj,
status: messageStatus.success,
});
CometChat.addReaction(messageId, emoji)
.then((response) => { })
.catch((error) => {
//console.log(error);
CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, {
message: msgObj,
status: messageStatus.success,
});
});
}
}, [messageObject]);
const removeReaction = (reactionObj) => {
let reactedByMe = loggedInUser.current.getUid() === reactionObj?.getReactedBy()?.getUid();
if (onPress) {
onPress(reactionObj, newMessageObj.current);
return;
}
if (reactedByMe) {
reactToMessage(reactionObj?.getReaction());
let newReactionList = reactionList
? [...reactionList]?.filter((reaction) => reaction?.getReactionId() !== reactionObj?.getReactionId())
: [];
setReactionList(newReactionList);
if (currentSelectedReaction === "All") {
reactionListMap.current[currentSelectedReaction] = [...newReactionList];
reactionListMap.current[reactionObj?.getReaction()] = reactionListMap.current[reactionObj?.getReaction()]?.filter((reaction) => reaction?.getReactionId() !== reactionObj?.getReactionId());
}
else {
reactionListMap.current[currentSelectedReaction] = [...newReactionList];
reactionListMap.current["All"] = reactionListMap.current["All"]?.filter((reaction) => reaction?.getReactionId() !== reactionObj?.getReactionId());
}
let newMessageReactions = [...messageReactions];
let messageReactionIndex = newMessageReactions.findIndex((reaction) => reaction?.getReaction() === reactionObj?.getReaction());
if (messageReactionIndex > -1) {
if (newMessageReactions[messageReactionIndex]?.getCount() > 1) {
newMessageReactions[messageReactionIndex].setCount(newMessageReactions[messageReactionIndex].getCount() - 1);
newMessageReactions[messageReactionIndex].setReactedByMe(false);
newMessageReactions.shift();
_setAllReactions(newMessageReactions);
}
else {
newMessageReactions.splice(messageReactionIndex, 1);
newMessageReactions.shift();
_setAllReactions(newMessageReactions);
setCurrentSelectedReaction("All");
}
}
newMessageObj.current.setReactions(newMessageReactions);
setMessageObject(newMessageObj.current);
}
};
const subtitleView = useCallback((item) => {
let reactedByMe = loggedInUser.current.getUid() === item?.reactedBy?.uid;
return reactedByMe ? (<Text style={reactionListStyleFromTheme.subtitleStyle}>{localize("TAP_TO_REMOVE")}</Text>) : null;
}, []);
const _render = ({ item, index }) => {
function getName() {
let reactedByMe = loggedInUser.current.getUid() === item?.reactedBy?.uid;
return reactedByMe ? localize("YOU") : item?.reactedBy?.name;
}
return (<>
<CometChatListItem id={item?.id || index} title={getName()} titleStyle={reactionListStyleFromTheme.titleStyle} avatarStyle={reactionListStyleFromTheme.avatarStyle} containerStyle={reactionListStyleFromTheme.containerStyle} avatarName={item?.reactedBy?.name} avatarURL={item?.reactedBy?.avatar ? { uri: item?.reactedBy?.avatar } : undefined} SubtitleView={subtitleView(item)} TrailingView={<Text style={[
reactionListStyleFromTheme.emojiStyle,
//toDoM: emojis look washed out on Android without a color
Platform.OS === "android" ? { color: theme.color.primary } : {},
]}>
{item?.reaction}
</Text>} onPress={(id) => removeReaction(item)} titleSubtitleContainerStyle={reactionListStyleFromTheme.titleContainerStyle}/>
</>);
};
const ErrorView = () => {
if (ErrorStateView)
return <ErrorStateView />;
return (<ErrorEmptyView title={errorStateText ?? "Oops!"} subTitle={localize("SOMETHING_WENT_WRONG")} tertiaryTitle={localize("WRONG_TEXT_TRY_AGAIN")} Icon={<Icon name='error-state' size={theme.spacing.spacing.s15 * 2} icon={theme.reactionListStyles.errorStateStyle?.icon} height={theme.reactionListStyles.errorStateStyle?.iconStyle?.height} width={theme.reactionListStyles.errorStateStyle?.iconStyle?.width} imageStyle={theme.reactionListStyles.errorStateStyle?.iconStyle} containerStyle={theme.reactionListStyles.errorStateStyle?.iconContainerStyle}/>} containerStyle={theme.reactionListStyles.errorStateStyle?.containerStyle} titleStyle={theme.reactionListStyles.errorStateStyle?.titleStyle} subTitleStyle={theme.reactionListStyles.errorStateStyle?.subtitleStyle}/>);
};
const LoadingView = () => {
if (LoadingStateView)
return <LoadingStateView />;
return (<View style={{
flex: 1,
}}>
<Skeleton />
</View>);
};
return (<View style={{ flex: 1 }}>
{/* Slider for reactions */}
{state != "error" && (<View>
<ScrollView showsHorizontalScrollIndicator={false} horizontal={true} style={tabStyleFromTheme.containerStyle}>
{messageReactions?.length > 0 &&
messageReactions.map((reactionObject, index) => {
return (<TouchableOpacity style={currentSelectedReaction === reactionObject?.getReaction()
? tabStyleFromTheme.selectedItemStyle
: tabStyleFromTheme.itemStyle} key={index} onPress={() => setCurrentSelectedReaction(reactionObject?.getReaction())}>
<Text style={currentSelectedReaction === reactionObject?.getReaction()
? reactionObject?.getReaction() === "All"
? tabStyleFromTheme.selectedItemTextStyle
: tabStyleFromTheme.selectedItemEmojiStyle
: reactionObject?.getReaction() === "All"
? tabStyleFromTheme.itemTextStyle
: tabStyleFromTheme.itemEmojiStyle}>
{reactionObject?.getReaction() === "All"
? localize("ALL")
: reactionObject?.getReaction()}
</Text>
<Text style={currentSelectedReaction === reactionObject?.getReaction()
? tabStyleFromTheme.selectedItemTextStyle
: tabStyleFromTheme.itemTextStyle}>
{reactionObject?.getCount()}
</Text>
</TouchableOpacity>);
})}
</ScrollView>
<View style={{
height: 1,
backgroundColor: theme.color.borderLight,
}}/>
</View>)}
{state === "error" ? (<ErrorView />) : state === "loading" ? (<LoadingView />) : (<View style={{ flex: 1, marginBottom: 20 }}>
<FlatList data={reactionList} ref={listRef} keyExtractor={(item, index) => index.toString()} style={{
flex: 1,
}} contentContainerStyle={{
paddingHorizontal: 5,
}} renderItem={_render} onMomentumScrollEnd={(event) => {
const contentOffsetY = event.nativeEvent.contentOffset.y; // The current scroll position
const contentHeight = event.nativeEvent.contentSize.height; // Total height of the content
const layoutHeight = event.nativeEvent.layoutMeasurement.height; // Height of the visible area
if (contentOffsetY + layoutHeight >= contentHeight - 10) {
setState("fetchNextOnScroll");
fetchNext();
}
}}/>
{state == "fetchNextOnScroll" && (<ActivityIndicator color={theme.color.primary} size={"large"} style={{
position: "absolute",
flex: 1,
alignSelf: "center",
paddingHorizontal: 5,
bottom: "50%",
}}/>)}
</View>)}
</View>);
};
//# sourceMappingURL=CometChatReactionList.js.map