import React from 'react' import {GestureResponderEvent, View} from 'react-native' import { AppBskyActorDefs, AppBskyFeedDefs, AppBskyGraphDefs, AtUri, } from '@atproto/api' import {msg, plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import { useAddSavedFeedsMutation, usePreferencesQuery, useRemoveFeedMutation, } from '#/state/queries/preferences' import {sanitizeHandle} from 'lib/strings/handles' import {precacheFeedFromGeneratorView, precacheList} from 'state/queries/feed' import {useSession} from 'state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import * as Toast from 'view/com/util/Toast' import {useTheme} from '#/alf' import {atoms as a} from '#/alf' import {Button, ButtonIcon} from '#/components/Button' import {useRichText} from '#/components/hooks/useRichText' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash' import {Link as InternalLink, LinkProps} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' type Props = | { type: 'feed' view: AppBskyFeedDefs.GeneratorView } | { type: 'list' view: AppBskyGraphDefs.ListView } export function Default(props: Props) { const {type, view} = props const displayName = type === 'feed' ? view.displayName : view.name const purpose = type === 'list' ? view.purpose : undefined return (
{type === 'feed' && }
) } export function Link({ type, view, label, children, }: Props & Omit) { const queryClient = useQueryClient() const href = React.useMemo(() => { return createProfileFeedHref({feed: view}) }, [view]) return ( { if (type === 'feed') { precacheFeedFromGeneratorView(queryClient, view) } else { precacheList(queryClient, view) } }}> {children} ) } export function Outer({children}: {children: React.ReactNode}) { return {children} } export function Header({children}: {children: React.ReactNode}) { return ( {children} ) } export type AvatarProps = {src: string | undefined; size?: number} export function Avatar({src, size = 40}: AvatarProps) { return } export function AvatarPlaceholder({size = 40}: Omit) { const t = useTheme() return ( ) } export function TitleAndByline({ title, creator, type, purpose, }: { title: string creator?: AppBskyActorDefs.ProfileViewBasic type: 'feed' | 'list' purpose?: AppBskyGraphDefs.ListView['purpose'] }) { const t = useTheme() return ( {title} {creator && ( {type === 'list' && purpose === 'app.bsky.graph.defs#curatelist' ? ( List by {sanitizeHandle(creator.handle, '@')} ) : type === 'list' && purpose === 'app.bsky.graph.defs#modlist' ? ( Moderation list by {sanitizeHandle(creator.handle, '@')} ) : ( Feed by {sanitizeHandle(creator.handle, '@')} )} )} ) } export function TitleAndBylinePlaceholder({creator}: {creator?: boolean}) { const t = useTheme() return ( {creator && ( )} ) } export function Description({description}: {description?: string}) { const [rt, isResolving] = useRichText(description || '') if (!description) return null return isResolving ? ( ) : ( ) } export function Likes({count}: {count: number}) { const t = useTheme() return ( {plural(count || 0, { one: 'Liked by # user', other: 'Liked by # users', })} ) } export function Action({ uri, pin, type, purpose, }: { uri: string pin?: boolean type: 'feed' | 'list' purpose?: AppBskyGraphDefs.ListView['purpose'] }) { const {hasSession} = useSession() if (!hasSession || purpose !== 'app.bsky.graph.defs#curatelist') return null return } function ActionInner({ uri, pin, type, }: { uri: string pin?: boolean type: 'feed' | 'list' }) { const {_} = useLingui() const {data: preferences} = usePreferencesQuery() const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} = useAddSavedFeedsMutation() const {isPending: isRemovePending, mutateAsync: removeFeed} = useRemoveFeedMutation() const savedFeedConfig = React.useMemo(() => { return preferences?.savedFeeds?.find(feed => feed.value === uri) }, [preferences?.savedFeeds, uri]) const removePromptControl = Prompt.usePromptControl() const isPending = isAddSavedFeedPending || isRemovePending const toggleSave = React.useCallback( async (e: GestureResponderEvent) => { e.preventDefault() e.stopPropagation() try { if (savedFeedConfig) { await removeFeed(savedFeedConfig) } else { await saveFeeds([ { type, value: uri, pinned: pin || false, }, ]) } Toast.show(_(msg`Feeds updated!`)) } catch (e: any) { logger.error(e, {context: `FeedCard: failed to update feeds`, pin}) Toast.show(_(msg`Failed to update feeds`)) } }, [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type], ) const onPrompRemoveFeed = React.useCallback( async (e: GestureResponderEvent) => { e.preventDefault() e.stopPropagation() removePromptControl.open() }, [removePromptControl], ) return ( <> ) } export function createProfileFeedHref({ feed, }: { feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView }) { const urip = new AtUri(feed.uri) const type = urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'list' const handleOrDid = feed.creator.handle || feed.creator.did return `/profile/${handleOrDid}/${type === 'feed' ? 'feed' : 'lists'}/${ urip.rkey }` }