Bluesky app fork with some witchin' additions ๐Ÿ’ซ

[๐Ÿด] Mark as read in convo menu (#3913)

* add mark as read option

* optimistic update + link up menu

* rm messageid

authored by samuel.fm and committed by

GitHub 38198fdf 56f71307

+52 -8
+1
assets/icons/bubble_stroke2_corner3_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="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" clip-rule="evenodd"/></svg>
+19
src/components/dms/ConvoMenu.tsx
··· 7 7 import {useNavigation} from '@react-navigation/native' 8 8 9 9 import {NavigationProp} from '#/lib/routes/types' 10 + import {useMarkAsReadMutation} from '#/state/queries/messages/conversation' 10 11 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 11 12 import { 12 13 useMuteConvo, ··· 24 25 import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 25 26 import * as Menu from '#/components/Menu' 26 27 import * as Prompt from '#/components/Prompt' 28 + import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' 27 29 28 30 let ConvoMenu = ({ 29 31 convo, ··· 32 34 control, 33 35 hideTrigger, 34 36 currentScreen, 37 + showMarkAsRead, 35 38 }: { 36 39 convo: ChatBskyConvoDefs.ConvoView 37 40 profile: AppBskyActorDefs.ProfileViewBasic ··· 39 42 control?: Menu.MenuControlProps 40 43 hideTrigger?: boolean 41 44 currentScreen: 'list' | 'conversation' 45 + showMarkAsRead?: boolean 42 46 }): React.ReactNode => { 43 47 const navigation = useNavigation<NavigationProp>() 44 48 const {_} = useLingui() 45 49 const t = useTheme() 46 50 const leaveConvoControl = Prompt.usePromptControl() 51 + const {mutate: markAsRead} = useMarkAsReadMutation() 47 52 48 53 const onNavigateToProfile = useCallback(() => { 49 54 navigation.navigate('Profile', {name: profile.did}) ··· 107 112 )} 108 113 <Menu.Outer> 109 114 <Menu.Group> 115 + {showMarkAsRead && ( 116 + <Menu.Item 117 + label={_(msg`Mark as read`)} 118 + onPress={() => 119 + markAsRead({ 120 + convoId: convo.id, 121 + }) 122 + }> 123 + <Menu.ItemText> 124 + <Trans>Mark as read</Trans> 125 + </Menu.ItemText> 126 + <Menu.ItemIcon icon={Bubble} /> 127 + </Menu.Item> 128 + )} 110 129 <Menu.Item 111 130 label={_(msg`Go to user's profile`)} 112 131 onPress={onNavigateToProfile}>
+8
src/components/icons/Bubble.tsx
··· 3 3 export const BubbleQuestion_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 4 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', 5 5 }) 6 + 7 + export const Bubble_Stroke2_Corner2_Rounded = createSinglePathSVG({ 8 + 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', 9 + }) 10 + 11 + export const Bubble_Stroke2_Corner3_Rounded = createSinglePathSVG({ 12 + 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', 13 + })
+2 -1
src/screens/Messages/List/index.tsx
··· 275 275 a.pl_md, 276 276 a.py_sm, 277 277 a.gap_md, 278 - a.pr_2xl, 278 + a.pr_xl, 279 279 (hovered || pressed) && t.atoms.bg_contrast_25, 280 280 ]}> 281 281 <View pointerEvents="none"> ··· 340 340 // tricky because it captures the mouse event 341 341 hideTrigger 342 342 currentScreen="list" 343 + showMarkAsRead={convo.unreadCount > 0} 343 344 /> 344 345 </View> 345 346 )}
+5 -7
src/state/queries/messages/conversation.ts
··· 1 1 import {BskyAgent} from '@atproto-labs/api' 2 - import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 2 + import {useMutation, useQuery} from '@tanstack/react-query' 3 3 4 - import {RQKEY as ListConvosQueryKey} from '#/state/queries/messages/list-converations' 4 + import {useOnMarkAsRead} from '#/state/queries/messages/list-converations' 5 5 import {useDmServiceUrlStorage} from '#/screens/Messages/Temp/useDmServiceUrlStorage' 6 6 import {useHeaders} from './temp-headers' 7 7 ··· 28 28 export function useMarkAsReadMutation() { 29 29 const headers = useHeaders() 30 30 const {serviceUrl} = useDmServiceUrlStorage() 31 - const queryClient = useQueryClient() 31 + const onMarkAsRead = useOnMarkAsRead() 32 32 33 33 return useMutation({ 34 34 mutationFn: async ({ ··· 50 50 }, 51 51 ) 52 52 }, 53 - onSuccess() { 54 - queryClient.invalidateQueries({ 55 - queryKey: ListConvosQueryKey, 56 - }) 53 + onSuccess(_, {convoId}) { 54 + onMarkAsRead(convoId) 57 55 }, 58 56 }) 59 57 }
+17
src/state/queries/messages/list-converations.ts
··· 111 111 }, [queryClient]) 112 112 } 113 113 114 + export function useOnMarkAsRead() { 115 + const queryClient = useQueryClient() 116 + 117 + return useCallback( 118 + (chatId: string) => { 119 + queryClient.setQueryData(RQKEY, (old: ConvoListQueryData) => { 120 + return optimisticUpdate(chatId, old, convo => ({ 121 + ...convo, 122 + unreadCount: 0, 123 + })) 124 + }) 125 + queryClient.invalidateQueries({queryKey: RQKEY}) 126 + }, 127 + [queryClient], 128 + ) 129 + } 130 + 114 131 function optimisticUpdate( 115 132 chatId: string, 116 133 old: ConvoListQueryData,