Dedicated screen for hashtags, POC ALF list (#3047)
* create dedicated hashtag "search" screen clarify loading component name more adjustments rework `ViewHeader` to keep chevron centered w/ first line adjustments adjustments use `author` instead of `handle` in route add web route for url add web route for url Add desktop list header support web keep header lowercase add optional subtitle to view header correct isFetching logic oops use `isFetching` for clarity in footer combine logic update bskyweb finish screen style, add footer, add spinner, etc add list add header, params create a screen * add variable to server path * localize `By` * add empty state * more adjustments * sanitize author * fix web * add custom message for hashtag not found error * ellipsis in middle * fix * fix trans * account for multiple # * encode # * replaceall * Use sanitized tag * don't call function in lingui * add share button --------- Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
parent
8900c67df2
commit
cf8b03801f
10 changed files with 502 additions and 81 deletions
157
src/screens/Hashtag.tsx
Normal file
157
src/screens/Hashtag.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
import React from 'react'
|
||||
import {ListRenderItemInfo, Pressable} from 'react-native'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useSetMinimalShellMode} from 'state/shell'
|
||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {useSearchPostsQuery} from 'state/queries/search-posts'
|
||||
import {Post} from 'view/com/post/Post'
|
||||
import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
||||
import {enforceLen} from 'lib/strings/helpers'
|
||||
import {
|
||||
ListFooter,
|
||||
ListHeaderDesktop,
|
||||
ListMaybePlaceholder,
|
||||
} from '#/components/Lists'
|
||||
import {List} from 'view/com/util/List'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox'
|
||||
import {shareUrl} from 'lib/sharing'
|
||||
import {HITSLOP_10} from 'lib/constants'
|
||||
|
||||
const renderItem = ({item}: ListRenderItemInfo<PostView>) => {
|
||||
return <Post post={item} />
|
||||
}
|
||||
|
||||
const keyExtractor = (item: PostView, index: number) => {
|
||||
return `${item.uri}-${index}`
|
||||
}
|
||||
|
||||
export default function HashtagScreen({
|
||||
route,
|
||||
}: NativeStackScreenProps<CommonNavigatorParams, 'Hashtag'>) {
|
||||
const {tag, author} = route.params
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {_} = useLingui()
|
||||
const [isPTR, setIsPTR] = React.useState(false)
|
||||
|
||||
const fullTag = React.useMemo(() => {
|
||||
return `#${tag.replaceAll('%23', '#')}`
|
||||
}, [tag])
|
||||
|
||||
const queryParam = React.useMemo(() => {
|
||||
if (!author) return fullTag
|
||||
return `${fullTag} from:${sanitizeHandle(author)}`
|
||||
}, [fullTag, author])
|
||||
|
||||
const headerTitle = React.useMemo(() => {
|
||||
return enforceLen(fullTag.toLowerCase(), 24, true, 'middle')
|
||||
}, [fullTag])
|
||||
|
||||
const sanitizedAuthor = React.useMemo(() => {
|
||||
if (!author) return
|
||||
return sanitizeHandle(author)
|
||||
}, [author])
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
} = useSearchPostsQuery({query: queryParam})
|
||||
|
||||
const posts = React.useMemo(() => {
|
||||
return data?.pages.flatMap(page => page.posts) || []
|
||||
}, [data])
|
||||
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
setMinimalShellMode(false)
|
||||
}, [setMinimalShellMode]),
|
||||
)
|
||||
|
||||
const onShare = React.useCallback(() => {
|
||||
const url = new URL('https://bsky.app')
|
||||
url.pathname = `/hashtag/${tag}`
|
||||
if (author) {
|
||||
url.searchParams.set('author', author)
|
||||
}
|
||||
shareUrl(url.toString())
|
||||
}, [tag, author])
|
||||
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
setIsPTR(true)
|
||||
await refetch()
|
||||
setIsPTR(false)
|
||||
}, [refetch])
|
||||
|
||||
const onEndReached = React.useCallback(() => {
|
||||
if (isFetching || !hasNextPage || error) return
|
||||
fetchNextPage()
|
||||
}, [isFetching, hasNextPage, error, fetchNextPage])
|
||||
|
||||
return (
|
||||
<CenteredView style={a.flex_1}>
|
||||
<ViewHeader
|
||||
title={headerTitle}
|
||||
subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined}
|
||||
canGoBack={true}
|
||||
renderButton={() => (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={onShare}
|
||||
hitSlop={HITSLOP_10}>
|
||||
<ArrowOutOfBox_Stroke2_Corner0_Rounded
|
||||
size="lg"
|
||||
onPress={onShare}
|
||||
/>
|
||||
</Pressable>
|
||||
)}
|
||||
/>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isLoading || isRefetching}
|
||||
isError={isError}
|
||||
isEmpty={posts.length < 1}
|
||||
onRetry={refetch}
|
||||
empty={_(msg`We couldn't find any results for that hashtag.`)}
|
||||
/>
|
||||
{!isLoading && posts.length > 0 && (
|
||||
<List<PostView>
|
||||
data={posts}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTR}
|
||||
onRefresh={onRefresh}
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
// @ts-ignore web only -prf
|
||||
desktopFixedHeight
|
||||
ListHeaderComponent={
|
||||
<ListHeaderDesktop
|
||||
title={headerTitle}
|
||||
subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
isFetching={isFetching && !isRefetching}
|
||||
isError={isError}
|
||||
error={error?.name}
|
||||
onRetry={fetchNextPage}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue