import {memo, useCallback, useMemo, useState} from 'react' import {ActivityIndicator, View} from 'react-native' import {type AppBskyFeedDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {urls} from '#/lib/constants' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' import {cleanError} from '#/lib/strings/errors' 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 {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' 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 {InlineLinkText} from '#/components/Link' import {ListFooter} from '#/components/Lists' import {SearchError} from '#/components/SearchError' import {Text} from '#/components/Typography' let SearchResults = ({ query, queryWithParams, activeTab, onPageSelected, headerHeight, initialPage = 0, }: { query: string queryWithParams: string activeTab: number onPageSelected: (page: number) => void headerHeight: number initialPage?: number }): React.ReactNode => { const {_} = useLingui() const sections = useMemo(() => { if (!queryWithParams) return [] const noParams = queryWithParams === query return [ { title: _(msg`Top`), component: ( ), }, { title: _(msg`Latest`), component: ( ), }, noParams && { title: _(msg`People`), component: ( ), }, noParams && { title: _(msg`Feeds`), component: ( ), }, ].filter(Boolean) as { title: string component: React.ReactNode }[] }, [_, query, queryWithParams, activeTab]) return ( ( section.title)} {...props} /> )} initialPage={initialPage}> {sections.map((section, i) => ( {section.component} ))} ) } SearchResults = memo(SearchResults) export {SearchResults} function Loader() { return ( ) } function EmptyState({ messageText, error, children, }: { messageText: React.ReactNode error?: string children?: React.ReactNode }) { const t = useTheme() return ( {messageText} {error && ( <> Error: {error} )} {children} ) } function NoResultsText({query}: {query: string}) { const t = useTheme() const {_} = useLingui() return ( <> No results found for " {query} ". {'\n\n'} Try a different search term, or{' '} read about how to use search filters . ) } 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, hasSession} = useSession() const [isPTR, setIsPTR] = useState(false) const trackPostView = usePostViewTracking('SearchResults') 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 t = useTheme() 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]) const closeAllActiveElements = useCloseAllActiveElements() const {requestSwitchToAccount} = useLoggedOutViewControls() const showSignIn = () => { closeAllActiveElements() requestSwitchToAccount({requestedAccount: 'none'}) } const showCreateAccount = () => { closeAllActiveElements() requestSwitchToAccount({requestedAccount: 'new'}) } if (!hasSession) { return ( Sign in or create an account to search for news, sports, politics, and everything else happening on Bluesky. ) } return error ? ( ) : ( <> {isFetched ? ( <> {posts.length ? ( { if (item.type === 'post') { return } else { return null } }} keyExtractor={item => item.key} refreshing={isPTR} onRefresh={onPullToRefresh} onEndReached={onEndReached} onItemSeen={item => { if (item.type === 'post') { trackPostView(item.post) } }} desktopFixedHeight ListFooterComponent={ } /> ) : ( } /> )} ) : ( )} ) } SearchScreenPostResults = memo(SearchScreenPostResults) let SearchScreenUserResults = ({ query, active, }: { query: string active: boolean }): React.ReactNode => { const {_} = useLingui() const {hasSession} = useSession() const [isPTR, setIsPTR] = useState(false) const { isFetched, data: results, isFetching, error, refetch, fetchNextPage, isFetchingNextPage, hasNextPage, } = useActorSearch({ query, enabled: active, }) const onPullToRefresh = useCallback(async () => { setIsPTR(true) await refetch() setIsPTR(false) }, [setIsPTR, refetch]) const onEndReached = useCallback(() => { if (!hasSession) return if (isFetching || !hasNextPage || error) return fetchNextPage() }, [isFetching, error, hasNextPage, fetchNextPage, hasSession]) const profiles = useMemo(() => { return results?.pages.flatMap(page => page.actors) || [] }, [results]) if (error) { return ( ) } return isFetched && profiles ? ( <> {profiles.length ? ( } keyExtractor={item => item.did} refreshing={isPTR} onRefresh={onPullToRefresh} onEndReached={onEndReached} desktopFixedHeight ListFooterComponent={ } /> ) : ( } /> )} ) : ( ) } SearchScreenUserResults = memo(SearchScreenUserResults) let SearchScreenFeedsResults = ({ query, active, }: { query: string active: boolean }): React.ReactNode => { const t = useTheme() const {data: results, isFetched} = usePopularFeedsSearch({ query, enabled: active, }) return isFetched && results ? ( <> {results.length ? ( ( )} keyExtractor={item => item.uri} desktopFixedHeight ListFooterComponent={} /> ) : ( } /> )} ) : ( ) } SearchScreenFeedsResults = memo(SearchScreenFeedsResults)