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