diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-04-03 03:21:15 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-02 17:21:15 -0700 |
commit | 87da619aaa92e0ec762e68c13b24e58a25da10a8 (patch) | |
tree | 4da902d3ca43a226f6da8e5c090ab33c2df3297a /src/screens/Search/SearchResults.tsx | |
parent | 8d1f97b5ffac5d86762f1d4e9384ff3097acbc52 (diff) | |
download | voidsky-87da619aaa92e0ec762e68c13b24e58a25da10a8.tar.zst |
[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>
Diffstat (limited to 'src/screens/Search/SearchResults.tsx')
-rw-r--r-- | src/screens/Search/SearchResults.tsx | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx new file mode 100644 index 000000000..bb51d2deb --- /dev/null +++ b/src/screens/Search/SearchResults.tsx @@ -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) |