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