diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx
index bd0649097e..7f3cb88ff3 100644
--- a/src/components/FeedCard.tsx
+++ b/src/components/FeedCard.tsx
@@ -8,6 +8,7 @@ import {
} 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 {
@@ -16,6 +17,7 @@ import {
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'
@@ -31,10 +33,7 @@ import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
-export function Default({
- type,
- view,
-}:
+type Props =
| {
type: 'feed'
view: AppBskyFeedDefs.GeneratorView
@@ -42,15 +41,24 @@ export function Default({
| {
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' && }
@@ -60,15 +68,31 @@ export function Default({
}
export function Link({
+ type,
+ view,
+ label,
children,
- feed,
-}: {
- feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView
-} & Omit) {
+}: Props & Omit) {
+ const queryClient = useQueryClient()
+
const href = React.useMemo(() => {
- return createProfileFeedHref({feed})
- }, [feed])
- return {children}
+ return createProfileFeedHref({feed: view})
+ }, [view])
+
+ return (
+ {
+ if (type === 'feed') {
+ precacheFeedFromGeneratorView(queryClient, view)
+ } else {
+ precacheList(queryClient, view)
+ }
+ }}>
+ {children}
+
+ )
}
export function Outer({children}: {children: React.ReactNode}) {
@@ -108,9 +132,13 @@ 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()
@@ -123,7 +151,15 @@ export function TitleAndByline({
- Feed by {sanitizeHandle(creator.handle, '@')}
+ {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, '@')}
+ )}
)}
@@ -184,13 +220,31 @@ export function Likes({count}: {count: number}) {
)
}
-export function Action({uri, pin}: {uri: string; pin?: boolean}) {
+export function Action({
+ uri,
+ pin,
+ type,
+ purpose,
+}: {
+ uri: string
+ pin?: boolean
+ type: 'feed' | 'list'
+ purpose?: AppBskyGraphDefs.ListView['purpose']
+}) {
const {hasSession} = useSession()
- if (!hasSession) return null
- return
+ if (!hasSession || purpose !== 'app.bsky.graph.defs#curatelist') return null
+ return
}
-function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
+function ActionInner({
+ uri,
+ pin,
+ type,
+}: {
+ uri: string
+ pin?: boolean
+ type: 'feed' | 'list'
+}) {
const {_} = useLingui()
const {data: preferences} = usePreferencesQuery()
const {isPending: isAddSavedFeedPending, mutateAsync: saveFeeds} =
@@ -198,9 +252,7 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
const {isPending: isRemovePending, mutateAsync: removeFeed} =
useRemoveFeedMutation()
const savedFeedConfig = React.useMemo(() => {
- return preferences?.savedFeeds?.find(
- feed => feed.type === 'feed' && feed.value === uri,
- )
+ return preferences?.savedFeeds?.find(feed => feed.value === uri)
}, [preferences?.savedFeeds, uri])
const removePromptControl = Prompt.usePromptControl()
const isPending = isAddSavedFeedPending || isRemovePending
@@ -216,7 +268,7 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
} else {
await saveFeeds([
{
- type: 'feed',
+ type,
value: uri,
pinned: pin || false,
},
@@ -228,7 +280,7 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
Toast.show(_(msg`Failed to update feeds`))
}
},
- [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig],
+ [_, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type],
)
const onPrompRemoveFeed = React.useCallback(
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index 972dbf995a..e5d6151775 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -578,7 +578,7 @@ function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) {
)
}
-function precacheList(
+export function precacheList(
queryClient: QueryClient,
list: AppBskyGraphDefs.ListView,
) {
@@ -588,3 +588,11 @@ function precacheList(
list,
)
}
+
+export function precacheFeedFromGeneratorView(
+ queryClient: QueryClient,
+ view: AppBskyFeedDefs.GeneratorView,
+) {
+ const hydratedFeed = hydrateFeedGenerator(view)
+ precacheFeed(queryClient, hydratedFeed)
+}
diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx
index 197f35e4d0..ec1a55e22e 100644
--- a/src/view/com/feeds/ProfileFeedgens.tsx
+++ b/src/view/com/feeds/ProfileFeedgens.tsx
@@ -3,7 +3,6 @@ import {
findNodeHandle,
ListRenderItemInfo,
StyleProp,
- StyleSheet,
View,
ViewStyle,
} from 'react-native'
@@ -12,18 +11,17 @@ import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {cleanError} from '#/lib/strings/errors'
-import {useTheme} from '#/lib/ThemeContext'
import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection'
-import {hydrateFeedGenerator} from '#/state/queries/feed'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens'
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 {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
-import {FeedSourceCardLoaded} from './FeedSourceCard'
const LOADING = {_reactKey: '__loading__'}
const EMPTY = {_reactKey: '__empty__'}
@@ -52,7 +50,7 @@ export const ProfileFeedgens = React.forwardRef<
ref,
) {
const {_} = useLingui()
- const theme = useTheme()
+ const t = useTheme()
const [isPTRing, setIsPTRing] = React.useState(false)
const opts = React.useMemo(() => ({enabled}), [enabled])
const {
@@ -79,10 +77,9 @@ export const ProfileFeedgens = React.forwardRef<
items = items.concat([EMPTY])
} else if (data?.pages) {
for (const page of data?.pages) {
- items = items.concat(page.feeds.map(feed => hydrateFeedGenerator(feed)))
+ items = items.concat(page.feeds)
}
- }
- if (isError && !isEmpty) {
+ } else if (isError && !isEmpty) {
items = items.concat([LOAD_MORE_ERROR_ITEM])
}
return items
@@ -132,48 +129,46 @@ export const ProfileFeedgens = React.forwardRef<
// rendering
// =
- const renderItemInner = React.useCallback(
- ({item, index}: ListRenderItemInfo) => {
- if (item === EMPTY) {
- return (
-
- )
- } else if (item === ERROR_ITEM) {
- return (
-
- )
- } else if (item === LOAD_MORE_ERROR_ITEM) {
- return (
-
- )
- } else if (item === LOADING) {
- return
- }
- if (preferences) {
- return (
-
- )
- }
- return null
- },
- [error, refetch, onPressRetryLoadMore, preferences, _],
- )
+ const renderItem = ({item, index}: ListRenderItemInfo) => {
+ if (item === EMPTY) {
+ return (
+
+ )
+ } else if (item === ERROR_ITEM) {
+ return (
+
+ )
+ } else if (item === LOAD_MORE_ERROR_ITEM) {
+ return (
+
+ )
+ } else if (item === LOADING) {
+ return
+ }
+ if (preferences) {
+ return (
+
+
+
+ )
+ }
+ return null
+ }
React.useEffect(() => {
if (enabled && scrollElRef.current) {
@@ -189,12 +184,12 @@ export const ProfileFeedgens = React.forwardRef<
ref={scrollElRef}
data={items}
keyExtractor={(item: any) => item._reactKey || item.uri}
- renderItem={renderItemInner}
+ renderItem={renderItem}
refreshing={isPTRing}
onRefresh={onRefresh}
headerOffset={headerOffset}
contentContainerStyle={isNative && {paddingBottom: headerOffset + 100}}
- indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
+ indicatorStyle={t.name === 'light' ? 'black' : 'white'}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
@@ -203,9 +198,3 @@ export const ProfileFeedgens = React.forwardRef<
)
})
-
-const styles = StyleSheet.create({
- item: {
- paddingHorizontal: 18,
- },
-})
diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx
index e7fdfe4bd5..62c944efcb 100644
--- a/src/view/com/lists/ProfileLists.tsx
+++ b/src/view/com/lists/ProfileLists.tsx
@@ -3,7 +3,6 @@ import {
findNodeHandle,
ListRenderItemInfo,
StyleProp,
- StyleSheet,
View,
ViewStyle,
} from 'react-native'
@@ -12,17 +11,17 @@ import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {cleanError} from '#/lib/strings/errors'
-import {useTheme} from '#/lib/ThemeContext'
import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection'
import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists'
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 {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
-import {ListCard} from './ListCard'
const LOADING = {_reactKey: '__loading__'}
const EMPTY = {_reactKey: '__empty__'}
@@ -48,7 +47,7 @@ export const ProfileLists = React.forwardRef(
{did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag},
ref,
) {
- const theme = useTheme()
+ const t = useTheme()
const {track} = useAnalytics()
const {_} = useLingui()
const [isPTRing, setIsPTRing] = React.useState(false)
@@ -166,15 +165,18 @@ export const ProfileLists = React.forwardRef(
return
}
return (
-
+
+
+
)
},
- [error, refetch, onPressRetryLoadMore, _],
+ [error, refetch, onPressRetryLoadMore, _, t.atoms.border_contrast_low],
)
React.useEffect(() => {
@@ -198,7 +200,7 @@ export const ProfileLists = React.forwardRef(
contentContainerStyle={
isNative && {paddingBottom: headerOffset + 100}
}
- indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
+ indicatorStyle={t.name === 'light' ? 'black' : 'white'}
removeClippedSubviews={true}
// @ts-ignore our .web version only -prf
desktopFixedHeight
@@ -208,9 +210,3 @@ export const ProfileLists = React.forwardRef(
)
},
)
-
-const styles = StyleSheet.create({
- item: {
- paddingHorizontal: 18,
- },
-})
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index 70437a9e76..2e5b485136 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -627,7 +627,7 @@ function FollowingFeed() {
fill={t.palette.white}
/>
-
+
)
@@ -644,7 +644,7 @@ function SavedFeed({
savedFeed.type === 'feed' ? savedFeed.view.displayName : savedFeed.view.name
return (
-
+
{({hovered, pressed}) => (
-
+