import React, { useEffect, useState } from 'react'; import { ActivityIndicator, Alert, Clipboard, ImageBackground, StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle, } from 'react-native'; import { Actions as ChatActions, Bubble, BubbleProps, Composer, Day, GiftedChat, MessageText, MessageTextProps, Send, SendProps, Time, TimeProps } from 'react-native-gifted-chat'; import { ICallEvent, IMessage, IRoomEvent, IUrgencyType } from '../../store/messages/types'; import { IFile } from '../../store/sharedFile/types'; import { Logger } from '../../utils/Log'; import { IStyledProps } from '../common/types'; import { eventEmitter, EventType } from '../../services/EventEmitter'; import { Strings } from '../../resources/localization/Strings' import { deletedMessageView, IStyleHeaderView, IStyleSendingExtraView, messageHeaderView, messageTypesIcon, repliedMessageView, sendingExtraView, systemMessageView } from './MessageUIProvider'; import { ImageHolder, IImageHolderStyle } from '../common/ImageHolder'; import Icon from 'react-native-vector-icons/Ionicons'; import moment from 'moment'; import { messagesService } from '../../services/messages-service'; const logger = new Logger('MessagesView View'); export interface IMessageProps extends IStyledProps { messages: IMessage[]; loading: boolean; alwaysShowSend?: boolean; loadMore: () => void; sendMessage: (message: IMessage, urgency: string, attachedFileUri: string[]) => void; replyMessage: (message: IMessage) => void; downloadFile: (fileDescriptorId: string) => void; onMessageLongPressed?: (context: any, message: IMessage) => void; renderSendingExtraView?: () => React.ReactNode; renderChatActions?: () => React.ReactNode; renderMessageCustomView?: (currentMessage: IMessage) => React.ReactNode; renderSystemMessage?: (currentMessage: IMessage) => React.ReactNode; renderMessageImage?: (currentMessage: IMessage) => React.ReactNode; renderAvatar?: (currentMessage: IMessage) => React.ReactNode; onSend?: (message: IMessage) => void; onInputTextChanged?: (text: string) => void; renderSendButton?: (sendProps: SendProps) => React.ReactNode; renderMessageText?: (messageText: MessageTextProps) => React.ReactNode; dateCustomStyle?: StyleProp; renderCustomTime?: (timeProps: TimeProps) => React.ReactNode; renderBubbleContainer?: (bubbleProps: BubbleProps) => React.ReactNode; } export interface IMessagesStyleProps { NoDataMessages?: TextStyle; container: ViewStyle; ImageBackground: ViewStyle; chatActionsContainer?: ViewStyle; messageTypesIcon: any; } enum IMessageOption { Copy = 'Copy Text', Forward = 'Forward', Reply = 'Reply', Delete = 'Delete', Cancel = 'Cancel', Download = 'Download file' } export const MessagesView: React.FunctionComponent = ( props: IMessageProps ) => { logger.info('MessagesView'); const [selectedMessage, setSelectedMessage] = useState(); const messagesMergedStyle = { ...defaultStyle, ...props.style }; const [filesToUpload, setFilesToUpload] = useState([]); const [textInput, setTextInput] = useState(''); const [downloadedFileIds, setDownloadedFileIds] = useState([]); const [showActivityIndicator, setShowActivityIndicator] = useState(true); let onScroll = false; useEffect(() => { logger.info('useEffect loading') // // To fix send attachment without text // GiftedChat.defaultProps.text = ' ' as unknown as undefined; if (props.messages.length > 0) { setShowActivityIndicator(false) } const timer = setTimeout(() => setShowActivityIndicator(false), 2000); const downloadResult = eventEmitter.addListener( EventType.FileDownloadFinished, (eventData: { isSuccess: boolean, fileId: string, errorMsg?: string }) => { const filteredIds = downloadedFileIds.filter((id: string) => id !== eventData.fileId); setDownloadedFileIds([...filteredIds]) if (eventData.isSuccess === false) { Alert.alert(Strings.failedToDownload, eventData.errorMsg); } } ); return () => { downloadResult.remove(); clearTimeout(timer); } }, [props, downloadedFileIds, filesToUpload]); const onSend = (messages: IMessage[]) => { const message: IMessage | undefined = messages.pop(); // setShowSend(false); if (message) { message.text = textInput if (props.onSend) { return props.onSend(message); } else { if (message.isReplied) { message.associatedMsgId = selectedMessage?._id; props.replyMessage(message); } else { message.attachedFile = filesToUpload; props.sendMessage(message); } } } resetToDefaultValues() }; const resetToDefaultValues = () => { setFilesToUpload([]); setSelectedMessage(undefined); } const renderCustomChatFooter = () => { if (props.renderSendingExtraView) { return props.renderSendingExtraView(); } return ( <> {selectedMessage?.urgency === IUrgencyType.MEDIUM ? sendingExtraView('Send Important...', close, importantMsgViewStyle) : null} {selectedMessage?.urgency === IUrgencyType.LOW ? sendingExtraView('Send Information...', close, infoMsgViewStyle) : null} {selectedMessage?.isReplied ? sendingExtraView(selectedMessage?.user.name, close, repliedViewStyle, selectedMessage?.text) : null} ) } const close = () => { setFilesToUpload([]); } const renderMessageImage = (Props: MessageImage['props']) => { const message = props.messages.find((item) => { return item._id === Props.currentMessage!._id }); if (message) { if (props.renderMessageImage) { return props.renderMessageImage(message); } else { const image = message!.image; const downloadedId = downloadedFileIds.find(id => id === message?.fileDescriptorId); const showLoadingIndicator = downloadedId !== undefined; return ( {message!.image === 'icon' ? : } {showLoadingIndicator && } ); } } return null; }; const onLongPress = (context: any, message: IMessage) => { if (props.onMessageLongPressed) { return props.onMessageLongPressed(context, message); } else { const options: IMessageOption[] = [IMessageOption.Reply, IMessageOption.Forward, IMessageOption.Copy]; if (message.fileDescriptorId) { // Incase the message obj have a `fileDescriptorId` this means this msg have file or image! options.push(IMessageOption.Download); } if (message.isSent && !message.isDeleted) { // Show delete option only for the sent msg options.push(IMessageOption.Delete); } options.push(IMessageOption.Cancel); const cancelButtonIndex = options.length - 1; context.actionSheet().showActionSheetWithOptions( { options, cancelButtonIndex, }, (buttonIndex: number) => { switch (options[buttonIndex]) { case IMessageOption.Copy: Clipboard.setString(message.text); break; case IMessageOption.Download: downloadedFileIds.push(message.fileDescriptorId) setDownloadedFileIds([...downloadedFileIds]) props.downloadFile(message.fileDescriptorId); break; case IMessageOption.Reply: message.isReplied = true; setSelectedMessage(message); break; case IMessageOption.Delete: messagesService.deleteMessage(message._id); break; default: break; } } ); } }; const renderActions = () => { if (props.renderChatActions) { return props.renderChatActions(); } else { return { const msg = { urgency: IUrgencyType.MEDIUM } setSelectedMessage(msg); }, 'Information msg': () => { const msg = { urgency: IUrgencyType.LOW } setSelectedMessage(msg); }, Cancel: () => { console.log('Cancel'); }, }} /> } }; const renderEmptyChat = () => { return ( No messages available ) } const onTextInputChanges = (text: string) => { if (props.onInputTextChanged) { props.onInputTextChanged(text); } setTextInput(text); } const renderComposer = (prop: any) => { return ( ); } const renderAvatar = (Props: Avatar['props']) => { const currentMsg = props.messages.find((item) => { return item._id === Props.currentMessage!._id }); if (currentMsg) { if (props.renderAvatar) { return props.renderAvatar(currentMsg); } else { const { user } = currentMsg; return ( ); } } return null; } const renderSystemMessage = (Props: any) => { if (props.renderSystemMessage) { return props.renderSystemMessage(Props.currentMessage); } else { const conference = Props.currentMessage.conference; const call: ICallEvent = Props.currentMessage.call; const roomEvent: IRoomEvent = Props.currentMessage.roomEvent; if (conference) { return systemMessageView(conference.msgContent); } else if (call) { return systemMessageView(call.msgContent); } else if (roomEvent) { return systemMessageView(roomEvent.msgContent); } else { return; } } } const renderCustomMessageText = (Props: MessageTextProps) => { if (props.renderMessageText) { return props.renderMessageText(Props) } else { return } } const renderCustomView = (Props: any) => { const currentMessage = Props.currentMessage; if (props.renderMessageCustomView) return props.renderMessageCustomView(currentMessage); const filterMessage: IMessage[] = props.messages.filter(data => data._id === currentMessage.associatedMsgId); const warningIcon = ; const infoIcon = ; const arrowForwardIcon = ; return ( <> {currentMessage.urgency === IUrgencyType.MEDIUM ? messageHeaderView(warningIcon, importantHeaderViewStyle, Strings.Important) : null} {currentMessage.urgency === IUrgencyType.LOW ? messageHeaderView(infoIcon, infoHeaderViewStyle, Strings.Information) : null} {currentMessage.isDeleted ? deletedMessageView() : null} {(currentMessage.isReplied && filterMessage.length > 0) ? repliedMessageView(filterMessage[0].text, filterMessage[0].user.name, null) : null} {currentMessage.isForwarded ? messageHeaderView(arrowForwardIcon, forwardHeaderViewStyle, Strings.Forwarded) : null} ) } const loadMore = () => { if (!onScroll) return; setShowActivityIndicator(true) props.loadMore(); onScroll = false } const shouldUpdateMessage = () => { return true } const shouldShowSendButton = () => { if (props.alwaysShowSend) { return props.alwaysShowSend; } return true } const renderSend = (sendProps: SendProps) => { if (props.renderSendButton) { return ( {props.renderSendButton(sendProps)} ); } return ; }; const renderCustomDay = (Props: Day['props']) => { if (props.dateCustomStyle) return else return ( {moment(Props.currentMessage?.createdAt, 'ddd MMM DD HH:mm:ss YYYY').format('MMM DD, YYYY')} ) } const renderCustomTime = (timeProps: TimeProps) => { if (props.renderCustomTime) return props.renderCustomTime(timeProps) else return