diff --git a/native/chat/inner-text-message.react.js b/native/chat/inner-text-message.react.js index 1b5d5244a..702184626 100644 --- a/native/chat/inner-text-message.react.js +++ b/native/chat/inner-text-message.react.js @@ -1,187 +1,224 @@ // @flow import * as React from 'react'; import { View, StyleSheet, TouchableWithoutFeedback } from 'react-native'; import Animated from 'react-native-reanimated'; import type { ReactionInfo } from 'lib/selectors/chat-selectors.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import { useComposedMessageMaxWidth } from './composed-message-width.js'; import { DummyInlineEngagementNode } from './inline-engagement.react.js'; import { useTextMessageMarkdownRules } from './message-list-types.js'; import { allCorners, filterCorners, getRoundedContainerStyle, } from './rounded-corners.js'; import { TextMessageMarkdownContext, useTextMessageMarkdown, } from './text-message-markdown-context.js'; import GestureTouchableOpacity from '../components/gesture-touchable-opacity.react.js'; import Markdown from '../markdown/markdown.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useColors, colors } from '../themes/colors.js'; import type { ChatTextMessageInfoItemWithHeight } from '../types/chat-types.js'; /* eslint-disable import/no-named-as-default-member */ const { Node } = Animated; /* eslint-enable import/no-named-as-default-member */ function dummyNodeForTextMessageHeightMeasurement( text: string, editedLabel?: ?string, sidebarInfo: ?ThreadInfo, reactions: ReactionInfo, ): React.Element { return ( {text} ); } type DummyTextNodeProps = { ...React.ElementConfig, +children: string, }; function DummyTextNode(props: DummyTextNodeProps): React.Node { const { children, style, ...rest } = props; const maxWidth = useComposedMessageMaxWidth(); const viewStyle = [props.style, styles.dummyMessage, { maxWidth }]; const rules = useTextMessageMarkdownRules(false); return ( {children} ); } type Props = { +item: ChatTextMessageInfoItemWithHeight, +onPress: () => void, +messageRef?: (message: ?React.ElementRef) => void, +threadColorOverride?: ?Node, +isThreadColorDarkOverride?: ?boolean, }; function InnerTextMessage(props: Props): React.Node { const { item } = props; const { text, creator } = item.messageInfo; const { isViewer } = creator; const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); const boundColors = useColors(); - const messageStyle = {}; - let darkColor; - if (isViewer) { - const threadColor = item.threadInfo.color; - messageStyle.backgroundColor = - props.threadColorOverride ?? `#${threadColor}`; - darkColor = props.isThreadColorDarkOverride ?? colorIsDark(threadColor); - } else { - messageStyle.backgroundColor = boundColors.listChatBubble; - darkColor = activeTheme === 'dark'; - } - - const cornerStyle = getRoundedContainerStyle(filterCorners(allCorners, item)); + const darkColor = !isViewer + ? activeTheme === 'dark' + : props.isThreadColorDarkOverride ?? colorIsDark(item.threadInfo.color); + + const messageStyle = React.useMemo( + () => ({ + backgroundColor: !isViewer + ? boundColors.listChatBubble + : props.threadColorOverride ?? `#${item.threadInfo.color}`, + }), + [ + boundColors.listChatBubble, + isViewer, + item.threadInfo.color, + props.threadColorOverride, + ], + ); + + const cornerStyle = React.useMemo( + () => getRoundedContainerStyle(filterCorners(allCorners, item)), + [item], + ); const rules = useTextMessageMarkdownRules(darkColor); const textMessageMarkdown = useTextMessageMarkdown(item.messageInfo); const markdownStyles = React.useMemo(() => { const textStyle = { color: darkColor ? colors.dark.listForegroundLabel : colors.light.listForegroundLabel, }; return [styles.text, textStyle]; }, [darkColor]); // If we need to render a Text with an onPress prop inside, we're going to // have an issue: the GestureTouchableOpacity below will trigger too when the // the onPress is pressed. We have to use a GestureTouchableOpacity in order // for the message touch gesture to play nice with the message swipe gesture, // so we need to find a way to disable the GestureTouchableOpacity. // // Our solution is to keep using the GestureTouchableOpacity for the padding // around the text, and to have the Texts inside ALL implement an onPress prop // that will default to the message touch gesture. Luckily, Text with onPress // plays nice with the message swipe gesture. - let secondMessage; - if (textMessageMarkdown.markdownHasPressable) { - secondMessage = ( - + const secondMessageStyle = React.useMemo( + () => [StyleSheet.absoluteFill, styles.message], + [], + ); + const secondMessage = React.useMemo(() => { + if (!textMessageMarkdown.markdownHasPressable) { + return undefined; + } + return ( + {text} ); - } - - const message = ( - - - - - - {text} - - - {secondMessage} - - - + }, [ + markdownStyles, + rules, + secondMessageStyle, + text, + textMessageMarkdown.markdownHasPressable, + ]); + + const gestureTouchableOpacityStyle = React.useMemo( + () => [styles.message, cornerStyle], + [cornerStyle], + ); + const message = React.useMemo( + () => ( + + + + + + {text} + + + {secondMessage} + + + + ), + [ + gestureTouchableOpacityStyle, + markdownStyles, + messageStyle, + props.onPress, + rules, + secondMessage, + text, + textMessageMarkdown, + ], ); // We need to set onLayout in order to allow .measure() to be on the ref const onLayout = React.useCallback(() => {}, []); - const { messageRef } = props; - if (!messageRef) { - return message; - } - return ( - - {message} - - ); + const innerTextMessage = React.useMemo(() => { + if (!messageRef) { + return message; + } + return ( + + {message} + + ); + }, [message, messageRef, onLayout]); + + return innerTextMessage; } const styles = StyleSheet.create({ dummyMessage: { paddingHorizontal: 12, paddingVertical: 6, }, message: { overflow: 'hidden', paddingHorizontal: 12, paddingVertical: 6, }, text: { fontFamily: 'Arial', fontSize: 18, }, }); export { InnerTextMessage, dummyNodeForTextMessageHeightMeasurement };