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 {usePalette} from '#/lib/hooks/usePalette' 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 {SearchError} from '#/components/SearchError' 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: ( ), }, { 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={0}> {sections.map((section, i) => ( {section.component} ))} ) } SearchResults = memo(SearchResults) export {SearchResults} function Loader() { return ( ) } function EmptyState({ message, error, children, }: { message: string error?: string children?: React.ReactNode }) { const t = useTheme() return ( {message} {error && ( <> Error: {error} )} {children} ) } 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 isLoggedin = Boolean(currentAccount?.did) 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 pal = usePalette('default') 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 (!isLoggedin) { 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} desktopFixedHeight contentContainerStyle={{paddingBottom: 100}} /> ) : ( )} ) : ( )} ) } 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 ? ( } keyExtractor={item => item.did} desktopFixedHeight contentContainerStyle={{paddingBottom: 100}} /> ) : ( )} ) : ( ) } 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 ? ( ( )} keyExtractor={item => item.uri} desktopFixedHeight contentContainerStyle={{paddingBottom: 100}} /> ) : ( )} ) : ( ) } SearchScreenFeedsResults = memo(SearchScreenFeedsResults)