import React, {memo} from 'react' import {StyleProp, ViewStyle, Pressable, PressableProps} from 'react-native' import Clipboard from '@react-native-clipboard/clipboard' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' import { AppBskyActorDefs, AppBskyFeedPost, AtUri, RichText as RichTextAPI, } from '@atproto/api' import {toShareUrl} from 'lib/strings/url-helpers' import {useTheme} from 'lib/ThemeContext' import {shareUrl} from 'lib/sharing' import * as Toast from '../Toast' import {EventStopper} from '../EventStopper' import {useModalControls} from '#/state/modals' import {makeProfileLink} from '#/lib/routes/links' import {CommonNavigatorParams} from '#/lib/routes/types' import {getCurrentRoute} from 'lib/routes/helpers' import {getTranslatorLink} from '#/locale/helpers' import {usePostDeleteMutation} from '#/state/queries/post' 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 {logger} from '#/logger' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useSession} from '#/state/session' import {isWeb} from '#/platform/detection' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {atoms as a, useTheme as useAlf} from '#/alf' import * as Menu from '#/components/Menu' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash' import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' let PostDropdownBtn = ({ testID, postAuthor, postCid, postUri, record, richText, style, showAppealLabelItem, hitSlop, }: { testID: string postAuthor: AppBskyActorDefs.ProfileViewBasic postCid: string postUri: string record: AppBskyFeedPost.Record richText: RichTextAPI style?: StyleProp showAppealLabelItem?: boolean hitSlop?: PressableProps['hitSlop'] }): React.ReactNode => { const {hasSession, currentAccount} = useSession() const theme = useTheme() const alf = useAlf() const {_} = useLingui() const defaultCtrlColor = theme.palette.default.postCtrl const {openModal} = useModalControls() 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 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) Clipboard.setString(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]) return ( {({props, state}) => { return ( ) }} {_(msg`Translate`)} {_(msg`Copy post text`)} { const url = toShareUrl(href) shareUrl(url) }}> {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} {hasSession && ( <> {isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`)} mutedWordsDialogControl.open()}> {_(msg`Mute words & tags`)} {!isAuthor && !isPostHidden && ( { openModal({ name: 'confirm', title: _(msg`Hide this post?`), message: _( msg`This will hide this post from your feeds.`, ), onPressConfirm: onHidePost, }) }}> {_(msg`Hide post`)} )} )} {!isAuthor && ( { openModal({ name: 'report', uri: postUri, cid: postCid, }) }}> {_(msg`Report post`)} )} {isAuthor && ( { openModal({ name: 'confirm', title: _(msg`Delete this post?`), message: _(msg`Are you sure? This cannot be undone.`), onPressConfirm: onDeletePost, }) }}> {_(msg`Delete post`)} )} {showAppealLabelItem && ( <> { openModal({ name: 'appeal-label', uri: postUri, cid: postCid, }) }}> {_(msg`Appeal content warning`)} )} ) } PostDropdownBtn = memo(PostDropdownBtn) export {PostDropdownBtn}