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