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}