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