@cometchat/chat-uikit-react-native
Version:
Ready-to-use Chat UI Components for React Native
197 lines (196 loc) • 9.85 kB
JavaScript
import { CometChat } from "@cometchat/chat-sdk-react-native";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { KeyboardAvoidingView, Platform, View } from "react-native";
import { CometChatList, localize, } from "../shared";
import { CometChatUIEventHandler } from "../shared/events/CometChatUIEventHandler/CometChatUIEventHandler";
import { deepMerge } from "../shared/helper/helperFunctions";
import { Icon } from "../shared/icons/Icon";
import { ErrorEmptyView } from "../shared/views/ErrorEmptyView/ErrorEmptyView";
import { useTheme } from "../theme";
import { Skeleton } from "./Skeleton";
import { CommonUtils } from "../shared/utils/CommonUtils";
import { CometChatTooltipMenu } from "../shared/views/CometChatTooltipMenu";
/**
* CometChatUsers component renders a list of users with support for search,
* selection, and custom empty/error/loading states.
*/
export const CometChatUsers = React.forwardRef((props, ref) => {
const userListenerId = "userStatus_" + new Date().getTime();
const theme = useTheme();
const [hideSearchError, setHideSearchError] = useState(false);
const { usersRequestBuilder, LoadingView, ErrorView, EmptyView, selectionMode = "none", title, addOptions, options, TrailingView, LeadingView, AppBarOptions, hideSearch = false, stickyHeaderVisibility = true, style = {}, onError, onLoad, onEmpty, hideError, hideLoadingState, usersStatusVisibility = true, showBackButton = false, searchKeyword = "", searchRequestBuilder, hideHeader, onSelection, onSubmit, searchPlaceholderText = localize("SEARCH"), ...newProps } = props;
const userRef = useRef(null);
const mergedStyle = deepMerge(theme.userStyles, style);
// ----- Tooltip functionality for users -----
const [tooltipVisible, setTooltipVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const tooltipPosition = useRef({ pageX: 0, pageY: 0 });
const buildMenuItems = (user) => {
if (options)
return options(user);
if (addOptions)
return addOptions(user);
return [];
};
const handleItemLongPress = (user, e) => {
if (props.onItemLongPress) {
props.onItemLongPress(user);
return;
}
const items = buildMenuItems(user);
if (items.length === 0)
return;
if (e && e.nativeEvent) {
tooltipPosition.current = {
pageX: e.nativeEvent.pageX,
pageY: e.nativeEvent.pageY,
};
}
else {
tooltipPosition.current = { pageX: 200, pageY: 100 };
}
setSelectedUser(user);
setTooltipVisible(true);
};
useEffect(() => {
// Listen for changes in user online/offline status.
CometChat.addUserListener(userListenerId, new CometChat.UserListener({
onUserOnline: (onlineUser) => {
if (!onlineUser.getBlockedByMe()) {
userRef.current.updateList(onlineUser);
}
},
onUserOffline: (offlineUser) => {
if (!offlineUser.getBlockedByMe()) {
userRef.current.updateList(offlineUser);
}
},
}));
return () => CometChat.removeUserListener(userListenerId);
}, []);
/**
* Handler for when a user is blocked.
*
* @param {{ user: CometChat.User }} param0 - The user object that is blocked.
*/
const handleccUserBlocked = ({ user }) => {
const clonedUser = CommonUtils.clone(user);
clonedUser.blockedByMe = true;
clonedUser.hasBlockedMe = true;
userRef.current.updateList(clonedUser);
};
/**
* Handler for when a user is unblocked.
*
* @param {{ user: CometChat.User }} param0 - The user object that is unblocked.
*/
const handleccUserUnBlocked = ({ user }) => {
const clonedUser = CommonUtils.clone(user);
clonedUser.blockedByMe = false;
clonedUser.hasBlockedMe = false;
userRef.current.updateList(clonedUser);
CometChat.getUser(clonedUser.getUid()).then((updatedUser) => {
userRef.current.updateList(updatedUser);
});
};
useEffect(() => {
CometChatUIEventHandler.addUserListener(userListenerId, {
ccUserBlocked: (item) => handleccUserBlocked(item),
ccUserUnBlocked: (item) => handleccUserUnBlocked(item),
});
return () => {
CometChatUIEventHandler.removeUserListener(userListenerId);
};
}, []);
/**
* Renders the empty state view when there are no users.
*
* @returns {JSX.Element} The empty state view.
*/
const EmptyStateView = useCallback(() => {
useEffect(() => {
onEmpty?.();
}, []);
return (<KeyboardAvoidingView keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 0} behavior={Platform.select({ ios: "padding", android: "height" })} style={{ flex: 1 }}>
<ErrorEmptyView title={localize("NO_USERS_AVAILABLE")} subTitle={localize("ADD_CONTACTS")} Icon={<Icon name='user-empty-icon' icon={mergedStyle.emptyStateStyle.icon} color={theme.color.neutral300} size={theme.spacing.spacing.s15 << 1} containerStyle={{
marginBottom: theme.spacing.spacing.s5,
}}/>} containerStyle={{
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: "10%",
}} titleStyle={mergedStyle.emptyStateStyle.titleStyle} subTitleStyle={mergedStyle.emptyStateStyle.subTitleStyle}/>
</KeyboardAvoidingView>);
}, [theme, mergedStyle]);
/**
* Renders the error state view when there is an error fetching users.
*
* @returns {JSX.Element} The error state view.
*/
const ErrorStateView = useCallback(() => {
useEffect(() => {
// Hide search when error view is active.
setHideSearchError(true);
}, []);
return (<View style={{ flex: 1 }}>
<ErrorEmptyView title={localize("OOPS")} subTitle={localize("SOMETHING_WENT_WRONG")} tertiaryTitle={localize("WRONG_TEXT_TRY_AGAIN")} Icon={<Icon name='error-state' size={theme.spacing.margin.m15 << 1} containerStyle={{
marginBottom: theme.spacing.margin.m5,
}} icon={mergedStyle.errorStateStyle.icon}/>} containerStyle={{
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingHorizontal: "10%",
}} titleStyle={mergedStyle.errorStateStyle.titleStyle} subTitleStyle={mergedStyle.errorStateStyle.subTitleStyle}/>
</View>);
}, [theme, mergedStyle]);
return (<View style={theme.userStyles.containerStyle}>
<CometChatList hideHeader={hideHeader ?? hideHeader} searchPlaceholderText={searchPlaceholderText} searchRequestBuilder={searchRequestBuilder} hideBackButton={!showBackButton} hideError={hideError} hideSubmitButton={props.hideSubmitButton} AppBarOptions={AppBarOptions} title={title ? title : localize("USERS")} onError={onError} TrailingView={TrailingView} selectionMode={selectionMode} LeadingView={LeadingView} onSelection={onSelection} onSubmit={onSubmit}
// Pass our custom long press handler which shows tooltip if options exist.
onItemLongPress={(user, e) => handleItemLongPress(user, e)} ItemView={props.ItemView} onListFetched={(users) => {
if (users.length === 0) {
onEmpty?.();
}
else {
onLoad?.(users);
}
}} ref={userRef} hideSearch={hideSearch ? hideSearch : hideSearchError} requestBuilder={(usersRequestBuilder && usersRequestBuilder.setSearchKeyword(searchKeyword)) ||
new CometChat.UsersRequestBuilder()
.setLimit(30)
.hideBlockedUsers(false)
.setRoles([])
.friendsOnly(false)
.setStatus("")
.setTags([])
.setUIDs([])
.setSearchKeyword(searchKeyword)} listStyle={mergedStyle} hideStickyHeader={!stickyHeaderVisibility} listItemKey='uid' LoadingView={hideLoadingState
? () => <></> // will not render anything if true
: LoadingView
? LoadingView
: () => <Skeleton style={mergedStyle.skeletonStyle}/>} EmptyView={EmptyView ? EmptyView : () => <EmptyStateView />} ErrorView={ErrorView ? ErrorView : () => <ErrorStateView />} statusIndicatorType={(user) => usersStatusVisibility
? user?.getBlockedByMe()
? "offline"
: user?.getStatus()
: null} {...newProps}/>
{/* Tooltip Menu: shows on long press if options exist and selectionMode is "none" */}
{selectedUser && selectionMode === "none" && tooltipVisible && (<View style={{
position: "absolute",
top: tooltipPosition.current.pageY,
left: tooltipPosition.current.pageX,
zIndex: 9999,
}}>
<CometChatTooltipMenu visible={tooltipVisible} onClose={() => setTooltipVisible(false)} onDismiss={() => setTooltipVisible(false)} event={{
nativeEvent: tooltipPosition.current,
}} menuItems={buildMenuItems(selectedUser).map((menuItem) => ({
text: menuItem.text,
onPress: () => {
menuItem.onPress();
setTooltipVisible(false);
},
textColor: menuItem.textColor,
iconColor: menuItem.iconColor,
disabled: menuItem.disabled,
}))}/>
</View>)}
</View>);
});
//# sourceMappingURL=CometChatUsers.js.map