[Explore] Base (#8053)
* migrate to #/screens * rm unneeded import * block drawer gesture on recent profiles * rm recommendations (#8056) * [Explore] Disable Trending videos (#8054) * remove giant header * disable * [Explore] Dynamic module ordering (#8066) * Dynamic module ordering * [Explore] New headers, metrics (#8067) * new sticky headers * improve spacing between modules * view metric on modules * update metrics names * [Explore] Suggested accounts module (#8072) * use modern profile card, update load more * add tab bar * tabbed suggested accounts * [Explore] Discover feeds module (#8073) * cap number of feeds to 3 * change feed pin button * Apply suggestions from code review Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * restore statsig to log events * filter out followed profiles, make suer enough are loaded (#8090) * [Explore] Trending topics (#8055) * redesigned trending topics * rm borders on web * get post count / age / ranking from api * spacing tweaks * fetch more topics then slice * use api data for avis/category * rm top border * Integrate new SDK, part out components * Clean up * Use status field * Bump SDK * Send up interests and langs --------- Co-authored-by: Eric Bailey <git@esb.lol> * Clean up module spacing and borders (cherry picked from commit 63d19b6c2d67e226e0e14709b1047a1f88b3ce1c) (cherry picked from commit 62d7d394ab1dc31b40b9c2cf59075adbf94737a1) * Switch back border ordering (cherry picked from commit 34e3789f8b410132c1390df3c2bb8257630ebdd9) * [Explore] Starter Packs (#8095) * Temp WIP (cherry picked from commit 43b5d7b1e64b3adb1ed162262d0310e0bf026c18) * New SP card * Load state * Revert change * Cleanup * Interests and caching * Count total * Format * Caching * [Explore] Feed previews module (#8075) * wip new hook * get fetching working, maybe * get feed previews rendering! * fix header height * working pin button * extract out FeedLink * add loader * only make preview:header sticky * Fix headers * Header tweaks * Fix moderation filter * Fix threading --------- Co-authored-by: Eric Bailey <git@esb.lol> * Space it out * Fix query key * Mock new endpoint, filter saved feeds * Make sure we're pinning, lower cache time * add news category * Remove log * Improve suggested accounts load state * Integrate new app view endpoint * fragment * Update src/screens/Search/modules/ExploreTrendingTopics.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Search/modules/ExploreTrendingTopics.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * lint * maybe fix this --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
import {memo, useCallback, useMemo, useState} from 'react'
|
||||
import {ActivityIndicator, View} from 'react-native'
|
||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {augmentSearchQuery} from '#/lib/strings/helpers'
|
||||
import {useActorSearch} from '#/state/queries/actor-search'
|
||||
import {usePopularFeedsSearch} from '#/state/queries/feed'
|
||||
import {useSearchPostsQuery} from '#/state/queries/search-posts'
|
||||
import {useSession} from '#/state/session'
|
||||
import {Pager} from '#/view/com/pager/Pager'
|
||||
import {TabBar} from '#/view/com/pager/TabBar'
|
||||
import {Post} from '#/view/com/post/Post'
|
||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
let SearchResults = ({
|
||||
query,
|
||||
queryWithParams,
|
||||
activeTab,
|
||||
onPageSelected,
|
||||
headerHeight,
|
||||
}: {
|
||||
query: string
|
||||
queryWithParams: string
|
||||
activeTab: number
|
||||
onPageSelected: (page: number) => void
|
||||
headerHeight: number
|
||||
}): React.ReactNode => {
|
||||
const {_} = useLingui()
|
||||
|
||||
const sections = useMemo(() => {
|
||||
if (!queryWithParams) return []
|
||||
const noParams = queryWithParams === query
|
||||
return [
|
||||
{
|
||||
title: _(msg`Top`),
|
||||
component: (
|
||||
<SearchScreenPostResults
|
||||
query={queryWithParams}
|
||||
sort="top"
|
||||
active={activeTab === 0}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: _(msg`Latest`),
|
||||
component: (
|
||||
<SearchScreenPostResults
|
||||
query={queryWithParams}
|
||||
sort="latest"
|
||||
active={activeTab === 1}
|
||||
/>
|
||||
),
|
||||
},
|
||||
noParams && {
|
||||
title: _(msg`People`),
|
||||
component: (
|
||||
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
||||
),
|
||||
},
|
||||
noParams && {
|
||||
title: _(msg`Feeds`),
|
||||
component: (
|
||||
<SearchScreenFeedsResults query={query} active={activeTab === 3} />
|
||||
),
|
||||
},
|
||||
].filter(Boolean) as {
|
||||
title: string
|
||||
component: React.ReactNode
|
||||
}[]
|
||||
}, [_, query, queryWithParams, activeTab])
|
||||
|
||||
return (
|
||||
<Pager
|
||||
onPageSelected={onPageSelected}
|
||||
renderTabBar={props => (
|
||||
<Layout.Center style={[a.z_10, web([a.sticky, {top: headerHeight}])]}>
|
||||
<TabBar items={sections.map(section => section.title)} {...props} />
|
||||
</Layout.Center>
|
||||
)}
|
||||
initialPage={0}>
|
||||
{sections.map((section, i) => (
|
||||
<View key={i}>{section.component}</View>
|
||||
))}
|
||||
</Pager>
|
||||
)
|
||||
}
|
||||
SearchResults = memo(SearchResults)
|
||||
export {SearchResults}
|
||||
|
||||
function Loader() {
|
||||
return (
|
||||
<Layout.Content>
|
||||
<View style={[a.py_xl]}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
function EmptyState({message, error}: {message: string; error?: string}) {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<Layout.Content>
|
||||
<View style={[a.p_xl]}>
|
||||
<View style={[t.atoms.bg_contrast_25, a.rounded_sm, a.p_lg]}>
|
||||
<Text style={[a.text_md]}>{message}</Text>
|
||||
|
||||
{error && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
marginVertical: 12,
|
||||
height: 1,
|
||||
width: '100%',
|
||||
backgroundColor: t.atoms.text.color,
|
||||
opacity: 0.2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
<Trans>Error: {error}</Trans>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
type SearchResultSlice =
|
||||
| {
|
||||
type: 'post'
|
||||
key: string
|
||||
post: AppBskyFeedDefs.PostView
|
||||
}
|
||||
| {
|
||||
type: 'loadingMore'
|
||||
key: string
|
||||
}
|
||||
|
||||
let SearchScreenPostResults = ({
|
||||
query,
|
||||
sort,
|
||||
active,
|
||||
}: {
|
||||
query: string
|
||||
sort?: 'top' | 'latest'
|
||||
active: boolean
|
||||
}): React.ReactNode => {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const [isPTR, setIsPTR] = useState(false)
|
||||
|
||||
const augmentedQuery = useMemo(() => {
|
||||
return augmentSearchQuery(query || '', {did: currentAccount?.did})
|
||||
}, [query, currentAccount])
|
||||
|
||||
const {
|
||||
isFetched,
|
||||
data: results,
|
||||
isFetching,
|
||||
error,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage,
|
||||
hasNextPage,
|
||||
} = useSearchPostsQuery({query: augmentedQuery, sort, enabled: active})
|
||||
|
||||
const onPullToRefresh = useCallback(async () => {
|
||||
setIsPTR(true)
|
||||
await refetch()
|
||||
setIsPTR(false)
|
||||
}, [setIsPTR, refetch])
|
||||
const onEndReached = useCallback(() => {
|
||||
if (isFetching || !hasNextPage || error) return
|
||||
fetchNextPage()
|
||||
}, [isFetching, error, hasNextPage, fetchNextPage])
|
||||
|
||||
const posts = useMemo(() => {
|
||||
return results?.pages.flatMap(page => page.posts) || []
|
||||
}, [results])
|
||||
const items = useMemo(() => {
|
||||
let temp: SearchResultSlice[] = []
|
||||
|
||||
const seenUris = new Set()
|
||||
for (const post of posts) {
|
||||
if (seenUris.has(post.uri)) {
|
||||
continue
|
||||
}
|
||||
temp.push({
|
||||
type: 'post',
|
||||
key: post.uri,
|
||||
post,
|
||||
})
|
||||
seenUris.add(post.uri)
|
||||
}
|
||||
|
||||
if (isFetchingNextPage) {
|
||||
temp.push({
|
||||
type: 'loadingMore',
|
||||
key: 'loadingMore',
|
||||
})
|
||||
}
|
||||
|
||||
return temp
|
||||
}, [posts, isFetchingNextPage])
|
||||
|
||||
return error ? (
|
||||
<EmptyState
|
||||
message={_(
|
||||
msg`We're sorry, but your search could not be completed. Please try again in a few minutes.`,
|
||||
)}
|
||||
error={error.toString()}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isFetched ? (
|
||||
<>
|
||||
{posts.length ? (
|
||||
<List
|
||||
data={items}
|
||||
renderItem={({item}) => {
|
||||
if (item.type === 'post') {
|
||||
return <Post post={item.post} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}}
|
||||
keyExtractor={item => item.key}
|
||||
refreshing={isPTR}
|
||||
onRefresh={onPullToRefresh}
|
||||
onEndReached={onEndReached}
|
||||
desktopFixedHeight
|
||||
contentContainerStyle={{paddingBottom: 100}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message={_(msg`No results found for ${query}`)} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
SearchScreenPostResults = memo(SearchScreenPostResults)
|
||||
|
||||
let SearchScreenUserResults = ({
|
||||
query,
|
||||
active,
|
||||
}: {
|
||||
query: string
|
||||
active: boolean
|
||||
}): React.ReactNode => {
|
||||
const {_} = useLingui()
|
||||
|
||||
const {data: results, isFetched} = useActorSearch({
|
||||
query,
|
||||
enabled: active,
|
||||
})
|
||||
|
||||
return isFetched && results ? (
|
||||
<>
|
||||
{results.length ? (
|
||||
<List
|
||||
data={results}
|
||||
renderItem={({item}) => (
|
||||
<ProfileCardWithFollowBtn profile={item} noBg />
|
||||
)}
|
||||
keyExtractor={item => item.did}
|
||||
desktopFixedHeight
|
||||
contentContainerStyle={{paddingBottom: 100}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message={_(msg`No results found for ${query}`)} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)
|
||||
}
|
||||
SearchScreenUserResults = memo(SearchScreenUserResults)
|
||||
|
||||
let SearchScreenFeedsResults = ({
|
||||
query,
|
||||
active,
|
||||
}: {
|
||||
query: string
|
||||
active: boolean
|
||||
}): React.ReactNode => {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
const {data: results, isFetched} = usePopularFeedsSearch({
|
||||
query,
|
||||
enabled: active,
|
||||
})
|
||||
|
||||
return isFetched && results ? (
|
||||
<>
|
||||
{results.length ? (
|
||||
<List
|
||||
data={results}
|
||||
renderItem={({item}) => (
|
||||
<View
|
||||
style={[
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_low,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
]}>
|
||||
<FeedCard.Default view={item} />
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => item.uri}
|
||||
desktopFixedHeight
|
||||
contentContainerStyle={{paddingBottom: 100}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message={_(msg`No results found for ${query}`)} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)
|
||||
}
|
||||
SearchScreenFeedsResults = memo(SearchScreenFeedsResults)
|
||||
Reference in New Issue
Block a user