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 update-sentry 231 lines 6.7 kB view raw
1import React from 'react' 2import {ListRenderItemInfo, 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 {useSearchPostsQuery} from '#/state/queries/search-posts' 17import {useSetMinimalShellMode} from '#/state/shell' 18import {Pager} from '#/view/com/pager/Pager' 19import {TabBar} from '#/view/com/pager/TabBar' 20import {Post} from '#/view/com/post/Post' 21import {List} from '#/view/com/util/List' 22import {atoms as a, web} from '#/alf' 23import {Button, ButtonIcon} from '#/components/Button' 24import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' 25import * as Layout from '#/components/Layout' 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 67 useFocusEffect( 68 React.useCallback(() => { 69 setMinimalShellMode(false) 70 }, [setMinimalShellMode]), 71 ) 72 73 const onPageSelected = React.useCallback( 74 (index: number) => { 75 setMinimalShellMode(false) 76 setActiveTab(index) 77 }, 78 [setMinimalShellMode], 79 ) 80 81 const sections = React.useMemo(() => { 82 return [ 83 { 84 title: _(msg`Top`), 85 component: ( 86 <HashtagScreenTab 87 fullTag={fullTag} 88 author={author} 89 sort="top" 90 active={activeTab === 0} 91 /> 92 ), 93 }, 94 { 95 title: _(msg`Latest`), 96 component: ( 97 <HashtagScreenTab 98 fullTag={fullTag} 99 author={author} 100 sort="latest" 101 active={activeTab === 1} 102 /> 103 ), 104 }, 105 ] 106 }, [_, fullTag, author, activeTab]) 107 108 return ( 109 <Layout.Screen> 110 <Pager 111 onPageSelected={onPageSelected} 112 renderTabBar={props => ( 113 <Layout.Center style={[a.z_10, web([a.sticky, {top: 0}])]}> 114 <Layout.Header.Outer noBottomBorder> 115 <Layout.Header.BackButton /> 116 <Layout.Header.Content> 117 <Layout.Header.TitleText>{headerTitle}</Layout.Header.TitleText> 118 {author && ( 119 <Layout.Header.SubtitleText> 120 {_(msg`From @${sanitizedAuthor}`)} 121 </Layout.Header.SubtitleText> 122 )} 123 </Layout.Header.Content> 124 <Layout.Header.Slot> 125 <Button 126 label={_(msg`Share`)} 127 size="small" 128 variant="ghost" 129 color="primary" 130 shape="round" 131 onPress={onShare} 132 hitSlop={HITSLOP_10} 133 style={[{right: -3}]}> 134 <ButtonIcon icon={Share} size="md" /> 135 </Button> 136 </Layout.Header.Slot> 137 </Layout.Header.Outer> 138 <TabBar items={sections.map(section => section.title)} {...props} /> 139 </Layout.Center> 140 )} 141 initialPage={0}> 142 {sections.map((section, i) => ( 143 <View key={i}>{section.component}</View> 144 ))} 145 </Pager> 146 </Layout.Screen> 147 ) 148} 149 150function HashtagScreenTab({ 151 fullTag, 152 author, 153 sort, 154 active, 155}: { 156 fullTag: string 157 author: string | undefined 158 sort: 'top' | 'latest' 159 active: boolean 160}) { 161 const {_} = useLingui() 162 const initialNumToRender = useInitialNumToRender() 163 const [isPTR, setIsPTR] = React.useState(false) 164 165 const queryParam = React.useMemo(() => { 166 if (!author) return fullTag 167 return `${fullTag} from:${author}` 168 }, [fullTag, author]) 169 170 const { 171 data, 172 isFetched, 173 isFetchingNextPage, 174 isLoading, 175 isError, 176 error, 177 refetch, 178 fetchNextPage, 179 hasNextPage, 180 } = useSearchPostsQuery({query: queryParam, sort, enabled: active}) 181 182 const posts = React.useMemo(() => { 183 return data?.pages.flatMap(page => page.posts) || [] 184 }, [data]) 185 186 const onRefresh = React.useCallback(async () => { 187 setIsPTR(true) 188 await refetch() 189 setIsPTR(false) 190 }, [refetch]) 191 192 const onEndReached = React.useCallback(() => { 193 if (isFetchingNextPage || !hasNextPage || error) return 194 fetchNextPage() 195 }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) 196 197 return ( 198 <> 199 {posts.length < 1 ? ( 200 <ListMaybePlaceholder 201 isLoading={isLoading || !isFetched} 202 isError={isError} 203 onRetry={refetch} 204 emptyType="results" 205 emptyMessage={_(msg`We couldn't find any results for that hashtag.`)} 206 /> 207 ) : ( 208 <List 209 data={posts} 210 renderItem={renderItem} 211 keyExtractor={keyExtractor} 212 refreshing={isPTR} 213 onRefresh={onRefresh} 214 onEndReached={onEndReached} 215 onEndReachedThreshold={4} 216 // @ts-ignore web only -prf 217 desktopFixedHeight 218 ListFooterComponent={ 219 <ListFooter 220 isFetchingNextPage={isFetchingNextPage} 221 error={cleanError(error)} 222 onRetry={fetchNextPage} 223 /> 224 } 225 initialNumToRender={initialNumToRender} 226 windowSize={11} 227 /> 228 )} 229 </> 230 ) 231}