my fork of the bluesky client
at main 235 lines 7.9 kB view raw
1import React, {useCallback} from 'react' 2import {Keyboard, Pressable, View} from 'react-native' 3import { 4 AppBskyActorDefs, 5 ChatBskyConvoDefs, 6 ModerationCause, 7} from '@atproto/api' 8import {msg, Trans} from '@lingui/macro' 9import {useLingui} from '@lingui/react' 10import {useNavigation} from '@react-navigation/native' 11 12import {NavigationProp} from '#/lib/routes/types' 13import {Shadow} from '#/state/cache/types' 14import { 15 useConvoQuery, 16 useMarkAsReadMutation, 17} from '#/state/queries/messages/conversation' 18import {useMuteConvo} from '#/state/queries/messages/mute-conversation' 19import {useProfileBlockMutationQueue} from '#/state/queries/profile' 20import * as Toast from '#/view/com/util/Toast' 21import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 22import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' 23import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' 24import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' 25import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft' 26import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' 27import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 28import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 29import { 30 Person_Stroke2_Corner0_Rounded as Person, 31 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, 32 PersonX_Stroke2_Corner0_Rounded as PersonX, 33} from '#/components/icons/Person' 34import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 35import * as Menu from '#/components/Menu' 36import * as Prompt from '#/components/Prompt' 37import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '../icons/Bubble' 38 39let ConvoMenu = ({ 40 convo: initialConvo, 41 profile, 42 control, 43 currentScreen, 44 showMarkAsRead, 45 hideTrigger, 46 blockInfo, 47 style, 48}: { 49 convo: ChatBskyConvoDefs.ConvoView 50 profile: Shadow<AppBskyActorDefs.ProfileViewBasic> 51 control?: Menu.MenuControlProps 52 currentScreen: 'list' | 'conversation' 53 showMarkAsRead?: boolean 54 hideTrigger?: boolean 55 blockInfo: { 56 listBlocks: ModerationCause[] 57 userBlock?: ModerationCause 58 } 59 style?: ViewStyleProp['style'] 60}): React.ReactNode => { 61 const navigation = useNavigation<NavigationProp>() 62 const {_} = useLingui() 63 const t = useTheme() 64 const leaveConvoControl = Prompt.usePromptControl() 65 const reportControl = Prompt.usePromptControl() 66 const blockedByListControl = Prompt.usePromptControl() 67 const {mutate: markAsRead} = useMarkAsReadMutation() 68 69 const {listBlocks, userBlock} = blockInfo 70 const isBlocking = userBlock || !!listBlocks.length 71 const isDeletedAccount = profile.handle === 'missing.invalid' 72 73 const {data: convo} = useConvoQuery(initialConvo) 74 75 const onNavigateToProfile = useCallback(() => { 76 navigation.navigate('Profile', {name: profile.did}) 77 }, [navigation, profile.did]) 78 79 const {mutate: muteConvo} = useMuteConvo(convo?.id, { 80 onSuccess: data => { 81 if (data.convo.muted) { 82 Toast.show(_(msg`Chat muted`)) 83 } else { 84 Toast.show(_(msg`Chat unmuted`)) 85 } 86 }, 87 onError: () => { 88 Toast.show(_(msg`Could not mute chat`), 'xmark') 89 }, 90 }) 91 92 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) 93 94 const toggleBlock = React.useCallback(() => { 95 if (listBlocks.length) { 96 blockedByListControl.open() 97 return 98 } 99 100 if (userBlock) { 101 queueUnblock() 102 } else { 103 queueBlock() 104 } 105 }, [userBlock, listBlocks, blockedByListControl, queueBlock, queueUnblock]) 106 107 return ( 108 <> 109 <Menu.Root control={control}> 110 {!hideTrigger && ( 111 <View style={[style]}> 112 <Menu.Trigger label={_(msg`Chat settings`)}> 113 {({props, state}) => ( 114 <Pressable 115 {...props} 116 onPress={() => { 117 Keyboard.dismiss() 118 119 props.onPress() 120 }} 121 style={[ 122 a.p_sm, 123 a.rounded_full, 124 (state.hovered || state.pressed) && t.atoms.bg_contrast_25, 125 // make sure pfp is in the middle 126 {marginLeft: -10}, 127 ]}> 128 <DotsHorizontal size="md" style={t.atoms.text} /> 129 </Pressable> 130 )} 131 </Menu.Trigger> 132 </View> 133 )} 134 135 {isDeletedAccount ? ( 136 <Menu.Outer> 137 <Menu.Item 138 label={_(msg`Leave conversation`)} 139 onPress={() => leaveConvoControl.open()}> 140 <Menu.ItemText> 141 <Trans>Leave conversation</Trans> 142 </Menu.ItemText> 143 <Menu.ItemIcon icon={ArrowBoxLeft} /> 144 </Menu.Item> 145 </Menu.Outer> 146 ) : ( 147 <Menu.Outer> 148 <Menu.Group> 149 {showMarkAsRead && ( 150 <Menu.Item 151 label={_(msg`Mark as read`)} 152 onPress={() => 153 markAsRead({ 154 convoId: convo?.id, 155 }) 156 }> 157 <Menu.ItemText> 158 <Trans>Mark as read</Trans> 159 </Menu.ItemText> 160 <Menu.ItemIcon icon={Bubble} /> 161 </Menu.Item> 162 )} 163 <Menu.Item 164 label={_(msg`Go to user's profile`)} 165 onPress={onNavigateToProfile}> 166 <Menu.ItemText> 167 <Trans>Go to profile</Trans> 168 </Menu.ItemText> 169 <Menu.ItemIcon icon={Person} /> 170 </Menu.Item> 171 <Menu.Item 172 label={_(msg`Mute conversation`)} 173 onPress={() => muteConvo({mute: !convo?.muted})}> 174 <Menu.ItemText> 175 {convo?.muted ? ( 176 <Trans>Unmute conversation</Trans> 177 ) : ( 178 <Trans>Mute conversation</Trans> 179 )} 180 </Menu.ItemText> 181 <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} /> 182 </Menu.Item> 183 </Menu.Group> 184 <Menu.Divider /> 185 <Menu.Group> 186 <Menu.Item 187 label={ 188 isBlocking ? _(msg`Unblock account`) : _(msg`Block account`) 189 } 190 onPress={toggleBlock}> 191 <Menu.ItemText> 192 {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 193 </Menu.ItemText> 194 <Menu.ItemIcon icon={isBlocking ? PersonCheck : PersonX} /> 195 </Menu.Item> 196 <Menu.Item 197 label={_(msg`Report conversation`)} 198 onPress={() => reportControl.open()}> 199 <Menu.ItemText> 200 <Trans>Report conversation</Trans> 201 </Menu.ItemText> 202 <Menu.ItemIcon icon={Flag} /> 203 </Menu.Item> 204 </Menu.Group> 205 <Menu.Divider /> 206 <Menu.Group> 207 <Menu.Item 208 label={_(msg`Leave conversation`)} 209 onPress={() => leaveConvoControl.open()}> 210 <Menu.ItemText> 211 <Trans>Leave conversation</Trans> 212 </Menu.ItemText> 213 <Menu.ItemIcon icon={ArrowBoxLeft} /> 214 </Menu.Item> 215 </Menu.Group> 216 </Menu.Outer> 217 )} 218 </Menu.Root> 219 220 <LeaveConvoPrompt 221 control={leaveConvoControl} 222 convoId={convo.id} 223 currentScreen={currentScreen} 224 /> 225 <ReportConversationPrompt control={reportControl} /> 226 <BlockedByListDialog 227 control={blockedByListControl} 228 listBlocks={listBlocks} 229 /> 230 </> 231 ) 232} 233ConvoMenu = React.memo(ConvoMenu) 234 235export {ConvoMenu}