Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F2770114
D9052.id30661.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
D9052.id30661.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D9052: [native] Convert `ComposedMessage` into a functional component
Attached
Detach File
Event Timeline
Log In to Comment