mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at verify-intent 243 lines 6.8 kB view raw
1import React from 'react' 2import {ListRenderItemInfo, Pressable, View} from 'react-native' 3import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useFocusEffect} from '@react-navigation/native' 7import {NativeStackScreenProps} from '@react-navigation/native-stack' 8 9import {HITSLOP_10} from 'lib/constants' 10import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' 11import {CommonNavigatorParams} from 'lib/routes/types' 12import {shareUrl} from 'lib/sharing' 13import {cleanError} from 'lib/strings/errors' 14import {sanitizeHandle} from 'lib/strings/handles' 15import {enforceLen} from 'lib/strings/helpers' 16import {isNative, isWeb} from 'platform/detection' 17import {useSearchPostsQuery} from 'state/queries/search-posts' 18import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from 'state/shell' 19import {Pager} from '#/view/com/pager/Pager' 20import {TabBar} from '#/view/com/pager/TabBar' 21import {CenteredView} from '#/view/com/util/Views' 22import {Post} from 'view/com/post/Post' 23import {List} from 'view/com/util/List' 24import {ViewHeader} from 'view/com/util/ViewHeader' 25import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox' 26import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 27 28const renderItem = ({item}: ListRenderItemInfo<PostView>) => { 29 return <Post post={item} /> 30} 31 32const keyExtractor = (item: PostView, index: number) => { 33 return `${item.uri}-${index}` 34} 35 36export default function HashtagScreen({ 37 route, 38}: NativeStackScreenProps<CommonNavigatorParams, 'Hashtag'>) { 39 const {tag, author} = route.params 40 const {_} = useLingui() 41 42 const fullTag = React.useMemo(() => { 43 return `#${decodeURIComponent(tag)}` 44 }, [tag]) 45 46 const headerTitle = React.useMemo(() => { 47 return enforceLen(fullTag.toLowerCase(), 24, true, 'middle') 48 }, [fullTag]) 49 50 const sanitizedAuthor = React.useMemo(() => { 51 if (!author) return 52 return sanitizeHandle(author) 53 }, [author]) 54 55 const onShare = React.useCallback(() => { 56 const url = new URL('https://bsky.app') 57 url.pathname = `/hashtag/${decodeURIComponent(tag)}` 58 if (author) { 59 url.searchParams.set('author', author) 60 } 61 shareUrl(url.toString()) 62 }, [tag, author]) 63 64 const [activeTab, setActiveTab] = React.useState(0) 65 const setMinimalShellMode = useSetMinimalShellMode() 66 const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() 67 68 useFocusEffect( 69 React.useCallback(() => { 70 setMinimalShellMode(false) 71 }, [setMinimalShellMode]), 72 ) 73 74 const onPageSelected = React.useCallback( 75 (index: number) => { 76 setMinimalShellMode(false) 77 setDrawerSwipeDisabled(index > 0) 78 setActiveTab(index) 79 }, 80 [setDrawerSwipeDisabled, setMinimalShellMode], 81 ) 82 83 const sections = React.useMemo(() => { 84 return [ 85 { 86 title: _(msg`Top`), 87 component: ( 88 <HashtagScreenTab 89 fullTag={fullTag} 90 author={author} 91 sort="top" 92 active={activeTab === 0} 93 /> 94 ), 95 }, 96 { 97 title: _(msg`Latest`), 98 component: ( 99 <HashtagScreenTab 100 fullTag={fullTag} 101 author={author} 102 sort="latest" 103 active={activeTab === 1} 104 /> 105 ), 106 }, 107 ] 108 }, [_, fullTag, author, activeTab]) 109 110 return ( 111 <> 112 <CenteredView sideBorders={true}> 113 <ViewHeader 114 showOnDesktop 115 title={headerTitle} 116 subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined} 117 canGoBack 118 renderButton={ 119 isNative 120 ? () => ( 121 <Pressable 122 accessibilityRole="button" 123 onPress={onShare} 124 hitSlop={HITSLOP_10}> 125 <ArrowOutOfBox_Stroke2_Corner0_Rounded 126 size="lg" 127 onPress={onShare} 128 /> 129 </Pressable> 130 ) 131 : undefined 132 } 133 /> 134 </CenteredView> 135 <Pager 136 onPageSelected={onPageSelected} 137 renderTabBar={props => ( 138 <CenteredView 139 sideBorders={true} 140 // @ts-ignore web only 141 style={ 142 isWeb 143 ? { 144 position: isWeb ? 'sticky' : '', 145 top: 0, 146 zIndex: 1, 147 } 148 : undefined 149 }> 150 <TabBar items={sections.map(section => section.title)} {...props} /> 151 </CenteredView> 152 )} 153 initialPage={0}> 154 {sections.map((section, i) => ( 155 <View key={i}>{section.component}</View> 156 ))} 157 </Pager> 158 </> 159 ) 160} 161 162function HashtagScreenTab({ 163 fullTag, 164 author, 165 sort, 166 active, 167}: { 168 fullTag: string 169 author: string | undefined 170 sort: 'top' | 'latest' 171 active: boolean 172}) { 173 const {_} = useLingui() 174 const initialNumToRender = useInitialNumToRender() 175 const [isPTR, setIsPTR] = React.useState(false) 176 177 const queryParam = React.useMemo(() => { 178 if (!author) return fullTag 179 return `${fullTag} from:${author}` 180 }, [fullTag, author]) 181 182 const { 183 data, 184 isFetched, 185 isFetchingNextPage, 186 isLoading, 187 isError, 188 error, 189 refetch, 190 fetchNextPage, 191 hasNextPage, 192 } = useSearchPostsQuery({query: queryParam, sort, enabled: active}) 193 194 const posts = React.useMemo(() => { 195 return data?.pages.flatMap(page => page.posts) || [] 196 }, [data]) 197 198 const onRefresh = React.useCallback(async () => { 199 setIsPTR(true) 200 await refetch() 201 setIsPTR(false) 202 }, [refetch]) 203 204 const onEndReached = React.useCallback(() => { 205 if (isFetchingNextPage || !hasNextPage || error) return 206 fetchNextPage() 207 }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) 208 209 return ( 210 <> 211 {posts.length < 1 ? ( 212 <ListMaybePlaceholder 213 isLoading={isLoading || !isFetched} 214 isError={isError} 215 onRetry={refetch} 216 emptyType="results" 217 emptyMessage={_(msg`We couldn't find any results for that hashtag.`)} 218 /> 219 ) : ( 220 <List 221 data={posts} 222 renderItem={renderItem} 223 keyExtractor={keyExtractor} 224 refreshing={isPTR} 225 onRefresh={onRefresh} 226 onEndReached={onEndReached} 227 onEndReachedThreshold={4} 228 // @ts-ignore web only -prf 229 desktopFixedHeight 230 ListFooterComponent={ 231 <ListFooter 232 isFetchingNextPage={isFetchingNextPage} 233 error={cleanError(error)} 234 onRetry={fetchNextPage} 235 /> 236 } 237 initialNumToRender={initialNumToRender} 238 windowSize={11} 239 /> 240 )} 241 </> 242 ) 243}