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)