UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

163 lines 8.73 kB
import React, { useRef } from "react"; import { ActivityIndicator, FlatList, Image, Text, TouchableOpacity, View } from "react-native"; import { Icon } from "../../../shared/icons/Icon"; import { localize } from "../../../shared/resources/CometChatLocalize"; import { ErrorEmptyView } from "../../../shared/views/ErrorEmptyView/ErrorEmptyView"; import { useTheme } from "../../../theme"; import { ExtensionConstants } from "../../ExtensionConstants"; import { Hooks } from "./hooks"; import { Skeleton } from "./Skeleton"; import { Styles } from "./style"; /** * StickerItem Component * Renders individual sticker items within the sticker keyboard. * Handles loading state, error state, and placeholder rendering. */ const StickerItem = ({ stickerItem, onPress, theme }) => { // State to manage loading status of the sticker image const [isLoading, setIsLoading] = React.useState(true); // State to manage error status if the image fails to load const [hasError, setHasError] = React.useState(false); // If stickerItem is null, render an empty placeholder to maintain grid structure if (!stickerItem) { return <View style={Styles.stickerItemStyle}/>; } return (<TouchableOpacity style={Styles.stickerItemStyle} onPress={() => onPress(stickerItem)} accessibilityRole='button'> {hasError ? ( // If there was an error loading the sticker, display a default icon <Image source={require("../../../../src/theme/default/resources/icons/Base_Icon.png")} style={Styles.stickerImageStyle} resizeMode='contain'/>) : ( // Otherwise, display the sticker image <Image source={{ uri: stickerItem?.stickerUrl }} style={Styles.stickerImageStyle} onLoad={() => setIsLoading(false)} // Update loading state when image loads onError={() => { setIsLoading(false); setHasError(true); // Update error state if image fails to load }} resizeMode='contain'/>)} {isLoading && !hasError && ( // Show a loading indicator while the image is loading <View style={Styles.activityIndicatorWrapper}> <ActivityIndicator size='small' color={theme.color.primary}/> </View>)} </TouchableOpacity>); }; /** * CometChatStickerKeyboard Component * Fetches stickers from the Stickers extension and displays them in a keyboard layout. */ export const CometChatStickerKeyboard = (props) => { const [stickerList, setStickerList] = React.useState([]); const [stickerSet, setStickerSet] = React.useState({}); const [activeStickerList, setActiveStickerList] = React.useState([]); const [activeStickerSetName, setActiveStickerSetName] = React.useState(undefined); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const theme = useTheme(); const flatListRef = useRef(null); /** * Function to handle sending a sticker message * @param stickerItem - The selected sticker item */ const sendStickerMessage = (stickerItem) => { if (props?.onPress) { // Invoke the onPress callback with the sticker message props.onPress({ ...stickerItem, sticker_url: stickerItem?.stickerUrl, sticker_name: stickerItem?.stickerSetName, }); } }; /** * Function to handle the selection of a sticker set * @param sectionItem - The name of the selected sticker set */ const onStickerSetClicked = (sectionItem) => { setActiveStickerList(stickerSet[sectionItem]); setActiveStickerSetName(sectionItem); // Scroll the sticker list to the top when a new set is selected if (flatListRef.current) { flatListRef.current.scrollToOffset({ offset: 0, animated: false }); } }; /** * Function to render the list of stickers based on the current state */ const getStickerList = () => { if (error) { return (<View style={[Styles.stickerContainer]}> <View style={Styles.stickerMsgStyle}> <Text style={[ theme.typography.body.regular, { color: theme.color.textTertiary, textAlign: "center" }, ]}> {localize("SOMETHING_WENT_WRONG")} </Text> </View> </View>); } // If data is still loading, display the skeleton loader if (loading) { return (<View style={Styles.skeletonContainer}> <Skeleton /> </View>); } // If there are no stickers available, display an empty state view if (stickerList.length === 0) { return (<ErrorEmptyView title='No Stickers Available' subTitle='You don’t have any stickers yet.' Icon={<Icon containerStyle={{ marginBottom: 10 }} name='base-icon' width={theme.spacing.spacing.s15} height={theme.spacing.spacing.s15}/>} containerStyle={[Styles.stickerContainer, Styles.emptyContainer]} titleStyle={[theme.typography.heading4.bold, { color: theme.color.textPrimary }]} subTitleStyle={[theme.typography.body.regular, { color: theme.color.textSecondary }]}/>); } // If sticker sets are available, render them in a grid if (stickerSet && Object.keys(stickerSet).length) { const numberOfStickers = activeStickerList.length; const numberOfPlaceholders = numberOfStickers % 3 === 0 ? 0 : 3 - (numberOfStickers % 3); const paddedStickerList = [...activeStickerList]; for (let i = 0; i < numberOfPlaceholders; i++) { paddedStickerList.push(null); } return (<FlatList ref={flatListRef} data={paddedStickerList} renderItem={({ item }) => (<StickerItem stickerItem={item} onPress={sendStickerMessage} theme={theme}/>)} keyExtractor={(item, index) => (item ? `${item.id}-${index}` : `placeholder-${index}`)} numColumns={3} contentContainerStyle={{ paddingHorizontal: theme.spacing.spacing.s5, }} // Adds horizontal padding showsVerticalScrollIndicator={false}/>); } return null; }; // Invoke custom hooks to fetch stickers and manage state Hooks(props, stickerList, stickerSet, activeStickerSetName, setStickerList, setStickerSet, setActiveStickerList, setActiveStickerSetName, setLoading, setError); return (<View style={[Styles.stickerWrapperStyle, { backgroundColor: theme.color.background1 }]}> {/* Header displaying the active sticker set name or a default label */} <View style={{ padding: 10 }}> <Text style={[ theme?.typography?.body.regular, { color: theme.color.textTertiary, paddingLeft: 10 }, ]}> {activeStickerSetName} </Text> </View> {/* Main content area for stickers and category selector */} <View style={Styles.stickerContentWrapper}> {/* Render the list of stickers based on current state */} {getStickerList()} {/* If there's no error, display the sticker set category selector */} {!error && (<View style={[Styles.categorySelectorWrapper, { borderTopColor: theme.color.borderLight }]}> <FlatList data={Object.keys(stickerSet)} // List of sticker set names renderItem={({ item }) => { // Retrieve the thumbnail for the sticker set (first sticker's URL) const stickerSetThumbnail = stickerSet[item][0][ExtensionConstants.stickerUrl]; // Determine if this sticker set is currently active const isActive = item === activeStickerSetName; return (<TouchableOpacity style={Styles.sectionListItemStyle} onPress={() => onStickerSetClicked(item)} accessibilityLabel={`Sticker category ${item}`} accessibilityRole='button'> {isActive && ( // Highlight the active sticker set <View style={[ Styles.activeCategoryBackground, { backgroundColor: theme.color.primary }, ]}/>)} {/* Display the sticker set thumbnail */} <Image source={{ uri: stickerSetThumbnail }} style={Styles.stickerCategoryImageStyle} resizeMode='contain'/> </TouchableOpacity>); }} keyExtractor={(item, index) => `${item}-${index}`} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingHorizontal: theme.spacing.spacing.s3, }}/> </View>)} </View> </View>); }; //# sourceMappingURL=CometChatStickerKeyboard.js.map