diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx
index 94d97cb6..bd064909 100644
--- a/src/components/FeedCard.tsx
+++ b/src/components/FeedCard.tsx
@@ -1,6 +1,11 @@
import React from 'react'
import {GestureResponderEvent, View} from 'react-native'
-import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api'
+import {
+ AppBskyActorDefs,
+ AppBskyFeedDefs,
+ AppBskyGraphDefs,
+ AtUri,
+} from '@atproto/api'
import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -20,23 +25,35 @@ 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} from '#/components/Link'
+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'
-export function Default({feed}: {feed: AppBskyFeedDefs.GeneratorView}) {
+export function Default({
+ type,
+ view,
+}:
+ | {
+ type: 'feed'
+ view: AppBskyFeedDefs.GeneratorView
+ }
+ | {
+ type: 'list'
+ view: AppBskyGraphDefs.ListView
+ }) {
+ const displayName = type === 'feed' ? view.displayName : view.name
return (
-
+
-
-
+
+ {type === 'feed' && }
)
@@ -46,13 +63,10 @@ export function Link({
children,
feed,
}: {
- children: React.ReactElement
- feed: AppBskyFeedDefs.GeneratorView
-}) {
+ feed: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView
+} & Omit) {
const href = React.useMemo(() => {
- const urip = new AtUri(feed.uri)
- const handleOrDid = feed.creator.handle || feed.creator.did
- return `/profile/${handleOrDid}/feed/${urip.rkey}`
+ return createProfileFeedHref({feed})
}, [feed])
return {children}
}
@@ -62,11 +76,33 @@ export function Outer({children}: {children: React.ReactNode}) {
}
export function Header({children}: {children: React.ReactNode}) {
- return {children}
+ return (
+
+ {children}
+
+ )
}
-export function Avatar({src}: {src: string | undefined}) {
- return
+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({
@@ -74,22 +110,54 @@ export function TitleAndByline({
creator,
}: {
title: string
- creator: AppBskyActorDefs.ProfileViewBasic
+ creator?: AppBskyActorDefs.ProfileViewBasic
}) {
const t = useTheme()
return (
-
+
{title}
-
- Feed by {sanitizeHandle(creator.handle, '@')}
-
+ {creator && (
+
+ Feed by {sanitizeHandle(creator.handle, '@')}
+
+ )}
+
+ )
+}
+
+export function TitleAndBylinePlaceholder({creator}: {creator?: boolean}) {
+ const t = useTheme()
+
+ return (
+
+
+
+ {creator && (
+
+ )}
)
}
@@ -203,3 +271,16 @@ function ActionInner({uri, pin}: {uri: string; pin?: boolean}) {
>
)
}
+
+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
+ }`
+}
diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts
index 83d6a763..972dbf99 100644
--- a/src/state/queries/feed.ts
+++ b/src/state/queries/feed.ts
@@ -9,20 +9,24 @@ import {
} from '@atproto/api'
import {
InfiniteData,
+ QueryClient,
QueryKey,
useInfiniteQuery,
useMutation,
useQuery,
+ useQueryClient,
} from '@tanstack/react-query'
import {DISCOVER_FEED_URI, DISCOVER_SAVED_FEED} from '#/lib/constants'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {STALE} from '#/state/queries'
+import {RQKEY as listQueryKey} from '#/state/queries/list'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent, useSession} from '#/state/session'
import {router} from '#/routes'
import {FeedDescriptor} from './post-feed'
+import {precacheResolvedUri} from './resolve-uri'
export type FeedSourceFeedInfo = {
type: 'feed'
@@ -201,6 +205,7 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
const agent = useAgent()
const limit = options?.limit || 10
const {data: preferences} = usePreferencesQuery()
+ const queryClient = useQueryClient()
// Make sure this doesn't invalidate unless really needed.
const selectArgs = useMemo(
@@ -225,6 +230,13 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) {
limit,
cursor: pageParam,
})
+
+ // precache feeds
+ for (const feed of res.data.feeds) {
+ const hydratedFeed = hydrateFeedGenerator(feed)
+ precacheFeed(queryClient, hydratedFeed)
+ }
+
return res.data
},
initialPageParam: undefined,
@@ -449,3 +461,130 @@ export function usePinnedFeedsInfos() {
},
})
}
+
+export type SavedFeedItem =
+ | {
+ type: 'feed'
+ config: AppBskyActorDefs.SavedFeed
+ view: AppBskyFeedDefs.GeneratorView
+ }
+ | {
+ type: 'list'
+ config: AppBskyActorDefs.SavedFeed
+ view: AppBskyGraphDefs.ListView
+ }
+ | {
+ type: 'timeline'
+ config: AppBskyActorDefs.SavedFeed
+ view: undefined
+ }
+
+export function useSavedFeeds() {
+ const agent = useAgent()
+ const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery()
+ const savedItems = preferences?.savedFeeds ?? []
+ const queryClient = useQueryClient()
+
+ return useQuery({
+ staleTime: STALE.INFINITY,
+ enabled: !isLoadingPrefs,
+ queryKey: [pinnedFeedInfosQueryKeyRoot, ...savedItems],
+ placeholderData: previousData => {
+ return (
+ previousData || {
+ count: savedItems.length,
+ feeds: [],
+ }
+ )
+ },
+ queryFn: async () => {
+ const resolvedFeeds = new Map()
+ const resolvedLists = new Map()
+
+ const savedFeeds = savedItems.filter(feed => feed.type === 'feed')
+ const savedLists = savedItems.filter(feed => feed.type === 'list')
+
+ let feedsPromise = Promise.resolve()
+ if (savedFeeds.length > 0) {
+ feedsPromise = agent.app.bsky.feed
+ .getFeedGenerators({
+ feeds: savedFeeds.map(f => f.value),
+ })
+ .then(res => {
+ res.data.feeds.forEach(f => {
+ resolvedFeeds.set(f.uri, f)
+ })
+ })
+ }
+
+ const listsPromises = savedLists.map(list =>
+ agent.app.bsky.graph
+ .getList({
+ list: list.value,
+ limit: 1,
+ })
+ .then(res => {
+ const listView = res.data.list
+ resolvedLists.set(listView.uri, listView)
+ }),
+ )
+
+ await Promise.allSettled([feedsPromise, ...listsPromises])
+
+ resolvedFeeds.forEach(feed => {
+ const hydratedFeed = hydrateFeedGenerator(feed)
+ precacheFeed(queryClient, hydratedFeed)
+ })
+ resolvedLists.forEach(list => {
+ precacheList(queryClient, list)
+ })
+
+ const res: SavedFeedItem[] = savedItems.map(s => {
+ if (s.type === 'timeline') {
+ return {
+ type: 'timeline',
+ config: s,
+ view: undefined,
+ }
+ }
+
+ return {
+ type: s.type,
+ config: s,
+ view:
+ s.type === 'feed'
+ ? resolvedFeeds.get(s.value)
+ : resolvedLists.get(s.value),
+ }
+ }) as SavedFeedItem[]
+
+ return {
+ count: savedItems.length,
+ feeds: res,
+ }
+ },
+ })
+}
+
+function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) {
+ precacheResolvedUri(
+ queryClient,
+ hydratedFeed.creatorHandle,
+ hydratedFeed.creatorDid,
+ )
+ queryClient.setQueryData(
+ feedSourceInfoQueryKey({uri: hydratedFeed.uri}),
+ hydratedFeed,
+ )
+}
+
+function precacheList(
+ queryClient: QueryClient,
+ list: AppBskyGraphDefs.ListView,
+) {
+ precacheResolvedUri(queryClient, list.creator.handle, list.creator.did)
+ queryClient.setQueryData(
+ listQueryKey(list.uri),
+ list,
+ )
+}
diff --git a/src/state/queries/resolve-uri.ts b/src/state/queries/resolve-uri.ts
index 7bd26435..c1fd8e24 100644
--- a/src/state/queries/resolve-uri.ts
+++ b/src/state/queries/resolve-uri.ts
@@ -1,5 +1,10 @@
import {AppBskyActorDefs, AtUri} from '@atproto/api'
-import {useQuery, useQueryClient, UseQueryResult} from '@tanstack/react-query'
+import {
+ QueryClient,
+ useQuery,
+ useQueryClient,
+ UseQueryResult,
+} from '@tanstack/react-query'
import {STALE} from '#/state/queries'
import {useAgent} from '#/state/session'
@@ -50,3 +55,11 @@ export function useResolveDidQuery(didOrHandle: string | undefined) {
enabled: !!didOrHandle,
})
}
+
+export function precacheResolvedUri(
+ queryClient: QueryClient,
+ handle: string,
+ did: string,
+) {
+ queryClient.setQueryData(RQKEY(handle), did)
+}
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index 13452117..70437a9e 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -1,8 +1,6 @@
import React from 'react'
import {ActivityIndicator, type FlatList, StyleSheet, View} from 'react-native'
-import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
+import {AppBskyFeedDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
@@ -10,12 +8,11 @@ import debounce from 'lodash.debounce'
import {isNative, isWeb} from '#/platform/detection'
import {
- getAvatarTypeFromUri,
- useFeedSourceInfoQuery,
+ SavedFeedItem,
useGetPopularFeedsQuery,
+ useSavedFeeds,
useSearchPopularFeedsMutation,
} from '#/state/queries/feed'
-import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import {useSetMinimalShellMode} from '#/state/shell'
import {useComposerControls} from '#/state/shell/composer'
@@ -28,14 +25,10 @@ import {s} from 'lib/styles'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {FAB} from 'view/com/util/fab/FAB'
import {SearchInput} from 'view/com/util/forms/SearchInput'
-import {Link, TextLink} from 'view/com/util/Link'
+import {TextLink} from 'view/com/util/Link'
import {List} from 'view/com/util/List'
-import {
- FeedFeedLoadingPlaceholder,
- LoadingPlaceholder,
-} from 'view/com/util/LoadingPlaceholder'
+import {FeedFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
import {Text} from 'view/com/util/text/Text'
-import {UserAvatar} from 'view/com/util/UserAvatar'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
@@ -47,6 +40,7 @@ import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkl
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'
type Props = NativeStackScreenProps
@@ -61,9 +55,8 @@ type FlatlistSlice =
key: string
}
| {
- type: 'savedFeedsLoading'
+ type: 'savedFeedPlaceholder'
key: string
- // pendingItems: number,
}
| {
type: 'savedFeedNoResults'
@@ -72,8 +65,7 @@ type FlatlistSlice =
| {
type: 'savedFeed'
key: string
- feedUri: string
- savedFeedConfig: AppBskyActorDefs.SavedFeed
+ savedFeed: SavedFeedItem
}
| {
type: 'savedFeedsLoadMore'
@@ -113,11 +105,11 @@ export function FeedsScreen(_props: Props) {
const [query, setQuery] = React.useState('')
const [isPTR, setIsPTR] = React.useState(false)
const {
- data: preferences,
- isLoading: isPreferencesLoading,
- error: preferencesError,
- refetch: refetchPreferences,
- } = usePreferencesQuery()
+ data: savedFeeds,
+ isPlaceholderData: isSavedFeedsPlaceholder,
+ error: savedFeedsError,
+ refetch: refetchSavedFeeds,
+ } = useSavedFeeds()
const {
data: popularFeeds,
isFetching: isPopularFeedsFetching,
@@ -173,11 +165,11 @@ export function FeedsScreen(_props: Props) {
const onPullToRefresh = React.useCallback(async () => {
setIsPTR(true)
await Promise.all([
- refetchPreferences().catch(_e => undefined),
+ refetchSavedFeeds().catch(_e => undefined),
refetchPopularFeeds().catch(_e => undefined),
])
setIsPTR(false)
- }, [setIsPTR, refetchPreferences, refetchPopularFeeds])
+ }, [setIsPTR, refetchSavedFeeds, refetchPopularFeeds])
const onEndReached = React.useCallback(() => {
if (
isPopularFeedsFetching ||
@@ -203,6 +195,11 @@ export function FeedsScreen(_props: Props) {
const items = React.useMemo(() => {
let slices: FlatlistSlice[] = []
+ const hasActualSavedCount =
+ !isSavedFeedsPlaceholder ||
+ (isSavedFeedsPlaceholder && (savedFeeds?.count || 0) > 0)
+ const canShowDiscoverSection =
+ !hasSession || (hasSession && hasActualSavedCount)
if (hasSession) {
slices.push({
@@ -210,47 +207,63 @@ export function FeedsScreen(_props: Props) {
type: 'savedFeedsHeader',
})
- if (preferencesError) {
+ if (savedFeedsError) {
slices.push({
key: 'savedFeedsError',
type: 'error',
- error: cleanError(preferencesError.toString()),
+ error: cleanError(savedFeedsError.toString()),
})
} else {
- if (isPreferencesLoading || !preferences?.savedFeeds) {
- slices.push({
- key: 'savedFeedsLoading',
- type: 'savedFeedsLoading',
- // pendingItems: this.rootStore.preferences.savedFeeds.length || 3,
- })
+ if (isSavedFeedsPlaceholder && !savedFeeds?.feeds.length) {
+ /*
+ * Initial render in placeholder state is 0 on a cold page load,
+ * because preferences haven't loaded yet.
+ *
+ * In practice, `savedFeeds` is always defined, but we check for TS
+ * and for safety.
+ *
+ * In both cases, we show 4 as the the loading state.
+ */
+ const min = 8
+ const count = savedFeeds
+ ? savedFeeds.count === 0
+ ? min
+ : savedFeeds.count
+ : min
+ Array(count)
+ .fill(0)
+ .forEach((_, i) => {
+ slices.push({
+ key: 'savedFeedPlaceholder' + i,
+ type: 'savedFeedPlaceholder',
+ })
+ })
} else {
- if (preferences.savedFeeds?.length) {
- const noFollowingFeed = preferences.savedFeeds.every(
+ if (savedFeeds?.feeds?.length) {
+ const noFollowingFeed = savedFeeds.feeds.every(
f => f.type !== 'timeline',
)
slices = slices.concat(
- preferences.savedFeeds
- .filter(f => {
- return f.pinned
+ savedFeeds.feeds
+ .filter(s => {
+ return s.config.pinned
})
- .map(feed => ({
- key: `savedFeed:${feed.value}:${feed.id}`,
+ .map(s => ({
+ key: `savedFeed:${s.view?.uri}:${s.config.id}`,
type: 'savedFeed',
- feedUri: feed.value,
- savedFeedConfig: feed,
+ savedFeed: s,
})),
)
slices = slices.concat(
- preferences.savedFeeds
- .filter(f => {
- return !f.pinned
+ savedFeeds.feeds
+ .filter(s => {
+ return !s.config.pinned
})
- .map(feed => ({
- key: `savedFeed:${feed.value}:${feed.id}`,
+ .map(s => ({
+ key: `savedFeed:${s.view?.uri}:${s.config.id}`,
type: 'savedFeed',
- feedUri: feed.value,
- savedFeedConfig: feed,
+ savedFeed: s,
})),
)
@@ -270,59 +283,36 @@ export function FeedsScreen(_props: Props) {
}
}
- slices.push({
- key: 'popularFeedsHeader',
- type: 'popularFeedsHeader',
- })
-
- if (popularFeedsError || searchError) {
+ if (!hasSession || (hasSession && canShowDiscoverSection)) {
slices.push({
- key: 'popularFeedsError',
- type: 'error',
- error: cleanError(
- popularFeedsError?.toString() ?? searchError?.toString() ?? '',
- ),
+ key: 'popularFeedsHeader',
+ type: 'popularFeedsHeader',
})
- } else {
- if (isUserSearching) {
- if (isSearchPending || !searchResults) {
- slices.push({
- key: 'popularFeedsLoading',
- type: 'popularFeedsLoading',
- })
- } else {
- if (!searchResults || searchResults?.length === 0) {
- slices.push({
- key: 'popularFeedsNoResults',
- type: 'popularFeedsNoResults',
- })
- } else {
- slices = slices.concat(
- searchResults.map(feed => ({
- key: `popularFeed:${feed.uri}`,
- type: 'popularFeed',
- feedUri: feed.uri,
- feed,
- })),
- )
- }
- }
+
+ if (popularFeedsError || searchError) {
+ slices.push({
+ key: 'popularFeedsError',
+ type: 'error',
+ error: cleanError(
+ popularFeedsError?.toString() ?? searchError?.toString() ?? '',
+ ),
+ })
} else {
- if (isPopularFeedsFetching && !popularFeeds?.pages) {
- slices.push({
- key: 'popularFeedsLoading',
- type: 'popularFeedsLoading',
- })
- } else {
- if (!popularFeeds?.pages) {
+ if (isUserSearching) {
+ if (isSearchPending || !searchResults) {
slices.push({
- key: 'popularFeedsNoResults',
- type: 'popularFeedsNoResults',
+ key: 'popularFeedsLoading',
+ type: 'popularFeedsLoading',
})
} else {
- for (const page of popularFeeds.pages || []) {
+ if (!searchResults || searchResults?.length === 0) {
+ slices.push({
+ key: 'popularFeedsNoResults',
+ type: 'popularFeedsNoResults',
+ })
+ } else {
slices = slices.concat(
- page.feeds.map(feed => ({
+ searchResults.map(feed => ({
key: `popularFeed:${feed.uri}`,
type: 'popularFeed',
feedUri: feed.uri,
@@ -330,12 +320,37 @@ export function FeedsScreen(_props: Props) {
})),
)
}
-
- if (isPopularFeedsFetchingNextPage) {
+ }
+ } else {
+ if (isPopularFeedsFetching && !popularFeeds?.pages) {
+ slices.push({
+ key: 'popularFeedsLoading',
+ type: 'popularFeedsLoading',
+ })
+ } else {
+ if (!popularFeeds?.pages) {
slices.push({
- key: 'popularFeedsLoadingMore',
- type: 'popularFeedsLoadingMore',
+ key: 'popularFeedsNoResults',
+ type: 'popularFeedsNoResults',
})
+ } else {
+ for (const page of popularFeeds.pages || []) {
+ slices = slices.concat(
+ page.feeds.map(feed => ({
+ key: `popularFeed:${feed.uri}`,
+ type: 'popularFeed',
+ feedUri: feed.uri,
+ feed,
+ })),
+ )
+ }
+
+ if (isPopularFeedsFetchingNextPage) {
+ slices.push({
+ key: 'popularFeedsLoadingMore',
+ type: 'popularFeedsLoadingMore',
+ })
+ }
}
}
}
@@ -345,9 +360,9 @@ export function FeedsScreen(_props: Props) {
return slices
}, [
hasSession,
- preferences,
- isPreferencesLoading,
- preferencesError,
+ savedFeeds,
+ isSavedFeedsPlaceholder,
+ savedFeedsError,
popularFeeds,
isPopularFeedsFetching,
popularFeedsError,
@@ -407,10 +422,7 @@ export function FeedsScreen(_props: Props) {
({item}: {item: FlatlistSlice}) => {
if (item.type === 'error') {
return
- } else if (
- item.type === 'popularFeedsLoadingMore' ||
- item.type === 'savedFeedsLoading'
- ) {
+ } else if (item.type === 'popularFeedsLoadingMore') {
return (
@@ -459,8 +471,10 @@ export function FeedsScreen(_props: Props) {
)
+ } else if (item.type === 'savedFeedPlaceholder') {
+ return
} else if (item.type === 'savedFeed') {
- return
+ return
} else if (item.type === 'popularFeedsHeader') {
return (
<>
@@ -481,7 +495,7 @@ export function FeedsScreen(_props: Props) {
} else if (item.type === 'popularFeed') {
return (
-
+
)
@@ -571,136 +585,103 @@ export function FeedsScreen(_props: Props) {
)
}
-function FeedOrFollowing({
- savedFeedConfig: feed,
-}: {
- savedFeedConfig: AppBskyActorDefs.SavedFeed
-}) {
- return feed.type === 'timeline' ? (
+function FeedOrFollowing({savedFeed}: {savedFeed: SavedFeedItem}) {
+ return savedFeed.type === 'timeline' ? (
) : (
-
+
)
}
function FollowingFeed() {
- const pal = usePalette('default')
const t = useTheme()
- const {isMobile} = useWebMediaQueries()
+ const {_} = useLingui()
return (
-
-
+
-
-
-
- Following
-
-
+ ]}>
+
+
+
+
)
}
function SavedFeed({
- savedFeedConfig: feed,
+ savedFeed,
}: {
- savedFeedConfig: AppBskyActorDefs.SavedFeed
+ savedFeed: SavedFeedItem & {type: 'feed' | 'list'}
}) {
- const pal = usePalette('default')
- const {isMobile} = useWebMediaQueries()
- const {data: info, error} = useFeedSourceInfoQuery({uri: feed.value})
- const typeAvatar = getAvatarTypeFromUri(feed.value)
-
- if (!info)
- return (
-
- )
+ const t = useTheme()
+ const {view: feed} = savedFeed
+ const displayName =
+ savedFeed.type === 'feed' ? savedFeed.view.displayName : savedFeed.view.name
return (
-
- {error ? (
+
+ {({hovered, pressed}) => (
-
-
- ) : (
-
- )}
-
-
- {info.displayName}
-
- {error ? (
-
-
- Feed offline
-
-
- ) : null}
-
+ style={[
+ a.flex_1,
+ a.px_lg,
+ a.py_md,
+ a.border_b,
+ t.atoms.border_contrast_low,
+ (hovered || pressed) && t.atoms.bg_contrast_25,
+ ]}>
+
+
+
- {isMobile && (
-
+
+
+
)}
-
+
)
}
-function SavedFeedLoadingPlaceholder() {
- const pal = usePalette('default')
- const {isMobile} = useWebMediaQueries()
+function SavedFeedPlaceholder() {
+ const t = useTheme()
return (
-
-
+
+
+
+
)
}
diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx
index f6988548..8f6f6d4b 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 0b1fe37a..76ffba93 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}