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