import React, { useMemo } from 'react'; import { I18nManager, Image, ImageProps, Platform, StyleSheet, Text, TextStyle, View, ViewStyle, } from 'react-native'; import { isFileAttachment, isImageAttachment, isVideoAttachment, MessageComposerState, } from 'stream-chat'; import { ReplyMessageView } from './ReplyMessageView'; import { useChatContext } from '../../contexts/chatContext/ChatContext'; import { useComponentsContext } from '../../contexts/componentsContext/ComponentsContext'; import { MessageContextValue, useMessageContext, } from '../../contexts/messageContext/MessageContext'; import { useMessageComposer } from '../../contexts/messageInputContext/hooks/useMessageComposer'; import { MessagesContextValue } from '../../contexts/messagesContext/MessagesContext'; import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { useStateStore } from '../../hooks'; import { primitives } from '../../theme'; import { FileTypes } from '../../types/types'; import { checkQuotedMessageEquality } from '../../utils/utils'; import { FileIcon } from '../Attachment/FileIcon'; import { AttachmentRemoveControl } from '../MessageInput/components/AttachmentPreview/AttachmentRemoveControl'; import { VideoPlayIndicator } from '../ui/VideoPlayIndicator'; const messageComposerStateStoreSelector = (state: MessageComposerState) => ({ quotedMessage: state.quotedMessage, }); const RightContent = React.memo( (props: Pick) => { const { ImageComponent, message } = props; const attachments = message?.attachments; const styles = useStyles(); if (!attachments || attachments.length > 1) { return null; } const attachment = attachments?.[0]; const uri = attachment?.image_url || attachment?.thumb_url; if ( attachment && (isImageAttachment(attachment) || attachment.type === FileTypes.Giphy || attachment.type === FileTypes.Imgur) ) { return ( ); } if (attachment && isVideoAttachment(attachment)) { return ( ); } if (attachment?.type === FileTypes.VoiceRecording) { return null; } if (attachment && isFileAttachment(attachment)) { return ; } return null; }, ); export type ReplyPropsWithContext = { ImageComponent: React.ComponentType } & Pick< MessageContextValue, 'message' > & Pick & { isMyMessage: boolean; onDismiss?: () => void; mode: 'reply' | 'edit'; // This is temporary for the MessageContent Component to style the Reply component styles?: { container?: ViewStyle; leftContainer?: ViewStyle; rightContainer?: ViewStyle; title?: TextStyle; subtitleContainer?: ViewStyle; dismissWrapper?: ViewStyle; }; }; export const ReplyWithContext = (props: ReplyPropsWithContext) => { const { t } = useTranslationContext(); const { isMyMessage, ImageComponent, message: messageFromContext, mode, onDismiss, quotedMessage, styles: stylesProp, } = props; const { theme: { reply: { wrapper, container, leftContainer, rightContainer, title: titleStyle, dismissWrapper, }, }, } = useTheme(); const styles = useStyles(); const title = useMemo( () => mode === 'edit' ? t('Edit Message') : isMyMessage ? t('You') : quotedMessage?.user?.name ? t('Reply to {{name}}', { name: quotedMessage?.user?.name }) : t('Reply'), [mode, isMyMessage, quotedMessage?.user?.name, t], ); if (!quotedMessage) { return null; } return ( {title} {onDismiss ? ( ) : null} ); }; const areEqual = (prevProps: ReplyPropsWithContext, nextProps: ReplyPropsWithContext) => { const { styles: prevStyles, isMyMessage: prevIsMyMessage, mode: prevMode, quotedMessage: prevQuotedMessage, onDismiss: prevOnDismiss, } = prevProps; const { styles: nextStyles, isMyMessage: nextIsMyMessage, mode: nextMode, quotedMessage: nextQuotedMessage, onDismiss: nextOnDismiss, } = nextProps; if (prevStyles !== nextStyles) { return false; } const isMyMessageEqual = prevIsMyMessage === nextIsMyMessage; if (!isMyMessageEqual) { return false; } const modeEqual = prevMode === nextMode; if (!modeEqual) { return false; } const onDismissEqual = prevOnDismiss === nextOnDismiss; if (!onDismissEqual) { return false; } const messageEqual = prevQuotedMessage && nextQuotedMessage && checkQuotedMessageEquality(prevQuotedMessage, nextQuotedMessage); if (!messageEqual) { return false; } return true; }; export const MemoizedReply = React.memo(ReplyWithContext, areEqual) as typeof ReplyWithContext; export type ReplyProps = Partial & Pick; export const Reply = (props: ReplyProps) => { const { message: messageFromContext } = useMessageContext(); const { client } = useChatContext(); const { ImageComponent } = useComponentsContext(); const messageComposer = useMessageComposer(); const { quotedMessage: quotedMessageFromComposer } = useStateStore( messageComposer.state, messageComposerStateStoreSelector, ); const quotedMessage = messageFromContext ? (messageFromContext.quoted_message as MessagesContextValue['quotedMessage']) : quotedMessageFromComposer; const isMyMessage = client.user?.id === quotedMessage?.user?.id; return ( ); }; const useStyles = () => { const { theme: { semantics }, } = useTheme(); const messageComposer = useMessageComposer(); const { quotedMessage: quotedMessageFromComposer } = useStateStore( messageComposer.state, messageComposerStateStoreSelector, ); const { client } = useChatContext(); const isMyMessage = client.user?.id === quotedMessageFromComposer?.user?.id; const isRTL = I18nManager.isRTL; return useMemo( () => StyleSheet.create({ attachmentContainer: { alignItems: 'center', flex: 1, justifyContent: 'center', }, container: { borderRadius: primitives.radiusLg, flexDirection: isRTL ? 'row-reverse' : 'row', padding: primitives.spacingXs, backgroundColor: isMyMessage ? semantics.chatBgOutgoing : semantics.chatBgIncoming, }, contentWrapper: { borderRadius: primitives.radiusMd, borderWidth: 1, height: 40, overflow: 'hidden', width: 40, }, contentBorder: { borderColor: semantics.borderCoreOpacitySubtle, }, dismissWrapper: { position: 'absolute', right: 0, top: 0, }, leftContainer: { flex: 1, justifyContent: 'center', paddingHorizontal: primitives.spacingXs, gap: primitives.spacingXxxs, alignItems: 'flex-start', ...(Platform.OS === 'android' ? { borderLeftColor: isMyMessage ? semantics.chatReplyIndicatorOutgoing : semantics.chatReplyIndicatorIncoming, borderLeftWidth: 2, } : isRTL ? { borderRightColor: isMyMessage ? semantics.chatReplyIndicatorOutgoing : semantics.chatReplyIndicatorIncoming, borderRightWidth: 2, } : { borderLeftColor: isMyMessage ? semantics.chatReplyIndicatorOutgoing : semantics.chatReplyIndicatorIncoming, borderLeftWidth: 2, }), }, rightContainer: {}, title: { color: isMyMessage ? semantics.chatTextOutgoing : semantics.chatTextIncoming, fontSize: primitives.typographyFontSizeXs, fontWeight: primitives.typographyFontWeightSemiBold, includeFontPadding: false, lineHeight: primitives.typographyLineHeightTight, textAlign: isRTL ? 'right' : 'left', }, wrapper: { padding: primitives.spacingXxs, }, }), [isMyMessage, isRTL, semantics], ); };