import React, { useContext } from 'react'; import { TouchableOpacity, View } from 'react-native'; import { Avatar, BottomSheetItem, Box, Divider, Icon, Text, createStyleSheet, useAlert, useBottomSheet, useToast, useUIKitTheme, } from '@sendbird/uikit-react-native-foundation'; import { Logger, SendbirdFileMessage, SendbirdMessage, SendbirdUserMessage, getAvailableUriFromFileMessage, getFileExtension, getFileType, isMyMessage, isVoiceMessage, shouldRenderReaction, toMegabyte, } from '@sendbird/uikit-utils'; import ThreadParentMessageRenderer, { ThreadParentMessageRendererProps, } from '../../../components/ThreadParentMessageRenderer'; import { useLocalization, usePlatformService, useSBUHandlers, useSendbirdChat } from '../../../hooks/useContext'; import SBUUtils from '../../../libs/SBUUtils'; import { GroupChannelThreadContexts } from '../module/moduleContext'; import type { GroupChannelThreadProps } from '../types'; import { ReactionAddons } from './../../../components/ReactionAddons'; type PressActions = { onPress?: () => void; onLongPress?: () => void; bottomSheetItem?: BottomSheetItem }; type HandleableMessage = SendbirdUserMessage | SendbirdFileMessage; type CreateMessagePressActions = (params: { message: SendbirdMessage }) => PressActions; const GroupChannelThreadParentMessageInfo = (props: GroupChannelThreadProps['ParentMessageInfo']) => { const { channel, parentMessage, setMessageToEdit } = useContext(GroupChannelThreadContexts.Fragment); const { STRINGS } = useLocalization(); const { colors } = useUIKitTheme(); const { sbOptions } = useSendbirdChat(); const nickName = parentMessage.sender?.nickname || STRINGS.LABELS.USER_NO_NAME; const messageTimestamp = STRINGS.GROUP_CHANNEL_THREAD.PARENT_MESSAGE_TIME(parentMessage); const replyCountText = STRINGS.GROUP_CHANNEL_THREAD.REPLY_COUNT(parentMessage.threadInfo?.replyCount || 0); const createMessagePressActions = useCreateMessagePressActions({ channel: props.channel, currentUserId: props.currentUserId, onDeleteMessage: props.onDeleteMessage, onPressMediaMessage: props.onPressMediaMessage, onEditMessage: setMessageToEdit, }); const { onPress, onLongPress, bottomSheetItem } = createMessagePressActions({ message: parentMessage }); const renderMessageInfoAndMenu = () => { return ( {nickName} {messageTimestamp} ); }; const renderReplyCount = (replyCountText: string) => { if (replyCountText) { return ( {replyCountText} ); } else { return null; } }; const renderReactionAddons = () => { const configs = sbOptions.uikitWithAppInfo.groupChannel.channel; if (shouldRenderReaction(channel, channel.isSuper ? configs.enableReactionsSupergroup : configs.enableReactions)) { return ( ); } else { return null; } }; const messageProps: ThreadParentMessageRendererProps = { parentMessage, onPress, onLongPress, }; return ( {renderMessageInfoAndMenu()} {renderReactionAddons()} {renderReplyCount(replyCountText)} ); }; const styles = createStyleSheet({ container: { flexDirection: 'column', }, infoAndMenuContainer: { flexDirection: 'row', height: 50, padding: 16, paddingBottom: 0, }, userNickAndTimeContainer: { flexDirection: 'column', flex: 1, marginStart: 8, }, userNickname: { marginBottom: 2, }, messageTime: { marginTop: 2, }, contextMenuButton: { width: 34, height: 34, justifyContent: 'flex-end', }, messageContainer: { paddingHorizontal: 16, paddingVertical: 8, }, reactionButtonContainer: { paddingStart: 16, marginBottom: 16, }, replyContainer: { flexDirection: 'column', }, replyText: { justifyContent: 'center', paddingHorizontal: 16, paddingVertical: 12, }, }); const useCreateMessagePressActions = ({ channel, currentUserId, onDeleteMessage, onPressMediaMessage, onEditMessage, }: Pick< GroupChannelThreadProps['ParentMessageInfo'], 'channel' | 'currentUserId' | 'onDeleteMessage' | 'onPressMediaMessage' > & { onEditMessage: (message: HandleableMessage) => void }): CreateMessagePressActions => { const handlers = useSBUHandlers(); const { STRINGS } = useLocalization(); const toast = useToast(); const { openSheet } = useBottomSheet(); const { alert } = useAlert(); const { clipboardService, fileService } = usePlatformService(); const { sbOptions } = useSendbirdChat(); const onDeleteFailure = (error: Error) => { toast.show(STRINGS.TOAST.DELETE_MSG_ERROR, 'error'); Logger.error(STRINGS.TOAST.DELETE_MSG_ERROR, error); }; const onCopyText = (message: HandleableMessage) => { if (message.isUserMessage()) { clipboardService.setString(message.message || ''); toast.show(STRINGS.TOAST.COPY_OK, 'success'); } }; const onDownloadFile = (message: HandleableMessage) => { if (message.isFileMessage()) { if (toMegabyte(message.size) > 4) { toast.show(STRINGS.TOAST.DOWNLOAD_START, 'success'); } fileService .save({ fileUrl: message.url, fileName: message.name, fileType: message.type }) .then((response) => { toast.show(STRINGS.TOAST.DOWNLOAD_OK, 'success'); Logger.log('File saved to', response); }) .catch((err) => { toast.show(STRINGS.TOAST.DOWNLOAD_ERROR, 'error'); Logger.log('File save failure', err); }); } }; const onOpenFile = (message: HandleableMessage) => { if (message.isFileMessage()) { const fileType = getFileType(message.type || getFileExtension(message.name)); if (['image', 'video', 'audio'].includes(fileType)) { onPressMediaMessage?.(message, () => onDeleteMessage(message), getAvailableUriFromFileMessage(message)); handlers.onOpenFileURL?.(message.url); } else { const openFile = handlers.onOpenFileURL ?? SBUUtils.openURL; openFile(message.url); } } }; const alertForMessageDelete = (message: HandleableMessage) => { alert({ title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_TITLE, buttons: [ { text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_CANCEL }, { text: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE_CONFIRM_OK, style: 'destructive', onPress: () => { onDeleteMessage(message).catch(onDeleteFailure); }, }, ], }); }; return ({ message }) => { if (!message.isUserMessage() && !message.isFileMessage()) return {}; const sheetItems: BottomSheetItem['sheetItems'] = []; const menu = { copy: (message: HandleableMessage) => ({ icon: 'copy' as const, title: STRINGS.LABELS.CHANNEL_MESSAGE_COPY, onPress: () => onCopyText(message), }), edit: (message: HandleableMessage) => ({ icon: 'edit' as const, title: STRINGS.LABELS.CHANNEL_MESSAGE_EDIT, onPress: () => onEditMessage?.(message), }), delete: (message: HandleableMessage) => ({ disabled: message.threadInfo ? message.threadInfo.replyCount > 0 : undefined, icon: 'delete' as const, title: STRINGS.LABELS.CHANNEL_MESSAGE_DELETE, onPress: () => alertForMessageDelete(message), }), download: (message: HandleableMessage) => ({ icon: 'download' as const, title: STRINGS.LABELS.CHANNEL_MESSAGE_SAVE, onPress: () => onDownloadFile(message), }), }; if (message.isUserMessage()) { sheetItems.push(menu.copy(message)); if (!channel.isEphemeral) { if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') { sheetItems.push(menu.edit(message)); sheetItems.push(menu.delete(message)); } } } if (message.isFileMessage()) { if (!isVoiceMessage(message)) { sheetItems.push(menu.download(message)); } if (!channel.isEphemeral) { if (isMyMessage(message, currentUserId) && message.sendingStatus === 'succeeded') { sheetItems.push(menu.delete(message)); } } } const configs = sbOptions.uikitWithAppInfo.groupChannel.channel; const bottomSheetItem: BottomSheetItem = { sheetItems, HeaderComponent: shouldRenderReaction( channel, channel.isGroupChannel() && (channel.isSuper ? configs.enableReactionsSupergroup : configs.enableReactions), ) ? ({ onClose }) => : undefined, }; if (message.isFileMessage()) { return { onPress: () => onOpenFile(message), onLongPress: () => openSheet(bottomSheetItem), bottomSheetItem, }; } else { return { onPress: undefined, onLongPress: () => openSheet(bottomSheetItem), bottomSheetItem, }; } }; }; export default React.memo(GroupChannelThreadParentMessageInfo);