Bluesky app fork with some witchin' additions 馃挮
at linkat-integration 290 lines 9.8 kB view raw
1import {type AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' 2import {msg, Trans} from '@lingui/macro' 3import {useLingui} from '@lingui/react' 4import {useNavigation} from '@react-navigation/native' 5 6import {type NavigationProp} from '#/lib/routes/types' 7import {shareUrl} from '#/lib/sharing' 8import {toShareUrl} from '#/lib/strings/url-helpers' 9import {logger} from '#/logger' 10import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 11import { 12 useListBlockMutation, 13 useListDeleteMutation, 14 useListMuteMutation, 15} from '#/state/queries/list' 16import {useRemoveFeedMutation} from '#/state/queries/preferences' 17import {useSession} from '#/state/session' 18import {Button, ButtonIcon} from '#/components/Button' 19import {useDialogControl} from '#/components/Dialog' 20import {CreateOrEditListDialog} from '#/components/dialogs/lists/CreateOrEditListDialog' 21import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' 22import {ChainLink_Stroke2_Corner0_Rounded as ChainLink} from '#/components/icons/ChainLink' 23import {DotGrid_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid' 24import {PencilLine_Stroke2_Corner0_Rounded as PencilLineIcon} from '#/components/icons/Pencil' 25import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheckIcon} from '#/components/icons/Person' 26import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 27import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 28import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 29import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 30import * as Menu from '#/components/Menu' 31import { 32 ReportDialog, 33 useReportDialogControl, 34} from '#/components/moderation/ReportDialog' 35import * as Prompt from '#/components/Prompt' 36import * as Toast from '#/components/Toast' 37import {useAnalytics} from '#/analytics' 38import {IS_WEB} from '#/env' 39 40export function MoreOptionsMenu({ 41 list, 42 savedFeedConfig, 43}: { 44 list: AppBskyGraphDefs.ListView 45 savedFeedConfig?: AppBskyActorDefs.SavedFeed 46}) { 47 const {_} = useLingui() 48 const ax = useAnalytics() 49 const {currentAccount} = useSession() 50 const editListDialogControl = useDialogControl() 51 const deleteListPromptControl = useDialogControl() 52 const reportDialogControl = useReportDialogControl() 53 const navigation = useNavigation<NavigationProp>() 54 55 const {mutateAsync: removeSavedFeed} = useRemoveFeedMutation() 56 const {mutateAsync: deleteList} = useListDeleteMutation() 57 const {mutateAsync: muteList} = useListMuteMutation() 58 const {mutateAsync: blockList} = useListBlockMutation() 59 60 const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 61 const isModList = list.purpose === AppBskyGraphDefs.MODLIST 62 const isBlocking = !!list.viewer?.blocked 63 const isMuting = !!list.viewer?.muted 64 const isPinned = Boolean(savedFeedConfig?.pinned) 65 const isOwner = currentAccount?.did === list.creator.did 66 67 const enableSquareButtons = useEnableSquareButtons() 68 69 const onPressShare = () => { 70 const {rkey} = new AtUri(list.uri) 71 const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`) 72 shareUrl(url) 73 } 74 75 const onRemoveFromSavedFeeds = async () => { 76 if (!savedFeedConfig) return 77 try { 78 await removeSavedFeed(savedFeedConfig) 79 Toast.show(_(msg`Removed from your feeds`)) 80 } catch (e) { 81 Toast.show(_(msg`There was an issue contacting the server`), { 82 type: 'error', 83 }) 84 logger.error('Failed to remove pinned list', {message: e}) 85 } 86 } 87 88 const onPressDelete = async () => { 89 await deleteList({uri: list.uri}) 90 91 if (savedFeedConfig) { 92 await removeSavedFeed(savedFeedConfig) 93 } 94 95 Toast.show(_(msg({message: 'List deleted', context: 'toast'}))) 96 if (navigation.canGoBack()) { 97 navigation.goBack() 98 } else { 99 navigation.navigate('Home') 100 } 101 } 102 103 const onUnpinModList = async () => { 104 try { 105 if (!savedFeedConfig) return 106 await removeSavedFeed(savedFeedConfig) 107 Toast.show(_(msg`Unpinned list`)) 108 } catch { 109 Toast.show(_(msg`Failed to unpin list`), { 110 type: 'error', 111 }) 112 } 113 } 114 115 const onUnsubscribeMute = async () => { 116 try { 117 await muteList({uri: list.uri, mute: false}) 118 Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) 119 ax.metric('moderation:unsubscribedFromList', {listType: 'mute'}) 120 } catch { 121 Toast.show( 122 _( 123 msg`There was an issue. Please check your internet connection and try again.`, 124 ), 125 ) 126 } 127 } 128 129 const onUnsubscribeBlock = async () => { 130 try { 131 await blockList({uri: list.uri, block: false}) 132 Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) 133 ax.metric('moderation:unsubscribedFromList', {listType: 'block'}) 134 } catch { 135 Toast.show( 136 _( 137 msg`There was an issue. Please check your internet connection and try again.`, 138 ), 139 ) 140 } 141 } 142 143 return ( 144 <> 145 <Menu.Root> 146 <Menu.Trigger label={_(msg`More options`)}> 147 {({props}) => ( 148 <Button 149 label={props.accessibilityLabel} 150 testID="moreOptionsBtn" 151 size="small" 152 color="secondary" 153 shape={enableSquareButtons ? 'square' : 'round'} 154 {...props}> 155 <ButtonIcon icon={DotGridIcon} /> 156 </Button> 157 )} 158 </Menu.Trigger> 159 <Menu.Outer> 160 <Menu.Group> 161 <Menu.Item 162 label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)} 163 onPress={onPressShare}> 164 <Menu.ItemText> 165 {IS_WEB ? ( 166 <Trans>Copy link to list</Trans> 167 ) : ( 168 <Trans>Share via...</Trans> 169 )} 170 </Menu.ItemText> 171 <Menu.ItemIcon 172 position="right" 173 icon={IS_WEB ? ChainLink : ShareIcon} 174 /> 175 </Menu.Item> 176 {savedFeedConfig && ( 177 <Menu.Item 178 label={_(msg`Remove from my feeds`)} 179 onPress={onRemoveFromSavedFeeds}> 180 <Menu.ItemText> 181 <Trans>Remove from my feeds</Trans> 182 </Menu.ItemText> 183 <Menu.ItemIcon position="right" icon={TrashIcon} /> 184 </Menu.Item> 185 )} 186 </Menu.Group> 187 188 <Menu.Divider /> 189 190 {isOwner ? ( 191 <Menu.Group> 192 <Menu.Item 193 label={_(msg`Edit list details`)} 194 onPress={editListDialogControl.open}> 195 <Menu.ItemText> 196 <Trans>Edit list details</Trans> 197 </Menu.ItemText> 198 <Menu.ItemIcon position="right" icon={PencilLineIcon} /> 199 </Menu.Item> 200 <Menu.Item 201 label={_(msg`Delete list`)} 202 onPress={deleteListPromptControl.open}> 203 <Menu.ItemText> 204 <Trans>Delete list</Trans> 205 </Menu.ItemText> 206 <Menu.ItemIcon position="right" icon={TrashIcon} /> 207 </Menu.Item> 208 </Menu.Group> 209 ) : ( 210 <Menu.Group> 211 <Menu.Item 212 label={_(msg`Report list`)} 213 onPress={reportDialogControl.open}> 214 <Menu.ItemText> 215 <Trans>Report list</Trans> 216 </Menu.ItemText> 217 <Menu.ItemIcon position="right" icon={WarningIcon} /> 218 </Menu.Item> 219 </Menu.Group> 220 )} 221 222 {isModList && isPinned && ( 223 <> 224 <Menu.Divider /> 225 <Menu.Group> 226 <Menu.Item 227 label={_(msg`Unpin moderation list`)} 228 onPress={onUnpinModList}> 229 <Menu.ItemText> 230 <Trans>Unpin moderation list</Trans> 231 </Menu.ItemText> 232 <Menu.ItemIcon icon={PinIcon} /> 233 </Menu.Item> 234 </Menu.Group> 235 </> 236 )} 237 238 {isCurateList && (isBlocking || isMuting) && ( 239 <> 240 <Menu.Divider /> 241 <Menu.Group> 242 {isBlocking && ( 243 <Menu.Item 244 label={_(msg`Unblock list`)} 245 onPress={onUnsubscribeBlock}> 246 <Menu.ItemText> 247 <Trans>Unblock list</Trans> 248 </Menu.ItemText> 249 <Menu.ItemIcon icon={PersonCheckIcon} /> 250 </Menu.Item> 251 )} 252 {isMuting && ( 253 <Menu.Item 254 label={_(msg`Unmute list`)} 255 onPress={onUnsubscribeMute}> 256 <Menu.ItemText> 257 <Trans>Unmute list</Trans> 258 </Menu.ItemText> 259 <Menu.ItemIcon icon={UnmuteIcon} /> 260 </Menu.Item> 261 )} 262 </Menu.Group> 263 </> 264 )} 265 </Menu.Outer> 266 </Menu.Root> 267 268 <CreateOrEditListDialog control={editListDialogControl} list={list} /> 269 270 <Prompt.Basic 271 control={deleteListPromptControl} 272 title={_(msg`Delete this list?`)} 273 description={_( 274 msg`If you delete this list, you won't be able to recover it.`, 275 )} 276 onConfirm={onPressDelete} 277 confirmButtonCta={_(msg`Delete`)} 278 confirmButtonColor="negative" 279 /> 280 281 <ReportDialog 282 control={reportDialogControl} 283 subject={{ 284 ...list, 285 $type: 'app.bsky.graph.defs#listView', 286 }} 287 /> 288 </> 289 ) 290}