Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 358 lines 10 kB view raw
1import {useCallback} from 'react' 2import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Trans} from '@lingui/react/macro' 6import {StackActions, useNavigation} from '@react-navigation/native' 7import {useQueryClient} from '@tanstack/react-query' 8 9import {type NavigationProp} from '#/lib/routes/types' 10import {useProfileShadow} from '#/state/cache/profile-shadow' 11import {useEmail} from '#/state/email-verification' 12import {useAcceptConversation} from '#/state/queries/messages/accept-conversation' 13import {precacheConvoQuery} from '#/state/queries/messages/conversation' 14import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 15import { 16 unstableCacheProfileView, 17 useProfileBlockMutationQueue, 18} from '#/state/queries/profile' 19import {atoms as a} from '#/alf' 20import { 21 Button, 22 ButtonIcon, 23 type ButtonProps, 24 ButtonText, 25} from '#/components/Button' 26import {useDialogControl} from '#/components/Dialog' 27import { 28 EmailDialogScreenID, 29 useEmailDialogControl, 30} from '#/components/dialogs/EmailDialog' 31import {AfterReportDialog} from '#/components/dms/AfterReportDialog' 32import {CircleX_Stroke2_Corner0_Rounded} from '#/components/icons/CircleX' 33import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag' 34import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person' 35import {Loader} from '#/components/Loader' 36import * as Menu from '#/components/Menu' 37import {ReportDialog} from '#/components/moderation/ReportDialog' 38import * as Toast from '#/components/Toast' 39 40export function RejectMenu({ 41 convo, 42 profile, 43 size = 'tiny', 44 color = 'secondary', 45 label, 46 showDeleteConvo, 47 currentScreen, 48 ...props 49}: Omit<ButtonProps, 'onPress' | 'children' | 'label'> & { 50 label?: string 51 convo: ChatBskyConvoDefs.ConvoView 52 profile: ChatBskyActorDefs.ProfileViewBasic 53 showDeleteConvo?: boolean 54 currentScreen: 'list' | 'conversation' 55}) { 56 const {_} = useLingui() 57 const shadowedProfile = useProfileShadow(profile) 58 const navigation = useNavigation<NavigationProp>() 59 const queryClient = useQueryClient() 60 61 const {mutate: leaveConvo} = useLeaveConvo(convo.id, { 62 onMutate: () => { 63 if (currentScreen === 'conversation') { 64 navigation.dispatch(StackActions.pop()) 65 } 66 }, 67 onError: () => { 68 Toast.show( 69 _( 70 msg({ 71 context: 'toast', 72 message: 'Failed to delete chat', 73 }), 74 ), 75 { 76 type: 'error', 77 }, 78 ) 79 }, 80 }) 81 const [queueBlock] = useProfileBlockMutationQueue(shadowedProfile) 82 83 const onPressDelete = useCallback(() => { 84 Toast.show( 85 _( 86 msg({ 87 context: 'toast', 88 message: 'Chat deleted', 89 }), 90 ), 91 { 92 type: 'success', 93 }, 94 ) 95 leaveConvo() 96 }, [leaveConvo, _]) 97 98 const onPressBlock = useCallback(() => { 99 Toast.show( 100 _( 101 msg({ 102 context: 'toast', 103 message: 'Account blocked', 104 }), 105 ), 106 { 107 type: 'success', 108 }, 109 ) 110 // block and also delete convo 111 queueBlock() 112 leaveConvo() 113 }, [queueBlock, leaveConvo, _]) 114 115 const reportControl = useDialogControl() 116 const blockOrDeleteControl = useDialogControl() 117 118 const lastMessage = ChatBskyConvoDefs.isMessageView(convo.lastMessage) 119 ? convo.lastMessage 120 : null 121 122 return ( 123 <> 124 <Menu.Root> 125 <Menu.Trigger label={_(msg`Reject chat request`)}> 126 {({props: triggerProps}) => ( 127 <Button 128 {...triggerProps} 129 {...props} 130 label={triggerProps.accessibilityLabel} 131 style={[a.flex_1]} 132 color={color} 133 size={size}> 134 <ButtonText> 135 {label || ( 136 <Trans comment="Reject a chat request, this opens a menu with options"> 137 Reject 138 </Trans> 139 )} 140 </ButtonText> 141 </Button> 142 )} 143 </Menu.Trigger> 144 <Menu.Outer showCancel> 145 <Menu.Group> 146 {showDeleteConvo && ( 147 <Menu.Item 148 label={_(msg`Delete conversation`)} 149 onPress={onPressDelete}> 150 <Menu.ItemText> 151 <Trans>Delete conversation</Trans> 152 </Menu.ItemText> 153 <Menu.ItemIcon icon={CircleX_Stroke2_Corner0_Rounded} /> 154 </Menu.Item> 155 )} 156 <Menu.Item label={_(msg`Block account`)} onPress={onPressBlock}> 157 <Menu.ItemText> 158 <Trans>Block account</Trans> 159 </Menu.ItemText> 160 <Menu.ItemIcon icon={PersonXIcon} /> 161 </Menu.Item> 162 {/* note: last message will almost certainly be defined, since you can't 163 delete messages for other people andit's impossible for a convo on this 164 screen to have a message sent by you */} 165 {lastMessage && ( 166 <Menu.Item 167 label={_(msg`Report conversation`)} 168 onPress={reportControl.open}> 169 <Menu.ItemText> 170 <Trans>Report conversation</Trans> 171 </Menu.ItemText> 172 <Menu.ItemIcon icon={FlagIcon} /> 173 </Menu.Item> 174 )} 175 </Menu.Group> 176 </Menu.Outer> 177 </Menu.Root> 178 {lastMessage && ( 179 <> 180 <ReportDialog 181 subject={{ 182 view: 'convo', 183 convoId: convo.id, 184 message: lastMessage, 185 }} 186 control={reportControl} 187 onAfterSubmit={() => { 188 const sender = convo.members.find( 189 member => member.did === lastMessage.sender.did, 190 ) 191 if (sender) { 192 unstableCacheProfileView(queryClient, sender) 193 } 194 blockOrDeleteControl.open() 195 }} 196 /> 197 <AfterReportDialog 198 control={blockOrDeleteControl} 199 currentScreen={currentScreen} 200 params={{ 201 convoId: convo.id, 202 message: lastMessage, 203 }} 204 /> 205 </> 206 )} 207 </> 208 ) 209} 210 211export function AcceptChatButton({ 212 convo, 213 size = 'tiny', 214 color = 'secondary_inverted', 215 label, 216 currentScreen, 217 onAcceptConvo, 218 ...props 219}: Omit<ButtonProps, 'onPress' | 'children' | 'label'> & { 220 label?: string 221 convo: ChatBskyConvoDefs.ConvoView 222 onAcceptConvo?: () => void 223 currentScreen: 'list' | 'conversation' 224}) { 225 const {_} = useLingui() 226 const queryClient = useQueryClient() 227 const navigation = useNavigation<NavigationProp>() 228 const {needsEmailVerification} = useEmail() 229 const emailDialogControl = useEmailDialogControl() 230 231 const {mutate: acceptConvo, isPending} = useAcceptConversation(convo.id, { 232 onMutate: () => { 233 onAcceptConvo?.() 234 if (currentScreen === 'list') { 235 precacheConvoQuery(queryClient, {...convo, status: 'accepted'}) 236 navigation.navigate('MessagesConversation', { 237 conversation: convo.id, 238 accept: true, 239 }) 240 } 241 }, 242 onError: () => { 243 // Should we show a toast here? They'll be on the convo screen, and it'll make 244 // no difference if the request failed - when they send a message, the convo will be accepted 245 // automatically. The only difference is that when they back out of the convo (without sending a message), the conversation will be rejected. 246 // the list will still have this chat in it -sfn 247 Toast.show( 248 _( 249 msg({ 250 context: 'toast', 251 message: 'Failed to accept chat', 252 }), 253 ), 254 { 255 type: 'error', 256 }, 257 ) 258 }, 259 }) 260 261 const onPressAccept = useCallback(() => { 262 if (needsEmailVerification) { 263 emailDialogControl.open({ 264 id: EmailDialogScreenID.Verify, 265 instructions: [ 266 <Trans key="request-btn"> 267 Before you can accept this chat request, you must first verify your 268 email. 269 </Trans>, 270 ], 271 }) 272 } else { 273 acceptConvo() 274 } 275 }, [acceptConvo, needsEmailVerification, emailDialogControl]) 276 277 return ( 278 <Button 279 {...props} 280 label={label || _(msg`Accept chat request`)} 281 size={size} 282 color={color} 283 style={a.flex_1} 284 onPress={onPressAccept}> 285 {isPending ? ( 286 <ButtonIcon icon={Loader} /> 287 ) : ( 288 <ButtonText> 289 {label || <Trans comment="Accept a chat request">Accept</Trans>} 290 </ButtonText> 291 )} 292 </Button> 293 ) 294} 295 296export function DeleteChatButton({ 297 convo, 298 size = 'tiny', 299 color = 'secondary', 300 label, 301 currentScreen, 302 ...props 303}: Omit<ButtonProps, 'children' | 'label'> & { 304 label?: string 305 convo: ChatBskyConvoDefs.ConvoView 306 currentScreen: 'list' | 'conversation' 307}) { 308 const {_} = useLingui() 309 const navigation = useNavigation<NavigationProp>() 310 311 const {mutate: leaveConvo} = useLeaveConvo(convo.id, { 312 onMutate: () => { 313 if (currentScreen === 'conversation') { 314 navigation.dispatch(StackActions.pop()) 315 } 316 }, 317 onError: () => { 318 Toast.show( 319 _( 320 msg({ 321 context: 'toast', 322 message: 'Failed to delete chat', 323 }), 324 ), 325 { 326 type: 'error', 327 }, 328 ) 329 }, 330 }) 331 332 const onPressDelete = useCallback(() => { 333 Toast.show( 334 _( 335 msg({ 336 context: 'toast', 337 message: 'Chat deleted', 338 }), 339 ), 340 { 341 type: 'success', 342 }, 343 ) 344 leaveConvo() 345 }, [leaveConvo, _]) 346 347 return ( 348 <Button 349 label={label || _(msg`Delete chat`)} 350 size={size} 351 color={color} 352 style={a.flex_1} 353 onPress={onPressDelete} 354 {...props}> 355 <ButtonText>{label || <Trans>Delete chat</Trans>}</ButtonText> 356 </Button> 357 ) 358}