optimistic fetch convo data

This commit is contained in:
Samuel Newman
2024-12-12 01:25:20 +00:00
parent e49dad2889
commit d9ffaddec1
6 changed files with 81 additions and 28 deletions
-1
View File
@@ -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}
/>
+40 -21
View File
@@ -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 (
<Layout.Center style={[a.flex_1]}>
{!readyToShow && <MessagesListHeader />}
{!readyToShow && (
<MessagesListHeader
profile={recipient}
moderation={moderation}
blockInfo={blockInfo}
/>
)}
<View style={[a.flex_1]}>
{moderationOpts && recipient ? (
{moderation && blockInfo && recipient ? (
<InnerReady
moderationOpts={moderationOpts}
moderation={moderation}
blockInfo={blockInfo}
recipient={recipient}
hasScrolled={hasScrolled}
setHasScrolled={setHasScrolled}
@@ -144,12 +173,17 @@ function Inner() {
}
function InnerReady({
moderationOpts,
moderation,
blockInfo,
recipient: recipientUnshadowed,
hasScrolled,
setHasScrolled,
}: {
moderationOpts: ModerationOpts
moderation: ModerationDecision
blockInfo: {
listBlocks: ModerationCause[]
userBlock: ModerationCause | undefined
}
recipient: AppBskyActorDefs.ProfileViewBasic
hasScrolled: boolean
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
@@ -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()
@@ -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(() => {
+33 -5
View File
@@ -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<ChatBskyConvoListConvos.OutputSchema, string | undefined>
>(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,
+1
View File
@@ -66,6 +66,7 @@ export function ConvoProvider({
convoId,
agent,
events,
queryClient,
}),
)
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
+2
View File
@@ -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 {