diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx index 5388593f14..34539f5102 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) => { return @@ -38,20 +38,13 @@ export default function HashtagScreen({ route, }: NativeStackScreenProps) { 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,27 +54,6 @@ export default function HashtagScreen({ return sanitizeHandle(author) }, [author]) - const { - data, - isFetchingNextPage, - isLoading, - 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/${decodeURIComponent(tag)}` @@ -91,6 +63,131 @@ export default function HashtagScreen({ 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: ( + + ), + }, + { + title: _(msg`Latest`), + component: ( + + ), + }, + ] + }, [_, fullTag, author, activeTab]) + + return ( + <> + + ( + + + + ) + : undefined + } + /> + + ( + + section.title)} {...props} /> + + )} + initialPage={0}> + {sections.map((section, i) => ( + {section.component} + ))} + + + ) +} + +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, + error, + refetch, + fetchNextPage, + hasNextPage, + } = useSearchPostsQuery({query: queryParam, sort, enabled: active}) + + const posts = React.useMemo(() => { + return data?.pages.flatMap(page => page.posts) || [] + }, [data]) + const onRefresh = React.useCallback(async () => { setIsPTR(true) await refetch() @@ -104,29 +201,9 @@ export default function HashtagScreen({ return ( <> - ( - - - - ) - : undefined - } - /> {posts.length < 1 ? ( - } ListFooterComponent={ ) } + +const styles = StyleSheet.create({ + tabBarContainer: { + // @ts-ignore web only + position: isWeb ? 'sticky' : '', + top: 0, + zIndex: 1, + }, +}) diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 872e10eef0..63a2b3de39 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -1,19 +1,20 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useNavigation} from '@react-navigation/native' -import {CenteredView} from './Views' -import {Text} from './text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useAnalytics} from 'lib/analytics/analytics' -import {NavigationProp} from 'lib/routes/types' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import Animated from 'react-native-reanimated' -import {useSetDrawerOpen} from '#/state/shell' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {useSetDrawerOpen} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {NavigationProp} from 'lib/routes/types' import {useTheme} from '#/alf' +import {Text} from './text/Text' +import {CenteredView} from './Views' const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} @@ -62,6 +63,7 @@ export function ViewHeader({ return ( @@ -136,14 +138,17 @@ export function ViewHeader({ function DesktopWebHeader({ title, + subtitle, renderButton, showBorder = true, }: { title: string + subtitle?: string renderButton?: () => JSX.Element showBorder?: boolean }) { const pal = usePalette('default') + const t = useTheme() return ( - - - {title} - + + + + {title} + + + {renderButton?.()} - {renderButton?.()} + {subtitle ? ( + + + + {subtitle} + + + + ) : null} ) } @@ -236,6 +258,9 @@ const styles = StyleSheet.create({ subtitle: { fontSize: 13, }, + subtitleDesktop: { + fontSize: 15, + }, backBtn: { width: 30, height: 30,