Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix convo header loading state #7603

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading