Fix convo header loading state (#7603)
* get initial convo state from cache * undo useConvoQuery changes * fix shadowing situation with new hook
This commit is contained in:
@@ -73,13 +73,14 @@ let ConvoMenu = ({
|
|||||||
const isBlocking = userBlock || !!listBlocks.length
|
const isBlocking = userBlock || !!listBlocks.length
|
||||||
const isDeletedAccount = profile.handle === 'missing.invalid'
|
const isDeletedAccount = profile.handle === 'missing.invalid'
|
||||||
|
|
||||||
|
const convoId = initialConvo.id
|
||||||
const {data: convo} = useConvoQuery(initialConvo)
|
const {data: convo} = useConvoQuery(initialConvo)
|
||||||
|
|
||||||
const onNavigateToProfile = useCallback(() => {
|
const onNavigateToProfile = useCallback(() => {
|
||||||
navigation.navigate('Profile', {name: profile.did})
|
navigation.navigate('Profile', {name: profile.did})
|
||||||
}, [navigation, profile.did])
|
}, [navigation, profile.did])
|
||||||
|
|
||||||
const {mutate: muteConvo} = useMuteConvo(convo?.id, {
|
const {mutate: muteConvo} = useMuteConvo(convoId, {
|
||||||
onSuccess: data => {
|
onSuccess: data => {
|
||||||
if (data.convo.muted) {
|
if (data.convo.muted) {
|
||||||
Toast.show(_(msg`Chat muted`))
|
Toast.show(_(msg`Chat muted`))
|
||||||
@@ -152,11 +153,7 @@ let ConvoMenu = ({
|
|||||||
{showMarkAsRead && (
|
{showMarkAsRead && (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
label={_(msg`Mark as read`)}
|
label={_(msg`Mark as read`)}
|
||||||
onPress={() =>
|
onPress={() => markAsRead({convoId})}>
|
||||||
markAsRead({
|
|
||||||
convoId: convo?.id,
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
<Menu.ItemText>
|
<Menu.ItemText>
|
||||||
<Trans>Mark as read</Trans>
|
<Trans>Mark as read</Trans>
|
||||||
</Menu.ItemText>
|
</Menu.ItemText>
|
||||||
@@ -222,7 +219,7 @@ let ConvoMenu = ({
|
|||||||
|
|
||||||
<LeaveConvoPrompt
|
<LeaveConvoPrompt
|
||||||
control={leaveConvoControl}
|
control={leaveConvoControl}
|
||||||
convoId={convo.id}
|
convoId={convoId}
|
||||||
currentScreen={currentScreen}
|
currentScreen={currentScreen}
|
||||||
/>
|
/>
|
||||||
{latestReportableMessage ? (
|
{latestReportableMessage ? (
|
||||||
@@ -230,7 +227,7 @@ let ConvoMenu = ({
|
|||||||
currentScreen={currentScreen}
|
currentScreen={currentScreen}
|
||||||
params={{
|
params={{
|
||||||
type: 'convoMessage',
|
type: 'convoMessage',
|
||||||
convoId: convo.id,
|
convoId: convoId,
|
||||||
message: latestReportableMessage,
|
message: latestReportableMessage,
|
||||||
}}
|
}}
|
||||||
control={reportControl}
|
control={reportControl}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {AppBskyActorDefs, ModerationCause} from '@atproto/api'
|
import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -19,15 +19,12 @@ export function MessagesListBlockedFooter({
|
|||||||
recipient: initialRecipient,
|
recipient: initialRecipient,
|
||||||
convoId,
|
convoId,
|
||||||
hasMessages,
|
hasMessages,
|
||||||
blockInfo,
|
moderation,
|
||||||
}: {
|
}: {
|
||||||
recipient: AppBskyActorDefs.ProfileViewBasic
|
recipient: AppBskyActorDefs.ProfileViewBasic
|
||||||
convoId: string
|
convoId: string
|
||||||
hasMessages: boolean
|
hasMessages: boolean
|
||||||
blockInfo: {
|
moderation: ModerationDecision
|
||||||
listBlocks: ModerationCause[]
|
|
||||||
userBlock: ModerationCause | undefined
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
@@ -39,7 +36,17 @@ export function MessagesListBlockedFooter({
|
|||||||
const reportControl = useDialogControl()
|
const reportControl = useDialogControl()
|
||||||
const blockedByListControl = 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 isBlocking = !!userBlock || !!listBlocks.length
|
||||||
|
|
||||||
const onUnblockPress = React.useCallback(() => {
|
const onUnblockPress = React.useCallback(() => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {makeProfileLink} from '#/lib/routes/links'
|
|||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {Shadow} from '#/state/cache/profile-shadow'
|
||||||
import {isConvoActive, useConvo} from '#/state/messages/convo'
|
import {isConvoActive, useConvo} from '#/state/messages/convo'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
@@ -30,20 +30,27 @@ const PFP_SIZE = isWeb ? 40 : 34
|
|||||||
export let MessagesListHeader = ({
|
export let MessagesListHeader = ({
|
||||||
profile,
|
profile,
|
||||||
moderation,
|
moderation,
|
||||||
blockInfo,
|
|
||||||
}: {
|
}: {
|
||||||
profile?: AppBskyActorDefs.ProfileViewBasic
|
profile?: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||||
moderation?: ModerationDecision
|
moderation?: ModerationDecision
|
||||||
blockInfo?: {
|
|
||||||
listBlocks: ModerationCause[]
|
|
||||||
userBlock?: ModerationCause
|
|
||||||
}
|
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtTablet} = useBreakpoints()
|
const {gtTablet} = useBreakpoints()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
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(() => {
|
const onPressBack = useCallback(() => {
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
navigation.replace('Messages', {})
|
navigation.replace('Messages', {})
|
||||||
@@ -127,11 +134,11 @@ export let MessagesListHeader = ({
|
|||||||
MessagesListHeader = React.memo(MessagesListHeader)
|
MessagesListHeader = React.memo(MessagesListHeader)
|
||||||
|
|
||||||
function HeaderReady({
|
function HeaderReady({
|
||||||
profile: profileUnshadowed,
|
profile,
|
||||||
moderation,
|
moderation,
|
||||||
blockInfo,
|
blockInfo,
|
||||||
}: {
|
}: {
|
||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||||
moderation: ModerationDecision
|
moderation: ModerationDecision
|
||||||
blockInfo: {
|
blockInfo: {
|
||||||
listBlocks: ModerationCause[]
|
listBlocks: ModerationCause[]
|
||||||
@@ -141,7 +148,6 @@ function HeaderReady({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const convoState = useConvo()
|
const convoState = useConvo()
|
||||||
const profile = useProfileShadow(profileUnshadowed)
|
|
||||||
|
|
||||||
const isDeletedAccount = profile?.handle === 'missing.invalid'
|
const isDeletedAccount = profile?.handle === 'missing.invalid'
|
||||||
const displayName = isDeletedAccount
|
const displayName = isDeletedAccount
|
||||||
|
|||||||
@@ -236,7 +236,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,10 @@
|
|||||||
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,
|
||||||
|
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'
|
||||||
@@ -10,7 +14,7 @@ import {useEmail} from '#/lib/hooks/useEmail'
|
|||||||
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
||||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
|
import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
|
||||||
import {ConvoStatus} from '#/state/messages/convo/types'
|
import {ConvoStatus} from '#/state/messages/convo/types'
|
||||||
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
||||||
@@ -72,9 +76,15 @@ function Inner() {
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const {data: recipient} = useProfileQuery({
|
const {data: recipientUnshadowed} = useProfileQuery({
|
||||||
did: convoState.recipients?.[0].did,
|
did: convoState.recipients?.[0].did,
|
||||||
})
|
})
|
||||||
|
const recipient = useMaybeProfileShadow(recipientUnshadowed)
|
||||||
|
|
||||||
|
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,
|
// 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
|
// we use `hasScrolled` to determine when to render. With that said however, there is a chance that the chat will be
|
||||||
@@ -110,11 +120,16 @@ function Inner() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Center style={[a.flex_1]}>
|
<Layout.Center style={[a.flex_1]}>
|
||||||
{!readyToShow && <MessagesListHeader />}
|
{!readyToShow &&
|
||||||
|
(moderation ? (
|
||||||
|
<MessagesListHeader moderation={moderation} profile={recipient} />
|
||||||
|
) : (
|
||||||
|
<MessagesListHeader />
|
||||||
|
))}
|
||||||
<View style={[a.flex_1]}>
|
<View style={[a.flex_1]}>
|
||||||
{moderationOpts && recipient ? (
|
{moderation && recipient ? (
|
||||||
<InnerReady
|
<InnerReady
|
||||||
moderationOpts={moderationOpts}
|
moderation={moderation}
|
||||||
recipient={recipient}
|
recipient={recipient}
|
||||||
hasScrolled={hasScrolled}
|
hasScrolled={hasScrolled}
|
||||||
setHasScrolled={setHasScrolled}
|
setHasScrolled={setHasScrolled}
|
||||||
@@ -144,38 +159,22 @@ function Inner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InnerReady({
|
function InnerReady({
|
||||||
moderationOpts,
|
moderation,
|
||||||
recipient: recipientUnshadowed,
|
recipient,
|
||||||
hasScrolled,
|
hasScrolled,
|
||||||
setHasScrolled,
|
setHasScrolled,
|
||||||
}: {
|
}: {
|
||||||
moderationOpts: ModerationOpts
|
moderation: ModerationDecision
|
||||||
recipient: AppBskyActorDefs.ProfileViewBasic
|
recipient: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
||||||
hasScrolled: boolean
|
hasScrolled: boolean
|
||||||
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
|
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const convoState = useConvo()
|
const convoState = useConvo()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const recipient = useProfileShadow(recipientUnshadowed)
|
|
||||||
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()
|
||||||
@@ -184,11 +183,7 @@ function InnerReady({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MessagesListHeader
|
<MessagesListHeader profile={recipient} moderation={moderation} />
|
||||||
profile={recipient}
|
|
||||||
moderation={moderation}
|
|
||||||
blockInfo={blockInfo}
|
|
||||||
/>
|
|
||||||
{isConvoActive(convoState) && (
|
{isConvoActive(convoState) && (
|
||||||
<MessagesList
|
<MessagesList
|
||||||
hasScrolled={hasScrolled}
|
hasScrolled={hasScrolled}
|
||||||
@@ -199,7 +194,7 @@ function InnerReady({
|
|||||||
recipient={recipient}
|
recipient={recipient}
|
||||||
convoId={convoState.convo.id}
|
convoId={convoState.convo.id}
|
||||||
hasMessages={convoState.items.length > 0}
|
hasMessages={convoState.items.length > 0}
|
||||||
blockInfo={blockInfo}
|
moderation={moderation}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -23,7 +24,11 @@ import {
|
|||||||
import {isNative} from '#/platform/detection'
|
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 {
|
||||||
|
precacheConvoQuery,
|
||||||
|
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'
|
||||||
@@ -89,6 +94,7 @@ function ChatListItemReady({
|
|||||||
[profile, moderationOpts],
|
[profile, moderationOpts],
|
||||||
)
|
)
|
||||||
const playHaptic = useHaptics()
|
const playHaptic = useHaptics()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
const isUnread = convo.unreadCount > 0
|
const isUnread = convo.unreadCount > 0
|
||||||
|
|
||||||
const blockInfo = useMemo(() => {
|
const blockInfo = useMemo(() => {
|
||||||
@@ -198,6 +204,8 @@ function ChatListItemReady({
|
|||||||
|
|
||||||
const onPress = useCallback(
|
const onPress = useCallback(
|
||||||
(e: GestureResponderEvent) => {
|
(e: GestureResponderEvent) => {
|
||||||
|
precacheProfile(queryClient, profile)
|
||||||
|
precacheConvoQuery(queryClient, convo)
|
||||||
decrementBadgeCount(convo.unreadCount)
|
decrementBadgeCount(convo.unreadCount)
|
||||||
if (isDeletedAccount) {
|
if (isDeletedAccount) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -207,7 +215,7 @@ function ChatListItemReady({
|
|||||||
logEvent('chat:open', {logContext: 'ChatsList'})
|
logEvent('chat:open', {logContext: 'ChatsList'})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[convo.unreadCount, isDeletedAccount, menuControl],
|
[isDeletedAccount, menuControl, queryClient, profile, convo],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onLongPress = useCallback(() => {
|
const onLongPress = useCallback(() => {
|
||||||
|
|||||||
Vendored
+38
@@ -63,6 +63,44 @@ export function useProfileShadow<
|
|||||||
}, [profile, shadow])
|
}, [profile, shadow])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as useProfileShadow, but allows for the profile to be undefined.
|
||||||
|
* This is useful for when the profile is not guaranteed to be loaded yet.
|
||||||
|
*/
|
||||||
|
export function useMaybeProfileShadow<
|
||||||
|
TProfileView extends AppBskyActorDefs.ProfileView,
|
||||||
|
>(profile?: TProfileView): Shadow<TProfileView> | undefined {
|
||||||
|
const [shadow, setShadow] = useState(() =>
|
||||||
|
profile ? shadows.get(profile) : undefined,
|
||||||
|
)
|
||||||
|
const [prevPost, setPrevPost] = useState(profile)
|
||||||
|
if (profile !== prevPost) {
|
||||||
|
setPrevPost(profile)
|
||||||
|
setShadow(profile ? shadows.get(profile) : undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!profile) return
|
||||||
|
function onUpdate() {
|
||||||
|
if (!profile) return
|
||||||
|
setShadow(shadows.get(profile))
|
||||||
|
}
|
||||||
|
emitter.addListener(profile.did, onUpdate)
|
||||||
|
return () => {
|
||||||
|
emitter.removeListener(profile.did, onUpdate)
|
||||||
|
}
|
||||||
|
}, [profile])
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!profile) return undefined
|
||||||
|
if (shadow) {
|
||||||
|
return mergeShadow(profile, shadow)
|
||||||
|
} else {
|
||||||
|
return castAsShadow(profile)
|
||||||
|
}
|
||||||
|
}, [profile, shadow])
|
||||||
|
}
|
||||||
|
|
||||||
export function updateProfileShadow(
|
export function updateProfileShadow(
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
did: string,
|
did: string,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class Convo {
|
|||||||
convoId: string
|
convoId: string
|
||||||
convo: ChatBskyConvoDefs.ConvoView | undefined
|
convo: ChatBskyConvoDefs.ConvoView | undefined
|
||||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
|
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined
|
||||||
snapshot: ConvoState | undefined
|
snapshot: ConvoState | undefined
|
||||||
|
|
||||||
constructor(params: ConvoParams) {
|
constructor(params: ConvoParams) {
|
||||||
@@ -91,6 +91,10 @@ export class Convo {
|
|||||||
this.events = params.events
|
this.events = params.events
|
||||||
this.senderUserDid = params.agent.session?.did!
|
this.senderUserDid = params.agent.session?.did!
|
||||||
|
|
||||||
|
if (params.placeholderData) {
|
||||||
|
this.setupPlaceholderData(params.placeholderData)
|
||||||
|
}
|
||||||
|
|
||||||
this.subscribe = this.subscribe.bind(this)
|
this.subscribe = this.subscribe.bind(this)
|
||||||
this.getSnapshot = this.getSnapshot.bind(this)
|
this.getSnapshot = this.getSnapshot.bind(this)
|
||||||
this.sendMessage = this.sendMessage.bind(this)
|
this.sendMessage = this.sendMessage.bind(this)
|
||||||
@@ -131,10 +135,10 @@ export class Convo {
|
|||||||
return {
|
return {
|
||||||
status: ConvoStatus.Initializing,
|
status: ConvoStatus.Initializing,
|
||||||
items: [],
|
items: [],
|
||||||
convo: undefined,
|
convo: this.convo,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
sender: undefined,
|
sender: this.sender,
|
||||||
recipients: undefined,
|
recipients: this.recipients,
|
||||||
isFetchingHistory: this.isFetchingHistory,
|
isFetchingHistory: this.isFetchingHistory,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
@@ -176,10 +180,10 @@ export class Convo {
|
|||||||
return {
|
return {
|
||||||
status: ConvoStatus.Uninitialized,
|
status: ConvoStatus.Uninitialized,
|
||||||
items: [],
|
items: [],
|
||||||
convo: undefined,
|
convo: this.convo,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
sender: undefined,
|
sender: this.sender,
|
||||||
recipients: undefined,
|
recipients: this.recipients,
|
||||||
isFetchingHistory: false,
|
isFetchingHistory: false,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
@@ -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() {
|
private async setup() {
|
||||||
try {
|
try {
|
||||||
const {convo, sender, recipients} = await this.fetchConvo()
|
const {convo, sender, recipients} = await this.fetchConvo()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, {useContext, useState, useSyncExternalStore} from 'react'
|
import React, {useContext, useState, useSyncExternalStore} from 'react'
|
||||||
|
import {ChatBskyConvoDefs} from '@atproto/api'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
@@ -14,7 +15,10 @@ import {
|
|||||||
} from '#/state/messages/convo/types'
|
} from '#/state/messages/convo/types'
|
||||||
import {isConvoActive} from '#/state/messages/convo/util'
|
import {isConvoActive} from '#/state/messages/convo/util'
|
||||||
import {useMessagesEventBus} from '#/state/messages/events'
|
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 ListConvosQueryKey} from '#/state/queries/messages/list-conversations'
|
||||||
import {RQKEY as createProfileQueryKey} from '#/state/queries/profile'
|
import {RQKEY as createProfileQueryKey} from '#/state/queries/profile'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
@@ -60,14 +64,17 @@ export function ConvoProvider({
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const events = useMessagesEventBus()
|
const events = useMessagesEventBus()
|
||||||
const [convo] = useState(
|
const [convo] = useState(() => {
|
||||||
() =>
|
const placeholder = queryClient.getQueryData<ChatBskyConvoDefs.ConvoView>(
|
||||||
new Convo({
|
getConvoKey(convoId),
|
||||||
|
)
|
||||||
|
return new Convo({
|
||||||
convoId,
|
convoId,
|
||||||
agent,
|
agent,
|
||||||
events,
|
events,
|
||||||
}),
|
placeholderData: placeholder ? {convo: placeholder} : undefined,
|
||||||
)
|
})
|
||||||
|
})
|
||||||
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
|
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
|
||||||
const {mutate: markAsRead} = useMarkAsReadMutation()
|
const {mutate: markAsRead} = useMarkAsReadMutation()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ export type ConvoParams = {
|
|||||||
convoId: string
|
convoId: string
|
||||||
agent: BskyAgent
|
agent: BskyAgent
|
||||||
events: MessagesEventBus
|
events: MessagesEventBus
|
||||||
|
placeholderData?: {
|
||||||
|
convo: ChatBskyConvoDefs.ConvoView
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ConvoStatus {
|
export enum ConvoStatus {
|
||||||
@@ -142,10 +145,10 @@ type FetchMessageHistory = () => Promise<void>
|
|||||||
export type ConvoStateUninitialized = {
|
export type ConvoStateUninitialized = {
|
||||||
status: ConvoStatus.Uninitialized
|
status: ConvoStatus.Uninitialized
|
||||||
items: []
|
items: []
|
||||||
convo: undefined
|
convo: ChatBskyConvoDefs.ConvoView | undefined
|
||||||
error: undefined
|
error: undefined
|
||||||
sender: undefined
|
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
recipients: undefined
|
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined
|
||||||
isFetchingHistory: false
|
isFetchingHistory: false
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
@@ -154,10 +157,10 @@ export type ConvoStateUninitialized = {
|
|||||||
export type ConvoStateInitializing = {
|
export type ConvoStateInitializing = {
|
||||||
status: ConvoStatus.Initializing
|
status: ConvoStatus.Initializing
|
||||||
items: []
|
items: []
|
||||||
convo: undefined
|
convo: ChatBskyConvoDefs.ConvoView | undefined
|
||||||
error: undefined
|
error: undefined
|
||||||
sender: undefined
|
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
recipients: undefined
|
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import {ChatBskyConvoDefs} from '@atproto/api'
|
import {ChatBskyConvoDefs} from '@atproto/api'
|
||||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
import {
|
||||||
|
QueryClient,
|
||||||
|
useMutation,
|
||||||
|
useQuery,
|
||||||
|
useQueryClient,
|
||||||
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
|
import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const'
|
||||||
@@ -20,7 +25,7 @@ export function useConvoQuery(convo: ChatBskyConvoDefs.ConvoView) {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: RQKEY(convo.id),
|
queryKey: RQKEY(convo.id),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const {data} = await agent.api.chat.bsky.convo.getConvo(
|
const {data} = await agent.chat.bsky.convo.getConvo(
|
||||||
{convoId: convo.id},
|
{convoId: convo.id},
|
||||||
{headers: DM_SERVICE_HEADERS},
|
{headers: DM_SERVICE_HEADERS},
|
||||||
)
|
)
|
||||||
@@ -31,6 +36,13 @@ export function useConvoQuery(convo: ChatBskyConvoDefs.ConvoView) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function precacheConvoQuery(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
convo: ChatBskyConvoDefs.ConvoView,
|
||||||
|
) {
|
||||||
|
queryClient.setQueryData(RQKEY(convo.id), convo)
|
||||||
|
}
|
||||||
|
|
||||||
export function useMarkAsReadMutation() {
|
export function useMarkAsReadMutation() {
|
||||||
const optimisticUpdate = useOnMarkAsRead()
|
const optimisticUpdate = useOnMarkAsRead()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|||||||
Reference in New Issue
Block a user