diff --git a/assets/icons/chainLink_stroke2_corner0_rounded.svg b/assets/icons/chainLink_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..c1626cc618 --- /dev/null +++ b/assets/icons/chainLink_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/src/components/icons/ChainLink.tsx b/src/components/icons/ChainLink.tsx new file mode 100644 index 0000000000..be19b556a4 --- /dev/null +++ b/src/components/icons/ChainLink.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ChainLink_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z', +}) diff --git a/src/components/post-ctrls/PostCtrlButton.tsx b/src/components/post-ctrls/PostCtrlButton.tsx new file mode 100644 index 0000000000..17566b93bb --- /dev/null +++ b/src/components/post-ctrls/PostCtrlButton.tsx @@ -0,0 +1,125 @@ +import React, {useContext, useMemo} from 'react' +import {type GestureResponderEvent, type View} from 'react-native' + +import {POST_CTRL_HITSLOP} from '#/lib/constants' +import {useHaptics} from '#/lib/haptics' +import {atoms as a, useTheme} from '#/alf' +import {Button, type ButtonProps} from '#/components/Button' +import {type Props as SVGIconProps} from '#/components/icons/common' +import {Text, type TextProps} from '#/components/Typography' + +const PostCtrlContext = React.createContext<{ + big?: boolean + active?: boolean + color?: {color: string} +}>({}) + +// Base button style, which the the other ones extend +export const PostCtrlButton = React.forwardRef< + View, + ButtonProps & { + active?: boolean + big?: boolean + color?: string + activeColor?: string + } +>( + ( + {onPress, onLongPress, children, big, active, activeColor, ...props}, + ref, + ) => { + const t = useTheme() + const playHaptic = useHaptics() + + const ctx = React.useMemo( + () => ({ + big, + active, + color: { + color: activeColor && active ? activeColor : t.palette.contrast_500, + }, + }), + [big, active, activeColor, t.palette.contrast_500], + ) + + const style = useMemo( + () => [ + a.flex_row, + a.align_center, + a.gap_xs, + a.bg_transparent, + {padding: 5}, + ], + [], + ) + + const handlePress = useMemo(() => { + if (!onPress) return + return (evt: GestureResponderEvent) => { + playHaptic('Light') + onPress(evt) + } + }, [onPress, playHaptic]) + + const handleLongPress = useMemo(() => { + if (!onLongPress) return + return (evt: GestureResponderEvent) => { + playHaptic('Heavy') + onLongPress(evt) + } + }, [onLongPress, playHaptic]) + + return ( + + ) + }, +) +PostCtrlButton.displayName = 'PostCtrlButton' + +export function PostCtrlButtonIcon({ + icon: Comp, +}: { + icon: React.ComponentType +}) { + const {big, color} = useContext(PostCtrlContext) + + return +} + +export function PostCtrlButtonText({style, ...props}: TextProps) { + const {big, active, color} = useContext(PostCtrlContext) + + return ( + + ) +} diff --git a/src/components/post-ctrls/PostCtrls.tsx b/src/components/post-ctrls/PostCtrls.tsx index a74a1c9c4f..d7acb46488 100644 --- a/src/components/post-ctrls/PostCtrls.tsx +++ b/src/components/post-ctrls/PostCtrls.tsx @@ -1,32 +1,22 @@ import React, {memo} from 'react' -import { - Pressable, - type PressableStateCallbackType, - type StyleProp, - View, - type ViewStyle, -} from 'react-native' +import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import * as Clipboard from 'expo-clipboard' import { type AppBskyFeedDefs, type AppBskyFeedPost, type AppBskyFeedThreadgate, - AtUri, type RichText as RichTextAPI, } from '@atproto/api' import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' import {IS_INTERNAL} from '#/lib/app-info' -import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants' +import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' import {CountWheel} from '#/lib/custom-animations/CountWheel' import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon' import {useHaptics} from '#/lib/haptics' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' -import {makeProfileLink} from '#/lib/routes/links' -import {shareUrl} from '#/lib/sharing' import {useGate} from '#/lib/statsig/statsig' -import {toShareUrl} from '#/lib/strings/url-helpers' import {type Shadow} from '#/state/cache/types' import {useFeedFeedbackContext} from '#/state/feed-feedback' import { @@ -41,13 +31,16 @@ import { import {formatCount} from '#/view/com/util/numeric/format' import {Text} from '#/view/com/util/text/Text' import * as Toast from '#/view/com/util/Toast' -import {atoms as a, useTheme} from '#/alf' -import {useDialogControl} from '#/components/Dialog' -import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' -import * as Prompt from '#/components/Prompt' -import {PostDropdownBtn} from './PostDropdownBtn' +import { + PostCtrlButton, + PostCtrlButtonIcon, + PostCtrlButtonText, +} from './PostCtrlButton' +import {PostMenuButton} from './PostMenuButton' import {RepostButton} from './RepostButton' +import {ShareMenuButton} from './ShareMenuButton' let PostCtrls = ({ big, @@ -77,6 +70,7 @@ let PostCtrls = ({ onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void }): React.ReactNode => { const t = useTheme() + const {gtMobile} = useBreakpoints() const {_, i18n} = useLingui() const {openComposer} = useOpenComposer() const {currentAccount} = useSession() @@ -86,7 +80,6 @@ let PostCtrls = ({ logContext, ) const requireAuth = useRequireAuth() - const loggedOutWarningPromptControl = useDialogControl() const {sendInteraction} = useFeedFeedbackContext() const {captureAction} = useProgressGuideControls() const playHaptic = useHaptics() @@ -102,20 +95,6 @@ let PostCtrls = ({ ) const replyDisabled = post.viewer?.replyDisabled - const shouldShowLoggedOutWarning = React.useMemo(() => { - return ( - post.author.did !== currentAccount?.did && - !!post.author.labels?.find(label => label.val === '!no-unauthenticated') - ) - }, [currentAccount, post]) - - const defaultCtrlColor = React.useMemo( - () => ({ - color: t.palette.contrast_500, - }), - [t], - ) as StyleProp - const [hasLikeIconBeenToggled, setHasLikeIconBeenToggled] = React.useState(false) @@ -200,10 +179,6 @@ let PostCtrls = ({ } const onShare = () => { - const urip = new AtUri(post.uri) - const href = makeProfileLink(post.author, 'post', urip.rkey) - const url = toShareUrl(href) - shareUrl(url) sendInteraction({ item: post.uri, event: 'app.bsky.feed.defs#interactionShare', @@ -212,20 +187,6 @@ let PostCtrls = ({ }) } - const btnStyle = React.useCallback( - ({pressed, hovered}: PressableStateCallbackType) => [ - a.gap_xs, - a.rounded_full, - a.flex_row, - a.justify_center, - a.align_center, - a.overflow_hidden, - {padding: 5}, - (pressed || hovered) && t.atoms.bg_contrast_25, - ], - [t.atoms.bg_contrast_25], - ) - return ( - { - if (!replyDisabled) { - playHaptic('Light') - requireAuth(() => onPressReply()) - } - }} - accessibilityRole="button" - accessibilityLabel={_( + onPress={ + !replyDisabled ? () => requireAuth(() => onPressReply()) : undefined + } + label={_( msg`Reply (${plural(post.replyCount || 0, { one: '# reply', other: '# replies', })})`, )} - accessibilityHint="" - hitSlop={POST_CTRL_HITSLOP}> - - {typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? ( - + big={big}> + + {typeof post.replyCount !== 'undefined' && post.replyCount > 0 && ( + {formatCount(i18n, post.replyCount)} - - ) : undefined} - + + )} + - requireAuth(() => onPressToggleLike())} - accessibilityRole="button" - accessibilityLabel={ + label={ post.viewer?.like ? _( msg`Unlike (${plural(post.likeCount || 0, { @@ -297,9 +243,7 @@ let PostCtrls = ({ other: '# likes', })})`, ) - } - accessibilityHint="" - hitSlop={POST_CTRL_HITSLOP}> + }> - + + + + - {big && ( - <> - - { - if (shouldShowLoggedOutWarning) { - loggedOutWarningPromptControl.open() - } else { - onShare() - } - }} - accessibilityRole="button" - accessibilityLabel={_(msg`Share`)} - accessibilityHint="" - hitSlop={POST_CTRL_HITSLOP}> - - - - - - )} - postFeedContext: string | undefined postReqId: string | undefined + big?: boolean record: AppBskyFeedPost.Record richText: RichTextAPI - style?: StyleProp - hitSlop?: PressableProps['hitSlop'] - size?: 'lg' | 'md' | 'sm' timestamp: string threadgateRecord?: AppBskyFeedThreadgate.Record onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void }): React.ReactNode => { - const theme = useTheme() - const alf = useAlf() const {_} = useLingui() - const defaultCtrlColor = theme.palette.default.postCtrl + const menuControl = useMenuControl() const [hasBeenOpen, setHasBeenOpen] = useState(false) const lazyMenuControl = useMemo( @@ -73,31 +60,21 @@ let PostDropdownBtn = ({ - {({props, state}) => { + {({props}) => { return ( - - - + + + ) }} {hasBeenOpen && ( // Lazily initialized. Once mounted, they stay mounted. - void }): React.ReactNode => { const {hasSession, currentAccount} = useSession() - const {gtMobile} = useBreakpoints() const {_} = useLingui() const langPrefs = useLanguagePrefs() const {mutateAsync: deletePostMutate} = usePostDeleteMutation() @@ -134,9 +126,6 @@ let PostDropdownMenuItems = ({ const reportDialogControl = useReportDialogControl() const deletePromptControl = useDialogControl() const hidePromptControl = useDialogControl() - const loggedOutWarningPromptControl = useDialogControl() - const embedPostControl = useDialogControl() - const sendViaChatControl = useDialogControl() const postInteractionSettingsDialogControl = useDialogControl() const quotePostDetachConfirmControl = useDialogControl() const hideReplyConfirmControl = useDialogControl() @@ -273,14 +262,6 @@ let PostDropdownMenuItems = ({ label => label.val === '!no-unauthenticated', ) - const showLoggedOutWarning = - postAuthor.did !== currentAccount?.did && hideInPWI - - const onSharePost = () => { - const url = toShareUrl(href) - shareUrl(url) - } - const onPressShowMore = () => { feedFeedback.sendInteraction({ event: 'app.bsky.feed.defs#requestMore', @@ -308,13 +289,6 @@ let PostDropdownMenuItems = ({ } } - const onSelectChatToShareTo = (conversation: string) => { - navigation.navigate('MessagesConversation', { - conversation, - embed: postUri, - }) - } - const onToggleQuotePostAttachment = async () => { if (!quoteEmbed) return @@ -341,7 +315,6 @@ let PostDropdownMenuItems = ({ } const canHidePostForMe = !isAuthor && !isPostHidden - const canEmbed = isWeb && gtMobile && !hideInPWI const canHideReplyForEveryone = !isAuthor && isRootPostAuthor && !isPostHidden && isReply const canDetachQuote = quoteEmbed && quoteEmbed.isOwnedByViewer @@ -482,44 +455,6 @@ let PostDropdownMenuItems = ({ )} - - {hasSession && ( - sendViaChatControl.open()}> - - Send via direct message - - - - )} - - { - if (showLoggedOutWarning) { - loggedOutWarningPromptControl.open() - } else { - onSharePost() - } - }}> - - {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} - - - - - {canEmbed && ( - embedPostControl.open()}> - {_(msg`Embed post`)} - - - )} {hasSession && feedFeedback.enabled && ( @@ -802,32 +737,6 @@ let PostDropdownMenuItems = ({ }} /> - - - {canEmbed && ( - - )} - - - ) } -PostDropdownMenuItems = memo(PostDropdownMenuItems) -export {PostDropdownMenuItems} +PostMenuItems = memo(PostMenuItems) +export {PostMenuItems} diff --git a/src/components/post-ctrls/RepostButton.tsx b/src/components/post-ctrls/RepostButton.tsx index b0566ddae1..1970c7c770 100644 --- a/src/components/post-ctrls/RepostButton.tsx +++ b/src/components/post-ctrls/RepostButton.tsx @@ -1,9 +1,8 @@ -import React, {memo, useCallback} from 'react' +import {memo, useCallback} from 'react' import {View} from 'react-native' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {POST_CTRL_HITSLOP} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {useRequireAuth} from '#/state/session' import {formatCount} from '#/view/com/util/numeric/format' @@ -13,6 +12,11 @@ import * as Dialog from '#/components/Dialog' import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote' import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' import {Text} from '#/components/Typography' +import { + PostCtrlButton, + PostCtrlButtonIcon, + PostCtrlButtonText, +} from './PostCtrlButton' interface Props { isReposted: boolean @@ -35,33 +39,16 @@ let RepostButton = ({ const {_, i18n} = useLingui() const requireAuth = useRequireAuth() const dialogControl = Dialog.useDialogControl() - const playHaptic = useHaptics() - const color = React.useMemo( - () => ({ - color: isReposted ? t.palette.positive_600 : t.palette.contrast_500, - }), - [t, isReposted], - ) + return ( <> - + + )} + diff --git a/src/components/post-ctrls/RepostButton.web.tsx b/src/components/post-ctrls/RepostButton.web.tsx index 3140e3e4b8..ace4c360ee 100644 --- a/src/components/post-ctrls/RepostButton.web.tsx +++ b/src/components/post-ctrls/RepostButton.web.tsx @@ -1,5 +1,3 @@ -import React from 'react' -import {Pressable, View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -7,12 +5,15 @@ import {useRequireAuth} from '#/state/session' import {useSession} from '#/state/session' import {EventStopper} from '#/view/com/util/EventStopper' import {formatCount} from '#/view/com/util/numeric/format' -import {atoms as a, useTheme} from '#/alf' -import {Button} from '#/components/Button' +import {useTheme} from '#/alf' import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote' import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' import * as Menu from '#/components/Menu' -import {Text} from '#/components/Typography' +import { + PostCtrlButton, + PostCtrlButtonIcon, + PostCtrlButtonText, +} from './PostCtrlButton' interface Props { isReposted: boolean @@ -32,38 +33,30 @@ export const RepostButton = ({ embeddingDisabled, }: Props) => { const t = useTheme() - const {_} = useLingui() + const {_, i18n} = useLingui() const {hasSession} = useSession() const requireAuth = useRequireAuth() - const color = React.useMemo( - () => ({ - color: isReposted ? t.palette.positive_600 : t.palette.contrast_500, - }), - [t, isReposted], - ) - return hasSession ? ( - {({props, state}) => { + {({props}) => { return ( - - - + + + {typeof repostCount !== 'undefined' && repostCount > 0 && ( + + {formatCount(i18n, repostCount)} + + )} + ) }} @@ -97,51 +90,18 @@ export const RepostButton = ({ ) : ( - - ) -} - -const RepostInner = ({ - isReposted, - color, - repostCount, - big, -}: { - isReposted: boolean - color: {color: string} - repostCount?: number - big?: boolean -}) => { - const {i18n} = useLingui() - return ( - - - {typeof repostCount !== 'undefined' && repostCount > 0 ? ( - + big={big}> + + {typeof repostCount !== 'undefined' && repostCount > 0 && ( + {formatCount(i18n, repostCount)} - - ) : undefined} - + + )} + ) } diff --git a/src/components/post-ctrls/ShareMenuButton.tsx b/src/components/post-ctrls/ShareMenuButton.tsx new file mode 100644 index 0000000000..123ac1a4db --- /dev/null +++ b/src/components/post-ctrls/ShareMenuButton.tsx @@ -0,0 +1,89 @@ +import {memo, useMemo, useState} from 'react' +import { + type AppBskyFeedDefs, + type AppBskyFeedPost, + type AppBskyFeedThreadgate, + type RichText as RichTextAPI, +} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import type React from 'react' + +import {type Shadow} from '#/state/cache/post-shadow' +import {EventStopper} from '#/view/com/util/EventStopper' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBoxIcon} from '#/components/icons/ArrowOutOfBox' +import {useMenuControl} from '#/components/Menu' +import * as Menu from '#/components/Menu' +import {PostCtrlButton, PostCtrlButtonIcon} from './PostCtrlButton' +import {ShareMenuItems} from './ShareMenuItems' + +let ShareMenuButton = ({ + testID, + post, + big, + record, + richText, + timestamp, + threadgateRecord, + onShare, +}: { + testID: string + post: Shadow + big?: boolean + record: AppBskyFeedPost.Record + richText: RichTextAPI + timestamp: string + threadgateRecord?: AppBskyFeedThreadgate.Record + onShare: () => void +}): React.ReactNode => { + const {_} = useLingui() + + const menuControl = useMenuControl() + const [hasBeenOpen, setHasBeenOpen] = useState(false) + const lazyMenuControl = useMemo( + () => ({ + ...menuControl, + open() { + setHasBeenOpen(true) + // HACK. We need the state update to be flushed by the time + // menuControl.open() fires but RN doesn't expose flushSync. + setTimeout(menuControl.open) + }, + }), + [menuControl, setHasBeenOpen], + ) + return ( + + + + {({props}) => { + return ( + + + + ) + }} + + {hasBeenOpen && ( + // Lazily initialized. Once mounted, they stay mounted. + + )} + + + ) +} + +ShareMenuButton = memo(ShareMenuButton) +export {ShareMenuButton} diff --git a/src/components/post-ctrls/ShareMenuItems.tsx b/src/components/post-ctrls/ShareMenuItems.tsx new file mode 100644 index 0000000000..82c40f058b --- /dev/null +++ b/src/components/post-ctrls/ShareMenuItems.tsx @@ -0,0 +1,150 @@ +import {memo, useMemo} from 'react' +import {AtUri} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {makeProfileLink} from '#/lib/routes/links' +import {type NavigationProp} from '#/lib/routes/types' +import {shareText, shareUrl} from '#/lib/sharing' +import {toShareUrl} from '#/lib/strings/url-helpers' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useSession} from '#/state/session' +import {useDialogControl} from '#/components/Dialog' +import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' +import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' +import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' +import * as Menu from '#/components/Menu' +import * as Prompt from '#/components/Prompt' +import {useDevMode} from '#/storage/hooks/dev-mode' +import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBoxIcon} from '../icons/ArrowOutOfBox' +import {type ShareMenuItemsProps} from './ShareMenuItems.types' + +let ShareMenuItems = ({ + post, + onShare: onShareProp, +}: ShareMenuItemsProps): React.ReactNode => { + const {hasSession, currentAccount} = useSession() + const {_} = useLingui() + const navigation = useNavigation() + const loggedOutWarningPromptControl = useDialogControl() + const sendViaChatControl = useDialogControl() + const [devModeEnabled] = useDevMode() + + const postUri = post.uri + const postAuthor = useProfileShadow(post.author) + + const href = useMemo(() => { + const urip = new AtUri(postUri) + return makeProfileLink(postAuthor, 'post', urip.rkey) + }, [postUri, postAuthor]) + + const hideInPWI = useMemo(() => { + return !!postAuthor.labels?.find( + label => label.val === '!no-unauthenticated', + ) + }, [postAuthor]) + + const showLoggedOutWarning = + postAuthor.did !== currentAccount?.did && hideInPWI + + const onSharePost = () => { + const url = toShareUrl(href) + shareUrl(url) + onShareProp() + } + + const onSelectChatToShareTo = (conversation: string) => { + navigation.navigate('MessagesConversation', { + conversation, + embed: postUri, + }) + } + + const onShareATURI = () => { + shareText(postUri) + } + + const onShareAuthorDID = () => { + shareText(postAuthor.did) + } + + return ( + <> + + + { + if (showLoggedOutWarning) { + loggedOutWarningPromptControl.open() + } else { + onSharePost() + } + }}> + + Share post + + + + + {hasSession && ( + sendViaChatControl.open()}> + + Send via direct message + + + + )} + + + {devModeEnabled && ( + <> + + + + + Share post at:// URI + + + + + + Share author DID + + + + + + )} + + + + + + + ) +} +ShareMenuItems = memo(ShareMenuItems) +export {ShareMenuItems} diff --git a/src/components/post-ctrls/ShareMenuItems.types.tsx b/src/components/post-ctrls/ShareMenuItems.types.tsx new file mode 100644 index 0000000000..5bc2a8fb6e --- /dev/null +++ b/src/components/post-ctrls/ShareMenuItems.types.tsx @@ -0,0 +1,22 @@ +import {type PressableProps, type StyleProp, type ViewStyle} from 'react-native' +import { + type AppBskyFeedDefs, + type AppBskyFeedPost, + type AppBskyFeedThreadgate, + type RichText as RichTextAPI, +} from '@atproto/api' + +import {type Shadow} from '#/state/cache/post-shadow' + +export interface ShareMenuItemsProps { + testID: string + post: Shadow + record: AppBskyFeedPost.Record + richText: RichTextAPI + style?: StyleProp + hitSlop?: PressableProps['hitSlop'] + size?: 'lg' | 'md' | 'sm' + timestamp: string + threadgateRecord?: AppBskyFeedThreadgate.Record + onShare: () => void +} diff --git a/src/components/post-ctrls/ShareMenuItems.web.tsx b/src/components/post-ctrls/ShareMenuItems.web.tsx new file mode 100644 index 0000000000..5a8a2e7db7 --- /dev/null +++ b/src/components/post-ctrls/ShareMenuItems.web.tsx @@ -0,0 +1,183 @@ +import {memo, useMemo} from 'react' +import {AtUri} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' +import type React from 'react' + +import {makeProfileLink} from '#/lib/routes/links' +import {type NavigationProp} from '#/lib/routes/types' +import {shareText, shareUrl} from '#/lib/sharing' +import {toShareUrl} from '#/lib/strings/url-helpers' +import {isWeb} from '#/platform/detection' +import {useProfileShadow} from '#/state/cache/profile-shadow' +import {useSession} from '#/state/session' +import {useBreakpoints} from '#/alf' +import {useDialogControl} from '#/components/Dialog' +import {EmbedDialog} from '#/components/dialogs/Embed' +import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' +import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' +import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' +import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets' +import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' +import * as Menu from '#/components/Menu' +import * as Prompt from '#/components/Prompt' +import {useDevMode} from '#/storage/hooks/dev-mode' +import {type ShareMenuItemsProps} from './ShareMenuItems.types' + +let ShareMenuItems = ({ + post, + record, + timestamp, + onShare: onShareProp, +}: ShareMenuItemsProps): React.ReactNode => { + const {hasSession, currentAccount} = useSession() + const {gtMobile} = useBreakpoints() + const {_} = useLingui() + const navigation = useNavigation() + const loggedOutWarningPromptControl = useDialogControl() + const embedPostControl = useDialogControl() + const sendViaChatControl = useDialogControl() + const [devModeEnabled] = useDevMode() + + const postUri = post.uri + const postCid = post.cid + const postAuthor = useProfileShadow(post.author) + + const href = useMemo(() => { + const urip = new AtUri(postUri) + return makeProfileLink(postAuthor, 'post', urip.rkey) + }, [postUri, postAuthor]) + + const hideInPWI = useMemo(() => { + return !!postAuthor.labels?.find( + label => label.val === '!no-unauthenticated', + ) + }, [postAuthor]) + + const showLoggedOutWarning = + postAuthor.did !== currentAccount?.did && hideInPWI + + const onSharePost = () => { + const url = toShareUrl(href) + shareUrl(url) + onShareProp() + } + + const onSelectChatToShareTo = (conversation: string) => { + navigation.navigate('MessagesConversation', { + conversation, + embed: postUri, + }) + } + + const canEmbed = isWeb && gtMobile && !hideInPWI + + const onShareATURI = () => { + shareText(postUri) + } + + const onShareAuthorDID = () => { + shareText(postAuthor.did) + } + + return ( + <> + + + { + if (showLoggedOutWarning) { + loggedOutWarningPromptControl.open() + } else { + onSharePost() + } + }}> + + Copy link to post + + + + + {hasSession && ( + sendViaChatControl.open()}> + + Send via direct message + + + + )} + + {canEmbed && ( + embedPostControl.open()}> + {_(msg`Embed post`)} + + + )} + + + {devModeEnabled && ( + <> + + + + + Copy post at:// URI + + + + + + Copy author DID + + + + + + )} + + + + + {canEmbed && ( + + )} + + + + ) +} +ShareMenuItems = memo(ShareMenuItems) +export {ShareMenuItems}