import React, {memo, useCallback} from 'react' import { Pressable, type PressableStateCallbackType, type StyleProp, View, type ViewStyle, } from 'react-native' import { AppBskyFeedDefs, AppBskyFeedPost, AtUri, RichText as RichTextAPI, } from '@atproto/api' import {msg, plural} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' import {useHaptics} from '#/lib/haptics' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' import {toShareUrl} from '#/lib/strings/url-helpers' import {s} from '#/lib/styles' import {Shadow} from '#/state/cache/types' import {useFeedFeedbackContext} from '#/state/feed-feedback' import { usePostLikeMutationQueue, usePostRepostMutationQueue, } from '#/state/queries/post' import {useRequireAuth} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {atoms as a, useTheme} from '#/alf' import {useDialogControl} from '#/components/Dialog' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble' import { Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled, Heart2_Stroke2_Corner0_Rounded as HeartIconOutline, } from '#/components/icons/Heart2' import * as Prompt from '#/components/Prompt' import {PostDropdownBtn} from '../forms/PostDropdownBtn' import {Text} from '../text/Text' import {RepostButton} from './RepostButton' let PostCtrls = ({ big, post, record, richText, feedContext, style, onPressReply, logContext, }: { big?: boolean post: Shadow record: AppBskyFeedPost.Record richText: RichTextAPI feedContext?: string | undefined style?: StyleProp onPressReply: () => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' }): React.ReactNode => { const t = useTheme() const {_} = useLingui() const {openComposer} = useComposerControls() const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext) const [queueRepost, queueUnrepost] = usePostRepostMutationQueue( post, logContext, ) const requireAuth = useRequireAuth() const loggedOutWarningPromptControl = useDialogControl() const {sendInteraction} = useFeedFeedbackContext() const playHaptic = useHaptics() const shouldShowLoggedOutWarning = React.useMemo(() => { return !!post.author.labels?.find( label => label.val === '!no-unauthenticated', ) }, [post]) const defaultCtrlColor = React.useMemo( () => ({ color: t.palette.contrast_500, }), [t], ) as StyleProp const onPressToggleLike = React.useCallback(async () => { try { if (!post.viewer?.like) { playHaptic() sendInteraction({ item: post.uri, event: 'app.bsky.feed.defs#interactionLike', feedContext, }) await queueLike() } else { await queueUnlike() } } catch (e: any) { if (e?.name !== 'AbortError') { throw e } } }, [ playHaptic, post.uri, post.viewer?.like, queueLike, queueUnlike, sendInteraction, feedContext, ]) const onRepost = useCallback(async () => { try { if (!post.viewer?.repost) { sendInteraction({ item: post.uri, event: 'app.bsky.feed.defs#interactionRepost', feedContext, }) await queueRepost() } else { await queueUnrepost() } } catch (e: any) { if (e?.name !== 'AbortError') { throw e } } }, [ post.uri, post.viewer?.repost, queueRepost, queueUnrepost, sendInteraction, feedContext, ]) const onQuote = useCallback(() => { sendInteraction({ item: post.uri, event: 'app.bsky.feed.defs#interactionQuote', feedContext, }) openComposer({ quote: { uri: post.uri, cid: post.cid, text: record.text, author: post.author, indexedAt: post.indexedAt, }, }) }, [ openComposer, post.uri, post.cid, post.author, post.indexedAt, record.text, sendInteraction, feedContext, ]) const onShare = useCallback(() => { const urip = new AtUri(post.uri) const href = makeProfileLink(post.author, 'post', urip.rkey) const url = toShareUrl(href) shareUrl(url) sendInteraction({ item: post.uri, event: 'app.bsky.feed.defs#interactionShare', feedContext, }) }, [post.uri, post.author, sendInteraction, feedContext]) const btnStyle = React.useCallback( ({pressed, hovered}: PressableStateCallbackType) => [ a.gap_xs, a.rounded_full, a.flex_row, a.align_center, a.justify_center, {padding: 5}, (pressed || hovered) && t.atoms.bg_contrast_25, ], [t.atoms.bg_contrast_25], ) return ( { if (!post.viewer?.replyDisabled) { requireAuth(() => onPressReply()) } }} accessibilityLabel={plural(post.replyCount || 0, { one: 'Reply (# reply)', other: 'Reply (# replies)', })} accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> {typeof post.replyCount !== 'undefined' && post.replyCount > 0 ? ( {post.replyCount} ) : undefined} requireAuth(() => onPressToggleLike())} accessibilityLabel={ post.viewer?.like ? plural(post.likeCount || 0, { one: 'Unlike (# like)', other: 'Unlike (# likes)', }) : plural(post.likeCount || 0, { one: 'Like (# like)', other: 'Like (# likes)', }) } accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> {post.viewer?.like ? ( ) : ( )} {typeof post.likeCount !== 'undefined' && post.likeCount > 0 ? ( {post.likeCount} ) : undefined} {big && ( <> { if (shouldShowLoggedOutWarning) { loggedOutWarningPromptControl.open() } else { onShare() } }} accessibilityLabel={_(msg`Share`)} accessibilityHint="" hitSlop={big ? HITSLOP_20 : HITSLOP_10}> )} ) } PostCtrls = memo(PostCtrls) export {PostCtrls}