diff --git a/assets/icons/bubble_stroke2_corner3_rounded.svg b/assets/icons/bubble_stroke2_corner3_rounded.svg new file mode 100644 index 00000000..def2f979 --- /dev/null +++ b/assets/icons/bubble_stroke2_corner3_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 16306bb5..263befd5 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -7,6 +7,7 @@ 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, @@ -24,6 +25,7 @@ import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/Per import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' +import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' let ConvoMenu = ({ convo, @@ -32,6 +34,7 @@ let ConvoMenu = ({ control, hideTrigger, currentScreen, + showMarkAsRead, }: { convo: ChatBskyConvoDefs.ConvoView profile: AppBskyActorDefs.ProfileViewBasic @@ -39,11 +42,13 @@ let ConvoMenu = ({ control?: Menu.MenuControlProps hideTrigger?: boolean currentScreen: 'list' | 'conversation' + showMarkAsRead?: boolean }): React.ReactNode => { const navigation = useNavigation() const {_} = useLingui() const t = useTheme() const leaveConvoControl = Prompt.usePromptControl() + const {mutate: markAsRead} = useMarkAsReadMutation() const onNavigateToProfile = useCallback(() => { navigation.navigate('Profile', {name: profile.did}) @@ -107,6 +112,20 @@ let ConvoMenu = ({ )} + {showMarkAsRead && ( + + markAsRead({ + convoId: convo.id, + }) + }> + + Mark as read + + + + )} diff --git a/src/components/icons/Bubble.tsx b/src/components/icons/Bubble.tsx index d4e08f6d..ff6da253 100644 --- a/src/components/icons/Bubble.tsx +++ b/src/components/icons/Bubble.tsx @@ -3,3 +3,11 @@ import {createSinglePathSVG} from './TEMPLATE' export const BubbleQuestion_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M5.002 17.036V5h14v12.036h-3.986a1 1 0 0 0-.639.23l-2.375 1.968-2.344-1.965a1 1 0 0 0-.643-.233H5.002ZM20.002 3h-16a1 1 0 0 0-1 1v14.036a1 1 0 0 0 1 1h4.65l2.704 2.266a1 1 0 0 0 1.28.004l2.74-2.27h4.626a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1Zm-7.878 3.663c-1.39 0-2.5 1.135-2.5 2.515a1 1 0 0 0 2 0c0-.294.232-.515.5-.515a.507.507 0 0 1 .489.6.174.174 0 0 1-.027.048 1.1 1.1 0 0 1-.267.226c-.508.345-1.128.923-1.286 1.978a1 1 0 1 0 1.978.297.762.762 0 0 1 .14-.359c.063-.086.155-.169.293-.262.436-.297 1.18-.885 1.18-2.013 0-1.38-1.11-2.515-2.5-2.515ZM12 15.75a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5Z', }) + +export const Bubble_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M2.002 6a3 3 0 0 1 3-3h14a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2h-1a3 3 0 0 1-3-3V6Zm3-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h7a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-14Z', +}) + +export const Bubble_Stroke2_Corner3_Rounded = createSinglePathSVG({ + path: 'M2.002 7a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2a4 4 0 0 1-4-4V7Zm4-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h1a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h6a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-12Z', +}) diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index ca757fac..a82f2f00 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -275,7 +275,7 @@ function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { a.pl_md, a.py_sm, a.gap_md, - a.pr_2xl, + a.pr_xl, (hovered || pressed) && t.atoms.bg_contrast_25, ]}> @@ -340,6 +340,7 @@ function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { // tricky because it captures the mouse event hideTrigger currentScreen="list" + showMarkAsRead={convo.unreadCount > 0} /> )} diff --git a/src/state/queries/messages/conversation.ts b/src/state/queries/messages/conversation.ts index c322e0c6..551fbe98 100644 --- a/src/state/queries/messages/conversation.ts +++ b/src/state/queries/messages/conversation.ts @@ -1,7 +1,7 @@ import {BskyAgent} from '@atproto-labs/api' -import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' +import {useMutation, useQuery} from '@tanstack/react-query' -import {RQKEY as ListConvosQueryKey} from '#/state/queries/messages/list-converations' +import {useOnMarkAsRead} from '#/state/queries/messages/list-converations' import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' import {useHeaders} from './temp-headers' @@ -28,7 +28,7 @@ export function useConvoQuery(convoId: string) { export function useMarkAsReadMutation() { const headers = useHeaders() const {serviceUrl} = useDmServiceUrlStorage() - const queryClient = useQueryClient() + const onMarkAsRead = useOnMarkAsRead() return useMutation({ mutationFn: async ({ @@ -50,10 +50,8 @@ export function useMarkAsReadMutation() { }, ) }, - onSuccess() { - queryClient.invalidateQueries({ - queryKey: ListConvosQueryKey, - }) + onSuccess(_, {convoId}) { + onMarkAsRead(convoId) }, }) } diff --git a/src/state/queries/messages/list-converations.ts b/src/state/queries/messages/list-converations.ts index e66551ce..32107c0c 100644 --- a/src/state/queries/messages/list-converations.ts +++ b/src/state/queries/messages/list-converations.ts @@ -111,6 +111,23 @@ export function useOnCreateConvo() { }, [queryClient]) } +export function useOnMarkAsRead() { + const queryClient = useQueryClient() + + return useCallback( + (chatId: string) => { + queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) => { + return optimisticUpdate(chatId, old, convo => ({ + ...convo, + unreadCount: 0, + })) + }) + queryClient.invalidateQueries({queryKey: RQKEY}) + }, + [queryClient], + ) +} + function optimisticUpdate( chatId: string, old: ConvoListQueryData,