diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx
index e0fc7ef5..b1200d9c 100644
--- a/src/components/FeedCard.tsx
+++ b/src/components/FeedCard.tsx
@@ -18,7 +18,7 @@ import {
useRemoveFeedMutation,
} from '#/state/queries/preferences'
import {sanitizeHandle} from 'lib/strings/handles'
-import {precacheFeedFromGeneratorView, precacheList} from 'state/queries/feed'
+import {precacheFeedFromGeneratorView} from 'state/queries/feed'
import {useSession} from 'state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import * as Toast from 'view/com/util/Toast'
@@ -33,45 +33,31 @@ 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
- }
+type Props = {
+ view: AppBskyFeedDefs.GeneratorView
+}
export function Default(props: Props) {
- const {type, view} = props
- const displayName = type === 'feed' ? view.displayName : view.name
- const purpose = type === 'list' ? view.purpose : undefined
+ const {view} = props
return (
-
+
- {type === 'feed' && }
+
)
}
export function Link({
- type,
view,
- label,
children,
+ ...props
}: Props & Omit) {
const queryClient = useQueryClient()
@@ -79,17 +65,12 @@ export function Link({
return createProfileFeedHref({feed: view})
}, [view])
+ React.useEffect(() => {
+ precacheFeedFromGeneratorView(queryClient, view)
+ }, [view, queryClient])
+
return (
- {
- if (type === 'feed') {
- precacheFeedFromGeneratorView(queryClient, view)
- } else {
- precacheList(queryClient, view)
- }
- }}>
+
{children}
)
@@ -132,13 +113,9 @@ export function AvatarPlaceholder({size = 40}: Omit) {
export function TitleAndByline({
title,
creator,
- type,
- purpose,
}: {
title: string
creator?: AppBskyActorDefs.ProfileViewBasic
- type: 'feed' | 'list'
- purpose?: AppBskyGraphDefs.ListView['purpose']
}) {
const t = useTheme()
@@ -151,15 +128,7 @@ export function TitleAndByline({
- {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, '@')}
- )}
+ Feed by {sanitizeHandle(creator.handle, '@')}
)}
@@ -221,34 +190,24 @@ export function Likes({count}: {count: number}) {
)
}
-export function Action({
- uri,
+export function SaveButton({
+ view,
pin,
- type,
- purpose,
}: {
- uri: string
+ view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView
pin?: boolean
- type: 'feed' | 'list'
- purpose?: AppBskyGraphDefs.ListView['purpose']
}) {
const {hasSession} = useSession()
- if (
- !hasSession ||
- (type === 'list' && purpose !== 'app.bsky.graph.defs#curatelist')
- )
- return null
- return
+ if (!hasSession) return null
+ return
}
-function ActionInner({
- uri,
+function SaveButtonInner({
+ view,
pin,
- type,
}: {
- uri: string
+ view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView
pin?: boolean
- type: 'feed' | 'list'
}) {
const {_} = useLingui()
const {data: preferences} = usePreferencesQuery()
@@ -256,6 +215,10 @@ function ActionInner({
useAddSavedFeedsMutation()
const {isPending: isRemovePending, mutateAsync: removeFeed} =
useRemoveFeedMutation()
+
+ const uri = view.uri
+ const type = view.uri.includes('app.bsky.feed.generator') ? 'feed' : 'list'
+
const savedFeedConfig = React.useMemo(() => {
return preferences?.savedFeeds?.find(feed => feed.value === uri)
}, [preferences?.savedFeeds, uri])
@@ -332,12 +295,9 @@ function ActionInner({
export function createProfileFeedHref({
feed,
}: {
- feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView
+ feed: AppBskyFeedDefs.GeneratorView
}) {
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
- }`
+ return `/profile/${handleOrDid}/feed/${urip.rkey}`
}
diff --git a/src/components/ListCard.tsx b/src/components/ListCard.tsx
new file mode 100644
index 00000000..c0e0d0e2
--- /dev/null
+++ b/src/components/ListCard.tsx
@@ -0,0 +1,129 @@
+import React from 'react'
+import {View} from 'react-native'
+import {AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api'
+import {Trans} from '@lingui/macro'
+import {useQueryClient} from '@tanstack/react-query'
+
+import {sanitizeHandle} from 'lib/strings/handles'
+import {precacheList} from 'state/queries/feed'
+import {useTheme} from '#/alf'
+import {atoms as a} from '#/alf'
+import {
+ Avatar,
+ Description,
+ Header,
+ Outer,
+ SaveButton,
+} from '#/components/FeedCard'
+import {Link as InternalLink, LinkProps} from '#/components/Link'
+import {Text} from '#/components/Typography'
+
+/*
+ * This component is based on `FeedCard` and is tightly coupled with that
+ * component. Please refer to `FeedCard` for more context.
+ */
+
+export {
+ Avatar,
+ AvatarPlaceholder,
+ Description,
+ Header,
+ Outer,
+ SaveButton,
+ TitleAndBylinePlaceholder,
+} from '#/components/FeedCard'
+
+const CURATELIST = 'app.bsky.graph.defs#curatelist'
+const MODLIST = 'app.bsky.graph.defs#modlist'
+
+type Props = {
+ view: AppBskyGraphDefs.ListView
+ showPinButton?: boolean
+}
+
+export function Default(props: Props) {
+ const {view, showPinButton} = props
+ return (
+
+
+
+
+
+ {showPinButton && view.purpose === CURATELIST && (
+
+ )}
+
+
+
+
+ )
+}
+
+export function Link({
+ view,
+ children,
+ ...props
+}: Props & Omit) {
+ const queryClient = useQueryClient()
+
+ const href = React.useMemo(() => {
+ return createProfileListHref({list: view})
+ }, [view])
+
+ React.useEffect(() => {
+ precacheList(queryClient, view)
+ }, [view, queryClient])
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function TitleAndByline({
+ title,
+ creator,
+ purpose = CURATELIST,
+}: {
+ title: string
+ creator?: AppBskyActorDefs.ProfileViewBasic
+ purpose?: AppBskyGraphDefs.ListView['purpose']
+}) {
+ const t = useTheme()
+
+ return (
+
+
+ {title}
+
+ {creator && (
+
+ {purpose === MODLIST ? (
+
+ Moderation list by {sanitizeHandle(creator.handle, '@')}
+
+ ) : (
+ List by {sanitizeHandle(creator.handle, '@')}
+ )}
+
+ )}
+
+ )
+}
+
+export function createProfileListHref({
+ list,
+}: {
+ list: AppBskyGraphDefs.ListView
+}) {
+ const urip = new AtUri(list.uri)
+ const handleOrDid = list.creator.handle || list.creator.did
+ return `/profile/${handleOrDid}/lists/${urip.rkey}`
+}
diff --git a/src/components/StarterPack/Main/FeedsList.tsx b/src/components/StarterPack/Main/FeedsList.tsx
index e350a422..7d7cd204 100644
--- a/src/components/StarterPack/Main/FeedsList.tsx
+++ b/src/components/StarterPack/Main/FeedsList.tsx
@@ -45,7 +45,7 @@ export const FeedsList = React.forwardRef(
(isWeb || index !== 0) && a.border_t,
t.atoms.border_contrast_low,
]}>
-
+
)
}
diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx
index 12420333..d34af1f6 100644
--- a/src/screens/StarterPack/StarterPackLandingScreen.tsx
+++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx
@@ -316,7 +316,7 @@ function LandingScreenLoaded({
t.atoms.border_contrast_low,
]}
key={feed.uri}>
-
+
))}
diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx
index ec1a55e2..831ab4d1 100644
--- a/src/view/com/feeds/ProfileFeedgens.tsx
+++ b/src/view/com/feeds/ProfileFeedgens.tsx
@@ -163,7 +163,7 @@ export const ProfileFeedgens = React.forwardRef<
a.px_lg,
a.py_lg,
]}>
-
+
)
}
diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx
index 62c944ef..dc385d43 100644
--- a/src/view/com/lists/ProfileLists.tsx
+++ b/src/view/com/lists/ProfileLists.tsx
@@ -18,7 +18,7 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {EmptyState} from 'view/com/util/EmptyState'
import {atoms as a, useTheme} from '#/alf'
-import * as FeedCard from '#/components/FeedCard'
+import * as ListCard from '#/components/ListCard'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@@ -172,7 +172,7 @@ export const ProfileLists = React.forwardRef(
a.px_lg,
a.py_lg,
]}>
-
+
)
},
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index 2e5b4851..82de30d5 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -41,6 +41,7 @@ import hairlineWidth = StyleSheet.hairlineWidth
import {Divider} from '#/components/Divider'
import * as FeedCard from '#/components/FeedCard'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
+import * as ListCard from '#/components/ListCard'
type Props = NativeStackScreenProps
@@ -495,7 +496,7 @@ export function FeedsScreen(_props: Props) {
} else if (item.type === 'popularFeed') {
return (
-
+
)
@@ -627,7 +628,7 @@ function FollowingFeed() {
fill={t.palette.white}
/>
-
+
)
@@ -639,34 +640,45 @@ function SavedFeed({
savedFeed: SavedFeedItem & {type: 'feed' | 'list'}
}) {
const t = useTheme()
- const {view: feed} = savedFeed
- const displayName =
- savedFeed.type === 'feed' ? savedFeed.view.displayName : savedFeed.view.name
- return (
-
+ const commonStyle = [
+ a.flex_1,
+ a.px_lg,
+ a.py_md,
+ a.border_b,
+ t.atoms.border_contrast_low,
+ ]
+
+ return savedFeed.type === 'feed' ? (
+
{({hovered, pressed}) => (
+ style={[commonStyle, (hovered || pressed) && t.atoms.bg_contrast_25]}>
-
-
+
+
)}
+ ) : (
+
+ {({hovered, pressed}) => (
+
+
+
+
+
+
+
+
+ )}
+
)
}
diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx
index 8f6f6d4b..85e8ffa4 100644
--- a/src/view/screens/Search/Explore.tsx
+++ b/src/view/screens/Search/Explore.tsx
@@ -505,7 +505,7 @@ export function Explore() {
a.px_lg,
a.py_lg,
]}>
-
+
)
}
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 76ffba93..0eef5cbd 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -306,7 +306,7 @@ let SearchScreenFeedsResults = ({
a.px_lg,
a.py_lg,
]}>
-
+
)}
keyExtractor={item => item.uri}