Add tab, query, factor out
This commit is contained in:
@@ -299,6 +299,34 @@ export function useSearchPopularFeedsMutation() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const popularFeedsSearchQueryKeyRoot = 'actor-search'
|
||||||
|
export const createPopularFeedsSearchQueryKey = (query: string) => [
|
||||||
|
popularFeedsSearchQueryKeyRoot,
|
||||||
|
query,
|
||||||
|
]
|
||||||
|
|
||||||
|
export function usePopularFeedsSearch({
|
||||||
|
query,
|
||||||
|
enabled,
|
||||||
|
}: {
|
||||||
|
query: string
|
||||||
|
enabled?: boolean
|
||||||
|
}) {
|
||||||
|
const agent = useAgent()
|
||||||
|
return useQuery({
|
||||||
|
enabled,
|
||||||
|
queryKey: createPopularFeedsSearchQueryKey(query),
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({
|
||||||
|
limit: 10,
|
||||||
|
query: query,
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.data.feeds
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export type SavedFeedSourceInfo = FeedSourceInfo & {
|
export type SavedFeedSourceInfo = FeedSourceInfo & {
|
||||||
savedFeed: AppBskyActorDefs.SavedFeed
|
savedFeed: AppBskyActorDefs.SavedFeed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
|
import {
|
||||||
|
AppBskyActorDefs,
|
||||||
|
AppBskyFeedDefs,
|
||||||
|
moderateProfile,
|
||||||
|
ModerationDecision,
|
||||||
|
ModerationOpts,
|
||||||
|
} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
@@ -20,7 +26,7 @@ import {
|
|||||||
FeedFeedLoadingPlaceholder,
|
FeedFeedLoadingPlaceholder,
|
||||||
ProfileCardFeedLoadingPlaceholder,
|
ProfileCardFeedLoadingPlaceholder,
|
||||||
} from 'view/com/util/LoadingPlaceholder'
|
} from 'view/com/util/LoadingPlaceholder'
|
||||||
import {atoms as a, useTheme as useThemeNew} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button} from '#/components/Button'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
@@ -33,14 +39,14 @@ function SuggestedItemsHeader({
|
|||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
}) {
|
}) {
|
||||||
const t = useThemeNew()
|
const t = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
isWeb
|
isWeb
|
||||||
? [a.flex_row, a.px_lg, a.py_lg, a.gap_md]
|
? [a.flex_row, a.px_lg, a.py_lg, a.pt_3xl, a.gap_md]
|
||||||
: [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md],
|
: [{flexDirection: 'row-reverse'}, a.p_lg, a.pt_2xl, a.gap_md],
|
||||||
{
|
{
|
||||||
borderBottomWidth: 4,
|
borderBottomWidth: 4,
|
||||||
borderColor: t.palette.primary_500,
|
borderColor: t.palette.primary_500,
|
||||||
@@ -56,6 +62,133 @@ function SuggestedItemsHeader({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoadMoreItems =
|
||||||
|
| {
|
||||||
|
type: 'profile'
|
||||||
|
key: string
|
||||||
|
avatar: string
|
||||||
|
moderation: ModerationDecision
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'feed'
|
||||||
|
key: string
|
||||||
|
avatar: string
|
||||||
|
moderation: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadMore({
|
||||||
|
item,
|
||||||
|
moderationOpts,
|
||||||
|
}: {
|
||||||
|
item: ExploreScreenItems & {type: 'loadMore'}
|
||||||
|
moderationOpts?: ModerationOpts
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const items = React.useMemo(() => {
|
||||||
|
return item.items
|
||||||
|
.map(_item => {
|
||||||
|
if (_item.type === 'profile') {
|
||||||
|
return {
|
||||||
|
type: 'profile',
|
||||||
|
key: _item.profile.did,
|
||||||
|
avatar: _item.profile.avatar,
|
||||||
|
moderation: moderateProfile(_item.profile, moderationOpts!),
|
||||||
|
}
|
||||||
|
} else if (_item.type === 'feed') {
|
||||||
|
return {
|
||||||
|
type: 'feed',
|
||||||
|
key: _item.feed.uri,
|
||||||
|
avatar: _item.feed.avatar,
|
||||||
|
moderation: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
.filter(Boolean) as LoadMoreItems[]
|
||||||
|
}, [item.items, moderationOpts])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.border_t, t.atoms.border_contrast_low]}>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Load more`)}
|
||||||
|
onPress={item.onLoadMore}
|
||||||
|
style={[a.relative, a.w_full]}>
|
||||||
|
{({hovered, pressed}) => (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.px_md,
|
||||||
|
a.py_md,
|
||||||
|
(hovered || pressed) && t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.relative,
|
||||||
|
{
|
||||||
|
height: 32,
|
||||||
|
width: 52,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{items.map((_item, i) => {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={_item.key}
|
||||||
|
style={[
|
||||||
|
a.border,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
a.absolute,
|
||||||
|
{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
left: i * 10,
|
||||||
|
borderColor: t.atoms.bg.backgroundColor,
|
||||||
|
borderRadius: _item.type === 'profile' ? 999 : 4,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{moderationOpts && (
|
||||||
|
<>
|
||||||
|
{_item.type === 'profile' ? (
|
||||||
|
<UserAvatar
|
||||||
|
size={28}
|
||||||
|
avatar={_item.avatar}
|
||||||
|
moderation={_item.moderation.ui('avatar')}
|
||||||
|
/>
|
||||||
|
) : _item.type === 'feed' ? (
|
||||||
|
<UserAvatar
|
||||||
|
size={28}
|
||||||
|
avatar={_item.avatar}
|
||||||
|
type="algo"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.pl_sm,
|
||||||
|
a.leading_snug,
|
||||||
|
hovered ? t.atoms.text : t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>Load more suggestions</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={[a.flex_1, a.align_end]}>
|
||||||
|
{item.isLoadingMore && <Loader size="lg" />}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type ExploreScreenItems =
|
type ExploreScreenItems =
|
||||||
| {
|
| {
|
||||||
type: 'header'
|
type: 'header'
|
||||||
@@ -97,7 +230,7 @@ type ExploreScreenItems =
|
|||||||
|
|
||||||
export function Explore() {
|
export function Explore() {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useThemeNew()
|
const t = useTheme()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {data: preferences, error: preferencesError} = usePreferencesQuery()
|
const {data: preferences, error: preferencesError} = usePreferencesQuery()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
@@ -304,89 +437,7 @@ export function Explore() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case 'loadMore': {
|
case 'loadMore': {
|
||||||
return (
|
return <LoadMore item={item} moderationOpts={moderationOpts} />
|
||||||
<View style={[a.border_t, a.pb_md, t.atoms.border_contrast_low]}>
|
|
||||||
<Button
|
|
||||||
label={_(msg`Load more`)}
|
|
||||||
onPress={item.onLoadMore}
|
|
||||||
style={[a.relative, a.w_full]}>
|
|
||||||
{({hovered}) => (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_1,
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.px_md,
|
|
||||||
a.py_md,
|
|
||||||
hovered && t.atoms.bg_contrast_25,
|
|
||||||
]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.relative,
|
|
||||||
{
|
|
||||||
height: 32,
|
|
||||||
width: 52,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
{item.items.map((_item, i) => {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
key={_item.key}
|
|
||||||
style={[
|
|
||||||
a.border,
|
|
||||||
t.atoms.bg_contrast_25,
|
|
||||||
a.absolute,
|
|
||||||
{
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
left: i * 10,
|
|
||||||
borderColor: t.atoms.bg.backgroundColor,
|
|
||||||
borderRadius:
|
|
||||||
_item.type === 'profile' ? 999 : 4,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
{moderationOpts && (
|
|
||||||
<>
|
|
||||||
{_item.type === 'profile' ? (
|
|
||||||
<UserAvatar
|
|
||||||
size={28}
|
|
||||||
avatar={_item.profile.avatar}
|
|
||||||
moderation={moderateProfile(
|
|
||||||
_item.profile,
|
|
||||||
moderationOpts!,
|
|
||||||
).ui('avatar')}
|
|
||||||
/>
|
|
||||||
) : _item.type === 'feed' ? (
|
|
||||||
<UserAvatar
|
|
||||||
size={28}
|
|
||||||
avatar={_item.feed.avatar}
|
|
||||||
type="algo"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.pl_sm,
|
|
||||||
a.leading_snug,
|
|
||||||
hovered ? t.atoms.text : t.atoms.text_contrast_medium,
|
|
||||||
]}>
|
|
||||||
<Trans>Load more suggestions</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<View style={[a.flex_1, a.align_end]}>
|
|
||||||
{item.isLoadingMore && <Loader size="lg" />}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case 'profilePlaceholder': {
|
case 'profilePlaceholder': {
|
||||||
return <ProfileCardFeedLoadingPlaceholder />
|
return <ProfileCardFeedLoadingPlaceholder />
|
||||||
@@ -431,7 +482,7 @@ export function Explore() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[_, t, hasSession, moderationOpts],
|
[t, hasSession, moderationOpts],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {listenSoftReset} from '#/state/events'
|
|||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||||
import {useActorSearch} from '#/state/queries/actor-search'
|
import {useActorSearch} from '#/state/queries/actor-search'
|
||||||
|
import {usePopularFeedsSearch} from '#/state/queries/feed'
|
||||||
import {useSearchPostsQuery} from '#/state/queries/search-posts'
|
import {useSearchPostsQuery} from '#/state/queries/search-posts'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useSetDrawerOpen} from '#/state/shell'
|
import {useSetDrawerOpen} from '#/state/shell'
|
||||||
@@ -56,6 +57,7 @@ import {Text} from '#/view/com/util/text/Text'
|
|||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||||
import {Explore} from '#/view/screens/Search/Explore'
|
import {Explore} from '#/view/screens/Search/Explore'
|
||||||
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
||||||
|
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||||
|
|
||||||
@@ -276,6 +278,50 @@ let SearchScreenUserResults = ({
|
|||||||
}
|
}
|
||||||
SearchScreenUserResults = React.memo(SearchScreenUserResults)
|
SearchScreenUserResults = React.memo(SearchScreenUserResults)
|
||||||
|
|
||||||
|
let SearchScreenFeedsResults = ({
|
||||||
|
query,
|
||||||
|
active,
|
||||||
|
}: {
|
||||||
|
query: string
|
||||||
|
active: boolean
|
||||||
|
}): React.ReactNode => {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {hasSession} = useSession()
|
||||||
|
|
||||||
|
const {data: results, isFetched} = usePopularFeedsSearch({
|
||||||
|
query,
|
||||||
|
enabled: active,
|
||||||
|
})
|
||||||
|
|
||||||
|
return isFetched && results ? (
|
||||||
|
<>
|
||||||
|
{results.length ? (
|
||||||
|
<List
|
||||||
|
data={results}
|
||||||
|
renderItem={({item}) => (
|
||||||
|
<FeedSourceCard
|
||||||
|
feedUri={item.uri}
|
||||||
|
showSaveBtn={hasSession}
|
||||||
|
showDescription
|
||||||
|
showLikes
|
||||||
|
pinOnSave
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
keyExtractor={item => item.did}
|
||||||
|
// @ts-ignore web only -prf
|
||||||
|
desktopFixedHeight
|
||||||
|
contentContainerStyle={{paddingBottom: 100}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyState message={_(msg`No results found for ${query}`)} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Loader />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SearchScreenFeedsResults = React.memo(SearchScreenFeedsResults)
|
||||||
|
|
||||||
let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
@@ -323,6 +369,12 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
|||||||
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: _(msg`Feeds`),
|
||||||
|
component: (
|
||||||
|
<SearchScreenFeedsResults query={query} active={activeTab === 3} />
|
||||||
|
),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}, [_, query, activeTab])
|
}, [_, query, activeTab])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user