forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {memo, useMemo} from 'react'
2import * as ExpoClipboard from 'expo-clipboard'
3import {AtUri} from '@atproto/api'
4import {isIOS} from '@bsky.app/alf'
5import {msg} from '@lingui/core/macro'
6import {useLingui} from '@lingui/react'
7import {Trans} from '@lingui/react/macro'
8import {useNavigation} from '@react-navigation/native'
9
10import {useOpenLink} from '#/lib/hooks/useOpenLink'
11import {makeProfileLink} from '#/lib/routes/links'
12import {type NavigationProp} from '#/lib/routes/types'
13import {shareText, shareUrl} from '#/lib/sharing'
14import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers'
15import {useProfileShadow} from '#/state/cache/profile-shadow'
16import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons'
17import {useSession} from '#/state/session'
18import {atoms as a} from '#/alf'
19import {Admonition} from '#/components/Admonition'
20import {useDialogControl} from '#/components/Dialog'
21import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
22import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox'
23import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
24import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
25import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
26import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight'
27import * as Menu from '#/components/Menu'
28import * as Toast from '#/components/Toast'
29import {useAgeAssurance} from '#/ageAssurance'
30import {useAnalytics} from '#/analytics'
31import {IS_IOS} from '#/env'
32import {useDevMode} from '#/storage/hooks/dev-mode'
33import {RecentChats} from './RecentChats'
34import {type ShareMenuItemsProps} from './ShareMenuItems.types'
35
36let ShareMenuItems = ({
37 post,
38 onShare: onShareProp,
39}: ShareMenuItemsProps): React.ReactNode => {
40 const ax = useAnalytics()
41 const {hasSession} = useSession()
42 const {_} = useLingui()
43 const navigation = useNavigation<NavigationProp>()
44 const sendViaChatControl = useDialogControl()
45 const [devModeEnabled] = useDevMode()
46 const aa = useAgeAssurance()
47 const openLink = useOpenLink()
48
49 const postUri = post.uri
50 const postAuthor = useProfileShadow(post.author)
51
52 const href = useMemo(() => {
53 const urip = new AtUri(postUri)
54 return makeProfileLink(postAuthor, 'post', urip.rkey)
55 }, [postUri, postAuthor])
56
57 const hideInPWI = useMemo(() => {
58 return !!postAuthor.labels?.find(
59 label => label.val === '!no-unauthenticated',
60 )
61 }, [postAuthor])
62
63 const onSharePost = () => {
64 ax.metric('share:press:nativeShare', {})
65 const url = toShareUrl(href)
66 shareUrl(url)
67 onShareProp()
68 }
69
70 const onSharePostBsky = () => {
71 ax.metric('share:press:nativeShare', {})
72 const url = toShareUrlBsky(href)
73 shareUrl(url)
74 onShareProp()
75 }
76
77 const onCopyLink = async () => {
78 ax.metric('share:press:copyLink', {})
79 const url = toShareUrl(href)
80 if (IS_IOS) {
81 // iOS only
82 await ExpoClipboard.setUrlAsync(url)
83 } else {
84 await ExpoClipboard.setStringAsync(url)
85 }
86 Toast.show(_(msg`Copied to clipboard`), {
87 type: 'success',
88 })
89 onShareProp()
90 }
91
92 const onCopyLinkBsky = async () => {
93 ax.metric('share:press:copyLink', {})
94 const url = toShareUrlBsky(href)
95 if (isIOS) {
96 // iOS only
97 await ExpoClipboard.setUrlAsync(url)
98 } else {
99 await ExpoClipboard.setStringAsync(url)
100 }
101 Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
102 onShareProp()
103 }
104
105 const onSelectChatToShareTo = (conversation: string) => {
106 navigation.navigate('MessagesConversation', {
107 conversation,
108 embed: postUri,
109 })
110 }
111
112 const onShareATURI = () => {
113 shareText(postUri)
114 }
115
116 const onShareAuthorDID = () => {
117 shareText(postAuthor.did)
118 }
119
120 const showExternalShareButtons = useShowExternalShareButtons()
121 const isBridgedPost =
122 !!post.record.bridgyOriginalUrl || !!post.record.fediverseId
123 const originalPostUrl = (post.record.bridgyOriginalUrl ||
124 post.record.fediverseId) as string | undefined
125
126 const onOpenOriginalPost = () => {
127 if (originalPostUrl) {
128 openLink(originalPostUrl, true)
129 }
130 }
131
132 const onOpenPostInPdsls = () => {
133 openLink(`https://pdsls.dev/${post.uri}`, true)
134 }
135
136 return (
137 <>
138 <Menu.Outer>
139 {hasSession && aa.state.access === aa.Access.Full && (
140 <Menu.Group>
141 <Menu.ContainerItem>
142 <RecentChats postUri={postUri} />
143 </Menu.ContainerItem>
144 <Menu.Item
145 testID="postDropdownSendViaDMBtn"
146 label={_(msg`Send via direct message`)}
147 onPress={() => {
148 ax.metric('share:press:openDmSearch', {})
149 sendViaChatControl.open()
150 }}>
151 <Menu.ItemText>
152 <Trans>Send via direct message</Trans>
153 </Menu.ItemText>
154 <Menu.ItemIcon icon={PaperPlaneIcon} position="right" />
155 </Menu.Item>
156 </Menu.Group>
157 )}
158
159 {showExternalShareButtons && (
160 <Menu.Group>
161 {isBridgedPost && (
162 <Menu.Item
163 testID="postDropdownOpenOriginalPost"
164 label={_(msg`Open original post`)}
165 onPress={onOpenOriginalPost}>
166 <Menu.ItemText>
167 <Trans>Open original post</Trans>
168 </Menu.ItemText>
169 <Menu.ItemIcon icon={ExternalIcon} position="right" />
170 </Menu.Item>
171 )}
172
173 <Menu.Item
174 testID="postDropdownOpenInPdsls"
175 label={_(msg`Open post in PDSls`)}
176 onPress={onOpenPostInPdsls}>
177 <Menu.ItemText>
178 <Trans>Open post in PDSls</Trans>
179 </Menu.ItemText>
180 <Menu.ItemIcon icon={ExternalIcon} position="right" />
181 </Menu.Item>
182 </Menu.Group>
183 )}
184
185 <Menu.Group>
186 <Menu.Item
187 testID="postDropdownShareBtn"
188 label={_(msg`Share via...`)}
189 onPress={onSharePost}>
190 <Menu.ItemText>
191 <Trans>Share via...</Trans>
192 </Menu.ItemText>
193 <Menu.ItemIcon icon={ArrowOutOfBoxIcon} position="right" />
194 </Menu.Item>
195
196 <Menu.Item
197 testID="postDropdownShareBtn"
198 label={_(msg`Share via bsky.app...`)}
199 onPress={onSharePostBsky}>
200 <Menu.ItemText>
201 <Trans>Share via bsky.app...</Trans>
202 </Menu.ItemText>
203 <Menu.ItemIcon icon={ArrowOutOfBoxIcon} position="right" />
204 </Menu.Item>
205
206 <Menu.Item
207 testID="postDropdownShareBtn"
208 label={_(msg`Copy link to post`)}
209 onPress={onCopyLink}>
210 <Menu.ItemText>
211 <Trans>Copy link to post</Trans>
212 </Menu.ItemText>
213 <Menu.ItemIcon icon={ChainLinkIcon} position="right" />
214 </Menu.Item>
215
216 <Menu.Item
217 testID="postDropdownShareBtn"
218 label={_(msg`Copy via bsky.app`)}
219 onPress={onCopyLinkBsky}>
220 <Menu.ItemText>
221 <Trans>Copy via bsky.app</Trans>
222 </Menu.ItemText>
223 <Menu.ItemIcon icon={ChainLinkIcon} position="right" />
224 </Menu.Item>
225 </Menu.Group>
226
227 {hideInPWI && (
228 <Menu.Group>
229 <Menu.ContainerItem>
230 <Admonition
231 type="warning"
232 style={[a.flex_1, a.border_0, a.p_0, a.bg_transparent]}>
233 <Trans>This post is only visible to logged-in users.</Trans>
234 </Admonition>
235 </Menu.ContainerItem>
236 </Menu.Group>
237 )}
238
239 {devModeEnabled && (
240 <Menu.Group>
241 <Menu.Item
242 testID="postAtUriShareBtn"
243 label={_(msg`Share post at:// URI`)}
244 onPress={onShareATURI}>
245 <Menu.ItemText>
246 <Trans>Share post at:// URI</Trans>
247 </Menu.ItemText>
248 <Menu.ItemIcon icon={ClipboardIcon} position="right" />
249 </Menu.Item>
250 <Menu.Item
251 testID="postAuthorDIDShareBtn"
252 label={_(msg`Share author DID`)}
253 onPress={onShareAuthorDID}>
254 <Menu.ItemText>
255 <Trans>Share author DID</Trans>
256 </Menu.ItemText>
257 <Menu.ItemIcon icon={ClipboardIcon} position="right" />
258 </Menu.Item>
259 </Menu.Group>
260 )}
261 </Menu.Outer>
262
263 <SendViaChatDialog
264 control={sendViaChatControl}
265 onSelectChat={onSelectChatToShareTo}
266 />
267 </>
268 )
269}
270ShareMenuItems = memo(ShareMenuItems)
271export {ShareMenuItems}