import React, {memo} from 'react' import {type AppBskyActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {useActorStatus} from '#/lib/actor-status' import {HITSLOP_20} from '#/lib/constants' 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 {logger} from '#/logger' import {type Shadow} from '#/state/cache/types' import {useModalControls} from '#/state/modals' import { RQKEY as profileQueryKey, useProfileBlockMutationQueue, useProfileFollowMutationQueue, useProfileMuteMutationQueue, } from '#/state/queries/profile' import {useSession} from '#/state/session' import {EventStopper} from '#/view/com/util/EventStopper' import * as Toast from '#/view/com/util/Toast' import {Button, ButtonIcon} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck' import {CircleX_Stroke2_Corner0_Rounded as CircleX} from '#/components/icons/CircleX' import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid' import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {ListSparkle_Stroke2_Corner0_Rounded as List} from '#/components/icons/ListSparkle' import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live' import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2' import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute' import {PeopleRemove2_Stroke2_Corner0_Rounded as UserMinus} from '#/components/icons/PeopleRemove2' import { PersonCheck_Stroke2_Corner0_Rounded as PersonCheck, PersonX_Stroke2_Corner0_Rounded as PersonX, } from '#/components/icons/Person' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker' import {EditLiveDialog} from '#/components/live/EditLiveDialog' import {GoLiveDialog} from '#/components/live/GoLiveDialog' import {temp__canGoLive} from '#/components/live/temp' import * as Menu from '#/components/Menu' import { ReportDialog, useReportDialogControl, } from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' import {useFullVerificationState} from '#/components/verification' import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' import {useDevMode} from '#/storage/hooks/dev-mode' let ProfileMenu = ({ profile, }: { profile: Shadow }): React.ReactNode => { const {_} = useLingui() const {currentAccount, hasSession} = useSession() const {openModal} = useModalControls() const reportDialogControl = useReportDialogControl() const queryClient = useQueryClient() const navigation = useNavigation() const isSelf = currentAccount?.did === profile.did const isFollowing = profile.viewer?.following const isBlocked = profile.viewer?.blocking || profile.viewer?.blockedBy const isFollowingBlockedAccount = isFollowing && isBlocked const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked const [devModeEnabled] = useDevMode() const verification = useFullVerificationState({profile}) const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile) const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( profile, 'ProfileMenu', ) const blockPromptControl = Prompt.usePromptControl() const loggedOutWarningPromptControl = Prompt.usePromptControl() const goLiveDialogControl = useDialogControl() const showLoggedOutWarning = React.useMemo(() => { return ( profile.did !== currentAccount?.did && !!profile.labels?.find(label => label.val === '!no-unauthenticated') ) }, [currentAccount, profile]) const invalidateProfileQuery = React.useCallback(() => { queryClient.invalidateQueries({ queryKey: profileQueryKey(profile.did), }) }, [queryClient, profile.did]) const onPressShare = React.useCallback(() => { shareUrl(toShareUrl(makeProfileLink(profile))) }, [profile]) const onPressAddRemoveLists = React.useCallback(() => { openModal({ name: 'user-add-remove-lists', subject: profile.did, handle: profile.handle, displayName: profile.displayName || profile.handle, onAdd: invalidateProfileQuery, onRemove: invalidateProfileQuery, }) }, [profile, openModal, invalidateProfileQuery]) const onPressMuteAccount = React.useCallback(async () => { if (profile.viewer?.muted) { try { await queueUnmute() Toast.show(_(msg({message: 'Account unmuted', context: 'toast'}))) } catch (e: any) { if (e?.name !== 'AbortError') { logger.error('Failed to unmute account', {message: e}) Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') } } } else { try { await queueMute() Toast.show(_(msg({message: 'Account muted', context: 'toast'}))) } catch (e: any) { if (e?.name !== 'AbortError') { logger.error('Failed to mute account', {message: e}) Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') } } } }, [profile.viewer?.muted, queueUnmute, _, queueMute]) const blockAccount = React.useCallback(async () => { if (profile.viewer?.blocking) { try { await queueUnblock() Toast.show(_(msg({message: 'Account unblocked', context: 'toast'}))) } catch (e: any) { if (e?.name !== 'AbortError') { logger.error('Failed to unblock account', {message: e}) Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') } } } else { try { await queueBlock() Toast.show(_(msg({message: 'Account blocked', context: 'toast'}))) } catch (e: any) { if (e?.name !== 'AbortError') { logger.error('Failed to block account', {message: e}) Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') } } } }, [profile.viewer?.blocking, _, queueUnblock, queueBlock]) const onPressFollowAccount = React.useCallback(async () => { try { await queueFollow() Toast.show(_(msg({message: 'Account followed', context: 'toast'}))) } catch (e: any) { if (e?.name !== 'AbortError') { logger.error('Failed to follow account', {message: e}) Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') } } }, [_, queueFollow]) const onPressUnfollowAccount = React.useCallback(async () => { try { await queueUnfollow() Toast.show(_(msg({message: 'Account unfollowed', context: 'toast'}))) } catch (e: any) { if (e?.name !== 'AbortError') { logger.error('Failed to unfollow account', {message: e}) Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark') } } }, [_, queueUnfollow]) const onPressReportAccount = React.useCallback(() => { reportDialogControl.open() }, [reportDialogControl]) const onPressShareATUri = React.useCallback(() => { shareText(`at://${profile.did}`) }, [profile.did]) const onPressShareDID = React.useCallback(() => { shareText(profile.did) }, [profile.did]) const onPressSearch = React.useCallback(() => { navigation.navigate('ProfileSearch', {name: profile.handle}) }, [navigation, profile.handle]) const verificationCreatePromptControl = Prompt.usePromptControl() const verificationRemovePromptControl = Prompt.usePromptControl() const currentAccountVerifications = profile.verification?.verifications?.filter(v => { return v.issuer === currentAccount?.did }) ?? [] const status = useActorStatus(profile) return ( {({props}) => { return ( ) }} { if (showLoggedOutWarning) { loggedOutWarningPromptControl.open() } else { onPressShare() } }}> Share Search posts {hasSession && ( <> {!isSelf && ( <> {(isLabelerAndNotBlocked || isFollowingBlockedAccount) && ( {isFollowing ? ( Unfollow account ) : ( Follow account )} )} )} Add to lists {isSelf && temp__canGoLive(profile) && ( {status.isActive ? ( Edit live status ) : ( Go live )} )} {verification.viewer.role === 'verifier' && !verification.profile.isViewer && (verification.viewer.hasIssuedVerification ? ( verificationRemovePromptControl.open()}> Remove verification ) : ( verificationCreatePromptControl.open()}> Verify account ))} {!isSelf && ( <> {!profile.viewer?.blocking && !profile.viewer?.mutedByList && ( {profile.viewer?.muted ? ( Unmute account ) : ( Mute account )} )} {!profile.viewer?.blockingByList && ( blockPromptControl.open()}> {profile.viewer?.blocking ? ( Unblock account ) : ( Block account )} )} Report account )} )} {devModeEnabled ? ( <> Copy at:// URI Copy DID ) : null} {status.isActive ? ( ) : ( )} ) } ProfileMenu = memo(ProfileMenu) export {ProfileMenu}