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/composed-message-width.js b/native/chat/composed-message-width.js --- a/native/chat/composed-message-width.js +++ b/native/chat/composed-message-width.js @@ -1,6 +1,8 @@ // @flow +import { avatarOffset } from './chat-constants.js'; import { useSelector } from '../redux/redux-utils.js'; +import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; function useMessageListScreenWidth(): number { return useSelector(state => { @@ -12,6 +14,12 @@ // Keep sorta synced with styles.alignment/styles.messageBox in ComposedMessage function useComposedMessageMaxWidth(): number { const messageListScreenWidth = useMessageListScreenWidth(); + const shouldRenderAvatars = useShouldRenderAvatars(); + + if (shouldRenderAvatars) { + return (messageListScreenWidth - 24 - avatarOffset) * 0.8; + } + return (messageListScreenWidth - 24) * 0.8; } 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,10 +25,12 @@ 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'; import { type AnimatedStyleObj, AnimatedView } from '../types/styles.js'; +import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; /* eslint-disable import/no-named-as-default-member */ const { Node } = Animated; @@ -51,6 +55,7 @@ // withInputState +inputState: ?InputState, +navigateToSidebar: () => mixed, + +shouldRenderAvatars: boolean, }; class ComposedMessage extends React.PureComponent { render() { @@ -67,6 +72,7 @@ navigateToSidebar, contentAndHeaderOpacity, deliveryIconOpacity, + shouldRenderAvatars, ...viewProps } = this.props; const { id, creator } = item.messageInfo; @@ -85,7 +91,16 @@ styles.alignment, { marginBottom: containerMarginBottom }, ]; - const messageBoxStyle = { maxWidth: composedMessageMaxWidth }; + const swipeableMessageBoxStyle = [ + styles.swipeableContainer, + { maxWidth: composedMessageMaxWidth }, + ]; + + const messageBoxStyleContainerStyle = [styles.messageBoxContainer]; + const positioningStyle = isViewer + ? { alignItems: 'flex-end' } + : { alignItems: 'flex-start' }; + messageBoxStyleContainerStyle.push(positioningStyle); let deliveryIcon = null; let failedSendInfo = null; @@ -121,15 +136,29 @@ swipeOptions === 'sidebar' || swipeOptions === 'both' ? navigateToSidebar : undefined; + + let avatar; + if (!isViewer && item.endsCluster && shouldRenderAvatars) { + const avatarInfo = getAvatarForUser(item.messageInfo.creator); + avatar = ( + + + + ); + } else if (!isViewer && shouldRenderAvatars) { + avatar = ; + } + const messageBox = ( - + + {avatar} {children} @@ -143,10 +172,17 @@ Object.keys(item.reactions).length > 0 ) { const positioning = isViewer ? 'right' : 'left'; - const inlineEngagementPositionStyle = - positioning === 'left' - ? styles.leftInlineEngagement - : styles.rightInlineEngagement; + + const inlineEngagementPositionStyle = []; + if (positioning === 'left') { + inlineEngagementPositionStyle.push(styles.leftInlineEngagement); + } else { + inlineEngagementPositionStyle.push(styles.rightInlineEngagement); + } + if (this.props.shouldRenderAvatars) { + inlineEngagementPositionStyle.push({ marginLeft: avatarOffset }); + } + inlineEngagement = ( = @@ -233,6 +280,8 @@ const navigateToSidebar = useNavigateToSidebar(props.item); const contentAndHeaderOpacity = useContentAndHeaderOpacity(props.item); const deliveryIconOpacity = useDeliveryIconOpacity(props.item); + const shouldRenderAvatars = useShouldRenderAvatars(); + return ( ); }); 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 @@ -5,12 +5,13 @@ import { useStringForUser } from 'lib/hooks/ens-cache.js'; -import { clusterEndHeight } from './chat-constants.js'; +import { clusterEndHeight, avatarOffset } from './chat-constants.js'; import type { DisplayType } from './timestamp.react.js'; import { Timestamp, timestampHeight } from './timestamp.react.js'; import { SingleLine } from '../components/single-line.react.js'; import { useStyles } from '../themes/colors.js'; import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js'; +import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; type Props = { +item: ChatMessageInfoItemWithHeight, @@ -27,12 +28,21 @@ const shouldShowUsername = !isViewer && (modalDisplay || item.startsCluster); const stringForUser = useStringForUser(shouldShowUsername ? creator : null); + const shouldRenderAvatars = useShouldRenderAvatars(); + let authorName = null; if (stringForUser) { const style = [styles.authorName]; if (modalDisplay) { style.push(styles.modal); } + + if (shouldRenderAvatars) { + style.push({ marginLeft: 12 + avatarOffset }); + } else { + style.push({ marginLeft: 12 }); + } + authorName = {stringForUser}; } @@ -69,7 +79,6 @@ color: 'listBackgroundSecondaryLabel', fontSize: 14, height: authorNameHeight, - marginLeft: 12, marginRight: 7, paddingHorizontal: 12, paddingVertical: 4, diff --git a/native/chat/message-tooltip-button-avatar.react.js b/native/chat/message-tooltip-button-avatar.react.js new file mode 100644 --- /dev/null +++ b/native/chat/message-tooltip-button-avatar.react.js @@ -0,0 +1,48 @@ +// @flow + +import * as React from 'react'; +import { View, StyleSheet } from 'react-native'; + +import { getAvatarForUser } from 'lib/shared/avatar-utils.js'; + +import { avatarOffset } from './chat-constants.js'; +import Avatar from '../components/avatar.react.js'; +import type { ChatMessageInfoItemWithHeight } from '../types/chat-types.js'; +import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; + +type Props = { + +item: ChatMessageInfoItemWithHeight, +}; + +function MessageTooltipButtonAvatar(props: Props): React.Node { + const { item } = props; + + const avatarInfo = React.useMemo( + () => getAvatarForUser(item.messageInfo.creator), + [item.messageInfo.creator], + ); + + const shouldRenderAvatars = useShouldRenderAvatars(); + + if (item.messageInfo.creator.isViewer || !shouldRenderAvatars) { + return null; + } + return ( + + + + ); +} + +const styles = StyleSheet.create({ + avatarContainer: { + bottom: 0, + left: -avatarOffset, + position: 'absolute', + }, +}); + +const MemoizedMessageTooltipButtonAvatar: React.ComponentType = + React.memo(MessageTooltipButtonAvatar); + +export default MemoizedMessageTooltipButtonAvatar; 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 @@ -10,6 +10,7 @@ import { TooltipInlineEngagement } from './inline-engagement.react.js'; import { InnerMultimediaMessage } from './inner-multimedia-message.react.js'; import { MessageHeader } from './message-header.react.js'; +import MessageTooltipButtonAvatar from './message-tooltip-button-avatar.react.js'; import { useSendReaction } from './reaction-message-utils.js'; import ReactionSelectionPopover from './reaction-selection-popover.react.js'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js'; @@ -161,6 +162,7 @@ + {reactionSelectionPopover} {innerMultimediaMessage} {inlineEngagement} 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 @@ -12,6 +12,7 @@ import { MessageHeader } from './message-header.react.js'; import { MessageListContextProvider } from './message-list-types.js'; import { MessagePressResponderContext } from './message-press-responder-context.js'; +import MessageTooltipButtonAvatar from './message-tooltip-button-avatar.react.js'; import { useSendReaction } from './reaction-message-utils.js'; import ReactionSelectionPopover from './reaction-selection-popover.react.js'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react.js'; @@ -158,6 +159,7 @@ + {reactionSelectionPopover}