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