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
}`
}