diff --git a/native/chat/chat-constants.js b/native/chat/chat-constants.js --- a/native/chat/chat-constants.js +++ b/native/chat/chat-constants.js @@ -24,3 +24,5 @@ }; 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 --- a/native/chat/chat-item-height-measurer.react.js +++ b/native/chat/chat-item-height-measurer.react.js @@ -8,6 +8,7 @@ import { messageTypes, type MessageType } from 'lib/types/message-types.js'; import { entityTextToRawString } from 'lib/utils/entity-text.js'; +import { avatarOffset } from './chat-constants.js'; import type { MeasurementTask } from './chat-context-provider.react.js'; import { useComposedMessageMaxWidth } from './composed-message-width.js'; import { dummyNodeForRobotextMessageHeightMeasurement } from './inner-robotext-message.react.js'; @@ -43,7 +44,10 @@ ); const { messageInfo } = item; if (messageInfo.type === messageTypes.TEXT) { - return dummyNodeForTextMessageHeightMeasurement(messageInfo.text); + return dummyNodeForTextMessageHeightMeasurement( + messageInfo.text, + messageInfo.creator.isViewer, + ); } else if (item.robotext) { return dummyNodeForRobotextMessageHeightMeasurement( item.robotext, @@ -85,7 +89,7 @@ const pendingUploads = inputStatePendingUploads?.[id]; const sizes = multimediaMessageContentSizes( messageInfo, - composedMessageMaxWidth, + composedMessageMaxWidth - avatarOffset, ); return { itemType: 'message', 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 @@ -6,6 +6,7 @@ import { StyleSheet, View } from 'react-native'; import Animated from 'react-native-reanimated'; +import { getAvatarForUser } from 'lib/shared/avatar-utils.js'; import { createMessageReply } from 'lib/shared/message-utils.js'; import { assertComposableMessageType } from 'lib/types/message-types.js'; @@ -15,6 +16,7 @@ inlineEngagementLeftStyle, inlineEngagementRightStyle, composedMessageStyle, + avatarOffset, } from './chat-constants.js'; import { useComposedMessageMaxWidth } from './composed-message-width.js'; import { FailedSend } from './failed-send.react.js'; @@ -23,6 +25,7 @@ import { useNavigateToSidebar } from './sidebar-navigation.js'; import SwipeableMessage from './swipeable-message.react.js'; import { useContentAndHeaderOpacity, useDeliveryIconOpacity } from './utils.js'; +import Avatar from '../components/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'; @@ -121,6 +124,19 @@ swipeOptions === 'sidebar' || swipeOptions === 'both' ? navigateToSidebar : undefined; + + let avatar; + if (!isViewer && item.endsCluster) { + const avatarInfo = getAvatarForUser(item.messageInfo.creator); + avatar = ( + + + + ); + } else if (!isViewer) { + avatar = ; + } + const messageBox = ( - - {children} - + + {avatar} + + {children} + + ); @@ -187,6 +206,12 @@ marginLeft: composedMessageStyle.marginLeft, marginRight: composedMessageStyle.marginRight, }, + avatarContainer: { + marginRight: 8, + }, + avatarOffset: { + width: avatarOffset, + }, content: { alignItems: 'center', flexDirection: 'row-reverse', @@ -208,6 +233,7 @@ }, leftInlineEngagement: { justifyContent: 'flex-start', + marginLeft: 32, position: 'relative', top: inlineEngagementLeftStyle.topOffset, }, @@ -223,6 +249,10 @@ right: inlineEngagementRightStyle.marginRight, top: inlineEngagementRightStyle.topOffset, }, + swipeableContainer: { + alignItems: 'flex-end', + flexDirection: 'row', + }, }); const ConnectedComposedMessage: React.ComponentType = diff --git a/native/chat/inner-text-message.react.js b/native/chat/inner-text-message.react.js --- a/native/chat/inner-text-message.react.js +++ b/native/chat/inner-text-message.react.js @@ -6,6 +6,7 @@ import { colorIsDark } from 'lib/shared/thread-utils.js'; +import { avatarOffset } from './chat-constants.js'; import { useComposedMessageMaxWidth } from './composed-message-width.js'; import { useTextMessageMarkdownRules } from './message-list-types.js'; import { @@ -29,17 +30,24 @@ function dummyNodeForTextMessageHeightMeasurement( text: string, + isViewer: boolean, ): React.Element { - return {text}; + return {text}; } type DummyTextNodeProps = { ...React.ElementConfig, +children: string, + +isViewer: boolean, }; function DummyTextNode(props: DummyTextNodeProps): React.Node { - const { children, style, ...rest } = props; - const maxWidth = useComposedMessageMaxWidth(); + const { children, isViewer, style, ...rest } = props; + + let maxWidth = useComposedMessageMaxWidth(); + if (!isViewer) { + maxWidth = maxWidth - avatarOffset; + } + const viewStyle = [props.style, styles.dummyMessage, { maxWidth }]; const rules = useTextMessageMarkdownRules(false); return ( @@ -99,6 +107,12 @@ return [styles.text, textStyle]; }, [darkColor]); + let maxWidth = useComposedMessageMaxWidth(); + if (!isViewer) { + maxWidth = maxWidth - avatarOffset; + } + const maxWidthStyle = React.useMemo(() => ({ maxWidth }), [maxWidth]); + // 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 @@ -131,7 +145,7 @@ onPress={props.onPress} onLongPress={props.onPress} activeOpacity={0.6} - style={[styles.message, cornerStyle]} + style={[styles.message, cornerStyle, maxWidthStyle]} animatedStyle={messageStyle} > diff --git a/native/chat/message-header.react.js b/native/chat/message-header.react.js --- a/native/chat/message-header.react.js +++ b/native/chat/message-header.react.js @@ -69,7 +69,7 @@ color: 'listBackgroundSecondaryLabel', fontSize: 14, height: authorNameHeight, - marginLeft: 12, + marginLeft: 44, marginRight: 7, paddingHorizontal: 12, paddingVertical: 4, diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js --- a/native/chat/multimedia-message-tooltip-button.react.js +++ b/native/chat/multimedia-message-tooltip-button.react.js @@ -1,12 +1,15 @@ // @flow import * as React from 'react'; +import { StyleSheet, View } from 'react-native'; import Animated from 'react-native-reanimated'; import EmojiPicker from 'rn-emoji-keyboard'; +import { getAvatarForUser } from 'lib/shared/avatar-utils.js'; import { localIDPrefix } from 'lib/shared/message-utils.js'; import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils.js'; +import { avatarOffset } from './chat-constants.js'; import { TooltipInlineEngagement } from './inline-engagement.react.js'; import { InnerMultimediaMessage } from './inner-multimedia-message.react.js'; import { MessageHeader } from './message-header.react.js'; @@ -14,6 +17,7 @@ import ReactionSelectionPopover from './reaction-selection-popover.react.js'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js'; import { useAnimatedMessageTooltipButton } from './utils.js'; +import Avatar from '../components/avatar.react.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; @@ -151,6 +155,22 @@ [sendReaction, dismissTooltip], ); + const avatarInfo = React.useMemo( + () => getAvatarForUser(item.messageInfo.creator), + [item.messageInfo.creator], + ); + + const avatar = React.useMemo(() => { + if (item.messageInfo.creator.isViewer) { + return null; + } + return ( + + + + ); + }, [avatarInfo, item.messageInfo.creator.isViewer]); + return ( <> @@ -161,6 +181,7 @@ + {avatar} {reactionSelectionPopover} {innerMultimediaMessage} {inlineEngagement} @@ -174,4 +195,12 @@ ); } +const styles = StyleSheet.create({ + avatarContainer: { + bottom: 0, + left: -avatarOffset, + position: 'absolute', + }, +}); + export default MultimediaMessageTooltipButton; diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js --- a/native/chat/text-message-tooltip-button.react.js +++ b/native/chat/text-message-tooltip-button.react.js @@ -1,12 +1,15 @@ // @flow import * as React from 'react'; +import { StyleSheet, View } from 'react-native'; import Animated from 'react-native-reanimated'; import EmojiPicker from 'rn-emoji-keyboard'; +import { getAvatarForUser } from 'lib/shared/avatar-utils.js'; import { localIDPrefix } from 'lib/shared/message-utils.js'; import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils.js'; +import { avatarOffset } from './chat-constants.js'; import { TooltipInlineEngagement } from './inline-engagement.react.js'; import { InnerTextMessage } from './inner-text-message.react.js'; import { MessageHeader } from './message-header.react.js'; @@ -16,6 +19,7 @@ import ReactionSelectionPopover from './reaction-selection-popover.react.js'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js'; import { useAnimatedMessageTooltipButton } from './utils.js'; +import Avatar from '../components/avatar.react.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useTooltipActions } from '../tooltip/tooltip-hooks.js'; @@ -148,6 +152,22 @@ [sendReaction, dismissTooltip], ); + const avatarInfo = React.useMemo( + () => getAvatarForUser(item.messageInfo.creator), + [item.messageInfo.creator], + ); + + const avatar = React.useMemo(() => { + if (item.messageInfo.creator.isViewer) { + return null; + } + return ( + + + + ); + }, [avatarInfo, item.messageInfo.creator.isViewer]); + return ( + {avatar} {reactionSelectionPopover}