@@ -3,7 +3,7 @@ import type { PostData } from '@/src/common/lib/polycentric-hooks';
33import { Atoms , useTheme } from '@/src/common/theme' ;
44import { isWeb } from '@/src/common/util/platform' ;
55import { FlashList } from '@shopify/flash-list' ;
6- import { useCallback , useState } from 'react' ;
6+ import { useCallback , useEffect , useRef , useState } from 'react' ;
77import {
88 ActivityIndicator ,
99 type LayoutChangeEvent ,
@@ -22,10 +22,94 @@ interface FeedViewerProps {
2222 bottomPadding ?: number ;
2323}
2424
25- export function FeedViewer ( {
25+ export function FeedViewer ( props : FeedViewerProps ) {
26+ if ( props . error ) {
27+ return (
28+ < View
29+ style = { [
30+ Atoms . flex_1 ,
31+ Atoms . items_center ,
32+ Atoms . justify_center ,
33+ Atoms . p_lg ,
34+ ] }
35+ >
36+ < Text color = "neutral_500" > Failed to load feed</ Text >
37+ </ View >
38+ ) ;
39+ }
40+
41+ // On web let the page scroll naturally — the Stack's content area
42+ // already has `overflow: auto`. Inline rendering keeps that scroll
43+ // chain intact instead of FlashList swallowing it.
44+ if ( isWeb ) {
45+ return < WebFeedViewer { ...props } /> ;
46+ }
47+ return < NativeFeedViewer { ...props } /> ;
48+ }
49+
50+ function WebFeedViewer ( {
51+ items,
52+ isLoading,
53+ onEndReached,
54+ hasMore,
55+ bottomPadding,
56+ } : FeedViewerProps ) {
57+ const { theme } = useTheme ( ) ;
58+ const sentinelRef = useRef < View > ( null ) ;
59+
60+ // IntersectionObserver on a sentinel at the bottom triggers
61+ // `onEndReached` when the user scrolls near it.
62+ useEffect ( ( ) => {
63+ if ( ! hasMore || ! onEndReached ) return ;
64+ const node = sentinelRef . current as unknown as Element | null ;
65+ if ( ! node || typeof IntersectionObserver === 'undefined' ) return ;
66+ const observer = new IntersectionObserver (
67+ ( entries ) => {
68+ if ( entries . some ( ( e ) => e . isIntersecting ) ) onEndReached ( ) ;
69+ } ,
70+ { rootMargin : '400px' } ,
71+ ) ;
72+ observer . observe ( node ) ;
73+ return ( ) => observer . disconnect ( ) ;
74+ } , [ hasMore , onEndReached , items . length ] ) ;
75+
76+ if ( items . length === 0 && ! isLoading ) {
77+ return (
78+ < View
79+ style = { [
80+ Atoms . flex_1 ,
81+ Atoms . items_center ,
82+ Atoms . justify_center ,
83+ Atoms . p_lg ,
84+ ] }
85+ >
86+ < Text color = "neutral_500" > No posts yet</ Text >
87+ </ View >
88+ ) ;
89+ }
90+
91+ return (
92+ < View style = { { paddingBottom : bottomPadding } } >
93+ { items . map ( ( item , i ) => (
94+ < Post key = { `${ item . id } :${ i } ` } post = { item } />
95+ ) ) }
96+ { hasMore && < View ref = { sentinelRef } style = { { height : 1 } } /> }
97+ { isLoading && items . length > 0 ? (
98+ < View style = { [ Atoms . items_center , Atoms . p_lg ] } >
99+ < ActivityIndicator
100+ size = "small"
101+ color = { theme . palette . neutral_500 }
102+ accessibilityLabel = "Loading more posts"
103+ />
104+ </ View >
105+ ) : null }
106+ </ View >
107+ ) ;
108+ }
109+
110+ function NativeFeedViewer ( {
26111 items,
27112 isLoading,
28- error,
29113 onRefresh,
30114 onEndReached,
31115 hasMore,
@@ -42,10 +126,8 @@ export function FeedViewer({
42126 setHasLayout ( true ) ;
43127 } , [ ] ) ;
44128
45- // Our routing and navigation leaves
46- // inactive screens mounted with tiny dimensions.
47- // When navigating back, this can cause a momentary visual glitch.
48- // Hiding the invalid layout prevents the visual glitch.
129+ // Inactive screens stay mounted with tiny dimensions. Hide the list
130+ // briefly until it has a valid layout to avoid a flash on back-nav.
49131 const layoutInvalid = hasLayout && ( layoutBox . w < 2 || layoutBox . h < 2 ) ;
50132
51133 const renderItem = useCallback (
@@ -58,31 +140,9 @@ export function FeedViewer({
58140 [ ] ,
59141 ) ;
60142
61- if ( error ) {
62- return (
63- < View
64- style = { [
65- Atoms . flex_1 ,
66- Atoms . items_center ,
67- Atoms . justify_center ,
68- Atoms . p_lg ,
69- ] }
70- >
71- < Text color = "neutral_500" > Failed to load feed</ Text >
72- </ View >
73- ) ;
74- }
75-
76143 return (
77144 < View
78- style = { [
79- Atoms . flex_1 ,
80- isWeb &&
81- layoutInvalid && {
82- opacity : 0 ,
83- pointerEvents : 'none' ,
84- } ,
85- ] }
145+ style = { [ Atoms . flex_1 , layoutInvalid && { opacity : 0 } ] }
86146 onLayout = { onFeedContainerLayout }
87147 >
88148 < FlashList
0 commit comments