diff --git a/native/chat/composed-message.react.js b/native/chat/composed-message.react.js index cf0c5cc6c..7504d8149 100644 --- a/native/chat/composed-message.react.js +++ b/native/chat/composed-message.react.js @@ -1,276 +1,283 @@ // @flow import Icon from '@expo/vector-icons/Feather.js'; import invariant from 'invariant'; import * as React from 'react'; import { StyleSheet, View } from 'react-native'; import Animated from 'react-native-reanimated'; import { getMessageLabel } from 'lib/shared/edit-messages-utils.js'; import { createMessageReply } from 'lib/shared/message-utils.js'; import { assertComposableMessageType } from 'lib/types/message-types.js'; import { clusterEndHeight, composedMessageStyle, avatarOffset, } from './chat-constants.js'; import { useComposedMessageMaxWidth } from './composed-message-width.js'; import { FailedSend } from './failed-send.react.js'; import { InlineEngagement } from './inline-engagement.react.js'; import { MessageHeader } from './message-header.react.js'; import { useNavigateToSidebar } from './sidebar-navigation.js'; import SwipeableMessage from './swipeable-message.react.js'; import { useContentAndHeaderOpacity, useDeliveryIconOpacity } from './utils.js'; import UserAvatar from '../avatars/user-avatar.react.js'; import { type InputState, InputStateContext } from '../input/input-state.js'; import { type Colors, useColors } from '../themes/colors.js'; import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js'; import { type AnimatedStyleObj, AnimatedView } from '../types/styles.js'; import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; /* eslint-disable import/no-named-as-default-member */ const { Node } = Animated; /* eslint-enable import/no-named-as-default-member */ type SwipeOptions = 'reply' | 'sidebar' | 'both' | 'none'; type BaseProps = { ...React.ElementConfig, +item: ChatMessageInfoItemWithHeight, +sendFailed: boolean, +focused: boolean, +swipeOptions: SwipeOptions, +children: React.Node, }; type Props = { ...BaseProps, // Redux state +composedMessageMaxWidth: number, +colors: Colors, +contentAndHeaderOpacity: number | Node, +deliveryIconOpacity: number | Node, // withInputState +inputState: ?InputState, +navigateToSidebar: () => mixed, +shouldRenderAvatars: boolean, }; class ComposedMessage extends React.PureComponent { render() { assertComposableMessageType(this.props.item.messageInfo.type); const { item, sendFailed, focused, swipeOptions, children, composedMessageMaxWidth, colors, inputState, navigateToSidebar, contentAndHeaderOpacity, deliveryIconOpacity, shouldRenderAvatars, ...viewProps } = this.props; const { id, creator } = item.messageInfo; const { hasBeenEdited } = item; const { isViewer } = creator; const alignStyle = isViewer ? styles.rightChatBubble : styles.leftChatBubble; let containerMarginBottom = 5; if (item.endsCluster) { containerMarginBottom += clusterEndHeight; } + const highlightStyle = + id && this.props.inputState?.editState.editedMessage?.id === id + ? { backgroundColor: `#${item.threadInfo.color}40` } + : null; + const containerStyle = [ - styles.alignment, { marginBottom: containerMarginBottom }, + highlightStyle, ]; const swipeableMessageBoxStyle = [ styles.swipeableContainer, { maxWidth: composedMessageMaxWidth }, ]; const messageBoxStyleContainerStyle = [styles.messageBoxContainer]; const positioningStyle = isViewer ? { alignItems: 'flex-end' } : { alignItems: 'flex-start' }; messageBoxStyleContainerStyle.push(positioningStyle); let deliveryIcon = null; let failedSendInfo = null; if (isViewer) { let deliveryIconName; let deliveryIconColor = `#${item.threadInfo.color}`; if (id !== null && id !== undefined) { deliveryIconName = 'check-circle'; } else if (sendFailed) { deliveryIconName = 'x-circle'; deliveryIconColor = colors.redText; failedSendInfo = ; } else { deliveryIconName = 'circle'; } const animatedStyle: AnimatedStyleObj = { opacity: deliveryIconOpacity }; deliveryIcon = ( ); } const triggerReply = swipeOptions === 'reply' || swipeOptions === 'both' ? this.reply : undefined; const triggerSidebar = swipeOptions === 'sidebar' || swipeOptions === 'both' ? navigateToSidebar : undefined; let avatar; if (!isViewer && item.endsCluster && shouldRenderAvatars) { avatar = ( ); } else if (!isViewer && shouldRenderAvatars) { avatar = ; } const messageBox = ( {avatar} {children} ); let inlineEngagement = null; const label = getMessageLabel(hasBeenEdited, item.threadInfo); if ( item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0 || label ) { const positioning = isViewer ? 'right' : 'left'; inlineEngagement = ( ); } return ( - - {deliveryIcon} - {messageBox} + + + {deliveryIcon} + {messageBox} + + {failedSendInfo} + {inlineEngagement} - {failedSendInfo} - {inlineEngagement} ); } reply = () => { const { inputState, item } = this.props; invariant(inputState, 'inputState should be set in reply'); invariant(item.messageInfo.text, 'text should be set in reply'); inputState.editInputMessage({ message: createMessageReply(item.messageInfo.text), mode: 'prepend', }); }; } const styles = StyleSheet.create({ alignment: { marginLeft: composedMessageStyle.marginLeft, marginRight: composedMessageStyle.marginRight, }, avatarContainer: { marginRight: 8, }, avatarOffset: { width: avatarOffset, }, content: { alignItems: 'center', flexDirection: 'row-reverse', }, icon: { fontSize: 16, textAlign: 'center', }, iconContainer: { marginLeft: 2, width: 16, }, leftChatBubble: { justifyContent: 'flex-end', }, messageBoxContainer: { flex: 1, marginRight: 5, }, rightChatBubble: { justifyContent: 'flex-start', }, swipeableContainer: { alignItems: 'flex-end', flexDirection: 'row', }, }); const ConnectedComposedMessage: React.ComponentType = React.memo(function ConnectedComposedMessage(props: BaseProps) { const composedMessageMaxWidth = useComposedMessageMaxWidth(); const colors = useColors(); const inputState = React.useContext(InputStateContext); const navigateToSidebar = useNavigateToSidebar(props.item); const contentAndHeaderOpacity = useContentAndHeaderOpacity(props.item); const deliveryIconOpacity = useDeliveryIconOpacity(props.item); const shouldRenderAvatars = useShouldRenderAvatars(); return ( ); }); export default ConnectedComposedMessage;