Bluesky app fork with some witchin' additions 馃挮
at 10c8e65793497cd7aa93d5b8f52ade3b09cfaab9 264 lines 8.9 kB view raw
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}