Page MenuHomePhabricator

D9052.id30661.diff
No OneTemporary

D9052.id30661.diff

diff --git a/native/chat/composed-message.react.js b/native/chat/composed-message.react.js
--- a/native/chat/composed-message.react.js
+++ b/native/chat/composed-message.react.js
@@ -4,7 +4,7 @@
import invariant from 'invariant';
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
-import Animated, {
+import {
useDerivedValue,
withTiming,
interpolateColor,
@@ -30,17 +30,13 @@
import { useContentAndHeaderOpacity, useDeliveryIconOpacity } from './utils.js';
import UserAvatar from '../avatars/user-avatar.react.js';
import CommIcon from '../components/comm-icon.react.js';
-import { type InputState, InputStateContext } from '../input/input-state.js';
-import { type Colors, useColors } from '../themes/colors.js';
+import { InputStateContext } from '../input/input-state.js';
+import { useColors } from '../themes/colors.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
import { type AnimatedStyleObj, AnimatedView } from '../types/styles.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 = {
+type Props = {
...React.ElementConfig<typeof View>,
+item: ChatMessageInfoItemWithHeight,
+sendFailed: boolean,
@@ -49,74 +45,93 @@
+shouldDisplayPinIndicator: boolean,
+children: React.Node,
};
-type Props = {
- ...BaseProps,
- // Redux state
- +composedMessageMaxWidth: number,
- +colors: Colors,
- +contentAndHeaderOpacity: number | Node,
- +deliveryIconOpacity: number | Node,
- // withInputState
- +inputState: ?InputState,
- +navigateToSidebar: () => mixed,
- +editedMessageStyle: AnimatedStyleObj,
-};
-class ComposedMessage extends React.PureComponent<Props> {
- render() {
- assertComposableMessageType(this.props.item.messageInfo.type);
+
+const ConnectedComposedMessage: React.ComponentType<Props> = React.memo<Props>(
+ function ConnectedComposedMessage(props: Props) {
+ 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 messageEditingContext = React.useContext(MessageEditingContext);
+ const progress = useDerivedValue(() => {
+ const isThisThread =
+ messageEditingContext?.editState.editedMessage?.threadID ===
+ props.item.threadInfo.id;
+ const isHighlighted =
+ messageEditingContext?.editState.editedMessage?.id ===
+ props.item.messageInfo.id && isThisThread;
+ return withTiming(isHighlighted ? 1 : 0);
+ });
+
+ const editedMessageStyle = useAnimatedStyle(() => {
+ const backgroundColor = interpolateColor(
+ progress.value,
+ [0, 1],
+ ['transparent', `#${props.item.threadInfo.color}40`],
+ );
+ return {
+ backgroundColor,
+ };
+ });
+
+ assertComposableMessageType(props.item.messageInfo.type);
const {
item,
sendFailed,
- focused,
swipeOptions,
shouldDisplayPinIndicator,
children,
- composedMessageMaxWidth,
- colors,
- inputState,
- navigateToSidebar,
- contentAndHeaderOpacity,
- deliveryIconOpacity,
- editedMessageStyle,
+ focused,
...viewProps
- } = this.props;
- const { id, creator } = item.messageInfo;
+ } = props;
+
const { hasBeenEdited, isPinned } = item;
+ const { id, creator } = item.messageInfo;
const { isViewer } = creator;
const alignStyle = isViewer
? styles.rightChatBubble
: styles.leftChatBubble;
- let containerMarginBottom = 5;
- if (item.endsCluster) {
- containerMarginBottom += clusterEndHeight;
- }
- const containerStyle = { marginBottom: containerMarginBottom };
-
- const messageBoxContainerStyle = [styles.messageBoxContainer];
- const positioningStyle = isViewer
- ? styles.rightChatContainer
- : styles.leftChatContainer;
- messageBoxContainerStyle.push(positioningStyle);
-
- let deliveryIcon = null;
- let failedSendInfo = null;
- if (isViewer) {
+ const containerStyle = React.useMemo(() => {
+ let containerMarginBottom = 5;
+ if (item.endsCluster) {
+ containerMarginBottom += clusterEndHeight;
+ }
+ return { marginBottom: containerMarginBottom };
+ }, [item.endsCluster]);
+
+ const messageBoxContainerStyle = React.useMemo(
+ () => [
+ styles.messageBoxContainer,
+ isViewer ? styles.rightChatContainer : styles.leftChatContainer,
+ ],
+ [isViewer],
+ );
+
+ const deliveryIcon = React.useMemo(() => {
+ if (!isViewer) {
+ return undefined;
+ }
+
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 = <FailedSend item={item} />;
} else {
deliveryIconName = 'circle';
}
const animatedStyle: AnimatedStyleObj = { opacity: deliveryIconOpacity };
- deliveryIcon = (
+
+ return (
<AnimatedView style={[styles.iconContainer, animatedStyle]}>
<Icon
name={deliveryIconName}
@@ -124,27 +139,45 @@
/>
</AnimatedView>
);
- }
+ }, [
+ colors.redText,
+ deliveryIconOpacity,
+ id,
+ isViewer,
+ item.threadInfo.color,
+ sendFailed,
+ ]);
+
+ const reply = React.useCallback(() => {
+ 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',
+ });
+ }, [inputState, item.messageInfo.text]);
const triggerReply =
- swipeOptions === 'reply' || swipeOptions === 'both'
- ? this.reply
- : undefined;
+ swipeOptions === 'reply' || swipeOptions === 'both' ? reply : undefined;
+
const triggerSidebar =
swipeOptions === 'sidebar' || swipeOptions === 'both'
? navigateToSidebar
: undefined;
- let avatar;
- if (!isViewer && item.endsCluster) {
- avatar = (
- <View style={styles.avatarContainer}>
- <UserAvatar size="small" userID={item.messageInfo.creator.id} />
- </View>
- );
- } else if (!isViewer) {
- avatar = <View style={styles.avatarOffset} />;
- }
+ const avatar = React.useMemo(() => {
+ if (!isViewer && item.endsCluster) {
+ return (
+ <View style={styles.avatarContainer}>
+ <UserAvatar size="small" userID={item.messageInfo.creator.id} />
+ </View>
+ );
+ } else if (!isViewer) {
+ return <View style={styles.avatarOffset} />;
+ } else {
+ return undefined;
+ }
+ }, [isViewer, item.endsCluster, item.messageInfo.creator.id]);
const pinIconPositioning = isViewer ? 'left' : 'right';
const pinIconName = pinIconPositioning === 'left' ? 'pin-mirror' : 'pin';
@@ -153,9 +186,11 @@
? styles.rightMessageBoxTopLevelContainerStyle
: styles.leftMessageBoxTopLevelContainerStyle;
- let pinIcon;
- if (isPinned && shouldDisplayPinIndicator) {
- pinIcon = (
+ const pinIcon = React.useMemo(() => {
+ if (!isPinned || !shouldDisplayPinIndicator) {
+ return undefined;
+ }
+ return (
<View style={styles.pinIconContainer}>
<CommIcon
name={pinIconName}
@@ -164,40 +199,63 @@
/>
</View>
);
- }
-
- const messageBoxStyle = {
- opacity: contentAndHeaderOpacity,
- maxWidth: composedMessageMaxWidth,
- };
-
- const messageBox = (
- <View style={messageBoxTopLevelContainerStyle}>
- {pinIcon}
- <View style={messageBoxContainerStyle}>
- <SwipeableMessage
- triggerReply={triggerReply}
- triggerSidebar={triggerSidebar}
- isViewer={isViewer}
- contentStyle={styles.swipeableContainer}
- threadColor={item.threadInfo.color}
- >
- {avatar}
- <AnimatedView style={messageBoxStyle}>{children}</AnimatedView>
- </SwipeableMessage>
- </View>
- </View>
+ }, [
+ isPinned,
+ item.threadInfo.color,
+ pinIconName,
+ shouldDisplayPinIndicator,
+ ]);
+
+ const messageBoxStyle = React.useMemo(
+ () => ({
+ opacity: contentAndHeaderOpacity,
+ maxWidth: composedMessageMaxWidth,
+ }),
+ [composedMessageMaxWidth, contentAndHeaderOpacity],
);
- let inlineEngagement = null;
- const label = getMessageLabel(hasBeenEdited, item.threadInfo.id);
- if (
- item.threadCreatedFromMessage ||
- Object.keys(item.reactions).length > 0 ||
- label
- ) {
+ const messageBox = React.useMemo(() => {
+ return (
+ <View style={messageBoxTopLevelContainerStyle}>
+ {pinIcon}
+ <View style={messageBoxContainerStyle}>
+ <SwipeableMessage
+ triggerReply={triggerReply}
+ triggerSidebar={triggerSidebar}
+ isViewer={isViewer}
+ contentStyle={styles.swipeableContainer}
+ threadColor={item.threadInfo.color}
+ >
+ {avatar}
+ <AnimatedView style={messageBoxStyle}>{children}</AnimatedView>
+ </SwipeableMessage>
+ </View>
+ </View>
+ );
+ }, [
+ avatar,
+ children,
+ isViewer,
+ item.threadInfo.color,
+ messageBoxContainerStyle,
+ messageBoxStyle,
+ messageBoxTopLevelContainerStyle,
+ pinIcon,
+ triggerReply,
+ triggerSidebar,
+ ]);
+
+ const inlineEngagement = React.useMemo(() => {
+ const label = getMessageLabel(hasBeenEdited, item.threadInfo.id);
+ if (
+ !item.threadCreatedFromMessage &&
+ Object.keys(item.reactions).length <= 0 &&
+ !label
+ ) {
+ return undefined;
+ }
const positioning = isViewer ? 'right' : 'left';
- inlineEngagement = (
+ return (
<InlineEngagement
messageInfo={item.messageInfo}
threadInfo={item.threadInfo}
@@ -207,51 +265,96 @@
label={label}
/>
);
- }
+ }, [
+ hasBeenEdited,
+ isViewer,
+ item.messageInfo,
+ item.reactions,
+ item.threadCreatedFromMessage,
+ item.threadInfo,
+ ]);
- const viewStyle = [styles.alignment];
- if (!__DEV__) {
- // We don't force view height in dev mode because we
- // want to measure it in Message to see if it's correct
+ const viewStyle = React.useMemo(() => {
+ const baseStyle = [styles.alignment];
+ if (__DEV__) {
+ return baseStyle;
+ }
if (item.messageShapeType === 'text') {
- viewStyle.push({ height: item.contentHeight });
+ baseStyle.push({ height: item.contentHeight });
} else if (item.messageShapeType === 'multimedia') {
const height = item.inlineEngagementHeight
? item.contentHeight + item.inlineEngagementHeight
: item.contentHeight;
- viewStyle.push({ height });
+ baseStyle.push({ height });
}
- }
+ return baseStyle;
+ }, [
+ item.contentHeight,
+ item.inlineEngagementHeight,
+ item.messageShapeType,
+ ]);
- return (
- <View {...viewProps}>
- <AnimatedView style={{ opacity: contentAndHeaderOpacity }}>
- <MessageHeader item={item} focused={focused} display="lowContrast" />
- </AnimatedView>
- <AnimatedView style={[containerStyle, editedMessageStyle]}>
- <View style={viewStyle}>
- <View style={[styles.content, alignStyle]}>
- {deliveryIcon}
- {messageBox}
- </View>
- {inlineEngagement}
- </View>
- {failedSendInfo}
- </AnimatedView>
- </View>
+ const messageHeaderStyle = React.useMemo(
+ () => ({
+ opacity: contentAndHeaderOpacity,
+ }),
+ [contentAndHeaderOpacity],
);
- }
-
- 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 animatedContainerStyle = React.useMemo(
+ () => [containerStyle, editedMessageStyle],
+ [containerStyle, editedMessageStyle],
+ );
+
+ const contentStyle = React.useMemo(
+ () => [styles.content, alignStyle],
+ [alignStyle],
+ );
+
+ const failedSend = React.useMemo(
+ () => (sendFailed ? <FailedSend item={item} /> : undefined),
+ [item, sendFailed],
+ );
+
+ const composedMessage = React.useMemo(() => {
+ return (
+ <View {...viewProps}>
+ <AnimatedView style={messageHeaderStyle}>
+ <MessageHeader
+ item={item}
+ focused={focused}
+ display="lowContrast"
+ />
+ </AnimatedView>
+ <AnimatedView style={animatedContainerStyle}>
+ <View style={viewStyle}>
+ <View style={contentStyle}>
+ {deliveryIcon}
+ {messageBox}
+ </View>
+ {inlineEngagement}
+ </View>
+ {failedSend}
+ </AnimatedView>
+ </View>
+ );
+ }, [
+ animatedContainerStyle,
+ contentStyle,
+ deliveryIcon,
+ failedSend,
+ focused,
+ inlineEngagement,
+ item,
+ messageBox,
+ messageHeaderStyle,
+ viewProps,
+ viewStyle,
+ ]);
+
+ return composedMessage;
+ },
+);
const styles = StyleSheet.create({
alignment: {
@@ -307,49 +410,4 @@
},
});
-const ConnectedComposedMessage: React.ComponentType<BaseProps> =
- React.memo<BaseProps>(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 messageEditingContext = React.useContext(MessageEditingContext);
- const progress = useDerivedValue(() => {
- const isThisThread =
- messageEditingContext?.editState.editedMessage?.threadID ===
- props.item.threadInfo.id;
- const isHighlighted =
- messageEditingContext?.editState.editedMessage?.id ===
- props.item.messageInfo.id && isThisThread;
- return withTiming(isHighlighted ? 1 : 0);
- });
-
- const editedMessageStyle = useAnimatedStyle(() => {
- const backgroundColor = interpolateColor(
- progress.value,
- [0, 1],
- ['transparent', `#${props.item.threadInfo.color}40`],
- );
- return {
- backgroundColor,
- };
- });
-
- return (
- <ComposedMessage
- {...props}
- composedMessageMaxWidth={composedMessageMaxWidth}
- colors={colors}
- inputState={inputState}
- navigateToSidebar={navigateToSidebar}
- contentAndHeaderOpacity={contentAndHeaderOpacity}
- deliveryIconOpacity={deliveryIconOpacity}
- editedMessageStyle={editedMessageStyle}
- />
- );
- });
-
export default ConnectedComposedMessage;

File Metadata

Mime Type
text/plain
Expires
Fri, Sep 20, 7:38 PM (18 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2153982
Default Alt Text
D9052.id30661.diff (15 KB)

Event Timeline