Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

[Starter Packs] Posts tab (#4660)

* [Starter Packs] Posts tab

* oops

authored by

dan and committed by
GitHub
da4dfeb9 3b0a1775

+103 -9
+51
src/components/StarterPack/Main/PostsList.tsx
··· 1 + import React, {useCallback} from 'react' 2 + import {View} from 'react-native' 3 + import {msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {FeedDescriptor} from '#/state/queries/post-feed' 7 + import {isNative} from 'platform/detection' 8 + import {Feed} from 'view/com/posts/Feed' 9 + import {EmptyState} from 'view/com/util/EmptyState' 10 + import {ListRef} from 'view/com/util/List' 11 + import {SectionRef} from '#/screens/Profile/Sections/types' 12 + 13 + interface ProfilesListProps { 14 + listUri: string 15 + headerHeight: number 16 + scrollElRef: ListRef 17 + } 18 + 19 + export const PostsList = React.forwardRef<SectionRef, ProfilesListProps>( 20 + function PostsListImpl({listUri, headerHeight, scrollElRef}, ref) { 21 + const feed: FeedDescriptor = `list|${listUri}|as_following` 22 + const {_} = useLingui() 23 + 24 + const onScrollToTop = useCallback(() => { 25 + scrollElRef.current?.scrollToOffset({ 26 + animated: isNative, 27 + offset: -headerHeight, 28 + }) 29 + }, [scrollElRef, headerHeight]) 30 + 31 + React.useImperativeHandle(ref, () => ({ 32 + scrollToTop: onScrollToTop, 33 + })) 34 + 35 + const renderPostsEmpty = useCallback(() => { 36 + return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 37 + }, [_]) 38 + 39 + return ( 40 + <View> 41 + <Feed 42 + feed={feed} 43 + pollInterval={60e3} 44 + scrollElRef={scrollElRef} 45 + renderEmptyState={renderPostsEmpty} 46 + headerOffset={headerHeight} 47 + /> 48 + </View> 49 + ) 50 + }, 51 + )
+22 -6
src/screens/StarterPack/StarterPackScreen.tsx
··· 55 55 import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' 56 56 import {RichText} from '#/components/RichText' 57 57 import {FeedsList} from '#/components/StarterPack/Main/FeedsList' 58 + import {PostsList} from '#/components/StarterPack/Main/PostsList' 58 59 import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList' 59 60 import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' 60 61 import {ShareDialog} from '#/components/StarterPack/ShareDialog' ··· 132 133 > 133 134 moderationOpts: ModerationOpts 134 135 }) { 136 + const showPeopleTab = Boolean(starterPack.list) 137 + const showFeedsTab = Boolean(starterPack.feeds?.length) 138 + const showPostsTab = Boolean(starterPack.list) 139 + 135 140 const tabs = [ 136 - ...(starterPack.list ? ['People'] : []), 137 - ...(starterPack.feeds?.length ? ['Feeds'] : []), 141 + ...(showPeopleTab ? ['People'] : []), 142 + ...(showFeedsTab ? ['Feeds'] : []), 143 + ...(showPostsTab ? ['Posts'] : []), 138 144 ] 139 145 140 146 const qrCodeDialogControl = useDialogControl() ··· 180 186 onOpenShareDialog={onOpenShareDialog} 181 187 /> 182 188 )}> 183 - {starterPack.list != null 189 + {showPeopleTab 184 190 ? ({headerHeight, scrollElRef}) => ( 185 191 <ProfilesList 186 - key={0} 187 192 // Validated above 188 193 listUri={starterPack!.list!.uri} 189 194 headerHeight={headerHeight} ··· 194 199 /> 195 200 ) 196 201 : null} 197 - {starterPack.feeds != null 202 + {showFeedsTab 198 203 ? ({headerHeight, scrollElRef}) => ( 199 204 <FeedsList 200 - key={1} 201 205 // @ts-expect-error ? 202 206 feeds={starterPack?.feeds} 203 207 headerHeight={headerHeight} 204 208 // @ts-expect-error 205 209 scrollElRef={scrollElRef} 210 + /> 211 + ) 212 + : null} 213 + {showPostsTab 214 + ? ({headerHeight, scrollElRef}) => ( 215 + <PostsList 216 + // Validated above 217 + listUri={starterPack!.list!.uri} 218 + headerHeight={headerHeight} 219 + // @ts-expect-error 220 + scrollElRef={scrollElRef} 221 + moderationOpts={moderationOpts} 206 222 /> 207 223 ) 208 224 : null}
+28 -3
src/state/preferences/feed-tuners.tsx
··· 19 19 ] 20 20 } 21 21 if (feedDesc.startsWith('list')) { 22 - return [FeedTuner.dedupReposts] 22 + const feedTuners = [] 23 + 24 + if (feedDesc.endsWith('|as_following')) { 25 + // Same as Following tuners below, copypaste for now. 26 + if (preferences?.feedViewPrefs.hideReposts) { 27 + feedTuners.push(FeedTuner.removeReposts) 28 + } else { 29 + feedTuners.push(FeedTuner.dedupReposts) 30 + } 31 + if (preferences?.feedViewPrefs.hideReplies) { 32 + feedTuners.push(FeedTuner.removeReplies) 33 + } else { 34 + feedTuners.push( 35 + FeedTuner.thresholdRepliesOnly({ 36 + userDid: currentAccount?.did || '', 37 + minLikes: preferences?.feedViewPrefs.hideRepliesByLikeCount || 0, 38 + followedOnly: 39 + !!preferences?.feedViewPrefs.hideRepliesByUnfollowed, 40 + }), 41 + ) 42 + } 43 + if (preferences?.feedViewPrefs.hideQuotePosts) { 44 + feedTuners.push(FeedTuner.removeQuotePosts) 45 + } 46 + } else { 47 + feedTuners.push(FeedTuner.dedupReposts) 48 + } 49 + return feedTuners 23 50 } 24 51 if (feedDesc === 'following') { 25 52 const feedTuners = [] ··· 29 56 } else { 30 57 feedTuners.push(FeedTuner.dedupReposts) 31 58 } 32 - 33 59 if (preferences?.feedViewPrefs.hideReplies) { 34 60 feedTuners.push(FeedTuner.removeReplies) 35 61 } else { ··· 41 67 }), 42 68 ) 43 69 } 44 - 45 70 if (preferences?.feedViewPrefs.hideQuotePosts) { 46 71 feedTuners.push(FeedTuner.removeQuotePosts) 47 72 }
+2
src/state/queries/post-feed.ts
··· 49 49 | 'posts_with_media' 50 50 type FeedUri = string 51 51 type ListUri = string 52 + type ListFilter = 'as_following' // Applies current Following settings. Currently client-side. 52 53 53 54 export type FeedDescriptor = 54 55 | 'following' ··· 56 57 | `feedgen|${FeedUri}` 57 58 | `likes|${ActorDid}` 58 59 | `list|${ListUri}` 60 + | `list|${ListUri}|${ListFilter}` 59 61 export interface FeedParams { 60 62 disableTuner?: boolean 61 63 mergeFeedEnabled?: boolean