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

Configure Feed

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

at 20bf2cd11723d81fe75cc74dc13abf085113dd4d 304 lines 9.6 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' 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 * as Toast from '#/view/com/util/Toast' 22import {type ViewStyleProp} from '#/alf' 23import {atoms as a} from '#/alf' 24import {Button, ButtonIcon} from '#/components/Button' 25import {AfterReportDialog} from '#/components/dms/AfterReportDialog' 26import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' 27import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' 28import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' 29import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components/icons/ArrowBoxLeft' 30import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' 31import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' 32import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 33import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' 34import { 35 Person_Stroke2_Corner0_Rounded as Person, 36 PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, 37 PersonX_Stroke2_Corner0_Rounded as PersonX, 38} from '#/components/icons/Person' 39import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' 40import * as Menu from '#/components/Menu' 41import {ReportDialog} from '#/components/moderation/ReportDialog' 42import * as Prompt from '#/components/Prompt' 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 = React.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`), 'xmark') 209 }, 210 }) 211 212 const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) 213 214 const toggleBlock = React.useCallback(() => { 215 if (listBlocks.length) { 216 blockedByListControl.open() 217 return 218 } 219 220 if (userBlock) { 221 queueUnblock() 222 } else { 223 queueBlock() 224 } 225 }, [userBlock, listBlocks, blockedByListControl, queueBlock, queueUnblock]) 226 227 return isDeletedAccount ? ( 228 <Menu.Item 229 label={_(msg`Leave conversation`)} 230 onPress={() => leaveConvoControl.open()}> 231 <Menu.ItemText> 232 <Trans>Leave conversation</Trans> 233 </Menu.ItemText> 234 <Menu.ItemIcon icon={ArrowBoxLeft} /> 235 </Menu.Item> 236 ) : ( 237 <> 238 <Menu.Group> 239 {showMarkAsRead && ( 240 <Menu.Item 241 label={_(msg`Mark as read`)} 242 onPress={() => markAsRead({convoId})}> 243 <Menu.ItemText> 244 <Trans>Mark as read</Trans> 245 </Menu.ItemText> 246 <Menu.ItemIcon icon={Bubble} /> 247 </Menu.Item> 248 )} 249 <Menu.Item 250 label={_(msg`Go to user's profile`)} 251 onPress={onNavigateToProfile}> 252 <Menu.ItemText> 253 <Trans>Go to profile</Trans> 254 </Menu.ItemText> 255 <Menu.ItemIcon icon={Person} /> 256 </Menu.Item> 257 <Menu.Item 258 label={_(msg`Mute conversation`)} 259 onPress={() => muteConvo({mute: !convo?.muted})}> 260 <Menu.ItemText> 261 {convo?.muted ? ( 262 <Trans>Unmute conversation</Trans> 263 ) : ( 264 <Trans>Mute conversation</Trans> 265 )} 266 </Menu.ItemText> 267 <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} /> 268 </Menu.Item> 269 </Menu.Group> 270 <Menu.Divider /> 271 <Menu.Group> 272 <Menu.Item 273 label={isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 274 onPress={toggleBlock}> 275 <Menu.ItemText> 276 {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 277 </Menu.ItemText> 278 <Menu.ItemIcon icon={isBlocking ? PersonCheck : PersonX} /> 279 </Menu.Item> 280 <Menu.Item 281 label={_(msg`Report conversation`)} 282 onPress={() => reportControl.open()}> 283 <Menu.ItemText> 284 <Trans>Report conversation</Trans> 285 </Menu.ItemText> 286 <Menu.ItemIcon icon={Flag} /> 287 </Menu.Item> 288 </Menu.Group> 289 <Menu.Divider /> 290 <Menu.Group> 291 <Menu.Item 292 label={_(msg`Leave conversation`)} 293 onPress={() => leaveConvoControl.open()}> 294 <Menu.ItemText> 295 <Trans>Leave conversation</Trans> 296 </Menu.ItemText> 297 <Menu.ItemIcon icon={ArrowBoxLeft} /> 298 </Menu.Item> 299 </Menu.Group> 300 </> 301 ) 302} 303 304export {ConvoMenu}