diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index a54bbe0119..f263efb850 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -226,7 +226,6 @@ export function MessagesScreen({navigation, route}: Props) { onEndReachedThreshold={isNative ? 1.5 : 0} initialNumToRender={initialNumToRender} windowSize={11} - // @ts-ignore our .web version only -sfn desktopFixedHeight sideBorders={false} /> diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index b8b0bfe0d3..3b174060b1 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -1,6 +1,11 @@ import React, {useCallback} from 'react' import {View} from 'react-native' -import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' +import { + AppBskyActorDefs, + moderateProfile, + ModerationCause, + ModerationDecision, +} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' @@ -86,6 +91,23 @@ function Inner() { !convoState.isFetchingHistory && convoState.items.length === 0) + const moderation = React.useMemo(() => { + if (!recipient || !moderationOpts) return + return moderateProfile(recipient, moderationOpts) + }, [recipient, moderationOpts]) + + const blockInfo = React.useMemo(() => { + if (!moderation) return + const modui = moderation.ui('profileView') + const blocks = modui.alerts.filter(alert => alert.type === 'blocking') + const listBlocks = blocks.filter(alert => alert.source.type === 'list') + const userBlock = blocks.find(alert => alert.source.type === 'user') + return { + listBlocks, + userBlock, + } + }, [moderation]) + // Any time that we re-render the `Initializing` state, we have to reset `hasScrolled` to false. After entering this // state, we know that we're resetting the list of messages and need to re-scroll to the bottom when they get added. React.useEffect(() => { @@ -110,11 +132,18 @@ function Inner() { return ( - {!readyToShow && } + {!readyToShow && ( + + )} - {moderationOpts && recipient ? ( + {moderation && blockInfo && recipient ? ( > @@ -161,21 +195,6 @@ function InnerReady({ const verifyEmailControl = useDialogControl() const {needsEmailVerification} = useEmail() - const moderation = React.useMemo(() => { - return moderateProfile(recipient, moderationOpts) - }, [recipient, moderationOpts]) - - const blockInfo = React.useMemo(() => { - const modui = moderation.ui('profileView') - const blocks = modui.alerts.filter(alert => alert.type === 'blocking') - const listBlocks = blocks.filter(alert => alert.source.type === 'list') - const userBlock = blocks.find(alert => alert.source.type === 'user') - return { - listBlocks, - userBlock, - } - }, [moderation]) - React.useEffect(() => { if (needsEmailVerification) { verifyEmailControl.open() diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index 6b8deea30e..11c4e57240 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -9,6 +9,7 @@ import { } from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useQueryClient} from '@tanstack/react-query' import {GestureActionView} from '#/lib/custom-animations/GestureActionView' import {useHaptics} from '#/lib/haptics' @@ -24,6 +25,7 @@ import {isNative} from '#/platform/detection' import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' +import {precacheProfile} from '#/state/queries/profile' import {useSession} from '#/state/session' import {TimeElapsed} from '#/view/com/util/TimeElapsed' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' @@ -77,6 +79,7 @@ function ChatListItemReady({ moderationOpts: ModerationOpts }) { const t = useTheme() + const queryClient = useQueryClient() const {_} = useLingui() const {currentAccount} = useSession() const menuControl = useMenuControl() @@ -189,6 +192,7 @@ function ChatListItemReady({ const onPress = useCallback( (e: GestureResponderEvent) => { decrementBadgeCount(convo.unreadCount) + precacheProfile(queryClient, profile) if (isDeletedAccount) { e.preventDefault() menuControl.open() @@ -197,7 +201,7 @@ function ChatListItemReady({ logEvent('chat:open', {logContext: 'ChatsList'}) } }, - [convo.unreadCount, isDeletedAccount, menuControl], + [convo.unreadCount, isDeletedAccount, menuControl, queryClient, profile], ) const onLongPress = useCallback(() => { diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 53d77046a2..d19f1728af 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -3,9 +3,11 @@ import { BskyAgent, ChatBskyConvoDefs, ChatBskyConvoGetLog, + ChatBskyConvoListConvos, ChatBskyConvoSendMessage, } from '@atproto/api' import {XRPCError} from '@atproto/xrpc' +import {InfiniteData, QueryClient} from '@tanstack/react-query' import EventEmitter from 'eventemitter3' import {nanoid} from 'nanoid/non-secure' @@ -33,6 +35,7 @@ import { import {MessagesEventBus} from '#/state/messages/events/agent' import {MessagesEventBusError} from '#/state/messages/events/types' import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' +import {RQKEY as RQKEY_LIST_CONVOS} from '#/state/queries/messages/list-conversations' export function isConvoItemMessage( item: ConvoItem, @@ -83,6 +86,7 @@ export class Convo { sender: AppBskyActorDefs.ProfileViewBasic | undefined recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined snapshot: ConvoState | undefined + queryClient: QueryClient | undefined constructor(params: ConvoParams) { this.id = nanoid(3) @@ -90,6 +94,7 @@ export class Convo { this.agent = params.agent this.events = params.events this.senderUserDid = params.agent.session?.did! + this.queryClient = params.queryClient this.subscribe = this.subscribe.bind(this) this.getSnapshot = this.getSnapshot.bind(this) @@ -99,6 +104,8 @@ export class Convo { this.ingestFirehose = this.ingestFirehose.bind(this) this.onFirehoseConnect = this.onFirehoseConnect.bind(this) this.onFirehoseError = this.onFirehoseError.bind(this) + this.optimisticFetchConvoFromCache = + this.optimisticFetchConvoFromCache.bind(this) } private commit() { @@ -426,6 +433,8 @@ export class Convo { private async setup() { try { + this.optimisticFetchConvoFromCache() + const {convo, sender, recipients} = await this.fetchConvo() this.convo = convo @@ -508,6 +517,25 @@ export class Convo { } } + private optimisticFetchConvoFromCache() { + if (!this.queryClient) return + if (this.convo) return // bail if already loaded + + const convoList = + this.queryClient.getQueryData< + InfiniteData + >(RQKEY_LIST_CONVOS) + + const convo = convoList?.pages + ?.flatMap(c => c.convos) + ?.find(c => c.id === this.convoId) + if (convo) { + this.convo = convo + this.sender = convo.members.find(m => m.did === this.senderUserDid) + this.recipients = convo.members.filter(m => m.did !== this.senderUserDid) + } + } + private pendingFetchConvo: | Promise<{ convo: ChatBskyConvoDefs.ConvoView @@ -525,7 +553,7 @@ export class Convo { }>(async (resolve, reject) => { try { const response = await networkRetry(2, () => { - return this.agent.api.chat.bsky.convo.getConvo( + return this.agent.chat.bsky.convo.getConvo( { convoId: this.convoId, }, @@ -592,7 +620,7 @@ export class Convo { const nextCursor = this.oldestRev // for TS const response = await networkRetry(2, () => { - return this.agent.api.chat.bsky.convo.getMessages( + return this.agent.chat.bsky.convo.getMessages( { cursor: nextCursor, convoId: this.convoId, @@ -793,7 +821,7 @@ export class Convo { const {id, message} = pendingMessage - const response = await this.agent.api.chat.bsky.convo.sendMessage( + const response = await this.agent.chat.bsky.convo.sendMessage( { convoId: this.convoId, message, @@ -888,7 +916,7 @@ export class Convo { ) try { - const {data} = await this.agent.api.chat.bsky.convo.sendMessageBatch( + const {data} = await this.agent.chat.bsky.convo.sendMessageBatch( { items: messageArray.map(({message}) => ({ convoId: this.convoId, @@ -935,7 +963,7 @@ export class Convo { try { await networkRetry(2, () => { - return this.agent.api.chat.bsky.convo.deleteMessageForSelf( + return this.agent.chat.bsky.convo.deleteMessageForSelf( { convoId: this.convoId, messageId, diff --git a/src/state/messages/convo/index.tsx b/src/state/messages/convo/index.tsx index 10ec2a348a..e1896fbf47 100644 --- a/src/state/messages/convo/index.tsx +++ b/src/state/messages/convo/index.tsx @@ -66,6 +66,7 @@ export function ConvoProvider({ convoId, agent, events, + queryClient, }), ) const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot) diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 21772262ea..0dc183cfe5 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -4,6 +4,7 @@ import { ChatBskyConvoDefs, ChatBskyConvoSendMessage, } from '@atproto/api' +import {QueryClient} from '@tanstack/react-query' import {MessagesEventBus} from '#/state/messages/events/agent' @@ -11,6 +12,7 @@ export type ConvoParams = { convoId: string agent: BskyAgent events: MessagesEventBus + queryClient?: QueryClient } export enum ConvoStatus {