* Rip out virtualization on the web * Screw around with layout * onEndReached * scrollToOffset * Fix background * onScroll * Shell bars * More scroll * Fixes * position: sticky * Clean up 1 * Clean up 2 * Undo PagerWithHeader changes and fork it * Trim down both versions * Cleanup 3 * Memoize, lint * Don't scroll away modal or lightbox * Add content-visibility for rows * Fix composer * Fix types * Fix borked scroll animation * Fixes to layout * More FlatList parity * Layout fixes * Fix more layout * More layout * More layouts * Fix profile layout * Remove onScroll * Display: none inactive pages * Add an intermediate List component * Fix type * Add onScrolledDownChange * Port pager to use onScrolledDownChange * Fix on mobile * Don't pass down onScroll (replacement TBD) * Remove resetMainScroll * Replace onMainScroll with MainScrollProvider * Hook ScrollProvider to pager * Fix the remaining special case * Optimize a bit * Enforce that onScroll cannot be passed * Keep value updated even if no handler * Also memo it * Move the fork to List.web * Add scroll handler * Consolidate List props a bit * More stuff * Rm unused * Simplify * Make isScrolledDown work * Oops * Fixes * Hook up context scroll handlers * Scroll restore for tabs * Route scroll restoration POC * Fix some issues with restoration * Remove bad idea * Fix pager scroll restoration * Undo accidental locale changes * onContentSizeChange * Scroll to post * Better positioning * Layout fixes * Factor out navigation stuff * Cleanup * Oops * Cleanup * Fixes and types * Naming etc * Fix crash * Match FL semantics * Snap the header scroll on the web * Add body scroll lock * Scroll to top on search * Fix types * Typos * Fix Safari overflow * Fix search positioning * Add border * Patch react navigation * Revert "Patch react navigation" This reverts commit 62516ed9c20410d166e1582b43b656c819495ddc. * fixes * scroll * scrollbar * cleanup unrelated * undo unrel * flatter * Fix css * twk
229 lines
6.9 KiB
TypeScript
229 lines
6.9 KiB
TypeScript
import React from 'react'
|
|
import {
|
|
FontAwesomeIcon,
|
|
FontAwesomeIconStyle,
|
|
} from '@fortawesome/react-native-fontawesome'
|
|
import {useNavigation} from '@react-navigation/native'
|
|
import {useAnalytics} from 'lib/analytics/analytics'
|
|
import {useQueryClient} from '@tanstack/react-query'
|
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
|
import {MainScrollProvider} from '../util/MainScrollProvider'
|
|
import {usePalette} from 'lib/hooks/usePalette'
|
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
import {useSetMinimalShellMode} from '#/state/shell'
|
|
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
|
import {ComposeIcon2} from 'lib/icons'
|
|
import {colors, s} from 'lib/styles'
|
|
import {View, useWindowDimensions} from 'react-native'
|
|
import {ListMethods} from '../util/List'
|
|
import {Feed} from '../posts/Feed'
|
|
import {TextLink} from '../util/Link'
|
|
import {FAB} from '../util/fab/FAB'
|
|
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
|
import {msg} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {useSession} from '#/state/session'
|
|
import {useComposerControls} from '#/state/shell/composer'
|
|
import {listenSoftReset, emitSoftReset} from '#/state/events'
|
|
import {truncateAndInvalidate} from '#/state/queries/util'
|
|
import {TabState, getTabState, getRootNavigation} from '#/lib/routes/helpers'
|
|
import {isNative} from '#/platform/detection'
|
|
|
|
const POLL_FREQ = 60e3 // 60sec
|
|
|
|
export function FeedPage({
|
|
testID,
|
|
isPageFocused,
|
|
feed,
|
|
feedParams,
|
|
renderEmptyState,
|
|
renderEndOfFeed,
|
|
}: {
|
|
testID?: string
|
|
feed: FeedDescriptor
|
|
feedParams?: FeedParams
|
|
isPageFocused: boolean
|
|
renderEmptyState: () => JSX.Element
|
|
renderEndOfFeed?: () => JSX.Element
|
|
}) {
|
|
const {isSandbox, hasSession} = useSession()
|
|
const pal = usePalette('default')
|
|
const {_} = useLingui()
|
|
const navigation = useNavigation()
|
|
const {isDesktop} = useWebMediaQueries()
|
|
const queryClient = useQueryClient()
|
|
const {openComposer} = useComposerControls()
|
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
|
const setMinimalShellMode = useSetMinimalShellMode()
|
|
const {screen, track} = useAnalytics()
|
|
const headerOffset = useHeaderOffset()
|
|
const scrollElRef = React.useRef<ListMethods>(null)
|
|
const [hasNew, setHasNew] = React.useState(false)
|
|
|
|
const scrollToTop = React.useCallback(() => {
|
|
scrollElRef.current?.scrollToOffset({
|
|
animated: isNative,
|
|
offset: -headerOffset,
|
|
})
|
|
setMinimalShellMode(false)
|
|
}, [headerOffset, setMinimalShellMode])
|
|
|
|
const onSoftReset = React.useCallback(() => {
|
|
const isScreenFocused =
|
|
getTabState(getRootNavigation(navigation).getState(), 'Home') ===
|
|
TabState.InsideAtRoot
|
|
if (isScreenFocused && isPageFocused) {
|
|
scrollToTop()
|
|
truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
|
|
setHasNew(false)
|
|
}
|
|
}, [navigation, isPageFocused, scrollToTop, queryClient, feed, setHasNew])
|
|
|
|
// fires when page within screen is activated/deactivated
|
|
React.useEffect(() => {
|
|
if (!isPageFocused) {
|
|
return
|
|
}
|
|
screen('Feed')
|
|
return listenSoftReset(onSoftReset)
|
|
}, [onSoftReset, screen, isPageFocused])
|
|
|
|
const onPressCompose = React.useCallback(() => {
|
|
track('HomeScreen:PressCompose')
|
|
openComposer({})
|
|
}, [openComposer, track])
|
|
|
|
const onPressLoadLatest = React.useCallback(() => {
|
|
scrollToTop()
|
|
truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
|
|
setHasNew(false)
|
|
}, [scrollToTop, feed, queryClient, setHasNew])
|
|
|
|
const ListHeaderComponent = React.useCallback(() => {
|
|
if (isDesktop) {
|
|
return (
|
|
<View
|
|
style={[
|
|
pal.view,
|
|
{
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 18,
|
|
paddingVertical: 12,
|
|
},
|
|
]}>
|
|
<TextLink
|
|
type="title-lg"
|
|
href="/"
|
|
style={[pal.text, {fontWeight: 'bold'}]}
|
|
text={
|
|
<>
|
|
{isSandbox ? 'SANDBOX' : 'Bluesky'}{' '}
|
|
{hasNew && (
|
|
<View
|
|
style={{
|
|
top: -8,
|
|
backgroundColor: colors.blue3,
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
}
|
|
onPress={emitSoftReset}
|
|
/>
|
|
{hasSession && (
|
|
<TextLink
|
|
type="title-lg"
|
|
href="/settings/home-feed"
|
|
style={{fontWeight: 'bold'}}
|
|
accessibilityLabel={_(msg`Feed Preferences`)}
|
|
accessibilityHint=""
|
|
text={
|
|
<FontAwesomeIcon
|
|
icon="sliders"
|
|
style={pal.textLight as FontAwesomeIconStyle}
|
|
/>
|
|
}
|
|
/>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
return <></>
|
|
}, [
|
|
isDesktop,
|
|
pal.view,
|
|
pal.text,
|
|
pal.textLight,
|
|
hasNew,
|
|
_,
|
|
isSandbox,
|
|
hasSession,
|
|
])
|
|
|
|
return (
|
|
<View testID={testID} style={s.h100pct}>
|
|
<MainScrollProvider>
|
|
<Feed
|
|
testID={testID ? `${testID}-feed` : undefined}
|
|
enabled={isPageFocused}
|
|
feed={feed}
|
|
feedParams={feedParams}
|
|
pollInterval={POLL_FREQ}
|
|
disablePoll={hasNew}
|
|
scrollElRef={scrollElRef}
|
|
onScrolledDownChange={setIsScrolledDown}
|
|
onHasNew={setHasNew}
|
|
renderEmptyState={renderEmptyState}
|
|
renderEndOfFeed={renderEndOfFeed}
|
|
ListHeaderComponent={ListHeaderComponent}
|
|
headerOffset={headerOffset}
|
|
/>
|
|
</MainScrollProvider>
|
|
{(isScrolledDown || hasNew) && (
|
|
<LoadLatestBtn
|
|
onPress={onPressLoadLatest}
|
|
label={_(msg`Load new posts`)}
|
|
showIndicator={hasNew}
|
|
/>
|
|
)}
|
|
|
|
{hasSession && (
|
|
<FAB
|
|
testID="composeFAB"
|
|
onPress={onPressCompose}
|
|
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(msg({message: `New post`, context: 'action'}))}
|
|
accessibilityHint=""
|
|
/>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
function useHeaderOffset() {
|
|
const {isDesktop, isTablet} = useWebMediaQueries()
|
|
const {fontScale} = useWindowDimensions()
|
|
const {hasSession} = useSession()
|
|
if (isDesktop || isTablet) {
|
|
return 0
|
|
}
|
|
if (hasSession) {
|
|
const navBarPad = 16
|
|
const navBarText = 21 * fontScale
|
|
const tabBarPad = 20 + 3 // nav bar padding + border
|
|
const tabBarText = 16 * fontScale
|
|
const magic = 7 * fontScale
|
|
return navBarPad + navBarText + tabBarPad + tabBarText + magic
|
|
} else {
|
|
const navBarPad = 16
|
|
const navBarText = 21 * fontScale
|
|
const magic = 4 * fontScale
|
|
return navBarPad + navBarText + magic
|
|
}
|
|
}
|