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)