From f928e0a54736803a8650c084b5a0977ff1881ecf Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 10 May 2024 08:46:51 -0700 Subject: [PATCH] =?UTF-8?q?[=F0=9F=90=B4]=20Mutate=20data=20instead=20of?= =?UTF-8?q?=20invalidating=20queries=20when=20muting=20or=20unmuting=20(#3?= =?UTF-8?q?946)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * mutate for mutes * mutate data for mutes * add initial data, `useConvoQuery` in `ConvoMenu` * `useInitialData` * don't use `identifier` for notifications, use `dates` instead * better implementation * simplify * simplify * fix types --- src/components/dms/ConvoMenu.tsx | 43 ++++---- src/screens/Messages/Conversation/index.tsx | 27 ++--- src/state/queries/messages/conversation.ts | 13 ++- .../queries/messages/leave-conversation.ts | 5 +- .../queries/messages/mute-conversation.ts | 101 +++++++++--------- 5 files changed, 92 insertions(+), 97 deletions(-) diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index cac4eb4d9d..8c8e7ed486 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -2,17 +2,18 @@ import React, {useCallback} from 'react' import {Keyboard, Pressable, View} from 'react-native' import {AppBskyActorDefs} from '@atproto/api' import {ChatBskyConvoDefs} from '@atproto-labs/api' +import {ConvoView} from '@atproto-labs/api/dist/client/types/chat/bsky/convo/defs' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from '#/lib/routes/types' -import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' -import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import { - useMuteConvo, - useUnmuteConvo, -} from '#/state/queries/messages/mute-conversation' + useConvoQuery, + useMarkAsReadMutation, +} from '#/state/queries/messages/conversation' +import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' +import {useMuteConvo} from '#/state/queries/messages/mute-conversation' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft' @@ -28,16 +29,15 @@ import * as Prompt from '#/components/Prompt' import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' let ConvoMenu = ({ - convo, + convo: initialConvo, profile, - onUpdateConvo, control, currentScreen, showMarkAsRead, hideTrigger, triggerOpacity, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: ConvoView profile: AppBskyActorDefs.ProfileViewBasic onUpdateConvo?: (convo: ChatBskyConvoDefs.ConvoView) => void control?: Menu.MenuControlProps @@ -52,31 +52,26 @@ let ConvoMenu = ({ const leaveConvoControl = Prompt.usePromptControl() const {mutate: markAsRead} = useMarkAsReadMutation() + const {data: convo} = useConvoQuery(initialConvo) + const onNavigateToProfile = useCallback(() => { navigation.navigate('Profile', {name: profile.did}) }, [navigation, profile.did]) - const {mutate: muteConvo} = useMuteConvo(convo.id, { + const {mutate: muteConvo} = useMuteConvo(convo?.id, { onSuccess: data => { - onUpdateConvo?.(data.convo) - Toast.show(_(msg`Chat muted`)) + if (data.convo.muted) { + Toast.show(_(msg`Chat muted`)) + } else { + Toast.show(_(msg`Chat unmuted`)) + } }, onError: () => { Toast.show(_(msg`Could not mute chat`)) }, }) - const {mutate: unmuteConvo} = useUnmuteConvo(convo.id, { - onSuccess: data => { - onUpdateConvo?.(data.convo) - Toast.show(_(msg`Chat unmuted`)) - }, - onError: () => { - Toast.show(_(msg`Could not unmute chat`)) - }, - }) - - const {mutate: leaveConvo} = useLeaveConvo(convo.id, { + const {mutate: leaveConvo} = useLeaveConvo(convo?.id, { onSuccess: () => { if (currentScreen === 'conversation') { navigation.replace('Messages') @@ -121,7 +116,7 @@ let ConvoMenu = ({ label={_(msg`Mark as read`)} onPress={() => markAsRead({ - convoId: convo.id, + convoId: convo?.id, }) }> @@ -140,7 +135,7 @@ let ConvoMenu = ({ (convo?.muted ? unmuteConvo() : muteConvo())}> + onPress={() => muteConvo({mute: !convo?.muted})}> {convo?.muted ? ( Unmute notifications diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx index fc4df0a24b..a783a0bd6d 100644 --- a/src/screens/Messages/Conversation/index.tsx +++ b/src/screens/Messages/Conversation/index.tsx @@ -56,7 +56,7 @@ export function MessagesConversationScreen({route}: Props) { function Inner() { const t = useTheme() - const convo = useConvo() + const convoState = useConvo() const {_} = useLingui() const [hasInitiallyRendered, setHasInitiallyRendered] = React.useState(false) @@ -72,23 +72,23 @@ function Inner() { React.useEffect(() => { if ( !hasInitiallyRendered && - convo.status === ConvoStatus.Ready && - !convo.isFetchingHistory + convoState.status === ConvoStatus.Ready && + !convoState.isFetchingHistory ) { setTimeout(() => { setHasInitiallyRendered(true) }, 15) } - }, [convo.isFetchingHistory, convo.items, convo.status, hasInitiallyRendered]) + }, [convoState.isFetchingHistory, convoState.status, hasInitiallyRendered]) - if (convo.status === ConvoStatus.Error) { + if (convoState.status === ConvoStatus.Error) { return (
convo.error.retry()} + onRetry={() => convoState.error.retry()} /> ) @@ -106,9 +106,9 @@ function Inner() { behavior="padding" contentContainerStyle={a.flex_1}> -
+
- {convo.status !== ConvoStatus.Ready ? ( + {convoState.status !== ConvoStatus.Ready ? ( ) : ( @@ -145,7 +145,7 @@ let Header = ({ const {_} = useLingui() const {gtTablet} = useBreakpoints() const navigation = useNavigation() - const convo = useConvo() + const convoState = useConvo() const onPressBack = useCallback(() => { if (isWeb) { @@ -155,10 +155,6 @@ let Header = ({ } }, [navigation]) - const onUpdateConvo = useCallback(() => { - // TODO eric update muted state - }, []) - return ( )} - {convo.status === ConvoStatus.Ready && profile ? ( + {convoState.status === ConvoStatus.Ready && profile ? ( ) : ( diff --git a/src/state/queries/messages/conversation.ts b/src/state/queries/messages/conversation.ts index b4861b5721..e420ba7363 100644 --- a/src/state/queries/messages/conversation.ts +++ b/src/state/queries/messages/conversation.ts @@ -1,4 +1,5 @@ import {BskyAgent} from '@atproto-labs/api' +import {ConvoView} from '@atproto-labs/api/dist/client/types/chat/bsky/convo/defs' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useOnMarkAsRead} from '#/state/queries/messages/list-converations' @@ -9,20 +10,21 @@ import {useHeaders} from './temp-headers' const RQKEY_ROOT = 'convo' export const RQKEY = (convoId: string) => [RQKEY_ROOT, convoId] -export function useConvoQuery(convoId: string) { +export function useConvoQuery(convo: ConvoView) { const headers = useHeaders() const {serviceUrl} = useDmServiceUrlStorage() return useQuery({ - queryKey: RQKEY(convoId), + queryKey: RQKEY(convo.id), queryFn: async () => { const agent = new BskyAgent({service: serviceUrl}) const {data} = await agent.api.chat.bsky.convo.getConvo( - {convoId}, + {convoId: convo.id}, {headers}, ) return data.convo }, + initialData: convo, }) } @@ -37,9 +39,11 @@ export function useMarkAsReadMutation() { convoId, messageId, }: { - convoId: string + convoId?: string messageId?: string }) => { + if (!convoId) throw new Error('No convoId provided') + const agent = new BskyAgent({service: serviceUrl}) await agent.api.chat.bsky.convo.updateRead( { @@ -53,6 +57,7 @@ export function useMarkAsReadMutation() { ) }, onMutate({convoId}) { + if (!convoId) throw new Error('No convoId provided') optimisticUpdate(convoId) }, onSettled() { diff --git a/src/state/queries/messages/leave-conversation.ts b/src/state/queries/messages/leave-conversation.ts index 0dd67fa0b1..5d5c64c5b9 100644 --- a/src/state/queries/messages/leave-conversation.ts +++ b/src/state/queries/messages/leave-conversation.ts @@ -11,7 +11,7 @@ import {RQKEY as CONVO_LIST_KEY} from './list-converations' import {useHeaders} from './temp-headers' export function useLeaveConvo( - convoId: string, + convoId: string | undefined, { onSuccess, onError, @@ -26,6 +26,8 @@ export function useLeaveConvo( return useMutation({ mutationFn: async () => { + if (!convoId) throw new Error('No convoId provided') + const agent = new BskyAgent({service: serviceUrl}) const {data} = await agent.api.chat.bsky.convo.leaveConvo( {convoId}, @@ -41,7 +43,6 @@ export function useLeaveConvo( pageParams: Array pages: Array }) => { - console.log('old', old) if (!old) return old return { ...old, diff --git a/src/state/queries/messages/mute-conversation.ts b/src/state/queries/messages/mute-conversation.ts index 4840c65ade..f30612c73e 100644 --- a/src/state/queries/messages/mute-conversation.ts +++ b/src/state/queries/messages/mute-conversation.ts @@ -1,18 +1,18 @@ import { BskyAgent, + ChatBskyConvoDefs, + ChatBskyConvoListConvos, ChatBskyConvoMuteConvo, - ChatBskyConvoUnmuteConvo, } from '@atproto-labs/api' -import {useMutation, useQueryClient} from '@tanstack/react-query' +import {InfiniteData, useMutation, useQueryClient} from '@tanstack/react-query' -import {logger} from '#/logger' import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' import {RQKEY as CONVO_KEY} from './conversation' import {RQKEY as CONVO_LIST_KEY} from './list-converations' import {useHeaders} from './temp-headers' export function useMuteConvo( - convoId: string, + convoId: string | undefined, { onSuccess, onError, @@ -26,59 +26,58 @@ export function useMuteConvo( const {serviceUrl} = useDmServiceUrlStorage() return useMutation({ - mutationFn: async () => { - const agent = new BskyAgent({service: serviceUrl}) - const {data} = await agent.api.chat.bsky.convo.muteConvo( - {convoId}, - {headers, encoding: 'application/json'}, - ) - - return data - }, - onSuccess: data => { - queryClient.invalidateQueries({queryKey: CONVO_LIST_KEY}) - queryClient.invalidateQueries({queryKey: CONVO_KEY(convoId)}) - onSuccess?.(data) - }, - onError: error => { - logger.error(error) - onError?.(error) - }, - }) -} + mutationFn: async ({mute}: {mute: boolean}) => { + if (!convoId) throw new Error('No convoId provided') -export function useUnmuteConvo( - convoId: string, - { - onSuccess, - onError, - }: { - onSuccess?: (data: ChatBskyConvoUnmuteConvo.OutputSchema) => void - onError?: (error: Error) => void - }, -) { - const queryClient = useQueryClient() - const headers = useHeaders() - const {serviceUrl} = useDmServiceUrlStorage() - - return useMutation({ - mutationFn: async () => { const agent = new BskyAgent({service: serviceUrl}) - const {data} = await agent.api.chat.bsky.convo.unmuteConvo( - {convoId}, - {headers, encoding: 'application/json'}, + if (mute) { + const {data} = await agent.api.chat.bsky.convo.muteConvo( + {convoId}, + {headers, encoding: 'application/json'}, + ) + return data + } else { + const {data} = await agent.api.chat.bsky.convo.unmuteConvo( + {convoId}, + {headers, encoding: 'application/json'}, + ) + return data + } + }, + onSuccess: (data, params) => { + queryClient.setQueryData( + CONVO_KEY(data.convo.id), + prev => { + if (!prev) return + return { + ...prev, + muted: params.mute, + } + }, ) + queryClient.setQueryData< + InfiniteData + >(CONVO_LIST_KEY, prev => { + if (!prev?.pages) return + return { + ...prev, + pages: prev.pages.map(page => ({ + ...page, + convos: page.convos.map(convo => { + if (convo.id !== data.convo.id) return convo + return { + ...convo, + muted: params.mute, + } + }), + })), + } + }) - return data - }, - onSuccess: data => { - queryClient.invalidateQueries({queryKey: CONVO_LIST_KEY}) - queryClient.invalidateQueries({queryKey: CONVO_KEY(convoId)}) onSuccess?.(data) }, - onError: error => { - logger.error(error) - onError?.(error) + onError: e => { + onError?.(e) }, }) }