import {memo, useCallback} from 'react' import {Platform} from 'react-native' import * as Clipboard from 'expo-clipboard' import { type ChatBskyConvoDefs, type ModerationOpts, RichText, } from '@atproto/api' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' import {EMOJI_REACTION_LIMIT} from '#/lib/constants' import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate' import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useConvoActive} from '#/state/messages/convo' import {useLanguagePrefs} from '#/state/preferences' import {useSession} from '#/state/session' import {atoms as a} from '#/alf' import * as ContextMenu from '#/components/ContextMenu' import {type TriggerProps} from '#/components/ContextMenu/types' import {useMessageDialogs} from '#/components/dms/MessageOverlays' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag' import {Language_Stroke2_Corner2_Rounded as LanguageIcon} from '#/components/icons/Language' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import * as Toast from '#/components/Toast' import {IS_NATIVE} from '#/env' import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' import {canReact, hasReachedReactionLimit} from './util' export let MessageContextMenu = ({ message, senderProfile, moderationOpts, children, }: { message: ChatBskyConvoDefs.MessageView senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined children: TriggerProps['children'] }): React.ReactNode => { const {t: l, i18n} = useLingui() const {currentAccount} = useSession() const convo = useConvoActive() const {openDeleteMessage, openReportMessage} = useMessageDialogs() const langPrefs = useLanguagePrefs() const translate = useGoogleTranslate() const isFromSelf = message.sender?.did === currentAccount?.did const isGroupChatEnabled = true const primaryMember = useMaybeProfileShadow(convo.convo.primaryMember) const reactionsAvailable = canReact({ convoState: convo, primaryMember, moderationOpts, }) const onCopyMessage = useCallback(() => { const str = richTextToString( new RichText({ text: message.text, facets: message.facets, }), true, ) void Clipboard.setStringAsync(str) Toast.show(l`Copied to clipboard`, { type: 'success', }) }, [l, message.text, message.facets]) const onPressTranslateMessage = useCallback(() => { void translate(message.text, langPrefs.primaryLanguage) }, [langPrefs.primaryLanguage, message.text, translate]) const onEmojiSelect = useCallback( (emoji: string) => { if ( message.reactions?.find( reaction => reaction.value === emoji && reaction.sender.did === currentAccount?.did, ) ) { convo .removeReaction(message.id, emoji) .catch(() => Toast.show(l`Failed to remove emoji reaction`)) } else { if (hasReachedReactionLimit(message, currentAccount?.did)) { Toast.show( l`You cannot add more than ${plural(EMOJI_REACTION_LIMIT, { one: '# emoji reaction', other: '# emoji reactions', })}`, { type: 'info', }, ) return } convo.addReaction(message.id, emoji).catch(() => Toast.show(l`Failed to add emoji reaction`, { type: 'error', }), ) } }, [l, convo, message, currentAccount?.did], ) const sender = senderProfile return ( {IS_NATIVE && reactionsAvailable && ( )} {children} {message.text.length > 0 && ( <> {l`Translate`} {l`Copy message text`} )} openDeleteMessage(message)}> {l`Delete for me`} {!isFromSelf && ( openReportMessage(message, senderProfile)}> {l`Report`} )} ) } MessageContextMenu = memo(MessageContextMenu)