Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at fbd1138d97dda2df66bee13ad3ca6e83d55ebc25 186 lines 6.1 kB view raw
1import { 2 type JSX, 3 useCallback, 4 useEffect, 5 useMemo, 6 useRef, 7 useState, 8} from 'react' 9import {View} from 'react-native' 10import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' 11import {msg} from '@lingui/macro' 12import {useLingui} from '@lingui/react' 13import {type NavigationProp, useNavigation} from '@react-navigation/native' 14import {useQueryClient} from '@tanstack/react-query' 15 16import {DISCOVER_FEED_URI, VIDEO_FEED_URIS} from '#/lib/constants' 17import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 18import {ComposeIcon2} from '#/lib/icons' 19import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers' 20import {type AllNavigatorParams} from '#/lib/routes/types' 21import {logEvent} from '#/lib/statsig/statsig' 22import {s} from '#/lib/styles' 23import {isNative} from '#/platform/detection' 24import {listenSoftReset} from '#/state/events' 25import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 26import {useSetHomeBadge} from '#/state/home-badge' 27import {type FeedSourceInfo} from '#/state/queries/feed' 28import { 29 type FeedDescriptor, 30 type FeedParams, 31 RQKEY as FEED_RQKEY, 32} from '#/state/queries/post-feed' 33import {truncateAndInvalidate} from '#/state/queries/util' 34import {useSession} from '#/state/session' 35import {useSetMinimalShellMode} from '#/state/shell' 36import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' 37import {PostFeed} from '../posts/PostFeed' 38import {FAB} from '../util/fab/FAB' 39import {type ListMethods} from '../util/List' 40import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn' 41import {MainScrollProvider} from '../util/MainScrollProvider' 42 43const POLL_FREQ = 60e3 // 60sec 44 45export function FeedPage({ 46 testID, 47 isPageFocused, 48 isPageAdjacent, 49 feed, 50 feedParams, 51 renderEmptyState, 52 renderEndOfFeed, 53 savedFeedConfig, 54 feedInfo, 55}: { 56 testID?: string 57 feed: FeedDescriptor 58 feedParams?: FeedParams 59 isPageFocused: boolean 60 isPageAdjacent: boolean 61 renderEmptyState: () => JSX.Element 62 renderEndOfFeed?: () => JSX.Element 63 savedFeedConfig?: AppBskyActorDefs.SavedFeed 64 feedInfo: FeedSourceInfo 65}) { 66 const {hasSession} = useSession() 67 const {_} = useLingui() 68 const navigation = useNavigation<NavigationProp<AllNavigatorParams>>() 69 const queryClient = useQueryClient() 70 const {openComposer} = useOpenComposer() 71 const [isScrolledDown, setIsScrolledDown] = useState(false) 72 const setMinimalShellMode = useSetMinimalShellMode() 73 const headerOffset = useHeaderOffset() 74 const feedFeedback = useFeedFeedback(feedInfo, hasSession) 75 const scrollElRef = useRef<ListMethods>(null) 76 const [hasNew, setHasNew] = useState(false) 77 const setHomeBadge = useSetHomeBadge() 78 const isVideoFeed = useMemo(() => { 79 const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri) 80 const feedIsVideoMode = 81 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 82 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 83 return isNative && _isVideoFeed 84 }, [feedInfo]) 85 86 useEffect(() => { 87 if (isPageFocused) { 88 setHomeBadge(hasNew) 89 } 90 }, [isPageFocused, hasNew, setHomeBadge]) 91 92 const scrollToTop = useCallback(() => { 93 scrollElRef.current?.scrollToOffset({ 94 animated: isNative, 95 offset: -headerOffset, 96 }) 97 setMinimalShellMode(false) 98 }, [headerOffset, setMinimalShellMode]) 99 100 const onSoftReset = useCallback(() => { 101 const isScreenFocused = 102 getTabState(getRootNavigation(navigation).getState(), 'Home') === 103 TabState.InsideAtRoot 104 if (isScreenFocused && isPageFocused) { 105 scrollToTop() 106 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) 107 setHasNew(false) 108 logEvent('feed:refresh', { 109 feedType: feed.split('|')[0], 110 feedUrl: feed, 111 reason: 'soft-reset', 112 }) 113 } 114 }, [navigation, isPageFocused, scrollToTop, queryClient, feed]) 115 116 // fires when page within screen is activated/deactivated 117 useEffect(() => { 118 if (!isPageFocused) { 119 return 120 } 121 return listenSoftReset(onSoftReset) 122 }, [onSoftReset, isPageFocused]) 123 124 const onPressCompose = useCallback(() => { 125 openComposer({}) 126 }, [openComposer]) 127 128 const onPressLoadLatest = useCallback(() => { 129 scrollToTop() 130 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) 131 setHasNew(false) 132 logEvent('feed:refresh', { 133 feedType: feed.split('|')[0], 134 feedUrl: feed, 135 reason: 'load-latest', 136 }) 137 }, [scrollToTop, feed, queryClient]) 138 139 const shouldPrefetch = isNative && isPageAdjacent 140 const isDiscoverFeed = feedInfo.uri === DISCOVER_FEED_URI 141 return ( 142 <View 143 testID={testID} 144 // @ts-expect-error web only -sfn 145 dataSet={{nosnippet: isDiscoverFeed ? '' : undefined}}> 146 <MainScrollProvider> 147 <FeedFeedbackProvider value={feedFeedback}> 148 <PostFeed 149 testID={testID ? `${testID}-feed` : undefined} 150 enabled={isPageFocused || shouldPrefetch} 151 feed={feed} 152 feedParams={feedParams} 153 pollInterval={POLL_FREQ} 154 disablePoll={hasNew || !isPageFocused} 155 scrollElRef={scrollElRef} 156 onScrolledDownChange={setIsScrolledDown} 157 onHasNew={setHasNew} 158 renderEmptyState={renderEmptyState} 159 renderEndOfFeed={renderEndOfFeed} 160 headerOffset={headerOffset} 161 savedFeedConfig={savedFeedConfig} 162 isVideoFeed={isVideoFeed} 163 /> 164 </FeedFeedbackProvider> 165 </MainScrollProvider> 166 {(isScrolledDown || hasNew) && ( 167 <LoadLatestBtn 168 onPress={onPressLoadLatest} 169 label={_(msg`Load new posts`)} 170 showIndicator={hasNew} 171 /> 172 )} 173 174 {hasSession && ( 175 <FAB 176 testID="composeFAB" 177 onPress={onPressCompose} 178 icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} 179 accessibilityRole="button" 180 accessibilityLabel={_(msg({message: `New post`, context: 'action'}))} 181 accessibilityHint="" 182 /> 183 )} 184 </View> 185 ) 186}