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,19 @@ styles.alignment, { marginBottom: containerMarginBottom }, ]; - const messageBoxStyle = { maxWidth: composedMessageMaxWidth }; + const messageBoxStyle = [{ maxWidth: composedMessageMaxWidth }]; + if (shouldRenderAvatars) { + messageBoxStyle.push(styles.swipeableContainer); + } + const messageBoxStyleContainerStyle = [styles.messageBox]; + if (shouldRenderAvatars) { + messageBoxStyleContainerStyle.push({ flex: 1 }); + + const positioningStyle = isViewer + ? { alignItems: 'flex-end' } + : { alignItems: 'flex-start' }; + messageBoxStyleContainerStyle.push(positioningStyle); + } let deliveryIcon = null; let failedSendInfo = null; @@ -121,8 +139,21 @@ 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 +175,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 +282,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/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,10 +17,12 @@ 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'; import type { TooltipRoute } from '../tooltip/tooltip.react.js'; +import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; /* eslint-disable import/no-named-as-default-member */ const { Node, Extrapolate, interpolateNode } = Animated; @@ -151,6 +156,23 @@ [sendReaction, dismissTooltip], ); + const avatarInfo = React.useMemo( + () => getAvatarForUser(item.messageInfo.creator), + [item.messageInfo.creator], + ); + const shouldRenderAvatars = useShouldRenderAvatars(); + + const avatar = React.useMemo(() => { + if (item.messageInfo.creator.isViewer || !shouldRenderAvatars) { + return null; + } + return ( + + + + ); + }, [avatarInfo, item.messageInfo.creator.isViewer, shouldRenderAvatars]); + return ( <> @@ -161,6 +183,7 @@ + {avatar} {reactionSelectionPopover} {innerMultimediaMessage} {inlineEngagement} @@ -174,4 +197,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,10 +19,12 @@ 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'; import type { TooltipRoute } from '../tooltip/tooltip.react.js'; +import { useShouldRenderAvatars } from '../utils/avatar-utils.js'; /* eslint-disable import/no-named-as-default-member */ const { Node, interpolateNode, Extrapolate } = Animated; @@ -148,6 +153,23 @@ [sendReaction, dismissTooltip], ); + const avatarInfo = React.useMemo( + () => getAvatarForUser(item.messageInfo.creator), + [item.messageInfo.creator], + ); + const shouldRenderAvatars = useShouldRenderAvatars(); + + const avatar = React.useMemo(() => { + if (item.messageInfo.creator.isViewer || !shouldRenderAvatars) { + return null; + } + return ( + + + + ); + }, [avatarInfo, item.messageInfo.creator.isViewer, shouldRenderAvatars]); + return ( + {avatar} {reactionSelectionPopover}