diff --git a/src/components/dms/ActionsWrapper.tsx b/src/components/dms/ActionsWrapper.tsx index 1e4b40206e..bc0505cda5 100644 --- a/src/components/dms/ActionsWrapper.tsx +++ b/src/components/dms/ActionsWrapper.tsx @@ -4,23 +4,29 @@ import {useLingui} from '@lingui/react/macro' import {atoms as a} from '#/alf' import {MessageContextMenu} from '#/components/dms/MessageContextMenu' +import type * as bsky from '#/types/bsky' export function ActionsWrapper({ message, isFromSelf, + senderProfile, children, onTap, }: { message: ChatBskyConvoDefs.MessageView hasReactions?: boolean isFromSelf: boolean + senderProfile?: bsky.profile.AnyProfileView children: React.ReactNode onTap?: () => void }) { const {t: l} = useLingui() return ( - + {trigger => // will always be true, since this file is platform split trigger.IS_NATIVE && ( diff --git a/src/components/dms/ActionsWrapper.web.tsx b/src/components/dms/ActionsWrapper.web.tsx index 05df7b0324..12ccea03ab 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -10,6 +10,7 @@ import {MessageContextMenu} from '#/components/dms/MessageContextMenu' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' import * as Toast from '#/components/Toast' +import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' import {hasReachedReactionLimit} from './util' @@ -17,12 +18,14 @@ export function ActionsWrapper({ message, hasReactions, isFromSelf, + senderProfile, children, onTap, }: { message: ChatBskyConvoDefs.MessageView hasReactions?: boolean isFromSelf: boolean + senderProfile?: bsky.profile.AnyProfileView children: React.ReactNode onTap?: () => void }) { @@ -114,7 +117,7 @@ export function ActionsWrapper({ ) }} - + {({props, state, IS_NATIVE, control}) => { // always false, file is platform split if (IS_NATIVE) return null diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx index 9ab4521f2f..bc18eae21a 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -25,15 +25,18 @@ import {usePromptControl} from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' import {hasReachedReactionLimit} from './util' export let MessageContextMenu = ({ message, + senderProfile, children, onTap, }: { message: ChatBskyConvoDefs.MessageView + senderProfile?: bsky.profile.AnyProfileView children: TriggerProps['children'] onTap?: () => void }): React.ReactNode => { @@ -110,9 +113,7 @@ export let MessageContextMenu = ({ [l, convo, message, currentAccount?.did], ) - const sender = convo.convo.members.find( - member => member.did === message.sender.did, - ) + const sender = senderProfile return ( <> diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index 638f163d92..7628354af8 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -30,7 +30,6 @@ import {useQueryClient} from '@tanstack/react-query' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {makeProfileLink} from '#/lib/routes/links' -import {useConvoActive} from '#/state/messages/convo' import {type ConvoItem} from '#/state/messages/convo/types' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' @@ -43,7 +42,6 @@ import {InlineLinkText, Link} from '#/components/Link' import * as ProfileCard from '#/components/ProfileCard' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' -import type * as bsky from '#/types/bsky' import {DateDivider} from './DateDivider' import {useDateDividerToggle} from './DateDividerToggle' import {MessageItemEmbed} from './MessageItemEmbed' @@ -93,19 +91,20 @@ function isWithinClusterBoundary({ let MessageItem = ({ item, isGroupChat = false, - profile, }: { item: ConvoItem & {type: 'message' | 'pending-message'} isGroupChat?: boolean - profile?: bsky.profile.AnyProfileView }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() const {t: l} = useLingui() - const {convo} = useConvoActive() const moderationOpts = useModerationOpts() const queryClient = useQueryClient() + const profile = item.relatedProfiles.find( + p => p.did === item.message.sender.did, + ) + const reactionsControl = useDialogControl() const reactionTapRef = useRef(false) @@ -277,9 +276,7 @@ let MessageItem = ({ return l`You reacted ${reaction.value}` } else { const senderDid = reaction.sender.did - const memberSender = convo.members.find( - member => member.did === senderDid, - ) + const memberSender = item.relatedProfiles.find(p => p.did === senderDid) if (memberSender) { return l`${createSanitizedDisplayName(memberSender)} reacted ${reaction.value}` } @@ -290,7 +287,13 @@ let MessageItem = ({ one: '# person', other: '# people', })} reacted – ${groupedReactions.map(g => g.value).join(' ')}` - }, [reactions, groupedReactions, currentAccount?.did, convo.members, l]) + }, [ + reactions, + groupedReactions, + currentAccount?.did, + item.relatedProfiles, + l, + ]) const appliedReactions = ( @@ -375,7 +378,7 @@ let MessageItem = ({ ) : null} { if (reactionTapRef.current) return if (!hasLargeGapFromPrev) { diff --git a/src/components/dms/getSystemMessageInfo.ts b/src/components/dms/getSystemMessageInfo.ts index f811e2b92c..3c2c4327fe 100644 --- a/src/components/dms/getSystemMessageInfo.ts +++ b/src/components/dms/getSystemMessageInfo.ts @@ -1,4 +1,4 @@ -import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' +import {ChatBskyConvoDefs} from '@atproto/api' import {type MessageDescriptor} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -15,6 +15,7 @@ import { Unlock_Stroke2_Corner2_Rounded as UnlockIcon, } from '#/components/icons/Lock' import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil' +import type * as bsky from '#/types/bsky' export type SystemMessageInfo = { message: MessageDescriptor @@ -23,7 +24,7 @@ export type SystemMessageInfo = { function getReferredDisplayName( user: ChatBskyConvoDefs.SystemMessageReferredUser, - relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[], + relatedProfiles: bsky.profile.AnyProfileView[], ): string | null { const profile = relatedProfiles.find(p => p.did === user.did) return profile ? createSanitizedDisplayName(profile) : null @@ -31,7 +32,7 @@ function getReferredDisplayName( export function getSystemMessageInfo( data: ChatBskyConvoDefs.SystemMessageView['data'], - relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[], + relatedProfiles: bsky.profile.AnyProfileView[], ): SystemMessageInfo | null { if (ChatBskyConvoDefs.isSystemMessageDataAddMember(data)) { const name = getReferredDisplayName(data.member, relatedProfiles) diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index f52dd708f1..e8263e423d 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -374,9 +374,6 @@ export function MessagesList({ return ( member.did === item.message.sender.did, - )} isGroupChat={convoState.convo.kind === 'group'} /> ) diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 749131864f..28220b6f72 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -43,6 +43,7 @@ import { parseConvoView, } from '#/components/dms/util' import {IS_NATIVE} from '#/env' +import type * as bsky from '#/types/bsky' const logger = Logger.create(Logger.Context.ConversationAgent) @@ -107,10 +108,7 @@ export class Convo { {id: string; message: ChatBskyConvoSendMessage.InputSchema['message']} > = new Map() private deletedMessages: Set = new Set() - private systemMessageProfiles: Map< - string, - ChatBskyActorDefs.ProfileViewBasic - > = new Map() + private relatedProfiles: Map = new Map() private isProcessingPendingMessages = false @@ -483,7 +481,7 @@ export class Convo { this.newMessages = new Map() this.pendingMessages = new Map() this.deletedMessages = new Set() - this.systemMessageProfiles = new Map() + this.relatedProfiles = new Map() this.pendingMessageFailure = null this.fetchMessageHistoryError = undefined @@ -510,6 +508,11 @@ export class Convo { private setConvo(convo: ChatBskyConvoDefs.ConvoView) { this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo + if (this.convo) { + for (const member of this.convo.members) { + this.relatedProfiles.set(member.did, member) + } + } } private updateConvo(convo: Partial) { @@ -517,6 +520,9 @@ export class Convo { this.convo = parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ?? this.convo + for (const member of this.convo.members) { + this.relatedProfiles.set(member.did, member) + } } } @@ -704,7 +710,7 @@ export class Convo { if (relatedProfiles) { for (const profile of relatedProfiles) { - this.systemMessageProfiles.set(profile.did, profile) + this.relatedProfiles.set(profile.did, profile) } } @@ -824,6 +830,12 @@ export class Convo { */ this.latestRev = ev.rev + if ('relatedProfiles' in ev && Array.isArray(ev.relatedProfiles)) { + for (const profile of ev.relatedProfiles) { + this.relatedProfiles.set(profile.did, profile) + } + } + if ( ChatBskyConvoDefs.isLogCreateMessage(ev) && ChatBskyConvoDefs.isMessageView(ev.message) @@ -876,14 +888,6 @@ export class Convo { const systemView = toSystemMessageView(ev) if (systemView) { this.newMessages.set(systemView.id, systemView) - if ( - 'relatedProfiles' in ev && - Array.isArray(ev.relatedProfiles) - ) { - for (const profile of ev.relatedProfiles) { - this.systemMessageProfiles.set(profile.did, profile) - } - } needsCommit = true } } @@ -1168,6 +1172,44 @@ export class Convo { } } + private getRelatedProfilesForItem( + m: + | ChatBskyConvoDefs.MessageView + | ChatBskyConvoDefs.DeletedMessageView + | ChatBskyConvoDefs.SystemMessageView, + ): bsky.profile.AnyProfileView[] { + const seen = new Set() + const profiles: bsky.profile.AnyProfileView[] = [] + + const add = (did: string) => { + if (seen.has(did)) return + seen.add(did) + const p = this.relatedProfiles.get(did) + if (p) profiles.push(p) + } + + if (ChatBskyConvoDefs.isMessageView(m)) { + add(m.sender.did) + if (m.reactions) { + for (const reaction of m.reactions) { + add(reaction.sender.did) + } + } + } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { + add(m.sender.did) + } else if (ChatBskyConvoDefs.isSystemMessageView(m)) { + const data = m.data + if ('member' in data && data.member?.did) { + add(data.member.did) + } + if ('addedBy' in data && data.addedBy?.did) { + add(data.addedBy.did) + } + } + + return profiles + } + /* * Items in reverse order, since FlatList inverts */ @@ -1180,6 +1222,7 @@ export class Convo { type: 'message', key: m.id, message: m, + relatedProfiles: this.getRelatedProfilesForItem(m), nextMessage: null, prevMessage: null, }) @@ -1188,6 +1231,7 @@ export class Convo { type: 'deleted-message', key: m.id, message: m, + relatedProfiles: this.getRelatedProfilesForItem(m), nextMessage: null, prevMessage: null, }) @@ -1196,7 +1240,7 @@ export class Convo { type: 'system-message', key: m.id, message: m, - relatedProfiles: Array.from(this.systemMessageProfiles.values()), + relatedProfiles: this.getRelatedProfilesForItem(m), }) } }) @@ -1218,6 +1262,7 @@ export class Convo { type: 'message', key: m.id, message: m, + relatedProfiles: this.getRelatedProfilesForItem(m), nextMessage: null, prevMessage: null, }) @@ -1226,6 +1271,7 @@ export class Convo { type: 'deleted-message', key: m.id, message: m, + relatedProfiles: this.getRelatedProfilesForItem(m), nextMessage: null, prevMessage: null, }) @@ -1234,12 +1280,13 @@ export class Convo { type: 'system-message', key: m.id, message: m, - relatedProfiles: Array.from(this.systemMessageProfiles.values()), + relatedProfiles: this.getRelatedProfilesForItem(m), }) } }) this.pendingMessages.forEach(m => { + const senderProfile = this.relatedProfiles.get(this.senderUserDid) items.push({ type: 'pending-message', key: m.id, @@ -1255,6 +1302,7 @@ export class Convo { did: this.senderUserDid, }, }, + relatedProfiles: senderProfile ? [senderProfile] : [], nextMessage: null, prevMessage: null, failed: this.pendingMessageFailure !== null, diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 9be32ff5de..8b25cdeab6 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -1,12 +1,12 @@ import { type BskyAgent, - type ChatBskyActorDefs, type ChatBskyConvoDefs, type ChatBskyConvoSendMessage, } from '@atproto/api' import {type MessagesEventBus} from '#/state/messages/events/agent' import {type ConvoWithDetails} from '#/components/dms/util' +import type * as bsky from '#/types/bsky' export type ConvoParams = { convoId: string @@ -87,6 +87,7 @@ export type ConvoItem = type: 'message' key: string message: ChatBskyConvoDefs.MessageView + relatedProfiles: bsky.profile.AnyProfileView[] nextMessage: | ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView @@ -100,6 +101,7 @@ export type ConvoItem = type: 'pending-message' key: string message: ChatBskyConvoDefs.MessageView + relatedProfiles: bsky.profile.AnyProfileView[] nextMessage: | ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView @@ -118,6 +120,7 @@ export type ConvoItem = type: 'deleted-message' key: string message: ChatBskyConvoDefs.DeletedMessageView + relatedProfiles: bsky.profile.AnyProfileView[] nextMessage: | ChatBskyConvoDefs.MessageView | ChatBskyConvoDefs.DeletedMessageView @@ -131,7 +134,7 @@ export type ConvoItem = type: 'system-message' key: string message: ChatBskyConvoDefs.SystemMessageView - relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[] + relatedProfiles: bsky.profile.AnyProfileView[] } | { type: 'error'