Skip to content

Commit

Permalink
get initial convo state from cache
Browse files Browse the repository at this point in the history
  • Loading branch information
mozzius committed Jan 27, 2025
1 parent ebb6d5c commit e7af5cd
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 78 deletions.
15 changes: 6 additions & 9 deletions src/components/dms/ConvoMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ let ConvoMenu = ({
const isBlocking = userBlock || !!listBlocks.length
const isDeletedAccount = profile.handle === 'missing.invalid'

const {data: convo} = useConvoQuery(initialConvo)
const convoId = initialConvo.id
const {data: convo} = useConvoQuery(convoId, initialConvo)

const onNavigateToProfile = useCallback(() => {
navigation.navigate('Profile', {name: profile.did})
}, [navigation, profile.did])

const {mutate: muteConvo} = useMuteConvo(convo?.id, {
const {mutate: muteConvo} = useMuteConvo(convoId, {
onSuccess: data => {
if (data.convo.muted) {
Toast.show(_(msg`Chat muted`))
Expand Down Expand Up @@ -152,11 +153,7 @@ let ConvoMenu = ({
{showMarkAsRead && (
<Menu.Item
label={_(msg`Mark as read`)}
onPress={() =>
markAsRead({
convoId: convo?.id,
})
}>
onPress={() => markAsRead({convoId})}>
<Menu.ItemText>
<Trans>Mark as read</Trans>
</Menu.ItemText>
Expand Down Expand Up @@ -222,15 +219,15 @@ let ConvoMenu = ({

<LeaveConvoPrompt
control={leaveConvoControl}
convoId={convo.id}
convoId={convoId}
currentScreen={currentScreen}
/>
{latestReportableMessage ? (
<ReportDialog
currentScreen={currentScreen}
params={{
type: 'convoMessage',
convoId: convo.id,
convoId: convoId,
message: latestReportableMessage,
}}
control={reportControl}
Expand Down
21 changes: 14 additions & 7 deletions src/components/dms/MessagesListBlockedFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs, ModerationCause} from '@atproto/api'
import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

Expand All @@ -19,15 +19,12 @@ export function MessagesListBlockedFooter({
recipient: initialRecipient,
convoId,
hasMessages,
blockInfo,
moderation,
}: {
recipient: AppBskyActorDefs.ProfileViewBasic
convoId: string
hasMessages: boolean
blockInfo: {
listBlocks: ModerationCause[]
userBlock: ModerationCause | undefined
}
moderation: ModerationDecision
}) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
Expand All @@ -39,7 +36,17 @@ export function MessagesListBlockedFooter({
const reportControl = useDialogControl()
const blockedByListControl = useDialogControl()

const {listBlocks, userBlock} = blockInfo
const {listBlocks, userBlock} = 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])

const isBlocking = !!userBlock || !!listBlocks.length

const onUnblockPress = React.useCallback(() => {
Expand Down
17 changes: 12 additions & 5 deletions src/components/dms/MessagesListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,27 @@ const PFP_SIZE = isWeb ? 40 : 34
export let MessagesListHeader = ({
profile,
moderation,
blockInfo,
}: {
profile?: AppBskyActorDefs.ProfileViewBasic
moderation?: ModerationDecision
blockInfo?: {
listBlocks: ModerationCause[]
userBlock?: ModerationCause
}
}): React.ReactNode => {
const t = useTheme()
const {_} = useLingui()
const {gtTablet} = useBreakpoints()
const navigation = useNavigation<NavigationProp>()

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])

const onPressBack = useCallback(() => {
if (isWeb) {
navigation.replace('Messages', {})
Expand Down
1 change: 0 additions & 1 deletion src/screens/Messages/ChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,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}
/>
Expand Down
49 changes: 22 additions & 27 deletions src/screens/Messages/Conversation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React, {useCallback} from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {
AppBskyActorDefs,
moderateProfile,
ModerationDecision,
} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
Expand Down Expand Up @@ -76,6 +80,11 @@ function Inner() {
did: convoState.recipients?.[0].did,
})

const moderation = React.useMemo(() => {
if (!recipient || !moderationOpts) return null
return moderateProfile(recipient, moderationOpts)
}, [recipient, moderationOpts])

// Because we want to give the list a chance to asynchronously scroll to the end before it is visible to the user,
// we use `hasScrolled` to determine when to render. With that said however, there is a chance that the chat will be
// empty. So, we also check for that possible state as well and render once we can.
Expand Down Expand Up @@ -110,11 +119,16 @@ function Inner() {

return (
<Layout.Center style={[a.flex_1]}>
{!readyToShow && <MessagesListHeader />}
{!readyToShow &&
(moderation ? (
<MessagesListHeader moderation={moderation} profile={recipient} />
) : (
<MessagesListHeader />
))}
<View style={[a.flex_1]}>
{moderationOpts && recipient ? (
{moderation && recipient ? (
<InnerReady
moderationOpts={moderationOpts}
moderation={moderation}
recipient={recipient}
hasScrolled={hasScrolled}
setHasScrolled={setHasScrolled}
Expand Down Expand Up @@ -144,12 +158,12 @@ function Inner() {
}

function InnerReady({
moderationOpts,
moderation,
recipient: recipientUnshadowed,
hasScrolled,
setHasScrolled,
}: {
moderationOpts: ModerationOpts
moderation: ModerationDecision
recipient: AppBskyActorDefs.ProfileViewBasic
hasScrolled: boolean
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
Expand All @@ -161,21 +175,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()
Expand All @@ -184,11 +183,7 @@ function InnerReady({

return (
<>
<MessagesListHeader
profile={recipient}
moderation={moderation}
blockInfo={blockInfo}
/>
<MessagesListHeader profile={recipient} moderation={moderation} />
{isConvoActive(convoState) && (
<MessagesList
hasScrolled={hasScrolled}
Expand All @@ -199,7 +194,7 @@ function InnerReady({
recipient={recipient}
convoId={convoState.convo.id}
hasMessages={convoState.items.length > 0}
blockInfo={blockInfo}
moderation={moderation}
/>
}
/>
Expand Down
12 changes: 10 additions & 2 deletions src/screens/Messages/components/ChatListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -23,7 +24,11 @@ import {
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 {
precacheConvoQuery,
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'
Expand Down Expand Up @@ -89,6 +94,7 @@ function ChatListItemReady({
[profile, moderationOpts],
)
const playHaptic = useHaptics()
const queryClient = useQueryClient()
const isUnread = convo.unreadCount > 0

const blockInfo = useMemo(() => {
Expand Down Expand Up @@ -198,6 +204,8 @@ function ChatListItemReady({

const onPress = useCallback(
(e: GestureResponderEvent) => {
precacheProfile(queryClient, profile)
precacheConvoQuery(queryClient, convo)
decrementBadgeCount(convo.unreadCount)
if (isDeletedAccount) {
e.preventDefault()
Expand All @@ -207,7 +215,7 @@ function ChatListItemReady({
logEvent('chat:open', {logContext: 'ChatsList'})
}
},
[convo.unreadCount, isDeletedAccount, menuControl],
[isDeletedAccount, menuControl, queryClient, profile, convo],
)

const onLongPress = useCallback(() => {
Expand Down
32 changes: 25 additions & 7 deletions src/state/messages/convo/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class Convo {
convoId: string
convo: ChatBskyConvoDefs.ConvoView | undefined
sender: AppBskyActorDefs.ProfileViewBasic | undefined
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined
snapshot: ConvoState | undefined

constructor(params: ConvoParams) {
Expand All @@ -91,6 +91,10 @@ export class Convo {
this.events = params.events
this.senderUserDid = params.agent.session?.did!

if (params.placeholderData) {
this.setupPlaceholderData(params.placeholderData)
}

this.subscribe = this.subscribe.bind(this)
this.getSnapshot = this.getSnapshot.bind(this)
this.sendMessage = this.sendMessage.bind(this)
Expand Down Expand Up @@ -131,10 +135,10 @@ export class Convo {
return {
status: ConvoStatus.Initializing,
items: [],
convo: undefined,
convo: this.convo,
error: undefined,
sender: undefined,
recipients: undefined,
sender: this.sender,
recipients: this.recipients,
isFetchingHistory: this.isFetchingHistory,
deleteMessage: undefined,
sendMessage: undefined,
Expand Down Expand Up @@ -176,10 +180,10 @@ export class Convo {
return {
status: ConvoStatus.Uninitialized,
items: [],
convo: undefined,
convo: this.convo,
error: undefined,
sender: undefined,
recipients: undefined,
sender: this.sender,
recipients: this.recipients,
isFetchingHistory: false,
deleteMessage: undefined,
sendMessage: undefined,
Expand Down Expand Up @@ -424,6 +428,20 @@ export class Convo {
}
}

/**
* Initialises the convo with placeholder data, if provided. We still refetch it before rendering the convo,
* but this allows us to render the convo header immediately.
*/
private setupPlaceholderData(
data: NonNullable<ConvoParams['placeholderData']>,
) {
this.convo = data.convo
this.sender = data.convo.members.find(m => m.did === this.senderUserDid)
this.recipients = data.convo.members.filter(
m => m.did !== this.senderUserDid,
)
}

private async setup() {
try {
const {convo, sender, recipients} = await this.fetchConvo()
Expand Down
25 changes: 16 additions & 9 deletions src/state/messages/convo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useContext, useState, useSyncExternalStore} from 'react'
import {ChatBskyConvoDefs} from '@atproto/api'
import {useFocusEffect} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'

Expand All @@ -14,7 +15,10 @@ import {
} from '#/state/messages/convo/types'
import {isConvoActive} from '#/state/messages/convo/util'
import {useMessagesEventBus} from '#/state/messages/events'
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
import {
RQKEY as getConvoKey,
useMarkAsReadMutation,
} from '#/state/queries/messages/conversation'
import {RQKEY as ListConvosQueryKey} from '#/state/queries/messages/list-conversations'
import {RQKEY as createProfileQueryKey} from '#/state/queries/profile'
import {useAgent} from '#/state/session'
Expand Down Expand Up @@ -60,14 +64,17 @@ export function ConvoProvider({
const queryClient = useQueryClient()
const agent = useAgent()
const events = useMessagesEventBus()
const [convo] = useState(
() =>
new Convo({
convoId,
agent,
events,
}),
)
const [convo] = useState(() => {
const placeholder = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
getConvoKey(convoId),
)
return new Convo({
convoId,
agent,
events,
placeholderData: placeholder ? {convo: placeholder} : undefined,
})
})
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
const {mutate: markAsRead} = useMarkAsReadMutation()

Expand Down
Loading

0 comments on commit e7af5cd

Please sign in to comment.