diff options
author | dan <dan.abramov@gmail.com> | 2024-04-19 23:37:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-19 23:37:11 +0100 |
commit | d3c0b48da3053727dd4e02acc353f6372121d944 (patch) | |
tree | f49641f379ea119622696824430a9393f7bf55fa /src/screens/Hashtag.tsx | |
parent | c0ca891501cbc60eb945e3235800ec1e29a15ccd (diff) | |
download | voidsky-d3c0b48da3053727dd4e02acc353f6372121d944.tar.zst |
Top/Latest for hashtags (#3625)
* Split HashtagScreen into two components * Hashtag tabs * Visual fixes
Diffstat (limited to 'src/screens/Hashtag.tsx')
-rw-r--r-- | src/screens/Hashtag.tsx | 198 |
1 files changed, 139 insertions, 59 deletions
diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx index 5388593f1..34539f510 100644 --- a/src/screens/Hashtag.tsx +++ b/src/screens/Hashtag.tsx @@ -1,11 +1,12 @@ import React from 'react' -import {ListRenderItemInfo, Pressable} from 'react-native' +import {ListRenderItemInfo, Pressable, StyleSheet, View} from 'react-native' import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {usePalette} from '#/lib/hooks/usePalette' import {HITSLOP_10} from 'lib/constants' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' import {CommonNavigatorParams} from 'lib/routes/types' @@ -13,18 +14,17 @@ import {shareUrl} from 'lib/sharing' import {cleanError} from 'lib/strings/errors' import {sanitizeHandle} from 'lib/strings/handles' import {enforceLen} from 'lib/strings/helpers' -import {isNative} from 'platform/detection' +import {isNative, isWeb} from 'platform/detection' import {useSearchPostsQuery} from 'state/queries/search-posts' -import {useSetMinimalShellMode} from 'state/shell' +import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from 'state/shell' +import {Pager} from '#/view/com/pager/Pager' +import {TabBar} from '#/view/com/pager/TabBar' +import {CenteredView} from '#/view/com/util/Views' import {Post} from 'view/com/post/Post' import {List} from 'view/com/util/List' import {ViewHeader} from 'view/com/util/ViewHeader' import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox' -import { - ListFooter, - ListHeaderDesktop, - ListMaybePlaceholder, -} from '#/components/Lists' +import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' const renderItem = ({item}: ListRenderItemInfo<PostView>) => { return <Post post={item} /> @@ -38,20 +38,13 @@ export default function HashtagScreen({ route, }: NativeStackScreenProps<CommonNavigatorParams, 'Hashtag'>) { const {tag, author} = route.params - const setMinimalShellMode = useSetMinimalShellMode() const {_} = useLingui() - const initialNumToRender = useInitialNumToRender() - const [isPTR, setIsPTR] = React.useState(false) + const pal = usePalette('default') const fullTag = React.useMemo(() => { return `#${decodeURIComponent(tag)}` }, [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]) @@ -61,8 +54,127 @@ export default function HashtagScreen({ return sanitizeHandle(author) }, [author]) + const onShare = React.useCallback(() => { + const url = new URL('https://bsky.app') + url.pathname = `/hashtag/${decodeURIComponent(tag)}` + if (author) { + url.searchParams.set('author', author) + } + shareUrl(url.toString()) + }, [tag, author]) + + const [activeTab, setActiveTab] = React.useState(0) + const setMinimalShellMode = useSetMinimalShellMode() + const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() + + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + const onPageSelected = React.useCallback( + (index: number) => { + setMinimalShellMode(false) + setDrawerSwipeDisabled(index > 0) + setActiveTab(index) + }, + [setDrawerSwipeDisabled, setMinimalShellMode], + ) + + const sections = React.useMemo(() => { + return [ + { + title: _(msg`Top`), + component: ( + <HashtagScreenTab + fullTag={fullTag} + author={author} + sort="top" + active={activeTab === 0} + /> + ), + }, + { + title: _(msg`Latest`), + component: ( + <HashtagScreenTab + fullTag={fullTag} + author={author} + sort="latest" + active={activeTab === 1} + /> + ), + }, + ] + }, [_, fullTag, author, activeTab]) + + return ( + <> + <CenteredView sideBorders style={[pal.border, pal.view]}> + <ViewHeader + showOnDesktop + title={headerTitle} + subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined} + canGoBack + renderButton={ + isNative + ? () => ( + <Pressable + accessibilityRole="button" + onPress={onShare} + hitSlop={HITSLOP_10}> + <ArrowOutOfBox_Stroke2_Corner0_Rounded + size="lg" + onPress={onShare} + /> + </Pressable> + ) + : undefined + } + /> + </CenteredView> + <Pager + onPageSelected={onPageSelected} + renderTabBar={props => ( + <CenteredView + sideBorders + style={[pal.border, pal.view, styles.tabBarContainer]}> + <TabBar items={sections.map(section => section.title)} {...props} /> + </CenteredView> + )} + initialPage={0}> + {sections.map((section, i) => ( + <View key={i}>{section.component}</View> + ))} + </Pager> + </> + ) +} + +function HashtagScreenTab({ + fullTag, + author, + sort, + active, +}: { + fullTag: string + author: string | undefined + sort: 'top' | 'latest' + active: boolean +}) { + const {_} = useLingui() + const initialNumToRender = useInitialNumToRender() + const [isPTR, setIsPTR] = React.useState(false) + + const queryParam = React.useMemo(() => { + if (!author) return fullTag + return `${fullTag} from:${sanitizeHandle(author)}` + }, [fullTag, author]) + const { data, + isFetched, isFetchingNextPage, isLoading, isError, @@ -70,27 +182,12 @@ export default function HashtagScreen({ refetch, fetchNextPage, hasNextPage, - } = useSearchPostsQuery({query: queryParam}) + } = useSearchPostsQuery({query: queryParam, sort, enabled: active}) 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/${decodeURIComponent(tag)}` - if (author) { - url.searchParams.set('author', author) - } - shareUrl(url.toString()) - }, [tag, author]) - const onRefresh = React.useCallback(async () => { setIsPTR(true) await refetch() @@ -104,29 +201,9 @@ export default function HashtagScreen({ return ( <> - <ViewHeader - title={headerTitle} - subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined} - canGoBack - renderButton={ - isNative - ? () => ( - <Pressable - accessibilityRole="button" - onPress={onShare} - hitSlop={HITSLOP_10}> - <ArrowOutOfBox_Stroke2_Corner0_Rounded - size="lg" - onPress={onShare} - /> - </Pressable> - ) - : undefined - } - /> {posts.length < 1 ? ( <ListMaybePlaceholder - isLoading={isLoading} + isLoading={isLoading || !isFetched} isError={isError} onRetry={refetch} emptyType="results" @@ -143,12 +220,6 @@ export default function HashtagScreen({ onEndReachedThreshold={4} // @ts-ignore web only -prf desktopFixedHeight - ListHeaderComponent={ - <ListHeaderDesktop - title={headerTitle} - subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined} - /> - } ListFooterComponent={ <ListFooter isFetchingNextPage={isFetchingNextPage} @@ -163,3 +234,12 @@ export default function HashtagScreen({ </> ) } + +const styles = StyleSheet.create({ + tabBarContainer: { + // @ts-ignore web only + position: isWeb ? 'sticky' : '', + top: 0, + zIndex: 1, + }, +}) |