Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

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

at a876aae44ea07494ebea9727350aa060b81f317b 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 ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft' 30import {Bubble_Stroke2_Corner2_Rounded as BubbleIcon} from '#/components/icons/Bubble' 31import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} 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={DotsHorizontalIcon} 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 void queueUnblock() 227 } else { 228 void 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={ArrowBoxLeftIcon} /> 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={BubbleIcon} /> 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={ArrowBoxLeftIcon} /> 303 </Menu.Item> 304 </Menu.Group> 305 </> 306 ) 307} 308 309export {ConvoMenu}