diff --git a/native/chat/chat-constants.js b/native/chat/chat-constants.js
index 0a78171ab..ad12f2603 100644
--- a/native/chat/chat-constants.js
+++ b/native/chat/chat-constants.js
@@ -1,30 +1,29 @@
// @flow
export const composedMessageStyle = {
marginLeft: 12,
marginRight: 7,
};
export const inlineEngagementStyle = {
- height: 38,
marginTop: 5,
marginBottom: 3,
topOffset: -10,
};
export const inlineEngagementCenterStyle = {
topOffset: -5,
};
export const inlineEngagementRightStyle = {
marginLeft: 22,
};
export const inlineEngagementLabelStyle = {
height: 16,
topOffset: 8,
};
export const clusterEndHeight = 7;
export const avatarOffset = 32;
diff --git a/native/chat/chat-item-height-measurer.react.js b/native/chat/chat-item-height-measurer.react.js
index 3cf82f3bc..6c2e66aff 100644
--- a/native/chat/chat-item-height-measurer.react.js
+++ b/native/chat/chat-item-height-measurer.react.js
@@ -1,212 +1,234 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { getMessageLabel } from 'lib/shared/edit-messages-utils.js';
import {
getInlineEngagementSidebarText,
reactionsToRawString,
} from 'lib/shared/inline-engagement-utils.js';
import { messageID } from 'lib/shared/message-utils.js';
import {
messageTypes,
type MessageType,
} from 'lib/types/message-types-enum.js';
import { entityTextToRawString } from 'lib/utils/entity-text.js';
import type { MeasurementTask } from './chat-context-provider.react.js';
import { useComposedMessageMaxWidth } from './composed-message-width.js';
+import { dummyNodeForInlineEngagementHeightMeasurement } from './inline-engagement.react.js';
import { dummyNodeForRobotextMessageHeightMeasurement } from './inner-robotext-message.react.js';
import { dummyNodeForTextMessageHeightMeasurement } from './inner-text-message.react.js';
import type { NativeChatMessageItem } from './message-data.react.js';
import { MessageListContextProvider } from './message-list-types.js';
import { multimediaMessageContentSizes } from './multimedia-message-utils.js';
import { chatMessageItemKey } from './utils.js';
import NodeHeightMeasurer from '../components/node-height-measurer.react.js';
import { InputStateContext } from '../input/input-state.js';
type Props = {
+measurement: MeasurementTask,
};
const heightMeasurerKey = (item: NativeChatMessageItem) => {
if (item.itemType !== 'message') {
return null;
}
const { messageInfo, hasBeenEdited, threadCreatedFromMessage, reactions } =
item;
if (messageInfo.type === messageTypes.TEXT) {
return JSON.stringify({
text: messageInfo.text,
edited: getMessageLabel(hasBeenEdited, messageInfo.threadID),
sidebar: getInlineEngagementSidebarText(threadCreatedFromMessage),
reactions: reactionsToRawString(reactions),
});
} else if (item.robotext) {
const { threadID } = item.messageInfo;
return JSON.stringify({
robotext: entityTextToRawString(item.robotext, { threadID }),
sidebar: getInlineEngagementSidebarText(threadCreatedFromMessage),
reactions: reactionsToRawString(reactions),
});
+ } else if (threadCreatedFromMessage || Object.keys(reactions).length > 0) {
+ // we enter this condition when the item is a multimedia message with an
+ // inline engagement
+ return JSON.stringify({
+ sidebar: getInlineEngagementSidebarText(threadCreatedFromMessage),
+ reactions: reactionsToRawString(reactions),
+ });
}
return null;
};
// ESLint doesn't recognize that invariant always throws
// eslint-disable-next-line consistent-return
const heightMeasurerDummy = (item: NativeChatMessageItem) => {
invariant(
item.itemType === 'message',
'NodeHeightMeasurer asked for dummy for non-message item',
);
- const { messageInfo, hasBeenEdited } = item;
+ const { messageInfo, hasBeenEdited, threadCreatedFromMessage, reactions } =
+ item;
+
if (messageInfo.type === messageTypes.TEXT) {
const label = getMessageLabel(hasBeenEdited, messageInfo.threadID);
return dummyNodeForTextMessageHeightMeasurement(
messageInfo.text,
label,
- item.threadCreatedFromMessage,
- item.reactions,
+ threadCreatedFromMessage,
+ reactions,
);
} else if (item.robotext) {
return dummyNodeForRobotextMessageHeightMeasurement(
item.robotext,
- item.messageInfo.threadID,
- item.threadCreatedFromMessage,
- item.reactions,
+ messageInfo.threadID,
+ threadCreatedFromMessage,
+ reactions,
+ );
+ } else if (threadCreatedFromMessage || Object.keys(reactions).length > 0) {
+ // we enter this condition when the item is a multimedia message with an
+ // inline engagement
+
+ return dummyNodeForInlineEngagementHeightMeasurement(
+ threadCreatedFromMessage,
+ reactions,
);
}
- invariant(false, 'NodeHeightMeasurer asked for dummy for non-text message');
+ invariant(
+ false,
+ 'NodeHeightMeasurer asked for dummy for multimedia message with no inline engagement',
+ );
};
function ChatItemHeightMeasurer(props: Props) {
const composedMessageMaxWidth = useComposedMessageMaxWidth();
const inputState = React.useContext(InputStateContext);
const inputStatePendingUploads = inputState?.pendingUploads;
const { measurement } = props;
const { threadInfo } = measurement;
const heightMeasurerMergeItem = React.useCallback(
(item: NativeChatMessageItem, height: ?number) => {
if (item.itemType !== 'message') {
return item;
}
const { messageInfo } = item;
const messageType: MessageType = messageInfo.type;
invariant(
messageType !== messageTypes.SIDEBAR_SOURCE,
'Sidebar source messages should be replaced by sourceMessage before being measured',
);
if (
messageInfo.type === messageTypes.IMAGES ||
messageInfo.type === messageTypes.MULTIMEDIA
) {
// Conditional due to Flow...
const localMessageInfo = item.localMessageInfo
? item.localMessageInfo
: null;
const id = messageID(messageInfo);
const pendingUploads = inputStatePendingUploads?.[id];
const sizes = multimediaMessageContentSizes(
messageInfo,
composedMessageMaxWidth,
);
return {
itemType: 'message',
messageShapeType: 'multimedia',
messageInfo,
localMessageInfo,
threadInfo,
startsConversation: item.startsConversation,
startsCluster: item.startsCluster,
endsCluster: item.endsCluster,
threadCreatedFromMessage: item.threadCreatedFromMessage,
pendingUploads,
reactions: item.reactions,
hasBeenEdited: item.hasBeenEdited,
isPinned: item.isPinned,
+ inlineEngagementHeight: height,
...sizes,
};
}
invariant(
height !== null && height !== undefined,
'height should be set',
);
if (messageInfo.type === messageTypes.TEXT) {
// Conditional due to Flow...
const localMessageInfo = item.localMessageInfo
? item.localMessageInfo
: null;
return {
itemType: 'message',
messageShapeType: 'text',
messageInfo,
localMessageInfo,
threadInfo,
startsConversation: item.startsConversation,
startsCluster: item.startsCluster,
endsCluster: item.endsCluster,
threadCreatedFromMessage: item.threadCreatedFromMessage,
contentHeight: height,
reactions: item.reactions,
hasBeenEdited: item.hasBeenEdited,
isPinned: item.isPinned,
};
}
invariant(
item.messageInfoType !== 'composable',
'ChatItemHeightMeasurer was handed a messageInfoType=composable, but ' +
`does not know how to handle MessageType ${messageInfo.type}`,
);
invariant(
item.messageInfoType === 'robotext',
'ChatItemHeightMeasurer was handed a messageInfoType that it does ' +
`not recognize: ${item.messageInfoType}`,
);
return {
itemType: 'message',
messageShapeType: 'robotext',
messageInfo,
threadInfo,
startsConversation: item.startsConversation,
startsCluster: item.startsCluster,
endsCluster: item.endsCluster,
threadCreatedFromMessage: item.threadCreatedFromMessage,
robotext: item.robotext,
contentHeight: height,
reactions: item.reactions,
};
},
[composedMessageMaxWidth, inputStatePendingUploads, threadInfo],
);
return (
);
}
const MemoizedChatItemHeightMeasurer: React.ComponentType =
React.memo(ChatItemHeightMeasurer);
export default MemoizedChatItemHeightMeasurer;
diff --git a/native/chat/inline-engagement.react.js b/native/chat/inline-engagement.react.js
index 9b815b040..9974f3553 100644
--- a/native/chat/inline-engagement.react.js
+++ b/native/chat/inline-engagement.react.js
@@ -1,550 +1,569 @@
// @flow
import { useNavigation } from '@react-navigation/native';
import invariant from 'invariant';
import * as React from 'react';
import { Text, View } from 'react-native';
import Animated, {
Extrapolate,
interpolateNode,
} from 'react-native-reanimated';
import type { ReactionInfo } from 'lib/selectors/chat-selectors.js';
import { getInlineEngagementSidebarText } from 'lib/shared/inline-engagement-utils.js';
import { localIDPrefix } from 'lib/shared/message-utils.js';
import type { MessageInfo } from 'lib/types/message-types.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import {
inlineEngagementLabelStyle,
inlineEngagementStyle,
inlineEngagementCenterStyle,
inlineEngagementRightStyle,
composedMessageStyle,
avatarOffset,
} from './chat-constants.js';
import { useNavigateToThread } from './message-list-types.js';
import { useSendReaction } from './reaction-message-utils.js';
import CommIcon from '../components/comm-icon.react.js';
import GestureTouchableOpacity from '../components/gesture-touchable-opacity.react.js';
import { MessageReactionsModalRouteName } from '../navigation/route-names.js';
import { useSelector } from '../redux/redux-utils.js';
import { useStyles } from '../themes/colors.js';
import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js';
+function dummyNodeForInlineEngagementHeightMeasurement(
+ sidebarInfo: ?ThreadInfo,
+ reactions: ReactionInfo,
+): React.Element {
+ return (
+
+
+
+ );
+}
+
type DummyInlineEngagementNodeProps = {
...React.ElementConfig,
+editedLabel?: ?string,
+sidebarInfo: ?ThreadInfo,
+reactions: ReactionInfo,
};
function DummyInlineEngagementNode(
props: DummyInlineEngagementNodeProps,
): React.Node {
const { editedLabel, sidebarInfo, reactions, ...rest } = props;
const dummyEditedLabel = React.useMemo(() => {
if (!editedLabel) {
return null;
}
return (
{editedLabel}
);
}, [editedLabel]);
const dummySidebarItem = React.useMemo(() => {
if (!sidebarInfo) {
return null;
}
const repliesText = getInlineEngagementSidebarText(sidebarInfo);
return (
{repliesText}
);
}, [sidebarInfo]);
const dummyReactionsList = React.useMemo(() => {
if (Object.keys(reactions).length === 0) {
return null;
}
return Object.keys(reactions).map(reaction => {
const numOfReacts = reactions[reaction].users.length;
return (
{`${reaction} ${numOfReacts}`}
);
});
}, [reactions]);
const dummyContainerStyle = React.useMemo(
() => [unboundStyles.inlineEngagement, unboundStyles.dummyInlineEngagement],
[],
);
if (!dummyEditedLabel && !dummySidebarItem && !dummyReactionsList) {
return null;
}
return (
{dummyEditedLabel}
{dummySidebarItem}
{dummyReactionsList}
);
}
type Props = {
+messageInfo: MessageInfo,
+threadInfo: ThreadInfo,
+sidebarThreadInfo: ?ThreadInfo,
+reactions: ReactionInfo,
+disabled?: boolean,
+positioning?: 'left' | 'right' | 'center',
+label?: ?string,
};
function InlineEngagement(props: Props): React.Node {
const {
messageInfo,
threadInfo,
sidebarThreadInfo,
reactions,
disabled = false,
positioning,
label,
} = props;
const isLeft = positioning === 'left';
const isRight = positioning === 'right';
const isCenter = positioning === 'center';
const navigateToThread = useNavigateToThread();
const { navigate } = useNavigation();
const styles = useStyles(unboundStyles);
const editedLabelStyle = React.useMemo(() => {
const stylesResult = [styles.messageLabel, styles.messageLabelColor];
if (isLeft) {
stylesResult.push(styles.messageLabelLeft);
} else {
stylesResult.push(styles.messageLabelRight);
}
return stylesResult;
}, [
isLeft,
styles.messageLabel,
styles.messageLabelColor,
styles.messageLabelLeft,
styles.messageLabelRight,
]);
const editedLabel = React.useMemo(() => {
if (!label) {
return null;
}
return (
{label}
);
}, [editedLabelStyle, label]);
const unreadStyle = sidebarThreadInfo?.currentUser.unread
? styles.unread
: null;
const repliesStyles = React.useMemo(
() => [styles.repliesText, styles.repliesTextColor, unreadStyle],
[styles.repliesText, styles.repliesTextColor, unreadStyle],
);
const onPressSidebar = React.useCallback(() => {
if (sidebarThreadInfo && !disabled) {
navigateToThread({ threadInfo: sidebarThreadInfo });
}
}, [disabled, navigateToThread, sidebarThreadInfo]);
const repliesText = getInlineEngagementSidebarText(sidebarThreadInfo);
const sidebarStyle = React.useMemo(() => {
const stylesResult = [styles.sidebar, styles.sidebarColor];
if (Object.keys(reactions).length === 0) {
return stylesResult;
}
if (isRight) {
stylesResult.push(styles.sidebarMarginLeft);
} else {
stylesResult.push(styles.sidebarMarginRight);
}
return stylesResult;
}, [
isRight,
reactions,
styles.sidebar,
styles.sidebarColor,
styles.sidebarMarginLeft,
styles.sidebarMarginRight,
]);
const sidebarItem = React.useMemo(() => {
if (!sidebarThreadInfo) {
return null;
}
return (
{repliesText}
);
}, [
sidebarThreadInfo,
onPressSidebar,
sidebarStyle,
styles.icon,
repliesStyles,
repliesText,
]);
const nextLocalID = useSelector(state => state.nextLocalID);
const localID = `${localIDPrefix}${nextLocalID}`;
const sendReaction = useSendReaction(
messageInfo.id,
localID,
threadInfo.id,
reactions,
);
const onPressReaction = React.useCallback(
(reaction: string) => sendReaction(reaction),
[sendReaction],
);
const onLongPressReaction = React.useCallback(() => {
navigate<'MessageReactionsModal'>({
name: MessageReactionsModalRouteName,
params: { reactions },
});
}, [navigate, reactions]);
const reactionStyle = React.useMemo(() => {
const stylesResult = [
styles.reactionsContainer,
styles.reactionsContainerColor,
];
if (isRight) {
stylesResult.push(styles.reactionsContainerMarginLeft);
} else {
stylesResult.push(styles.reactionsContainerMarginRight);
}
return stylesResult;
}, [
isRight,
styles.reactionsContainer,
styles.reactionsContainerColor,
styles.reactionsContainerMarginLeft,
styles.reactionsContainerMarginRight,
]);
const reactionList = React.useMemo(() => {
if (Object.keys(reactions).length === 0) {
return null;
}
return Object.keys(reactions).map(reaction => {
const reactionInfo = reactions[reaction];
const numOfReacts = reactionInfo.users.length;
const style = reactionInfo.viewerReacted
? [...reactionStyle, styles.reactionsContainerSelected]
: reactionStyle;
return (
onPressReaction(reaction)}
onLongPress={onLongPressReaction}
activeOpacity={0.7}
key={reaction}
>
{`${reaction} ${numOfReacts}`}
);
});
}, [
onLongPressReaction,
onPressReaction,
reactionStyle,
reactions,
styles.reaction,
styles.reactionColor,
styles.reactionsContainerSelected,
]);
const inlineEngagementPositionStyle = React.useMemo(() => {
const styleResult = [styles.inlineEngagement];
if (isRight) {
styleResult.push(styles.rightInlineEngagement);
} else if (isCenter) {
styleResult.push(styles.centerInlineEngagement);
}
return styleResult;
}, [
isCenter,
isRight,
styles.centerInlineEngagement,
styles.inlineEngagement,
styles.rightInlineEngagement,
]);
return (
{editedLabel}
{sidebarItem}
{reactionList}
);
}
const unboundStyles = {
inlineEngagement: {
flexDirection: 'row',
marginBottom: inlineEngagementStyle.marginBottom,
marginLeft: avatarOffset,
flexWrap: 'wrap',
top: inlineEngagementStyle.topOffset,
},
dummyInlineEngagement: {
marginRight: 8,
},
centerInlineEngagement: {
marginLeft: 20,
marginRight: 20,
justifyContent: 'center',
},
rightInlineEngagement: {
flexDirection: 'row-reverse',
marginLeft: inlineEngagementRightStyle.marginLeft,
},
sidebar: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
marginTop: inlineEngagementStyle.marginTop,
},
dummySidebar: {
paddingRight: 8,
// 14 (icon) + 4 (marginRight of icon) + 8 (original left padding)
paddingLeft: 26,
marginRight: 4,
},
sidebarColor: {
backgroundColor: 'inlineEngagementBackground',
},
sidebarMarginLeft: {
marginLeft: 4,
},
sidebarMarginRight: {
marginRight: 4,
},
icon: {
color: 'inlineEngagementLabel',
marginRight: 4,
},
repliesText: {
fontSize: 14,
lineHeight: 22,
},
repliesTextColor: {
color: 'inlineEngagementLabel',
},
unread: {
color: 'listForegroundLabel',
fontWeight: 'bold',
},
reactionsContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
marginTop: inlineEngagementStyle.marginTop,
},
dummyReactionContainer: {
marginRight: 4,
},
reactionsContainerColor: {
backgroundColor: 'inlineEngagementBackground',
},
reactionsContainerSelected: {
borderWidth: 1,
borderColor: 'inlineEngagementLabel',
paddingHorizontal: 7,
paddingVertical: 3,
},
reactionsContainerMarginLeft: {
marginLeft: 4,
},
reactionsContainerMarginRight: {
marginRight: 4,
},
reaction: {
fontSize: 14,
lineHeight: 22,
},
reactionColor: {
color: 'inlineEngagementLabel',
},
messageLabel: {
paddingHorizontal: 3,
fontSize: 13,
top: inlineEngagementLabelStyle.topOffset,
height: inlineEngagementLabelStyle.height,
marginTop: inlineEngagementStyle.marginTop,
},
dummyMessageLabel: {
marginLeft: 9,
marginRight: 4,
},
messageLabelColor: {
color: 'messageLabel',
},
messageLabelLeft: {
marginLeft: 9,
marginRight: 4,
},
messageLabelRight: {
marginRight: 10,
marginLeft: 4,
},
avatarOffset: {
width: avatarOffset,
},
};
type TooltipInlineEngagementProps = {
+item: ChatMessageInfoItemWithHeight,
+isOpeningSidebar: boolean,
+progress: Animated.Node,
+windowWidth: number,
+positioning: 'left' | 'right' | 'center',
+initialCoordinates: {
+x: number,
+y: number,
+width: number,
+height: number,
},
};
function TooltipInlineEngagement(
props: TooltipInlineEngagementProps,
): React.Node {
const {
item,
isOpeningSidebar,
progress,
windowWidth,
initialCoordinates,
positioning,
} = props;
// ESLint doesn't recognize that invariant always throws
// eslint-disable-next-line consistent-return
const inlineEngagementStyles = React.useMemo(() => {
if (positioning === 'left') {
return {
position: 'absolute',
top: inlineEngagementStyle.marginTop + inlineEngagementStyle.topOffset,
left: composedMessageStyle.marginLeft,
};
} else if (positioning === 'right') {
return {
position: 'absolute',
right:
inlineEngagementRightStyle.marginLeft +
composedMessageStyle.marginRight,
top: inlineEngagementStyle.marginTop + inlineEngagementStyle.topOffset,
};
} else if (positioning === 'center') {
return {
alignSelf: 'center',
top: inlineEngagementCenterStyle.topOffset,
};
}
invariant(
false,
`${positioning} is not a valid positioning value for InlineEngagement`,
);
}, [positioning]);
const inlineEngagementContainer = React.useMemo(() => {
const opacity = isOpeningSidebar
? 0
: interpolateNode(progress, {
inputRange: [0, 1],
outputRange: [1, 0],
extrapolate: Extrapolate.CLAMP,
});
return {
position: 'absolute',
width: windowWidth,
top: initialCoordinates.height,
left: -initialCoordinates.x,
opacity,
};
}, [
initialCoordinates.height,
initialCoordinates.x,
isOpeningSidebar,
progress,
windowWidth,
]);
return (
);
}
-export { InlineEngagement, TooltipInlineEngagement, DummyInlineEngagementNode };
+export {
+ InlineEngagement,
+ TooltipInlineEngagement,
+ DummyInlineEngagementNode,
+ dummyNodeForInlineEngagementHeightMeasurement,
+};
diff --git a/native/chat/multimedia-message-utils.js b/native/chat/multimedia-message-utils.js
index 1269d2ce6..3f92c8098 100644
--- a/native/chat/multimedia-message-utils.js
+++ b/native/chat/multimedia-message-utils.js
@@ -1,143 +1,149 @@
// @flow
import invariant from 'invariant';
import { messageKey } from 'lib/shared/message-utils.js';
import type { MediaInfo } from 'lib/types/media-types.js';
import type { MultimediaMessageInfo } from 'lib/types/message-types.js';
-import { inlineEngagementStyle, clusterEndHeight } from './chat-constants.js';
+import { clusterEndHeight } from './chat-constants.js';
import { failedSendHeight } from './failed-send.react.js';
import { authorNameHeight } from './message-header.react.js';
import type {
ChatMultimediaMessageInfoItem,
MultimediaContentSizes,
} from '../types/chat-types.js';
const spaceBetweenImages = 4;
function getMediaPerRow(mediaCount: number): number {
if (mediaCount === 0) {
return 0; // ???
} else if (mediaCount === 1) {
return 1;
} else if (mediaCount === 2) {
return 2;
} else if (mediaCount === 3) {
return 3;
} else if (mediaCount === 4) {
return 2;
} else {
return 3;
}
}
function multimediaMessageSendFailed(
item: ChatMultimediaMessageInfoItem,
): boolean {
const { messageInfo, localMessageInfo, pendingUploads } = item;
const { id: serverID } = messageInfo;
if (serverID !== null && serverID !== undefined) {
return false;
}
const { isViewer } = messageInfo.creator;
if (!isViewer) {
return false;
}
if (localMessageInfo && localMessageInfo.sendFailed) {
return true;
}
for (const media of messageInfo.media) {
const pendingUpload = pendingUploads && pendingUploads[media.id];
if (pendingUpload && pendingUpload.failed) {
return true;
}
}
return !pendingUploads;
}
// The results are merged into ChatMultimediaMessageInfoItem
function multimediaMessageContentSizes(
messageInfo: MultimediaMessageInfo,
composedMessageMaxWidth: number,
): MultimediaContentSizes {
invariant(messageInfo.media.length > 0, 'should have media');
if (messageInfo.media.length === 1) {
const [media] = messageInfo.media;
const { height, width } = media.dimensions;
let imageHeight = height;
if (width > composedMessageMaxWidth) {
imageHeight = (height * composedMessageMaxWidth) / width;
}
if (imageHeight < 50) {
imageHeight = 50;
}
let contentWidth = height ? (width * imageHeight) / height : 0;
if (contentWidth > composedMessageMaxWidth) {
contentWidth = composedMessageMaxWidth;
}
return { imageHeight, contentHeight: imageHeight, contentWidth };
}
const contentWidth = composedMessageMaxWidth;
const mediaPerRow = getMediaPerRow(messageInfo.media.length);
const marginSpace = spaceBetweenImages * (mediaPerRow - 1);
const imageHeight = (contentWidth - marginSpace) / mediaPerRow;
const numRows = Math.ceil(messageInfo.media.length / mediaPerRow);
const contentHeight =
numRows * imageHeight + (numRows - 1) * spaceBetweenImages;
return { imageHeight, contentHeight, contentWidth };
}
// Given a ChatMultimediaMessageInfoItem, determines exact height of row
function multimediaMessageItemHeight(
item: ChatMultimediaMessageInfoItem,
): number {
- const { messageInfo, contentHeight, startsCluster, endsCluster } = item;
+ const {
+ messageInfo,
+ contentHeight,
+ startsCluster,
+ endsCluster,
+ inlineEngagementHeight,
+ } = item;
+
const { creator } = messageInfo;
const { isViewer } = creator;
- let height = 5 + contentHeight; // 5 from marginBottom in ComposedMessage
+
+ // 5 from marginBottom in ComposedMessage
+ let height = 5 + contentHeight;
if (!isViewer && startsCluster) {
height += authorNameHeight;
}
if (endsCluster) {
height += clusterEndHeight;
}
if (multimediaMessageSendFailed(item)) {
height += failedSendHeight;
}
- if (item.threadCreatedFromMessage || Object.keys(item.reactions).length > 0) {
- height +=
- inlineEngagementStyle.height +
- inlineEngagementStyle.marginTop +
- inlineEngagementStyle.marginBottom;
+ if (inlineEngagementHeight) {
+ height += inlineEngagementHeight;
}
return height;
}
function getMediaKey(
item: ChatMultimediaMessageInfoItem,
mediaInfo: MediaInfo,
): string {
return `multimedia|${messageKey(item.messageInfo)}|${mediaInfo.index}`;
}
export {
multimediaMessageContentSizes,
multimediaMessageItemHeight,
multimediaMessageSendFailed,
getMediaPerRow,
spaceBetweenImages,
getMediaKey,
};
diff --git a/native/types/chat-types.js b/native/types/chat-types.js
index 74b84807e..ccfabb4b8 100644
--- a/native/types/chat-types.js
+++ b/native/types/chat-types.js
@@ -1,74 +1,78 @@
// @flow
import type { ReactionInfo } from 'lib/selectors/chat-selectors.js';
import type {
LocalMessageInfo,
MultimediaMessageInfo,
RobotextMessageInfo,
} from 'lib/types/message-types.js';
import type { TextMessageInfo } from 'lib/types/messages/text.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import type { EntityText } from 'lib/utils/entity-text.js';
import type { MessagePendingUploads } from '../input/input-state.js';
export type ChatRobotextMessageInfoItemWithHeight = {
+itemType: 'message',
+messageShapeType: 'robotext',
+messageInfo: RobotextMessageInfo,
+threadInfo: ThreadInfo,
+startsConversation: boolean,
+startsCluster: boolean,
+endsCluster: boolean,
+robotext: EntityText,
+threadCreatedFromMessage: ?ThreadInfo,
+contentHeight: number,
+reactions: ReactionInfo,
};
export type ChatTextMessageInfoItemWithHeight = {
+itemType: 'message',
+messageShapeType: 'text',
+messageInfo: TextMessageInfo,
+localMessageInfo: ?LocalMessageInfo,
+threadInfo: ThreadInfo,
+startsConversation: boolean,
+startsCluster: boolean,
+endsCluster: boolean,
+contentHeight: number,
+threadCreatedFromMessage: ?ThreadInfo,
+reactions: ReactionInfo,
+hasBeenEdited: ?boolean,
+isPinned: ?boolean,
};
+// We "measure" the contentHeight of a multimedia message using the media
+// dimensions. This means for multimedia messages we only need to actually
+// measure the inline engagement node
export type MultimediaContentSizes = {
+imageHeight: number,
+contentHeight: number,
+contentWidth: number,
};
export type ChatMultimediaMessageInfoItem = {
...MultimediaContentSizes,
+itemType: 'message',
+messageShapeType: 'multimedia',
+messageInfo: MultimediaMessageInfo,
+localMessageInfo: ?LocalMessageInfo,
+threadInfo: ThreadInfo,
+startsConversation: boolean,
+startsCluster: boolean,
+endsCluster: boolean,
+threadCreatedFromMessage: ?ThreadInfo,
+pendingUploads: ?MessagePendingUploads,
+reactions: ReactionInfo,
+hasBeenEdited: ?boolean,
+isPinned: ?boolean,
+ +inlineEngagementHeight: ?number,
};
export type ChatMessageInfoItemWithHeight =
| ChatRobotextMessageInfoItemWithHeight
| ChatTextMessageInfoItemWithHeight
| ChatMultimediaMessageInfoItem;
export type ChatMessageItemWithHeight =
| { itemType: 'loader' }
| ChatMessageInfoItemWithHeight;