optimistic fetch convo data
This commit is contained in:
@@ -226,7 +226,6 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
onEndReachedThreshold={isNative ? 1.5 : 0}
|
onEndReachedThreshold={isNative ? 1.5 : 0}
|
||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
// @ts-ignore our .web version only -sfn
|
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
sideBorders={false}
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import React, {useCallback} from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {View} from 'react-native'
|
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 {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||||
@@ -86,6 +91,23 @@ function Inner() {
|
|||||||
!convoState.isFetchingHistory &&
|
!convoState.isFetchingHistory &&
|
||||||
convoState.items.length === 0)
|
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
|
// 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.
|
// 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(() => {
|
React.useEffect(() => {
|
||||||
@@ -110,11 +132,18 @@ function Inner() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Center style={[a.flex_1]}>
|
<Layout.Center style={[a.flex_1]}>
|
||||||
{!readyToShow && <MessagesListHeader />}
|
{!readyToShow && (
|
||||||
|
<MessagesListHeader
|
||||||
|
profile={recipient}
|
||||||
|
moderation={moderation}
|
||||||
|
blockInfo={blockInfo}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<View style={[a.flex_1]}>
|
<View style={[a.flex_1]}>
|
||||||
{moderationOpts && recipient ? (
|
{moderation && blockInfo && recipient ? (
|
||||||
<InnerReady
|
<InnerReady
|
||||||
moderationOpts={moderationOpts}
|
moderation={moderation}
|
||||||
|
blockInfo={blockInfo}
|
||||||
recipient={recipient}
|
recipient={recipient}
|
||||||
hasScrolled={hasScrolled}
|
hasScrolled={hasScrolled}
|
||||||
setHasScrolled={setHasScrolled}
|
setHasScrolled={setHasScrolled}
|
||||||
@@ -144,12 +173,17 @@ function Inner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InnerReady({
|
function InnerReady({
|
||||||
moderationOpts,
|
moderation,
|
||||||
|
blockInfo,
|
||||||
recipient: recipientUnshadowed,
|
recipient: recipientUnshadowed,
|
||||||
hasScrolled,
|
hasScrolled,
|
||||||
setHasScrolled,
|
setHasScrolled,
|
||||||
}: {
|
}: {
|
||||||
moderationOpts: ModerationOpts
|
moderation: ModerationDecision
|
||||||
|
blockInfo: {
|
||||||
|
listBlocks: ModerationCause[]
|
||||||
|
userBlock: ModerationCause | undefined
|
||||||
|
}
|
||||||
recipient: AppBskyActorDefs.ProfileViewBasic
|
recipient: AppBskyActorDefs.ProfileViewBasic
|
||||||
hasScrolled: boolean
|
hasScrolled: boolean
|
||||||
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
|
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
@@ -161,21 +195,6 @@ function InnerReady({
|
|||||||
const verifyEmailControl = useDialogControl()
|
const verifyEmailControl = useDialogControl()
|
||||||
const {needsEmailVerification} = useEmail()
|
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(() => {
|
React.useEffect(() => {
|
||||||
if (needsEmailVerification) {
|
if (needsEmailVerification) {
|
||||||
verifyEmailControl.open()
|
verifyEmailControl.open()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {GestureActionView} from '#/lib/custom-animations/GestureActionView'
|
import {GestureActionView} from '#/lib/custom-animations/GestureActionView'
|
||||||
import {useHaptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
@@ -24,6 +25,7 @@ import {isNative} from '#/platform/detection'
|
|||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
|
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
|
||||||
|
import {precacheProfile} from '#/state/queries/profile'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
@@ -77,6 +79,7 @@ function ChatListItemReady({
|
|||||||
moderationOpts: ModerationOpts
|
moderationOpts: ModerationOpts
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const menuControl = useMenuControl()
|
const menuControl = useMenuControl()
|
||||||
@@ -189,6 +192,7 @@ function ChatListItemReady({
|
|||||||
const onPress = useCallback(
|
const onPress = useCallback(
|
||||||
(e: GestureResponderEvent) => {
|
(e: GestureResponderEvent) => {
|
||||||
decrementBadgeCount(convo.unreadCount)
|
decrementBadgeCount(convo.unreadCount)
|
||||||
|
precacheProfile(queryClient, profile)
|
||||||
if (isDeletedAccount) {
|
if (isDeletedAccount) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
menuControl.open()
|
menuControl.open()
|
||||||
@@ -197,7 +201,7 @@ function ChatListItemReady({
|
|||||||
logEvent('chat:open', {logContext: 'ChatsList'})
|
logEvent('chat:open', {logContext: 'ChatsList'})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[convo.unreadCount, isDeletedAccount, menuControl],
|
[convo.unreadCount, isDeletedAccount, menuControl, queryClient, profile],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onLongPress = useCallback(() => {
|
const onLongPress = useCallback(() => {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import {
|
|||||||
BskyAgent,
|
BskyAgent,
|
||||||
ChatBskyConvoDefs,
|
ChatBskyConvoDefs,
|
||||||
ChatBskyConvoGetLog,
|
ChatBskyConvoGetLog,
|
||||||
|
ChatBskyConvoListConvos,
|
||||||
ChatBskyConvoSendMessage,
|
ChatBskyConvoSendMessage,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {XRPCError} from '@atproto/xrpc'
|
import {XRPCError} from '@atproto/xrpc'
|
||||||
|
import {InfiniteData, QueryClient} from '@tanstack/react-query'
|
||||||
import EventEmitter from 'eventemitter3'
|
import EventEmitter from 'eventemitter3'
|
||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ import {
|
|||||||
import {MessagesEventBus} from '#/state/messages/events/agent'
|
import {MessagesEventBus} from '#/state/messages/events/agent'
|
||||||
import {MessagesEventBusError} from '#/state/messages/events/types'
|
import {MessagesEventBusError} from '#/state/messages/events/types'
|
||||||
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
|
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
|
||||||
|
import {RQKEY as RQKEY_LIST_CONVOS} from '#/state/queries/messages/list-conversations'
|
||||||
|
|
||||||
export function isConvoItemMessage(
|
export function isConvoItemMessage(
|
||||||
item: ConvoItem,
|
item: ConvoItem,
|
||||||
@@ -83,6 +86,7 @@ export class Convo {
|
|||||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
|
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
|
||||||
snapshot: ConvoState | undefined
|
snapshot: ConvoState | undefined
|
||||||
|
queryClient: QueryClient | undefined
|
||||||
|
|
||||||
constructor(params: ConvoParams) {
|
constructor(params: ConvoParams) {
|
||||||
this.id = nanoid(3)
|
this.id = nanoid(3)
|
||||||
@@ -90,6 +94,7 @@ export class Convo {
|
|||||||
this.agent = params.agent
|
this.agent = params.agent
|
||||||
this.events = params.events
|
this.events = params.events
|
||||||
this.senderUserDid = params.agent.session?.did!
|
this.senderUserDid = params.agent.session?.did!
|
||||||
|
this.queryClient = params.queryClient
|
||||||
|
|
||||||
this.subscribe = this.subscribe.bind(this)
|
this.subscribe = this.subscribe.bind(this)
|
||||||
this.getSnapshot = this.getSnapshot.bind(this)
|
this.getSnapshot = this.getSnapshot.bind(this)
|
||||||
@@ -99,6 +104,8 @@ export class Convo {
|
|||||||
this.ingestFirehose = this.ingestFirehose.bind(this)
|
this.ingestFirehose = this.ingestFirehose.bind(this)
|
||||||
this.onFirehoseConnect = this.onFirehoseConnect.bind(this)
|
this.onFirehoseConnect = this.onFirehoseConnect.bind(this)
|
||||||
this.onFirehoseError = this.onFirehoseError.bind(this)
|
this.onFirehoseError = this.onFirehoseError.bind(this)
|
||||||
|
this.optimisticFetchConvoFromCache =
|
||||||
|
this.optimisticFetchConvoFromCache.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private commit() {
|
private commit() {
|
||||||
@@ -426,6 +433,8 @@ export class Convo {
|
|||||||
|
|
||||||
private async setup() {
|
private async setup() {
|
||||||
try {
|
try {
|
||||||
|
this.optimisticFetchConvoFromCache()
|
||||||
|
|
||||||
const {convo, sender, recipients} = await this.fetchConvo()
|
const {convo, sender, recipients} = await this.fetchConvo()
|
||||||
|
|
||||||
this.convo = convo
|
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:
|
private pendingFetchConvo:
|
||||||
| Promise<{
|
| Promise<{
|
||||||
convo: ChatBskyConvoDefs.ConvoView
|
convo: ChatBskyConvoDefs.ConvoView
|
||||||
@@ -525,7 +553,7 @@ export class Convo {
|
|||||||
}>(async (resolve, reject) => {
|
}>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const response = await networkRetry(2, () => {
|
const response = await networkRetry(2, () => {
|
||||||
return this.agent.api.chat.bsky.convo.getConvo(
|
return this.agent.chat.bsky.convo.getConvo(
|
||||||
{
|
{
|
||||||
convoId: this.convoId,
|
convoId: this.convoId,
|
||||||
},
|
},
|
||||||
@@ -592,7 +620,7 @@ export class Convo {
|
|||||||
|
|
||||||
const nextCursor = this.oldestRev // for TS
|
const nextCursor = this.oldestRev // for TS
|
||||||
const response = await networkRetry(2, () => {
|
const response = await networkRetry(2, () => {
|
||||||
return this.agent.api.chat.bsky.convo.getMessages(
|
return this.agent.chat.bsky.convo.getMessages(
|
||||||
{
|
{
|
||||||
cursor: nextCursor,
|
cursor: nextCursor,
|
||||||
convoId: this.convoId,
|
convoId: this.convoId,
|
||||||
@@ -793,7 +821,7 @@ export class Convo {
|
|||||||
|
|
||||||
const {id, message} = pendingMessage
|
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,
|
convoId: this.convoId,
|
||||||
message,
|
message,
|
||||||
@@ -888,7 +916,7 @@ export class Convo {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {data} = await this.agent.api.chat.bsky.convo.sendMessageBatch(
|
const {data} = await this.agent.chat.bsky.convo.sendMessageBatch(
|
||||||
{
|
{
|
||||||
items: messageArray.map(({message}) => ({
|
items: messageArray.map(({message}) => ({
|
||||||
convoId: this.convoId,
|
convoId: this.convoId,
|
||||||
@@ -935,7 +963,7 @@ export class Convo {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await networkRetry(2, () => {
|
await networkRetry(2, () => {
|
||||||
return this.agent.api.chat.bsky.convo.deleteMessageForSelf(
|
return this.agent.chat.bsky.convo.deleteMessageForSelf(
|
||||||
{
|
{
|
||||||
convoId: this.convoId,
|
convoId: this.convoId,
|
||||||
messageId,
|
messageId,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export function ConvoProvider({
|
|||||||
convoId,
|
convoId,
|
||||||
agent,
|
agent,
|
||||||
events,
|
events,
|
||||||
|
queryClient,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
|
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ChatBskyConvoDefs,
|
ChatBskyConvoDefs,
|
||||||
ChatBskyConvoSendMessage,
|
ChatBskyConvoSendMessage,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
import {QueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {MessagesEventBus} from '#/state/messages/events/agent'
|
import {MessagesEventBus} from '#/state/messages/events/agent'
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ export type ConvoParams = {
|
|||||||
convoId: string
|
convoId: string
|
||||||
agent: BskyAgent
|
agent: BskyAgent
|
||||||
events: MessagesEventBus
|
events: MessagesEventBus
|
||||||
|
queryClient?: QueryClient
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConvoStatus {
|
export enum ConvoStatus {
|
||||||
|
|||||||
Reference in New Issue
Block a user