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