import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' import { type AppBskyActorDefs, type AppBskyLabelerDefs, moderateProfile, type ModerationOpts, type RichText as RichTextAPI, } from '@atproto/api' import {msg, Plural, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' // eslint-disable-next-line @typescript-eslint/no-unused-vars import {MAX_LABELERS} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {isAppLabeler} from '#/lib/moderation' import {useProfileShadow} from '#/state/cache/profile-shadow' import {type Shadow} from '#/state/cache/types' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' import {usePreferencesQuery} from '#/state/queries/preferences' import {useRequireAuth, useSession} from '#/state/session' import {ProfileMenu} from '#/view/com/profile/ProfileMenu' import {atoms as a, tokens, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {type DialogOuterProps, useDialogControl} from '#/components/Dialog' import { Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled, Heart2_Stroke2_Corner0_Rounded as Heart, } from '#/components/icons/Heart2' import {Link} from '#/components/Link' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' import {ProfileHeaderDisplayName} from './DisplayName' import {EditProfileDialog} from './EditProfileDialog' import {ProfileHeaderHandle} from './Handle' import {ProfileHeaderMetrics} from './Metrics' import {ProfileHeaderShell} from './Shell' interface Props { profile: AppBskyActorDefs.ProfileViewDetailed labeler: AppBskyLabelerDefs.LabelerViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean isPlaceholderProfile?: boolean } let ProfileHeaderLabeler = ({ profile: profileUnshadowed, labeler, descriptionRT, moderationOpts, hideBackButton = false, isPlaceholderProfile, }: Props): React.ReactNode => { const profile: Shadow = useProfileShadow(profileUnshadowed) const t = useTheme() const ax = useAnalytics() const {_} = useLingui() const {currentAccount, hasSession} = useSession() const playHaptic = useHaptics() const isSelf = currentAccount?.did === profile.did const enableSquareButtons = useEnableSquareButtons() const moderation = useMemo( () => moderateProfile(profile, moderationOpts), [profile, moderationOpts], ) const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation() const {mutateAsync: unlikeMod, isPending: isUnlikePending} = useUnlikeMutation() const [likeUri, setLikeUri] = useState(labeler.viewer?.like || '') const [likeCount, setLikeCount] = useState(labeler.likeCount || 0) const onToggleLiked = useCallback(async () => { if (!labeler) { return } try { playHaptic() if (likeUri) { await unlikeMod({uri: likeUri}) setLikeCount(c => c - 1) setLikeUri('') } else { const res = await likeMod({uri: labeler.uri, cid: labeler.cid}) setLikeCount(c => c + 1) setLikeUri(res.uri) } } catch (e: any) { Toast.show( _( msg`There was an issue contacting the server, please check your internet connection and try again.`, ), {type: 'error'}, ) ax.logger.error(`Failed to toggle labeler like`, {message: e.message}) } }, [ax, labeler, playHaptic, likeUri, unlikeMod, likeMod, _]) return ( {!isPlaceholderProfile && ( <> {isSelf && } {descriptionRT && !moderation.ui('profileView').blur ? ( ) : undefined} {!isAppLabeler(profile.did) && ( {typeof likeCount === 'number' && ( {({hovered, focused, pressed}) => ( Liked by{' '} )} )} )} )} ) } ProfileHeaderLabeler = memo(ProfileHeaderLabeler) export {ProfileHeaderLabeler} /** * Keep this in sync with the value of {@link MAX_LABELERS} */ function CantSubscribePrompt({ control, }: { control: DialogOuterProps['control'] }) { const {_} = useLingui() return ( Unable to subscribe We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty. control.close()} cta={_(msg`OK`)} /> ) } export function HeaderLabelerButtons({ profile, minimal = false, }: { profile: Shadow /** disable the subscribe button */ minimal?: boolean }) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() const {currentAccount} = useSession() const requireAuth = useRequireAuth() const playHaptic = useHaptics() const editProfileControl = useDialogControl() const {data: preferences} = usePreferencesQuery() const { mutateAsync: toggleSubscription, variables, reset, } = useLabelerSubscriptionMutation() const isSubscribed = variables?.subscribe ?? preferences?.moderationPrefs.labelers.find(l => l.did === profile.did) const cantSubscribePrompt = Prompt.usePromptControl() const isMe = currentAccount?.did === profile.did const onPressSubscribe = () => requireAuth(async (): Promise => { playHaptic() const subscribe = !isSubscribed try { await toggleSubscription({ did: profile.did, subscribe, }) ax.metric( subscribe ? 'moderation:subscribedToLabeler' : 'moderation:unsubscribedFromLabeler', {}, ) } catch (e: any) { reset() if (e.message === 'MAX_LABELERS') { cantSubscribePrompt.open() return } ax.logger.error(`Failed to subscribe to labeler`, {message: e.message}) } }) return ( <> {isMe ? ( <> ) : !isAppLabeler(profile.did) && !minimal ? ( // hidden in the minimal header, because it's not shadowed so the two buttons // can get out of sync. if you want to reenable, you'll need to add shadowing // to the subscribed state -sfn ) : null} ) }