Bluesky app fork with some witchin' additions 馃挮
at feat/tealfm 295 lines 9.3 kB view raw
1import React, {useCallback} from 'react' 2import {Keyboard, View} from 'react-native' 3import {type ChatBskyConvoDefs, type ModerationCause} from '@atproto/api' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useNavigation} from '@react-navigation/native' 7 8import {type NavigationProp} from '#/lib/routes/types' 9import {type Shadow} from '#/state/cache/types' 10import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11import { 12 useConvoQuery, 13 useMarkAsReadMutation, 14} from '#/state/queries/messages/conversation' 15import {useMuteConvo} from '#/state/queries/messages/mute-conversation' 16import {useProfileBlockMutationQueue} from '#/state/queries/profile' 17import * as Toast from '#/view/com/util/Toast' 18import {type ViewStyleProp} from '#/alf' 19import {atoms as a} from '#/alf' 20import {Button, ButtonIcon} from '#/components/Button' 21import {AfterReportDialog} from '#/components/dms/AfterReportDialog' 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 {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' 27import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' 28import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 29import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 30import { 31 Person_Stroke2_Corner0_Rounded as Person, 32 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, 33 PersonX_Stroke2_Corner0_Rounded as PersonX, 34} from '#/components/icons/Person' 35import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 36import * as Menu from '#/components/Menu' 37import {ReportDialog} from '#/components/moderation/ReportDialog' 38import * as Prompt from '#/components/Prompt' 39import type * as bsky from '#/types/bsky' 40 41let ConvoMenu = ({ 42 convo, 43 profile, 44 control, 45 currentScreen, 46 showMarkAsRead, 47 hideTrigger, 48 blockInfo, 49 latestReportableMessage, 50 style, 51}: { 52 convo: ChatBskyConvoDefs.ConvoView 53 profile: Shadow<bsky.profile.AnyProfileView> 54 control?: Menu.MenuControlProps 55 currentScreen: 'list' | 'conversation' 56 showMarkAsRead?: boolean 57 hideTrigger?: boolean 58 blockInfo: { 59 listBlocks: ModerationCause[] 60 userBlock?: ModerationCause 61 } 62 latestReportableMessage?: ChatBskyConvoDefs.MessageView 63 style?: ViewStyleProp['style'] 64}): React.ReactNode => { 65 const {_} = useLingui() 66 67 const leaveConvoControl = Prompt.usePromptControl() 68 const reportControl = Prompt.usePromptControl() 69 const blockedByListControl = Prompt.usePromptControl() 70 const blockOrDeleteControl = Prompt.usePromptControl() 71 72 const {listBlocks} = blockInfo 73 74 const enableSquareButtons = useEnableSquareButtons() 75 76 return ( 77 <> 78 <Menu.Root control={control}> 79 {!hideTrigger && ( 80 <View style={[style]}> 81 <Menu.Trigger label={_(msg`Chat settings`)}> 82 {({props}) => ( 83 <Button 84 label={props.accessibilityLabel} 85 {...props} 86 onPress={() => { 87 Keyboard.dismiss() 88 props.onPress() 89 }} 90 size="small" 91 color="secondary" 92 shape={enableSquareButtons ? 'square' : 'round'} 93 variant="ghost" 94 style={[a.bg_transparent]}> 95 <ButtonIcon icon={DotsHorizontal} size="md" /> 96 </Button> 97 )} 98 </Menu.Trigger> 99 </View> 100 )} 101 102 <Menu.Outer> 103 <MenuContent 104 profile={profile} 105 showMarkAsRead={showMarkAsRead} 106 blockInfo={blockInfo} 107 convo={convo} 108 leaveConvoControl={leaveConvoControl} 109 reportControl={reportControl} 110 blockedByListControl={blockedByListControl} 111 /> 112 </Menu.Outer> 113 </Menu.Root> 114 115 <LeaveConvoPrompt 116 control={leaveConvoControl} 117 convoId={convo.id} 118 currentScreen={currentScreen} 119 /> 120 {latestReportableMessage ? ( 121 <> 122 <ReportDialog 123 subject={{ 124 view: 'convo', 125 convoId: convo.id, 126 message: latestReportableMessage, 127 }} 128 control={reportControl} 129 onAfterSubmit={() => { 130 blockOrDeleteControl.open() 131 }} 132 /> 133 <AfterReportDialog 134 control={blockOrDeleteControl} 135 currentScreen={currentScreen} 136 params={{ 137 convoId: convo.id, 138 message: latestReportableMessage, 139 }} 140 /> 141 </> 142 ) : ( 143 <ReportConversationPrompt control={reportControl} /> 144 )} 145 146 <BlockedByListDialog 147 control={blockedByListControl} 148 listBlocks={listBlocks} 149 /> 150 </> 151 ) 152} 153ConvoMenu = React.memo(ConvoMenu) 154 155function MenuContent({ 156 convo: initialConvo, 157 profile, 158 showMarkAsRead, 159 blockInfo, 160 leaveConvoControl, 161 reportControl, 162 blockedByListControl, 163}: { 164 convo: ChatBskyConvoDefs.ConvoView 165 profile: Shadow<bsky.profile.AnyProfileView> 166 showMarkAsRead?: boolean 167 blockInfo: { 168 listBlocks: ModerationCause[] 169 userBlock?: ModerationCause 170 } 171 leaveConvoControl: Prompt.PromptControlProps 172 reportControl: Prompt.PromptControlProps 173 blockedByListControl: Prompt.PromptControlProps 174}) { 175 const navigation = useNavigation<NavigationProp>() 176 const {_} = useLingui() 177 const {mutate: markAsRead} = useMarkAsReadMutation() 178 179 const {listBlocks, userBlock} = blockInfo 180 const isBlocking = userBlock || !!listBlocks.length 181 const isDeletedAccount = profile.handle === 'missing.invalid' 182 183 const convoId = initialConvo.id 184 const {data: convo} = useConvoQuery(initialConvo) 185 186 const onNavigateToProfile = useCallback(() => { 187 navigation.navigate('Profile', {name: profile.did}) 188 }, [navigation, profile.did]) 189 190 const {mutate: muteConvo} = useMuteConvo(convoId, { 191 onSuccess: data => { 192 if (data.convo.muted) { 193 Toast.show(_(msg({message: 'Chat muted', context: 'toast'}))) 194 } else { 195 Toast.show(_(msg({message: 'Chat unmuted', context: 'toast'}))) 196 } 197 }, 198 onError: () => { 199 Toast.show(_(msg`Could not mute chat`), 'xmark') 200 }, 201 }) 202 203 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) 204 205 const toggleBlock = React.useCallback(() => { 206 if (listBlocks.length) { 207 blockedByListControl.open() 208 return 209 } 210 211 if (userBlock) { 212 queueUnblock() 213 } else { 214 queueBlock() 215 } 216 }, [userBlock, listBlocks, blockedByListControl, queueBlock, queueUnblock]) 217 218 return isDeletedAccount ? ( 219 <Menu.Item 220 label={_(msg`Leave conversation`)} 221 onPress={() => leaveConvoControl.open()}> 222 <Menu.ItemText> 223 <Trans>Leave conversation</Trans> 224 </Menu.ItemText> 225 <Menu.ItemIcon icon={ArrowBoxLeft} /> 226 </Menu.Item> 227 ) : ( 228 <> 229 <Menu.Group> 230 {showMarkAsRead && ( 231 <Menu.Item 232 label={_(msg`Mark as read`)} 233 onPress={() => markAsRead({convoId})}> 234 <Menu.ItemText> 235 <Trans>Mark as read</Trans> 236 </Menu.ItemText> 237 <Menu.ItemIcon icon={Bubble} /> 238 </Menu.Item> 239 )} 240 <Menu.Item 241 label={_(msg`Go to user's profile`)} 242 onPress={onNavigateToProfile}> 243 <Menu.ItemText> 244 <Trans>Go to profile</Trans> 245 </Menu.ItemText> 246 <Menu.ItemIcon icon={Person} /> 247 </Menu.Item> 248 <Menu.Item 249 label={_(msg`Mute conversation`)} 250 onPress={() => muteConvo({mute: !convo?.muted})}> 251 <Menu.ItemText> 252 {convo?.muted ? ( 253 <Trans>Unmute conversation</Trans> 254 ) : ( 255 <Trans>Mute conversation</Trans> 256 )} 257 </Menu.ItemText> 258 <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} /> 259 </Menu.Item> 260 </Menu.Group> 261 <Menu.Divider /> 262 <Menu.Group> 263 <Menu.Item 264 label={isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 265 onPress={toggleBlock}> 266 <Menu.ItemText> 267 {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 268 </Menu.ItemText> 269 <Menu.ItemIcon icon={isBlocking ? PersonCheck : PersonX} /> 270 </Menu.Item> 271 <Menu.Item 272 label={_(msg`Report conversation`)} 273 onPress={() => reportControl.open()}> 274 <Menu.ItemText> 275 <Trans>Report conversation</Trans> 276 </Menu.ItemText> 277 <Menu.ItemIcon icon={Flag} /> 278 </Menu.Item> 279 </Menu.Group> 280 <Menu.Divider /> 281 <Menu.Group> 282 <Menu.Item 283 label={_(msg`Leave conversation`)} 284 onPress={() => leaveConvoControl.open()}> 285 <Menu.ItemText> 286 <Trans>Leave conversation</Trans> 287 </Menu.ItemText> 288 <Menu.ItemIcon icon={ArrowBoxLeft} /> 289 </Menu.Item> 290 </Menu.Group> 291 </> 292 ) 293} 294 295export {ConvoMenu}