import React, {memo} from 'react' import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' import {setStringAsync} from 'expo-clipboard' import { AppBskyActorDefs, AppBskyFeedPost, AtUri, RichText as RichTextAPI, } from '@atproto/api' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {makeProfileLink} from '#/lib/routes/links' import {CommonNavigatorParams} from '#/lib/routes/types' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {getTranslatorLink} from '#/locale/helpers' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads' import {useLanguagePrefs} from '#/state/preferences' import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences' import {useOpenLink} from '#/state/preferences/in-app-browser' import {usePostDeleteMutation} from '#/state/queries/post' import {useSession} from '#/state/session' import {getCurrentRoute} from 'lib/routes/helpers' import {shareUrl} from 'lib/sharing' import {toShareUrl} from 'lib/strings/url-helpers' import {useTheme} from 'lib/ThemeContext' import {atoms as a, useTheme as useAlf} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' import {EventStopper} from '../EventStopper' import * as Toast from '../Toast' let PostDropdownBtn = ({ testID, postAuthor, postCid, postUri, record, richText, style, hitSlop, }: { testID: string postAuthor: AppBskyActorDefs.ProfileViewBasic postCid: string postUri: string record: AppBskyFeedPost.Record richText: RichTextAPI style?: StyleProp hitSlop?: PressableProps['hitSlop'] }): React.ReactNode => { const {hasSession, currentAccount} = useSession() const theme = useTheme() const alf = useAlf() const {_} = useLingui() const defaultCtrlColor = theme.palette.default.postCtrl const langPrefs = useLanguagePrefs() const mutedThreads = useMutedThreads() const toggleThreadMute = useToggleThreadMute() const postDeleteMutation = usePostDeleteMutation() const hiddenPosts = useHiddenPosts() const {hidePost} = useHiddenPostsApi() const openLink = useOpenLink() const navigation = useNavigation() const {mutedWordsDialogControl} = useGlobalDialogsControlContext() const reportDialogControl = useReportDialogControl() const deletePromptControl = useDialogControl() const hidePromptControl = useDialogControl() const loggedOutWarningPromptControl = useDialogControl() const rootUri = record.reply?.root?.uri || postUri const isThreadMuted = mutedThreads.includes(rootUri) const isPostHidden = hiddenPosts && hiddenPosts.includes(postUri) const isAuthor = postAuthor.did === currentAccount?.did const href = React.useMemo(() => { const urip = new AtUri(postUri) return makeProfileLink(postAuthor, 'post', urip.rkey) }, [postUri, postAuthor]) const translatorUrl = getTranslatorLink( record.text, langPrefs.primaryLanguage, ) const onDeletePost = React.useCallback(() => { postDeleteMutation.mutateAsync({uri: postUri}).then( () => { Toast.show(_(msg`Post deleted`)) const route = getCurrentRoute(navigation.getState()) if (route.name === 'PostThread') { const params = route.params as CommonNavigatorParams['PostThread'] if ( currentAccount && isAuthor && (params.name === currentAccount.handle || params.name === currentAccount.did) ) { const currentHref = makeProfileLink(postAuthor, 'post', params.rkey) if (currentHref === href && navigation.canGoBack()) { navigation.goBack() } } } }, e => { logger.error('Failed to delete post', {message: e}) Toast.show(_(msg`Failed to delete post, please try again`)) }, ) }, [ navigation, postUri, postDeleteMutation, postAuthor, currentAccount, isAuthor, href, _, ]) const onToggleThreadMute = React.useCallback(() => { try { const muted = toggleThreadMute(rootUri) if (muted) { Toast.show( _(msg`You will no longer receive notifications for this thread`), ) } else { Toast.show(_(msg`You will now receive notifications for this thread`)) } } catch (e) { logger.error('Failed to toggle thread mute', {message: e}) } }, [rootUri, toggleThreadMute, _]) const onCopyPostText = React.useCallback(() => { const str = richTextToString(richText, true) setStringAsync(str) Toast.show(_(msg`Copied to clipboard`)) }, [_, richText]) const onOpenTranslate = React.useCallback(() => { openLink(translatorUrl) }, [openLink, translatorUrl]) const onHidePost = React.useCallback(() => { hidePost({uri: postUri}) }, [postUri, hidePost]) const shouldShowLoggedOutWarning = React.useMemo(() => { return !!postAuthor.labels?.find( label => label.val === '!no-unauthenticated', ) }, [postAuthor]) const onSharePost = React.useCallback(() => { const url = toShareUrl(href) shareUrl(url) }, [href]) return ( {({props, state}) => { return ( ) }} {_(msg`Translate`)} {_(msg`Copy post text`)} { if (shouldShowLoggedOutWarning) { loggedOutWarningPromptControl.open() } else { onSharePost() } }}> {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} {hasSession && ( <> {isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`)} mutedWordsDialogControl.open()}> {_(msg`Mute words & tags`)} {!isAuthor && !isPostHidden && ( {_(msg`Hide post`)} )} )} {!isAuthor && ( reportDialogControl.open()}> {_(msg`Report post`)} )} {isAuthor && ( {_(msg`Delete post`)} )} ) } PostDropdownBtn = memo(PostDropdownBtn) export {PostDropdownBtn}