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