this repo has no description
0
fork

Configure Feed

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

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