UNPKG

react-native-chating-ui-kit

Version:

CometChat React Native UI Kit is a collection of custom UI Components designed to build text , chat and calling features in your application. The UI Kit is developed to keep developers in mind and aims to reduce development efforts significantly

537 lines 24.2 kB
import React, { useContext, useEffect } from 'react'; import { View, Image, TouchableOpacity, NativeModules, Platform, KeyboardAvoidingView, Modal, } from 'react-native'; import { Style } from './styles'; import { localize, CometChatLiveReactions, CometChatBottomSheet, CometChatMessagePreview, MessagePreviewStyle, CometChatActionSheet, CometChatContext, } from '../shared'; import { CheckPropertyExists } from '../shared/helper/functions'; //@ts-ignore import { CometChat } from '@cometchat-pro/react-native-chat'; import { getUnixTimestamp, messageStatus, } from '../shared/utils/CometChatMessageHelper'; import { CometChatSoundManager, ChatConfigurator } from '../shared'; import { ConversationOptionConstants, MessageTypeConstants, MetadataConstants, ReceiverTypeConstants, ViewAlignment, } from '../shared/constants/UIKitConstants'; import { ICONS } from './resources'; import { CometChatMessageInput } from '../shared'; import { MessageEvents } from '../shared/events'; import { CometChatUIEventHandler } from '../shared/events/CometChatUIEventHandler/CometChatUIEventHandler'; const { FileManager } = NativeModules; const uiEventListenerShow = 'uiEvent_show_' + new Date().getTime(); const uiEventListenerHide = 'uiEvent_hide_' + new Date().getTime(); const MessagePreviewTray = (props) => { const { shouldShow = false, text = '', onClose = () => { } } = props; return shouldShow ? (<CometChatMessagePreview style={new MessagePreviewStyle({ width: 'auto' })} messagePreviewTitle={localize('EDIT_MESSAGE')} messagePreviewSubtitle={text} closeIconURL={ICONS.CLOSE} onCloseClick={onClose}/>) : null; }; const ImageButton = (props) => { const { image, onClick, buttonStyle, imageStyle } = props; return (<TouchableOpacity onPress={onClick} style={buttonStyle}> <Image source={image} style={[{ height: 24, width: 24 }, imageStyle]}/> </TouchableOpacity>); }; const AttachIconButton = (props) => { const { icon, show = false, onClick = () => { }, style, theme } = props; if (show) { return (<ImageButton image={icon} imageStyle={[Style.imageStyle, style]} onClick={onClick}/>); } else { return <View style={[Style.imageStyle, style]}/>; } }; const EmojiBoard = (props) => { const { shouldShow = false, onClose = () => { }, emojiSelection = (emoji) => { }, ...otherProps } = props; return shouldShow ? (<CometChatBottomSheet onClose={onClose}> {/* <CometChatEmojiKeyboard onClick={emojiSelection} {...otherProps} /> */} </CometChatBottomSheet>) : null; }; const ActionSheetBoard = (props) => { const { shouldShow = false, onClose = () => { }, options = [], cometChatBottomSheetStyle = {}, sheetRef, ...otherProps } = props; return shouldShow ? (<CometChatBottomSheet ref={sheetRef} onClose={onClose} style={cometChatBottomSheetStyle}> <CometChatActionSheet actions={options} {...otherProps} onPress={props.onClick}/> </CometChatBottomSheet>) : null; }; const LiveReaction = (props) => { const { show = false, reaction } = props; if (show) { return <CometChatLiveReactions reaction={reaction}/>; } return null; }; export const CometChatMessageComposer = React.forwardRef((props, ref) => { const editMessageListenerID = 'editMessageListener_' + new Date().getTime(); const UiEventListenerID = 'UiEventListener_' + new Date().getTime(); const { theme } = useContext(CometChatContext); const { id, user, group, disableSoundForMessages, customSoundForMessage, disableTypingEvents, text, placeHolderText, HeaderView, FooterView, onChangeText, maxHeight, attachmentIcon, attachmentOptions, SecondaryButtonView, AuxiliaryButtonView, auxiliaryButtonsAlignment, SendButtonView, parentMessageId, hideLiveReaction, liveReactionIcon, messageComposerStyle, onSendButtonPress, onError, } = props; const defaultAttachmentOptions = ChatConfigurator.dataSource.getAttachmentOptions(user, group, id); const composerIdMap = new Map().set('parentMessageId', parentMessageId); const defaultAuxiliaryButtonOptions = ChatConfigurator.dataSource.getAuxiliaryOptions(user, group, composerIdMap); const loggedInUser = React.useRef({}); const chatWith = React.useRef(null); const chatWithId = React.useRef(null); const messageInputRef = React.useRef(null); const chatRef = React.useRef(chatWith); const [selectionPosition, setSelectionPosition] = React.useState({}); const [inputMessage, setInputMessage] = React.useState(text || ''); const [showReaction, setShowReaction] = React.useState(false); const [showEmojiboard, setShowEmojiboard] = React.useState(false); const [showActionSheet, setShowActionSheet] = React.useState(false); const [actionSheetItems, setActionSheetItems] = React.useState([]); const [messagePreview, setMessagePreview] = React.useState(null); const [CustomView, setCustomView] = React.useState(null); const [CustomViewHeader, setCustomViewHeader] = React.useState(null); const [CustomViewFooter, setCustomViewFooter] = React.useState(null); const [isVisible, setIsVisible] = React.useState(false); const bottomSheetRef = React.useRef(null); let isTyping = null; /** * Event callback */ React.useImperativeHandle(ref, () => ({ previewMessageForEdit: previewMessage, })); const previewMessage = ({ message, status }) => { if (status === messageStatus.inprogress) { setMessagePreview({ message: message, mode: ConversationOptionConstants.edit, }); setInputMessage(message.text ?? ''); messageInputRef.current.focus(); } }; const fileInputHandler = async (fileType) => { if (fileType === MessageTypeConstants.takePhoto) FileManager.openCamera(fileType, async (cameraImage) => { if (CheckPropertyExists(cameraImage, 'error')) { return; } const { name, uri, type } = cameraImage; let file = { name, type, uri, }; sendMediaMessage(chatWithId.current, file, MessageTypeConstants.image, chatWith.current); }); else FileManager.openFileChooser(fileType, async (fileInfo) => { if (CheckPropertyExists(fileInfo, 'error')) { return; } let { name, uri, type } = fileInfo; let file = { name, type, uri, }; sendMediaMessage(chatWithId.current, file, fileType, chatWith.current); }); }; const playAudio = () => { if (customSoundForMessage) { CometChatSoundManager.play(CometChatSoundManager.SoundOutput.outgoingMessage, customSoundForMessage); } else { CometChatSoundManager.play(CometChatSoundManager.SoundOutput.outgoingMessage); } }; const textChangeHandler = (txt) => { onChangeText && onChangeText(txt); startTyping(); setInputMessage(txt); }; const liveReactionHandler = () => { if (hideLiveReaction) return; if (!showReaction) { setShowReaction(true); //send reaction event const data = { type: MetadataConstants.liveReaction, reaction: liveReactionIcon, }; /*** send transient message */ let transientMessage = new CometChat.TransientMessage(chatWithId.current, chatWith.current, data); CometChat.sendTransientMessage(transientMessage); setTimeout(() => { setShowReaction(false); CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageLiveReaction, { liveReactionIcon }); }, 1500); } }; const clearInputBox = () => { setInputMessage(''); }; const sendTextMessage = () => { //ignore sending new message if (messagePreview != null) { editMessage(messagePreview.message); return; } let textMessage = new CometChat.TextMessage(chatWithId.current, inputMessage, chatWith.current); textMessage.setSender(loggedInUser.current); textMessage.setReceiver(chatWith.current); textMessage.setText(inputMessage); textMessage.setMuid(String(getUnixTimestamp())); parentMessageId && textMessage.setParentMessageId(parentMessageId); onSendButtonPress && onSendButtonPress(textMessage); if (inputMessage.trim().length == 0) { return; } CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, { message: textMessage, status: messageStatus.inprogress, }); if (!disableSoundForMessages) playAudio(); clearInputBox(); CometChat.sendMessage(textMessage) .then((message) => { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, { message: message, status: messageStatus.success, }); }) .catch((error) => { onError && onError(error); clearInputBox(); }); }; /** edit message */ const editMessage = (message) => { endTyping(null, null); let messageText = inputMessage.trim(); let textMessage = new CometChat.TextMessage(chatWithId.current, messageText, chatWith.current); textMessage.setId(message.id); parentMessageId && textMessage.setParentMessageId(parentMessageId); setInputMessage(''); messageInputRef.current.textContent = ''; if (!disableSoundForMessages) playAudio(); setMessagePreview(null); CometChat.editMessage(textMessage) .then((editedMessage) => { setInputMessage(''); CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageEdited, { message: editedMessage, status: messageStatus.success, }); }) .catch((error) => { onError && onError(error); }); }; /** send media message */ const sendMediaMessage = (receiverId, messageInput, messageType, receiverType) => { setShowActionSheet(false); let mediaMessage = new CometChat.MediaMessage(receiverId, messageInput, messageType, receiverType); mediaMessage.setSender(loggedInUser.current); mediaMessage.setReceiver(receiverType); mediaMessage.setType(messageType); mediaMessage['_composedAt'] = Date.now(); mediaMessage['_id'] = '_' + Math.random().toString(36).substr(2, 9); mediaMessage.setId(mediaMessage['_id']); mediaMessage.setMuid(String(getUnixTimestamp())); mediaMessage.setData({ type: messageType, category: CometChat.CATEGORY_MESSAGE, name: messageInput['name'], file: messageInput, url: messageInput['uri'], sender: loggedInUser.current, }); parentMessageId && mediaMessage.setParentMessageId(parentMessageId); let localMessage = new CometChat.MediaMessage(receiverId, messageInput, messageType, receiverType); localMessage.setSender(loggedInUser.current); localMessage.setReceiver(receiverType); localMessage.setType(messageType); localMessage['_composedAt'] = Date.now(); localMessage['_id'] = '_' + Math.random().toString(36).substr(2, 9); localMessage.setId(localMessage['_id']); localMessage.setMuid(String(getUnixTimestamp())); localMessage.setData({ type: messageType, category: CometChat.CATEGORY_MESSAGE, name: messageInput['name'], file: messageInput, url: messageInput['uri'], sender: loggedInUser.current, }); parentMessageId && localMessage.setParentMessageId(parentMessageId); localMessage.setData({ type: messageType, category: CometChat.CATEGORY_MESSAGE, name: messageInput['name'], file: messageInput, url: messageInput['uri'], sender: loggedInUser.current, attachments: [messageInput], }); CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, { message: localMessage, status: messageStatus.inprogress, }); if (!disableSoundForMessages) playAudio(); CometChat.sendMediaMessage(mediaMessage) .then((message) => { CometChatUIEventHandler.emitMessageEvent(MessageEvents.ccMessageSent, { message: message, status: messageStatus.success, }); }) .catch((error) => { onError && onError(error); console.log('media message sent error', error); }); }; const startTyping = (endTypingTimeout, typingMetadata) => { //if typing is disabled if (disableTypingEvents) { return false; } //if typing is in progress if (isTyping) { return false; } let typingInterval = endTypingTimeout || 5000; let metadata = typingMetadata || undefined; let typingNotification = new CometChat.TypingIndicator(chatWithId.current, chatWith.current, metadata); CometChat.startTyping(typingNotification); isTyping = setTimeout(() => { endTyping(null, typingMetadata); }, typingInterval); return false; }; const endTyping = (event, typingMetadata) => { if (event) { event.persist(); } if (disableTypingEvents) { return false; } let metadata = typingMetadata || undefined; let typingNotification = new CometChat.TypingIndicator(chatWithId.current, chatWith.current, metadata); CometChat.endTyping(typingNotification); clearTimeout(isTyping); isTyping = null; return false; }; // const getActionSheetStyle = () => { // return new ActionSheetStyles({ // layoutModeIconTint: // messageComposerStyle.actionSheetLayoutModeIconTint ?? // theme.palette.getPrimary(), // titleFont: // messageComposerStyle.actionSheetTitleFont ?? theme.typography.text1, // titleColor: // messageComposerStyle.actionSheetTitleColor ?? // theme.palette.getAccent800(), // }); // }; const GetEmojiIconView = () => { return (<ImageButton image={ICONS.EMOJI} imageStyle={Style.imageStyle} onClick={() => setShowEmojiboard(true)}/>); }; const AuxiliaryButtonViewElem = () => { if (AuxiliaryButtonView) return (<AuxiliaryButtonView user={user} group={group} composerId={id}/>); else if (defaultAuxiliaryButtonOptions) return defaultAuxiliaryButtonOptions; return <GetEmojiIconView />; }; const SendButtonViewElem = () => { if (SendButtonView) return <SendButtonView user={user} group={group} composerId={id}/>; return (<ImageButton image={ICONS.SEND} imageStyle={[ Style.imageStyle, { tintColor: messageComposerStyle.sendIconTint || theme.palette.getPrimary(), }, ]} onClick={sendTextMessage}/>); }; const SecondaryButtonViewElem = () => { if (SecondaryButtonView) return (<SecondaryButtonView user={user} group={group} composerId={id}/>); return (<AttachIconButton icon={attachmentIcon} show={true} onClick={() => setShowActionSheet(true)} style={{ height: 23, width: 23, resizeMode: 'contain', tintColor: messageComposerStyle.attachIcontint ? messageComposerStyle.attachIcontint : theme.palette.getAccent500(), }}/>); }; const PrimaryButtonView = () => { return inputMessage.length || hideLiveReaction ? (<SendButtonViewElem />) : (<View> <View style={Style.liveReactionStyle}> <LiveReaction show={showReaction} reaction={liveReactionIcon}/> </View> <ImageButton image={liveReactionIcon} imageStyle={[Style.imageStyle, Style.liveReactionBtnStyle]} onClick={liveReactionHandler}/> </View>); }; //fetch logged in user useEffect(() => { CometChat.getLoggedinUser().then((user) => (loggedInUser.current = user)); }, []); useEffect(() => { //update receiver user if (user && user.getUid()) { chatRef.current = { chatWith: ReceiverTypeConstants.user, chatWithId: user.getUid(), }; chatWith.current = ReceiverTypeConstants.user; chatWithId.current = user.getUid(); } else if (group && group.getGuid()) { chatRef.current = { chatWith: ReceiverTypeConstants.group, chatWithId: group.getGuid(), }; chatWith.current = ReceiverTypeConstants.group; chatWithId.current = group.getGuid(); } }, [user, group, chatRef]); const handleOnClick = (CustomView) => { let view = CustomView(user, group, { uid: user?.getUid(), guid: group?.getGuid(), parentMessageId: parentMessageId, }, { onClose: () => setIsVisible(false), }); bottomSheetRef.current?.togglePanel(); setTimeout(() => { setCustomView(() => view); setIsVisible(true); }, 200); }; useEffect(() => { const defaultAttachmentOptions = ChatConfigurator.dataSource.getAttachmentOptions(user, group, composerIdMap); setActionSheetItems(() => attachmentOptions && typeof attachmentOptions === 'function' ? attachmentOptions({ user, group, composerId: composerIdMap })?.map((item) => { if (typeof item.CustomView === 'function') return { ...item, onPress: () => handleOnClick(item.CustomView), }; if (typeof item.onPress == 'function') return { ...item, onPress: () => item.onPress(user, group), }; return { ...item, onPress: () => fileInputHandler(item.id), }; }) : defaultAttachmentOptions.map((item) => { if (typeof item.CustomView === 'function') return { ...item, onPress: () => handleOnClick(item.CustomView), }; if (typeof item.onPress === 'function') return { ...item, onPress: () => item.onPress(user, group), }; return { ...item, onPress: () => fileInputHandler(item.id), }; })); }, [user, group, id, parentMessageId]); useEffect(() => { CometChatUIEventHandler.addMessageListener(editMessageListenerID, { ccMessageEdited: (item) => previewMessage(item), }); CometChatUIEventHandler.addUIListener(UiEventListenerID, { ccToggleBottomSheet: (item) => { setIsVisible(false); bottomSheetRef.current?.togglePanel(); } }); return () => { CometChatUIEventHandler.removeMessageListener(editMessageListenerID); CometChatUIEventHandler.removeUIListener(UiEventListenerID); }; }, []); const handlePannel = (item) => { if (item.child) { if (item.alignment === ViewAlignment.composerTop) setCustomViewHeader(() => item.child); else if (item.alignment === ViewAlignment.composerBottom) setCustomViewFooter(() => item.child); } else { if (item.alignment === ViewAlignment.composerTop) setCustomViewHeader(null); else if (item.alignment === ViewAlignment.composerBottom) setCustomViewFooter(null); } }; useEffect(() => { CometChatUIEventHandler.addUIListener(uiEventListenerShow, { showPanel: (item) => handlePannel(item), }); CometChatUIEventHandler.addUIListener(uiEventListenerHide, { hidePanel: (item) => handlePannel(item), }); return () => { CometChatUIEventHandler.removeUIListener(uiEventListenerShow); CometChatUIEventHandler.removeUIListener(uiEventListenerHide); }; }, []); return (<> <Modal visible={isVisible} onRequestClose={() => { setIsVisible(false); }} presentationStyle={'pageSheet'}> {CustomView && CustomView} </Modal> <KeyboardAvoidingView key={id} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.select({ ios: 60 })}> <View style={[ Style.container, { backgroundColor: theme.palette.getAccent100(), }, messageComposerStyle, ]}> <EmojiBoard hideSearch={true} shouldShow={showEmojiboard} onClose={() => setShowEmojiboard(false)} emojiSelection={(emoji) => { let pre = inputMessage.substring(0, selectionPosition.start); let post = inputMessage.substring(selectionPosition.end, inputMessage.length); setInputMessage((prev) => selectionPosition.start && selectionPosition.end ? `${pre}${emoji}${post}` : `${prev}${emoji}`); setShowEmojiboard(false); }}/> <ActionSheetBoard sheetRef={bottomSheetRef} options={actionSheetItems} shouldShow={showActionSheet} onClose={() => setShowActionSheet(false)} style={{ // ...getActionSheetStyle(), ...(messageComposerStyle.actionSheetSeparatorTint ? { actionSheetSeparatorTint: messageComposerStyle.actionSheetSeparatorTint, } : {}), }} cometChatBottomSheetStyle={messageComposerStyle.actionSheetCancelButtonIconTint ? { lineColor: messageComposerStyle.actionSheetCancelButtonIconTint, } : {}}/> {HeaderView ? (<HeaderView />) : (CustomViewHeader && <CustomViewHeader />)} <View> <MessagePreviewTray onClose={() => setMessagePreview(null)} text={messagePreview?.message?.text} shouldShow={messagePreview != null}/> </View> <CometChatMessageInput messageInputRef={messageInputRef} text={inputMessage} placeHolderText={placeHolderText} style={messageComposerStyle} onSelectionChange={({ nativeEvent: { selection } }) => setSelectionPosition(selection)} maxHeight={maxHeight ?? null} onChangeText={textChangeHandler} SecondaryButtonView={SecondaryButtonViewElem} AuxiliaryButtonView={AuxiliaryButtonViewElem} PrimaryButtonView={PrimaryButtonView} auxiliaryButtonAlignment={auxiliaryButtonsAlignment ? auxiliaryButtonsAlignment : 'right'}/> {FooterView ? (<FooterView />) : (CustomViewFooter && <CustomViewFooter />)} </View> </KeyboardAvoidingView> </>); }); CometChatMessageComposer.defaultProps = { user: undefined, group: undefined, // style: new MessageComposerStyle({}), attachmentIcon: ICONS.ADD, liveReactionIcon: ICONS.HEART, hideLiveReaction: false, disableSoundForMessages: true, messageComposerStyle: {}, }; //# sourceMappingURL=CometChatMessageComposer.js.map