Make scroll handling contextual (#2200)
* 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 itzio/stable
parent
fa3ccafa80
commit
7fd7970237
|
@ -0,0 +1,35 @@
|
||||||
|
import React, {createContext, useContext, useMemo} from 'react'
|
||||||
|
import {ScrollHandlers} from 'react-native-reanimated'
|
||||||
|
|
||||||
|
const ScrollContext = createContext<ScrollHandlers<any>>({
|
||||||
|
onBeginDrag: undefined,
|
||||||
|
onEndDrag: undefined,
|
||||||
|
onScroll: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useScrollHandlers(): ScrollHandlers<any> {
|
||||||
|
return useContext(ScrollContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderProps = {children: React.ReactNode} & ScrollHandlers<any>
|
||||||
|
|
||||||
|
// Note: this completely *overrides* the parent handlers.
|
||||||
|
// It's up to you to compose them with the parent ones via useScrollHandlers() if needed.
|
||||||
|
export function ScrollProvider({
|
||||||
|
children,
|
||||||
|
onBeginDrag,
|
||||||
|
onEndDrag,
|
||||||
|
onScroll,
|
||||||
|
}: ProviderProps) {
|
||||||
|
const handlers = useMemo(
|
||||||
|
() => ({
|
||||||
|
onBeginDrag,
|
||||||
|
onEndDrag,
|
||||||
|
onScroll,
|
||||||
|
}),
|
||||||
|
[onBeginDrag, onEndDrag, onScroll],
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<ScrollContext.Provider value={handlers}>{children}</ScrollContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
|
@ -7,13 +7,15 @@ import {useNavigation} from '@react-navigation/native'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||||
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
import {MainScrollProvider} from '../util/MainScrollProvider'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
||||||
import {ComposeIcon2} from 'lib/icons'
|
import {ComposeIcon2} from 'lib/icons'
|
||||||
import {colors, s} from 'lib/styles'
|
import {colors, s} from 'lib/styles'
|
||||||
import {FlatList, View, useWindowDimensions} from 'react-native'
|
import {View, useWindowDimensions} from 'react-native'
|
||||||
|
import {ListMethods} from '../util/List'
|
||||||
import {Feed} from '../posts/Feed'
|
import {Feed} from '../posts/Feed'
|
||||||
import {TextLink} from '../util/Link'
|
import {TextLink} from '../util/Link'
|
||||||
import {FAB} from '../util/fab/FAB'
|
import {FAB} from '../util/fab/FAB'
|
||||||
|
@ -51,10 +53,11 @@ export function FeedPage({
|
||||||
const {isDesktop} = useWebMediaQueries()
|
const {isDesktop} = useWebMediaQueries()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {screen, track} = useAnalytics()
|
const {screen, track} = useAnalytics()
|
||||||
const headerOffset = useHeaderOffset()
|
const headerOffset = useHeaderOffset()
|
||||||
const scrollElRef = React.useRef<FlatList>(null)
|
const scrollElRef = React.useRef<ListMethods>(null)
|
||||||
const [hasNew, setHasNew] = React.useState(false)
|
const [hasNew, setHasNew] = React.useState(false)
|
||||||
|
|
||||||
const scrollToTop = React.useCallback(() => {
|
const scrollToTop = React.useCallback(() => {
|
||||||
|
@ -62,8 +65,8 @@ export function FeedPage({
|
||||||
animated: isNative,
|
animated: isNative,
|
||||||
offset: -headerOffset,
|
offset: -headerOffset,
|
||||||
})
|
})
|
||||||
resetMainScroll()
|
setMinimalShellMode(false)
|
||||||
}, [headerOffset, resetMainScroll])
|
}, [headerOffset, setMinimalShellMode])
|
||||||
|
|
||||||
const onSoftReset = React.useCallback(() => {
|
const onSoftReset = React.useCallback(() => {
|
||||||
const isScreenFocused =
|
const isScreenFocused =
|
||||||
|
@ -164,6 +167,7 @@ export function FeedPage({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={s.h100pct}>
|
<View testID={testID} style={s.h100pct}>
|
||||||
|
<MainScrollProvider>
|
||||||
<Feed
|
<Feed
|
||||||
testID={testID ? `${testID}-feed` : undefined}
|
testID={testID ? `${testID}-feed` : undefined}
|
||||||
enabled={isPageFocused}
|
enabled={isPageFocused}
|
||||||
|
@ -171,14 +175,14 @@ export function FeedPage({
|
||||||
feedParams={feedParams}
|
feedParams={feedParams}
|
||||||
pollInterval={POLL_FREQ}
|
pollInterval={POLL_FREQ}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
onScroll={onMainScroll}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
onHasNew={setHasNew}
|
onHasNew={setHasNew}
|
||||||
scrollEventThrottle={1}
|
|
||||||
renderEmptyState={renderEmptyState}
|
renderEmptyState={renderEmptyState}
|
||||||
renderEndOfFeed={renderEndOfFeed}
|
renderEndOfFeed={renderEndOfFeed}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
headerOffset={headerOffset}
|
headerOffset={headerOffset}
|
||||||
/>
|
/>
|
||||||
|
</MainScrollProvider>
|
||||||
{(isScrolledDown || hasNew) && (
|
{(isScrolledDown || hasNew) && (
|
||||||
<LoadLatestBtn
|
<LoadLatestBtn
|
||||||
onPress={onPressLoadLatest}
|
onPress={onPressLoadLatest}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {MutableRefObject} from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
|
@ -8,18 +8,16 @@ import {
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {FlatList} from '../util/Views'
|
import {List, ListRef} from '../util/List'
|
||||||
import {FeedSourceCardLoaded} from './FeedSourceCard'
|
import {FeedSourceCardLoaded} from './FeedSourceCard'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens'
|
import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens'
|
||||||
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
|
||||||
import {useTheme} from '#/lib/ThemeContext'
|
import {useTheme} from '#/lib/ThemeContext'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {hydrateFeedGenerator} from '#/state/queries/feed'
|
import {hydrateFeedGenerator} from '#/state/queries/feed'
|
||||||
|
@ -37,9 +35,7 @@ interface SectionRef {
|
||||||
|
|
||||||
interface ProfileFeedgensProps {
|
interface ProfileFeedgensProps {
|
||||||
did: string
|
did: string
|
||||||
scrollElRef: MutableRefObject<FlatList<any> | null>
|
scrollElRef: ListRef
|
||||||
onScroll?: OnScrollHandler
|
|
||||||
scrollEventThrottle?: number
|
|
||||||
headerOffset: number
|
headerOffset: number
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
|
@ -50,16 +46,7 @@ export const ProfileFeedgens = React.forwardRef<
|
||||||
SectionRef,
|
SectionRef,
|
||||||
ProfileFeedgensProps
|
ProfileFeedgensProps
|
||||||
>(function ProfileFeedgensImpl(
|
>(function ProfileFeedgensImpl(
|
||||||
{
|
{did, scrollElRef, headerOffset, enabled, style, testID},
|
||||||
did,
|
|
||||||
scrollElRef,
|
|
||||||
onScroll,
|
|
||||||
scrollEventThrottle,
|
|
||||||
headerOffset,
|
|
||||||
enabled,
|
|
||||||
style,
|
|
||||||
testID,
|
|
||||||
},
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
@ -185,10 +172,9 @@ export const ProfileFeedgens = React.forwardRef<
|
||||||
[error, refetch, onPressRetryLoadMore, pal, preferences],
|
[error, refetch, onPressRetryLoadMore, pal, preferences],
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
<FlatList
|
<List
|
||||||
testID={testID ? `${testID}-flatlist` : undefined}
|
testID={testID ? `${testID}-flatlist` : undefined}
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={items}
|
data={items}
|
||||||
|
@ -207,8 +193,6 @@ export const ProfileFeedgens = React.forwardRef<
|
||||||
minHeight: Dimensions.get('window').height * 1.5,
|
minHeight: Dimensions.get('window').height * 1.5,
|
||||||
}}
|
}}
|
||||||
style={{paddingTop: headerOffset}}
|
style={{paddingTop: headerOffset}}
|
||||||
onScroll={onScroll != null ? scrollHandler : undefined}
|
|
||||||
scrollEventThrottle={scrollEventThrottle}
|
|
||||||
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
|
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
contentOffset={{x: 0, y: headerOffset * -1}}
|
contentOffset={{x: 0, y: headerOffset * -1}}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {MutableRefObject} from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
|
@ -8,7 +8,7 @@ import {
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||||
import {FlatList} from '../util/Views'
|
import {List, ListRef} from '../util/List'
|
||||||
import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
|
@ -18,10 +18,8 @@ import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {useListMembersQuery} from '#/state/queries/list-members'
|
import {useListMembersQuery} from '#/state/queries/list-members'
|
||||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
|
|
||||||
|
@ -34,24 +32,22 @@ export function ListMembers({
|
||||||
list,
|
list,
|
||||||
style,
|
style,
|
||||||
scrollElRef,
|
scrollElRef,
|
||||||
onScroll,
|
onScrolledDownChange,
|
||||||
onPressTryAgain,
|
onPressTryAgain,
|
||||||
renderHeader,
|
renderHeader,
|
||||||
renderEmptyState,
|
renderEmptyState,
|
||||||
testID,
|
testID,
|
||||||
scrollEventThrottle,
|
|
||||||
headerOffset = 0,
|
headerOffset = 0,
|
||||||
desktopFixedHeightOffset,
|
desktopFixedHeightOffset,
|
||||||
}: {
|
}: {
|
||||||
list: string
|
list: string
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
scrollElRef?: ListRef
|
||||||
onScroll: OnScrollHandler
|
onScrolledDownChange: (isScrolledDown: boolean) => void
|
||||||
onPressTryAgain?: () => void
|
onPressTryAgain?: () => void
|
||||||
renderHeader: () => JSX.Element
|
renderHeader: () => JSX.Element
|
||||||
renderEmptyState: () => JSX.Element
|
renderEmptyState: () => JSX.Element
|
||||||
testID?: string
|
testID?: string
|
||||||
scrollEventThrottle?: number
|
|
||||||
headerOffset?: number
|
headerOffset?: number
|
||||||
desktopFixedHeightOffset?: number
|
desktopFixedHeightOffset?: number
|
||||||
}) {
|
}) {
|
||||||
|
@ -209,10 +205,9 @@ export function ListMembers({
|
||||||
[isFetching],
|
[isFetching],
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollHandler = useAnimatedScrollHandler(onScroll)
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
<FlatList
|
<List
|
||||||
testID={testID ? `${testID}-flatlist` : undefined}
|
testID={testID ? `${testID}-flatlist` : undefined}
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={items}
|
data={items}
|
||||||
|
@ -233,10 +228,9 @@ export function ListMembers({
|
||||||
minHeight: Dimensions.get('window').height * 1.5,
|
minHeight: Dimensions.get('window').height * 1.5,
|
||||||
}}
|
}}
|
||||||
style={{paddingTop: headerOffset}}
|
style={{paddingTop: headerOffset}}
|
||||||
onScroll={scrollHandler}
|
onScrolledDownChange={onScrolledDownChange}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={0.6}
|
onEndReachedThreshold={0.6}
|
||||||
scrollEventThrottle={scrollEventThrottle}
|
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
contentOffset={{x: 0, y: headerOffset * -1}}
|
contentOffset={{x: 0, y: headerOffset * -1}}
|
||||||
// @ts-ignore our .web version only -prf
|
// @ts-ignore our .web version only -prf
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {FlatList} from '../util/Views'
|
import {List} from '../util/List'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
|
@ -119,7 +119,7 @@ export function MyLists({
|
||||||
[error, onRefresh, renderItem, pal],
|
[error, onRefresh, renderItem, pal],
|
||||||
)
|
)
|
||||||
|
|
||||||
const FlatListCom = inline ? RNFlatList : FlatList
|
const FlatListCom = inline ? RNFlatList : List
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
{items.length > 0 && (
|
{items.length > 0 && (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {MutableRefObject} from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
|
@ -8,7 +8,7 @@ import {
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {FlatList} from '../util/Views'
|
import {List, ListRef} from '../util/List'
|
||||||
import {ListCard} from './ListCard'
|
import {ListCard} from './ListCard'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
|
@ -16,11 +16,9 @@ import {Text} from '../util/text/Text'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists'
|
import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists'
|
||||||
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
|
||||||
import {useTheme} from '#/lib/ThemeContext'
|
import {useTheme} from '#/lib/ThemeContext'
|
||||||
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
|
@ -36,9 +34,7 @@ interface SectionRef {
|
||||||
|
|
||||||
interface ProfileListsProps {
|
interface ProfileListsProps {
|
||||||
did: string
|
did: string
|
||||||
scrollElRef: MutableRefObject<FlatList<any> | null>
|
scrollElRef: ListRef
|
||||||
onScroll?: OnScrollHandler
|
|
||||||
scrollEventThrottle?: number
|
|
||||||
headerOffset: number
|
headerOffset: number
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
|
@ -47,16 +43,7 @@ interface ProfileListsProps {
|
||||||
|
|
||||||
export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
|
export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
|
||||||
function ProfileListsImpl(
|
function ProfileListsImpl(
|
||||||
{
|
{did, scrollElRef, headerOffset, enabled, style, testID},
|
||||||
did,
|
|
||||||
scrollElRef,
|
|
||||||
onScroll,
|
|
||||||
scrollEventThrottle,
|
|
||||||
headerOffset,
|
|
||||||
enabled,
|
|
||||||
style,
|
|
||||||
testID,
|
|
||||||
},
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
@ -187,10 +174,9 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
|
||||||
[error, refetch, onPressRetryLoadMore, pal],
|
[error, refetch, onPressRetryLoadMore, pal],
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
<FlatList
|
<List
|
||||||
testID={testID ? `${testID}-flatlist` : undefined}
|
testID={testID ? `${testID}-flatlist` : undefined}
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={items}
|
data={items}
|
||||||
|
@ -209,8 +195,6 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
|
||||||
minHeight: Dimensions.get('window').height * 1.5,
|
minHeight: Dimensions.get('window').height * 1.5,
|
||||||
}}
|
}}
|
||||||
style={{paddingTop: headerOffset}}
|
style={{paddingTop: headerOffset}}
|
||||||
onScroll={onScroll != null ? scrollHandler : undefined}
|
|
||||||
scrollEventThrottle={scrollEventThrottle}
|
|
||||||
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
|
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
contentOffset={{x: 0, y: headerOffset * -1}}
|
contentOffset={{x: 0, y: headerOffset * -1}}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import React, {MutableRefObject} from 'react'
|
import React from 'react'
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
||||||
import {FeedItem} from './FeedItem'
|
import {FeedItem} from './FeedItem'
|
||||||
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
import {EmptyState} from '../util/EmptyState'
|
import {EmptyState} from '../util/EmptyState'
|
||||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
|
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
|
||||||
|
@ -15,6 +13,7 @@ import {useUnreadNotificationsApi} from '#/state/queries/notifications/unread'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useModerationOpts} from '#/state/queries/preferences'
|
import {useModerationOpts} from '#/state/queries/preferences'
|
||||||
|
import {List, ListRef} from '../util/List'
|
||||||
|
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
||||||
|
@ -23,12 +22,12 @@ const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||||
export function Feed({
|
export function Feed({
|
||||||
scrollElRef,
|
scrollElRef,
|
||||||
onPressTryAgain,
|
onPressTryAgain,
|
||||||
onScroll,
|
onScrolledDownChange,
|
||||||
ListHeaderComponent,
|
ListHeaderComponent,
|
||||||
}: {
|
}: {
|
||||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
scrollElRef?: ListRef
|
||||||
onPressTryAgain?: () => void
|
onPressTryAgain?: () => void
|
||||||
onScroll?: OnScrollHandler
|
onScrolledDownChange: (isScrolledDown: boolean) => void
|
||||||
ListHeaderComponent?: () => JSX.Element
|
ListHeaderComponent?: () => JSX.Element
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
@ -135,7 +134,6 @@ export function Feed({
|
||||||
[isFetchingNextPage],
|
[isFetchingNextPage],
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
{error && (
|
{error && (
|
||||||
|
@ -146,7 +144,7 @@ export function Feed({
|
||||||
/>
|
/>
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
)}
|
)}
|
||||||
<FlatList
|
<List
|
||||||
testID="notifsFeed"
|
testID="notifsFeed"
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={items}
|
data={items}
|
||||||
|
@ -164,8 +162,7 @@ export function Feed({
|
||||||
}
|
}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={0.6}
|
onEndReachedThreshold={0.6}
|
||||||
onScroll={scrollHandler}
|
onScrolledDownChange={onScrolledDownChange}
|
||||||
scrollEventThrottle={1}
|
|
||||||
contentContainerStyle={s.contentContainer}
|
contentContainerStyle={s.contentContainer}
|
||||||
// @ts-ignore our .web version only -prf
|
// @ts-ignore our .web version only -prf
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import {
|
import {
|
||||||
LayoutChangeEvent,
|
LayoutChangeEvent,
|
||||||
FlatList,
|
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
|
@ -20,17 +19,14 @@ import Animated, {
|
||||||
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||||
import {TabBar} from './TabBar'
|
import {TabBar} from './TabBar'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
|
import {ListMethods} from '../util/List'
|
||||||
const SCROLLED_DOWN_LIMIT = 200
|
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||||
|
|
||||||
export interface PagerWithHeaderChildParams {
|
export interface PagerWithHeaderChildParams {
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
isFocused: boolean
|
isFocused: boolean
|
||||||
onScroll: OnScrollHandler
|
scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
|
||||||
isScrolledDown: boolean
|
|
||||||
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PagerWithHeaderProps {
|
export interface PagerWithHeaderProps {
|
||||||
|
@ -62,7 +58,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||||
const [currentPage, setCurrentPage] = React.useState(0)
|
const [currentPage, setCurrentPage] = React.useState(0)
|
||||||
const [tabBarHeight, setTabBarHeight] = React.useState(0)
|
const [tabBarHeight, setTabBarHeight] = React.useState(0)
|
||||||
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
||||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
|
||||||
const scrollY = useSharedValue(0)
|
const scrollY = useSharedValue(0)
|
||||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||||
|
|
||||||
|
@ -155,15 +150,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||||
if (!throttleTimeout.current) {
|
if (!throttleTimeout.current) {
|
||||||
throttleTimeout.current = setTimeout(() => {
|
throttleTimeout.current = setTimeout(() => {
|
||||||
throttleTimeout.current = null
|
throttleTimeout.current = null
|
||||||
|
|
||||||
runOnUI(adjustScrollForOtherPages)()
|
runOnUI(adjustScrollForOtherPages)()
|
||||||
|
|
||||||
const nextIsScrolledDown = scrollY.value > SCROLLED_DOWN_LIMIT
|
|
||||||
if (isScrolledDown !== nextIsScrolledDown) {
|
|
||||||
React.startTransition(() => {
|
|
||||||
setIsScrolledDown(nextIsScrolledDown)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 80 /* Sync often enough you're unlikely to catch it unsynced */)
|
}, 80 /* Sync often enough you're unlikely to catch it unsynced */)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -211,7 +198,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||||
index={i}
|
index={i}
|
||||||
isReady={isReady}
|
isReady={isReady}
|
||||||
isFocused={i === currentPage}
|
isFocused={i === currentPage}
|
||||||
isScrolledDown={isScrolledDown}
|
|
||||||
onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
|
onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
|
||||||
registerRef={registerRef}
|
registerRef={registerRef}
|
||||||
renderTab={child}
|
renderTab={child}
|
||||||
|
@ -293,7 +279,6 @@ function PagerItem({
|
||||||
index,
|
index,
|
||||||
isReady,
|
isReady,
|
||||||
isFocused,
|
isFocused,
|
||||||
isScrolledDown,
|
|
||||||
onScrollWorklet,
|
onScrollWorklet,
|
||||||
renderTab,
|
renderTab,
|
||||||
registerRef,
|
registerRef,
|
||||||
|
@ -302,7 +287,6 @@ function PagerItem({
|
||||||
index: number
|
index: number
|
||||||
isFocused: boolean
|
isFocused: boolean
|
||||||
isReady: boolean
|
isReady: boolean
|
||||||
isScrolledDown: boolean
|
|
||||||
registerRef: (scrollRef: AnimatedRef<any> | null, atIndex: number) => void
|
registerRef: (scrollRef: AnimatedRef<any> | null, atIndex: number) => void
|
||||||
onScrollWorklet: (e: NativeScrollEvent) => void
|
onScrollWorklet: (e: NativeScrollEvent) => void
|
||||||
renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
|
renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
|
||||||
|
@ -316,24 +300,21 @@ function PagerItem({
|
||||||
}
|
}
|
||||||
}, [scrollElRef, registerRef, index])
|
}, [scrollElRef, registerRef, index])
|
||||||
|
|
||||||
const scrollHandler = React.useMemo(
|
|
||||||
() => ({onScroll: onScrollWorklet}),
|
|
||||||
[onScrollWorklet],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isReady || renderTab == null) {
|
if (!isReady || renderTab == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderTab({
|
return (
|
||||||
|
<ScrollProvider onScroll={onScrollWorklet}>
|
||||||
|
{renderTab({
|
||||||
headerHeight,
|
headerHeight,
|
||||||
isFocused,
|
isFocused,
|
||||||
isScrolledDown,
|
|
||||||
onScroll: scrollHandler,
|
|
||||||
scrollElRef: scrollElRef as React.MutableRefObject<
|
scrollElRef: scrollElRef as React.MutableRefObject<
|
||||||
FlatList<any> | ScrollView | null
|
ListMethods | ScrollView | null
|
||||||
>,
|
>,
|
||||||
})
|
})}
|
||||||
|
</ScrollProvider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, {useCallback, useMemo, useState} from 'react'
|
import React, {useCallback, useMemo, useState} from 'react'
|
||||||
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
||||||
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
|
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
|
import {List} from '../util/List'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
|
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -84,7 +85,7 @@ export function PostLikedBy({uri}: {uri: string}) {
|
||||||
// loaded
|
// loaded
|
||||||
// =
|
// =
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<List
|
||||||
data={likes}
|
data={likes}
|
||||||
keyExtractor={item => item.actor.did}
|
keyExtractor={item => item.actor.did}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, {useMemo, useCallback, useState} from 'react'
|
import React, {useMemo, useCallback, useState} from 'react'
|
||||||
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
||||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
|
import {List} from '../util/List'
|
||||||
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
|
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -85,7 +86,7 @@ export function PostRepostedBy({uri}: {uri: string}) {
|
||||||
// loaded
|
// loaded
|
||||||
// =
|
// =
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<List
|
||||||
data={repostedBy}
|
data={repostedBy}
|
||||||
keyExtractor={item => item.did}
|
keyExtractor={item => item.did}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
|
|
|
@ -8,7 +8,8 @@ import {
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {AppBskyFeedDefs} from '@atproto/api'
|
import {AppBskyFeedDefs} from '@atproto/api'
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
|
import {List, ListMethods} from '../util/List'
|
||||||
import {
|
import {
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
FontAwesomeIconStyle,
|
FontAwesomeIconStyle,
|
||||||
|
@ -140,7 +141,7 @@ function PostThreadLoaded({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isTablet, isDesktop} = useWebMediaQueries()
|
const {isTablet, isDesktop} = useWebMediaQueries()
|
||||||
const ref = useRef<FlatList>(null)
|
const ref = useRef<ListMethods>(null)
|
||||||
const highlightedPostRef = useRef<View | null>(null)
|
const highlightedPostRef = useRef<View | null>(null)
|
||||||
const needsScrollAdjustment = useRef<boolean>(
|
const needsScrollAdjustment = useRef<boolean>(
|
||||||
!isNative || // web always uses scroll adjustment
|
!isNative || // web always uses scroll adjustment
|
||||||
|
@ -335,7 +336,7 @@ function PostThreadLoaded({
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data={posts}
|
data={posts}
|
||||||
initialNumToRender={!isNative ? posts.length : undefined}
|
initialNumToRender={!isNative ? posts.length : undefined}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {memo, MutableRefObject} from 'react'
|
import React, {memo} from 'react'
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -10,15 +10,13 @@ import {
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {FlatList} from '../util/Views'
|
import {List, ListRef} from '../util/List'
|
||||||
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||||
import {FeedErrorMessage} from './FeedErrorMessage'
|
import {FeedErrorMessage} from './FeedErrorMessage'
|
||||||
import {FeedSlice} from './FeedSlice'
|
import {FeedSlice} from './FeedSlice'
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {
|
import {
|
||||||
|
@ -45,9 +43,8 @@ let Feed = ({
|
||||||
enabled,
|
enabled,
|
||||||
pollInterval,
|
pollInterval,
|
||||||
scrollElRef,
|
scrollElRef,
|
||||||
onScroll,
|
onScrolledDownChange,
|
||||||
onHasNew,
|
onHasNew,
|
||||||
scrollEventThrottle,
|
|
||||||
renderEmptyState,
|
renderEmptyState,
|
||||||
renderEndOfFeed,
|
renderEndOfFeed,
|
||||||
testID,
|
testID,
|
||||||
|
@ -62,10 +59,9 @@ let Feed = ({
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
pollInterval?: number
|
pollInterval?: number
|
||||||
scrollElRef?: MutableRefObject<FlatList<any> | null>
|
scrollElRef?: ListRef
|
||||||
onHasNew?: (v: boolean) => void
|
onHasNew?: (v: boolean) => void
|
||||||
onScroll?: OnScrollHandler
|
onScrolledDownChange?: (isScrolledDown: boolean) => void
|
||||||
scrollEventThrottle?: number
|
|
||||||
renderEmptyState: () => JSX.Element
|
renderEmptyState: () => JSX.Element
|
||||||
renderEndOfFeed?: () => JSX.Element
|
renderEndOfFeed?: () => JSX.Element
|
||||||
testID?: string
|
testID?: string
|
||||||
|
@ -270,10 +266,9 @@ let Feed = ({
|
||||||
)
|
)
|
||||||
}, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset])
|
}, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset])
|
||||||
|
|
||||||
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
<FlatList
|
<List
|
||||||
testID={testID ? `${testID}-flatlist` : undefined}
|
testID={testID ? `${testID}-flatlist` : undefined}
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={feedItems}
|
data={feedItems}
|
||||||
|
@ -294,8 +289,7 @@ let Feed = ({
|
||||||
minHeight: Dimensions.get('window').height * 1.5,
|
minHeight: Dimensions.get('window').height * 1.5,
|
||||||
}}
|
}}
|
||||||
style={{paddingTop: headerOffset}}
|
style={{paddingTop: headerOffset}}
|
||||||
onScroll={onScroll != null ? scrollHandler : undefined}
|
onScrolledDownChange={onScrolledDownChange}
|
||||||
scrollEventThrottle={scrollEventThrottle}
|
|
||||||
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
|
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={2} // number of posts left to trigger load more
|
onEndReachedThreshold={2} // number of posts left to trigger load more
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
||||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
|
import {List} from '../util/List'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -86,7 +87,7 @@ export function ProfileFollowers({name}: {name: string}) {
|
||||||
// loaded
|
// loaded
|
||||||
// =
|
// =
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<List
|
||||||
data={followers}
|
data={followers}
|
||||||
keyExtractor={item => item.did}
|
keyExtractor={item => item.did}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
|
||||||
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
|
||||||
import {CenteredView, FlatList} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
|
import {List} from '../util/List'
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
import {ProfileCardWithFollowBtn} from './ProfileCard'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -86,7 +87,7 @@ export function ProfileFollows({name}: {name: string}) {
|
||||||
// loaded
|
// loaded
|
||||||
// =
|
// =
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<List
|
||||||
data={follows}
|
data={follows}
|
||||||
keyExtractor={item => item.did}
|
keyExtractor={item => item.did}
|
||||||
refreshControl={
|
refreshControl={
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import React, {memo, startTransition} from 'react'
|
||||||
|
import {FlatListProps} from 'react-native'
|
||||||
|
import {FlatList_INTERNAL} from './Views'
|
||||||
|
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||||
|
import {runOnJS, useSharedValue} from 'react-native-reanimated'
|
||||||
|
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
||||||
|
|
||||||
|
export type ListMethods = FlatList_INTERNAL
|
||||||
|
export type ListProps<ItemT> = Omit<
|
||||||
|
FlatListProps<ItemT>,
|
||||||
|
'onScroll' // Use ScrollContext instead.
|
||||||
|
> & {
|
||||||
|
onScrolledDownChange?: (isScrolledDown: boolean) => void
|
||||||
|
}
|
||||||
|
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
|
||||||
|
|
||||||
|
const SCROLLED_DOWN_LIMIT = 200
|
||||||
|
|
||||||
|
function ListImpl<ItemT>(
|
||||||
|
{onScrolledDownChange, ...props}: ListProps<ItemT>,
|
||||||
|
ref: React.Ref<ListMethods>,
|
||||||
|
) {
|
||||||
|
const isScrolledDown = useSharedValue(false)
|
||||||
|
const contextScrollHandlers = useScrollHandlers()
|
||||||
|
|
||||||
|
function handleScrolledDownChange(didScrollDown: boolean) {
|
||||||
|
startTransition(() => {
|
||||||
|
onScrolledDownChange?.(didScrollDown)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollHandler = useAnimatedScrollHandler({
|
||||||
|
onBeginDrag(e, ctx) {
|
||||||
|
contextScrollHandlers.onBeginDrag?.(e, ctx)
|
||||||
|
},
|
||||||
|
onEndDrag(e, ctx) {
|
||||||
|
contextScrollHandlers.onEndDrag?.(e, ctx)
|
||||||
|
},
|
||||||
|
onScroll(e, ctx) {
|
||||||
|
contextScrollHandlers.onScroll?.(e, ctx)
|
||||||
|
|
||||||
|
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
|
||||||
|
if (isScrolledDown.value !== didScrollDown) {
|
||||||
|
isScrolledDown.value = didScrollDown
|
||||||
|
if (onScrolledDownChange != null) {
|
||||||
|
runOnJS(handleScrolledDownChange)(didScrollDown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList_INTERNAL
|
||||||
|
{...props}
|
||||||
|
onScroll={scrollHandler}
|
||||||
|
scrollEventThrottle={1}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
|
||||||
|
props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
|
||||||
|
) => React.ReactElement
|
|
@ -1,30 +1,18 @@
|
||||||
import {useState, useCallback, useMemo} from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
|
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||||
|
import {NativeScrollEvent} from 'react-native'
|
||||||
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {
|
import {useSharedValue, interpolate} from 'react-native-reanimated'
|
||||||
useSharedValue,
|
|
||||||
interpolate,
|
|
||||||
runOnJS,
|
|
||||||
ScrollHandlers,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
|
|
||||||
function clamp(num: number, min: number, max: number) {
|
function clamp(num: number, min: number, max: number) {
|
||||||
'worklet'
|
'worklet'
|
||||||
return Math.min(Math.max(num, min), max)
|
return Math.min(Math.max(num, min), max)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnScrollCb = (
|
export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||||
event: NativeSyntheticEvent<NativeScrollEvent>,
|
|
||||||
) => void
|
|
||||||
export type OnScrollHandler = ScrollHandlers<any>
|
|
||||||
export type ResetCb = () => void
|
|
||||||
|
|
||||||
export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
|
|
||||||
const {headerHeight} = useShellLayout()
|
const {headerHeight} = useShellLayout()
|
||||||
const [isScrolledDown, setIsScrolledDown] = useState(false)
|
|
||||||
const mode = useMinimalShellMode()
|
const mode = useMinimalShellMode()
|
||||||
const setMode = useSetMinimalShellMode()
|
const setMode = useSetMinimalShellMode()
|
||||||
const startDragOffset = useSharedValue<number | null>(null)
|
const startDragOffset = useSharedValue<number | null>(null)
|
||||||
|
@ -58,13 +46,6 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
|
||||||
const onScroll = useCallback(
|
const onScroll = useCallback(
|
||||||
(e: NativeScrollEvent) => {
|
(e: NativeScrollEvent) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
// Keep track of whether we want to show "scroll to top".
|
|
||||||
if (!isScrolledDown && e.contentOffset.y > s.window.height) {
|
|
||||||
runOnJS(setIsScrolledDown)(true)
|
|
||||||
} else if (isScrolledDown && e.contentOffset.y < s.window.height) {
|
|
||||||
runOnJS(setIsScrolledDown)(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startDragOffset.value === null || startMode.value === null) {
|
if (startDragOffset.value === null || startMode.value === null) {
|
||||||
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
|
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
|
||||||
// If we're close enough to the top, always show the shell.
|
// If we're close enough to the top, always show the shell.
|
||||||
|
@ -102,24 +83,15 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
|
||||||
startMode.value = mode.value
|
startMode.value = mode.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[headerHeight, mode, setMode, isScrolledDown, startDragOffset, startMode],
|
[headerHeight, mode, setMode, startDragOffset, startMode],
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollHandler: ScrollHandlers<any> = useMemo(
|
return (
|
||||||
() => ({
|
<ScrollProvider
|
||||||
onBeginDrag,
|
onBeginDrag={onBeginDrag}
|
||||||
onEndDrag,
|
onEndDrag={onEndDrag}
|
||||||
onScroll,
|
onScroll={onScroll}>
|
||||||
}),
|
{children}
|
||||||
[onBeginDrag, onEndDrag, onScroll],
|
</ScrollProvider>
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
|
||||||
scrollHandler,
|
|
||||||
isScrolledDown,
|
|
||||||
useCallback(() => {
|
|
||||||
setIsScrolledDown(false)
|
|
||||||
setMode(false)
|
|
||||||
}, [setMode]),
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {
|
import {
|
||||||
|
NativeSyntheticEvent,
|
||||||
|
NativeScrollEvent,
|
||||||
Pressable,
|
Pressable,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {FlatList} from './Views'
|
import {FlatList_INTERNAL} from './Views'
|
||||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
||||||
import {Text} from './text/Text'
|
import {Text} from './text/Text'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
@ -38,7 +39,7 @@ export const ViewSelector = React.forwardRef<
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined
|
||||||
onSelectView?: (viewIndex: number) => void
|
onSelectView?: (viewIndex: number) => void
|
||||||
onScroll?: OnScrollCb
|
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
|
||||||
onRefresh?: () => void
|
onRefresh?: () => void
|
||||||
onEndReached?: (info: {distanceFromEnd: number}) => void
|
onEndReached?: (info: {distanceFromEnd: number}) => void
|
||||||
}
|
}
|
||||||
|
@ -59,7 +60,7 @@ export const ViewSelector = React.forwardRef<
|
||||||
) {
|
) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const [selectedIndex, setSelectedIndex] = useState<number>(0)
|
const [selectedIndex, setSelectedIndex] = useState<number>(0)
|
||||||
const flatListRef = React.useRef<FlatList>(null)
|
const flatListRef = React.useRef<FlatList_INTERNAL>(null)
|
||||||
|
|
||||||
// events
|
// events
|
||||||
// =
|
// =
|
||||||
|
@ -110,7 +111,7 @@ export const ViewSelector = React.forwardRef<
|
||||||
[items],
|
[items],
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList_INTERNAL
|
||||||
ref={flatListRef}
|
ref={flatListRef}
|
||||||
data={data}
|
data={data}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ViewProps} from 'react-native'
|
import {ViewProps} from 'react-native'
|
||||||
export {FlatList, ScrollView} from 'react-native'
|
export {FlatList as FlatList_INTERNAL, ScrollView} from 'react-native'
|
||||||
export function CenteredView({
|
export function CenteredView({
|
||||||
style,
|
style,
|
||||||
sideBorders,
|
sideBorders,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import Animated from 'react-native-reanimated'
|
import Animated from 'react-native-reanimated'
|
||||||
|
|
||||||
export const FlatList = Animated.FlatList
|
export const FlatList_INTERNAL = Animated.FlatList
|
||||||
export const ScrollView = Animated.ScrollView
|
export const ScrollView = Animated.ScrollView
|
||||||
export function CenteredView(props) {
|
export function CenteredView(props) {
|
||||||
return <View {...props} />
|
return <View {...props} />
|
||||||
|
|
|
@ -49,7 +49,7 @@ export function CenteredView({
|
||||||
return <View style={style} {...props} />
|
return <View style={style} {...props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FlatList = React.forwardRef(function FlatListImpl<ItemT>(
|
export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
|
||||||
{
|
{
|
||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
style,
|
style,
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {FlatList} from 'view/com/util/Views'
|
import {List} from 'view/com/util/List'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
||||||
import {Trans, msg} from '@lingui/macro'
|
import {Trans, msg} from '@lingui/macro'
|
||||||
|
@ -481,7 +481,7 @@ export function FeedsScreen(_props: Props) {
|
||||||
|
|
||||||
{preferences ? <View /> : <ActivityIndicator />}
|
{preferences ? <View /> : <ActivityIndicator />}
|
||||||
|
|
||||||
<FlatList
|
<List
|
||||||
style={[!isTabletOrDesktop && s.flex1, styles.list]}
|
style={[!isTabletOrDesktop && s.flex1, styles.list]}
|
||||||
data={items}
|
data={items}
|
||||||
keyExtractor={item => item.key}
|
keyExtractor={item => item.key}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FlatList, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
|
@ -9,8 +9,9 @@ import {
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
import {ViewHeader} from '../com/util/ViewHeader'
|
||||||
import {Feed} from '../com/notifications/Feed'
|
import {Feed} from '../com/notifications/Feed'
|
||||||
import {TextLink} from 'view/com/util/Link'
|
import {TextLink} from 'view/com/util/Link'
|
||||||
|
import {ListMethods} from 'view/com/util/List'
|
||||||
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
import {MainScrollProvider} from '../com/util/MainScrollProvider'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
|
@ -35,8 +36,8 @@ type Props = NativeStackScreenProps<
|
||||||
export function NotificationsScreen({}: Props) {
|
export function NotificationsScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
const scrollElRef = React.useRef<FlatList>(null)
|
const scrollElRef = React.useRef<ListMethods>(null)
|
||||||
const checkLatestRef = React.useRef<() => void | null>()
|
const checkLatestRef = React.useRef<() => void | null>()
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
@ -50,8 +51,8 @@ export function NotificationsScreen({}: Props) {
|
||||||
// =
|
// =
|
||||||
const scrollToTop = React.useCallback(() => {
|
const scrollToTop = React.useCallback(() => {
|
||||||
scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
|
scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
|
||||||
resetMainScroll()
|
setMinimalShellMode(false)
|
||||||
}, [scrollElRef, resetMainScroll])
|
}, [scrollElRef, setMinimalShellMode])
|
||||||
|
|
||||||
const onPressLoadLatest = React.useCallback(() => {
|
const onPressLoadLatest = React.useCallback(() => {
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
|
@ -130,11 +131,13 @@ export function NotificationsScreen({}: Props) {
|
||||||
return (
|
return (
|
||||||
<View testID="notificationsScreen" style={s.hContentRegion}>
|
<View testID="notificationsScreen" style={s.hContentRegion}>
|
||||||
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
|
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
|
||||||
|
<MainScrollProvider>
|
||||||
<Feed
|
<Feed
|
||||||
onScroll={onMainScroll}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
/>
|
/>
|
||||||
|
</MainScrollProvider>
|
||||||
{(isScrolledDown || hasNew) && (
|
{(isScrolledDown || hasNew) && (
|
||||||
<LoadLatestBtn
|
<LoadLatestBtn
|
||||||
onPress={onPressLoadLatest}
|
onPress={onPressLoadLatest}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||||
import {CenteredView, FlatList} from '../com/util/Views'
|
import {CenteredView} from '../com/util/Views'
|
||||||
|
import {ListRef} from '../com/util/List'
|
||||||
import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
|
import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
|
||||||
import {Feed} from 'view/com/posts/Feed'
|
import {Feed} from 'view/com/posts/Feed'
|
||||||
import {ProfileLists} from '../com/lists/ProfileLists'
|
import {ProfileLists} from '../com/lists/ProfileLists'
|
||||||
|
@ -20,7 +21,6 @@ import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {ComposeIcon2} from 'lib/icons'
|
import {ComposeIcon2} from 'lib/icons'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {combinedDisplayName} from 'lib/strings/display-names'
|
import {combinedDisplayName} from 'lib/strings/display-names'
|
||||||
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
|
|
||||||
import {FeedDescriptor} from '#/state/queries/post-feed'
|
import {FeedDescriptor} from '#/state/queries/post-feed'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
|
@ -277,103 +277,67 @@ function ProfileScreenLoaded({
|
||||||
onPageSelected={onPageSelected}
|
onPageSelected={onPageSelected}
|
||||||
onCurrentPageSelected={onCurrentPageSelected}
|
onCurrentPageSelected={onCurrentPageSelected}
|
||||||
renderHeader={renderHeader}>
|
renderHeader={renderHeader}>
|
||||||
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
{({headerHeight, isFocused, scrollElRef}) => (
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={postsSectionRef}
|
ref={postsSectionRef}
|
||||||
feed={`author|${profile.did}|posts_and_author_threads`}
|
feed={`author|${profile.did}|posts_and_author_threads`}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
isScrolledDown={isScrolledDown}
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef={
|
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
ignoreFilterFor={profile.did}
|
ignoreFilterFor={profile.did}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showRepliesTab
|
{showRepliesTab
|
||||||
? ({
|
? ({headerHeight, isFocused, scrollElRef}) => (
|
||||||
onScroll,
|
|
||||||
headerHeight,
|
|
||||||
isFocused,
|
|
||||||
isScrolledDown,
|
|
||||||
scrollElRef,
|
|
||||||
}) => (
|
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={repliesSectionRef}
|
ref={repliesSectionRef}
|
||||||
feed={`author|${profile.did}|posts_with_replies`}
|
feed={`author|${profile.did}|posts_with_replies`}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
isScrolledDown={isScrolledDown}
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef={
|
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
ignoreFilterFor={profile.did}
|
ignoreFilterFor={profile.did}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
{({headerHeight, isFocused, scrollElRef}) => (
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={mediaSectionRef}
|
ref={mediaSectionRef}
|
||||||
feed={`author|${profile.did}|posts_with_media`}
|
feed={`author|${profile.did}|posts_with_media`}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
isScrolledDown={isScrolledDown}
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef={
|
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
ignoreFilterFor={profile.did}
|
ignoreFilterFor={profile.did}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showLikesTab
|
{showLikesTab
|
||||||
? ({
|
? ({headerHeight, isFocused, scrollElRef}) => (
|
||||||
onScroll,
|
|
||||||
headerHeight,
|
|
||||||
isFocused,
|
|
||||||
isScrolledDown,
|
|
||||||
scrollElRef,
|
|
||||||
}) => (
|
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={likesSectionRef}
|
ref={likesSectionRef}
|
||||||
feed={`likes|${profile.did}`}
|
feed={`likes|${profile.did}`}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
isScrolledDown={isScrolledDown}
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef={
|
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
ignoreFilterFor={profile.did}
|
ignoreFilterFor={profile.did}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
{showFeedsTab
|
{showFeedsTab
|
||||||
? ({onScroll, headerHeight, isFocused, scrollElRef}) => (
|
? ({headerHeight, isFocused, scrollElRef}) => (
|
||||||
<ProfileFeedgens
|
<ProfileFeedgens
|
||||||
ref={feedsSectionRef}
|
ref={feedsSectionRef}
|
||||||
did={profile.did}
|
did={profile.did}
|
||||||
scrollElRef={
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
onScroll={onScroll}
|
|
||||||
scrollEventThrottle={1}
|
|
||||||
headerOffset={headerHeight}
|
headerOffset={headerHeight}
|
||||||
enabled={isFocused}
|
enabled={isFocused}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
{showListsTab
|
{showListsTab
|
||||||
? ({onScroll, headerHeight, isFocused, scrollElRef}) => (
|
? ({headerHeight, isFocused, scrollElRef}) => (
|
||||||
<ProfileLists
|
<ProfileLists
|
||||||
ref={listsSectionRef}
|
ref={listsSectionRef}
|
||||||
did={profile.did}
|
did={profile.did}
|
||||||
scrollElRef={
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
onScroll={onScroll}
|
|
||||||
scrollEventThrottle={1}
|
|
||||||
headerOffset={headerHeight}
|
headerOffset={headerHeight}
|
||||||
enabled={isFocused}
|
enabled={isFocused}
|
||||||
/>
|
/>
|
||||||
|
@ -396,28 +360,19 @@ function ProfileScreenLoaded({
|
||||||
|
|
||||||
interface FeedSectionProps {
|
interface FeedSectionProps {
|
||||||
feed: FeedDescriptor
|
feed: FeedDescriptor
|
||||||
onScroll: OnScrollHandler
|
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
isFocused: boolean
|
isFocused: boolean
|
||||||
isScrolledDown: boolean
|
scrollElRef: ListRef
|
||||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
|
||||||
ignoreFilterFor?: string
|
ignoreFilterFor?: string
|
||||||
}
|
}
|
||||||
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
function FeedSectionImpl(
|
function FeedSectionImpl(
|
||||||
{
|
{feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor},
|
||||||
feed,
|
|
||||||
onScroll,
|
|
||||||
headerHeight,
|
|
||||||
isFocused,
|
|
||||||
isScrolledDown,
|
|
||||||
scrollElRef,
|
|
||||||
ignoreFilterFor,
|
|
||||||
},
|
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const [hasNew, setHasNew] = React.useState(false)
|
const [hasNew, setHasNew] = React.useState(false)
|
||||||
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
|
|
||||||
const onScrollToTop = React.useCallback(() => {
|
const onScrollToTop = React.useCallback(() => {
|
||||||
scrollElRef.current?.scrollToOffset({
|
scrollElRef.current?.scrollToOffset({
|
||||||
|
@ -443,8 +398,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
feed={feed}
|
feed={feed}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
onHasNew={setHasNew}
|
onHasNew={setHasNew}
|
||||||
onScroll={onScroll}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
scrollEventThrottle={1}
|
|
||||||
renderEmptyState={renderPostsEmpty}
|
renderEmptyState={renderPostsEmpty}
|
||||||
headerOffset={headerHeight}
|
headerOffset={headerHeight}
|
||||||
renderEndOfFeed={ProfileEndOfFeed}
|
renderEndOfFeed={ProfileEndOfFeed}
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import React, {useMemo, useCallback} from 'react'
|
import React, {useMemo, useCallback} from 'react'
|
||||||
import {
|
import {Dimensions, StyleSheet, View, ActivityIndicator} from 'react-native'
|
||||||
Dimensions,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
ActivityIndicator,
|
|
||||||
FlatList,
|
|
||||||
} from 'react-native'
|
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
@ -20,6 +14,7 @@ import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
||||||
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
|
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
|
||||||
import {Feed} from 'view/com/posts/Feed'
|
import {Feed} from 'view/com/posts/Feed'
|
||||||
import {TextLink} from 'view/com/util/Link'
|
import {TextLink} from 'view/com/util/Link'
|
||||||
|
import {ListRef} from 'view/com/util/List'
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
import {Button} from 'view/com/util/forms/Button'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {RichText} from 'view/com/util/text/RichText'
|
import {RichText} from 'view/com/util/text/RichText'
|
||||||
|
@ -29,12 +24,13 @@ import {EmptyState} from 'view/com/util/EmptyState'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
import * as Toast from 'view/com/util/Toast'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {shareUrl} from 'lib/sharing'
|
import {shareUrl} from 'lib/sharing'
|
||||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
import {toShareUrl} from 'lib/strings/url-helpers'
|
||||||
import {Haptics} from 'lib/haptics'
|
import {Haptics} from 'lib/haptics'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
|
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
|
||||||
|
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||||
|
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
||||||
import {makeCustomFeedLink} from 'lib/routes/links'
|
import {makeCustomFeedLink} from 'lib/routes/links'
|
||||||
import {pluralize} from 'lib/strings/helpers'
|
import {pluralize} from 'lib/strings/helpers'
|
||||||
import {CenteredView, ScrollView} from 'view/com/util/Views'
|
import {CenteredView, ScrollView} from 'view/com/util/Views'
|
||||||
|
@ -46,7 +42,6 @@ import {logger} from '#/logger'
|
||||||
import {Trans, msg} from '@lingui/macro'
|
import {Trans, msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
|
||||||
import {
|
import {
|
||||||
useFeedSourceInfoQuery,
|
useFeedSourceInfoQuery,
|
||||||
FeedSourceFeedInfo,
|
FeedSourceFeedInfo,
|
||||||
|
@ -403,17 +398,13 @@ export function ProfileFeedScreenInner({
|
||||||
isHeaderReady={true}
|
isHeaderReady={true}
|
||||||
renderHeader={renderHeader}
|
renderHeader={renderHeader}
|
||||||
onCurrentPageSelected={onCurrentPageSelected}>
|
onCurrentPageSelected={onCurrentPageSelected}>
|
||||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef, isFocused}) =>
|
{({headerHeight, scrollElRef, isFocused}) =>
|
||||||
isPublicResponse?.isPublic ? (
|
isPublicResponse?.isPublic ? (
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={feedSectionRef}
|
ref={feedSectionRef}
|
||||||
feed={`feedgen|${feedInfo.uri}`}
|
feed={`feedgen|${feedInfo.uri}`}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isScrolledDown={isScrolledDown}
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef={
|
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -422,13 +413,12 @@ export function ProfileFeedScreenInner({
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{({onScroll, headerHeight, scrollElRef}) => (
|
{({headerHeight, scrollElRef}) => (
|
||||||
<AboutSection
|
<AboutSection
|
||||||
feedOwnerDid={feedInfo.creatorDid}
|
feedOwnerDid={feedInfo.creatorDid}
|
||||||
feedRkey={feedInfo.route.params.rkey}
|
feedRkey={feedInfo.route.params.rkey}
|
||||||
feedInfo={feedInfo}
|
feedInfo={feedInfo}
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
onScroll={onScroll}
|
|
||||||
scrollElRef={
|
scrollElRef={
|
||||||
scrollElRef as React.MutableRefObject<ScrollView | null>
|
scrollElRef as React.MutableRefObject<ScrollView | null>
|
||||||
}
|
}
|
||||||
|
@ -497,18 +487,14 @@ function NonPublicFeedMessage({rawError}: {rawError?: Error}) {
|
||||||
|
|
||||||
interface FeedSectionProps {
|
interface FeedSectionProps {
|
||||||
feed: FeedDescriptor
|
feed: FeedDescriptor
|
||||||
onScroll: OnScrollHandler
|
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
isScrolledDown: boolean
|
scrollElRef: ListRef
|
||||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
|
||||||
isFocused: boolean
|
isFocused: boolean
|
||||||
}
|
}
|
||||||
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
function FeedSectionImpl(
|
function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) {
|
||||||
{feed, onScroll, headerHeight, isScrolledDown, scrollElRef, isFocused},
|
|
||||||
ref,
|
|
||||||
) {
|
|
||||||
const [hasNew, setHasNew] = React.useState(false)
|
const [hasNew, setHasNew] = React.useState(false)
|
||||||
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const onScrollToTop = useCallback(() => {
|
const onScrollToTop = useCallback(() => {
|
||||||
|
@ -536,8 +522,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
pollInterval={30e3}
|
pollInterval={30e3}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
onHasNew={setHasNew}
|
onHasNew={setHasNew}
|
||||||
onScroll={onScroll}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
scrollEventThrottle={5}
|
|
||||||
renderEmptyState={renderPostsEmpty}
|
renderEmptyState={renderPostsEmpty}
|
||||||
headerOffset={headerHeight}
|
headerOffset={headerHeight}
|
||||||
/>
|
/>
|
||||||
|
@ -558,7 +543,6 @@ function AboutSection({
|
||||||
feedRkey,
|
feedRkey,
|
||||||
feedInfo,
|
feedInfo,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
onScroll,
|
|
||||||
scrollElRef,
|
scrollElRef,
|
||||||
isOwner,
|
isOwner,
|
||||||
}: {
|
}: {
|
||||||
|
@ -566,13 +550,13 @@ function AboutSection({
|
||||||
feedRkey: string
|
feedRkey: string
|
||||||
feedInfo: FeedSourceFeedInfo
|
feedInfo: FeedSourceFeedInfo
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
onScroll: OnScrollHandler
|
|
||||||
scrollElRef: React.MutableRefObject<ScrollView | null>
|
scrollElRef: React.MutableRefObject<ScrollView | null>
|
||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const scrollHandler = useAnimatedScrollHandler(onScroll)
|
const scrollHandlers = useScrollHandlers()
|
||||||
|
const onScroll = useAnimatedScrollHandler(scrollHandlers)
|
||||||
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
|
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
@ -608,12 +592,12 @@ function AboutSection({
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
|
onScroll={onScroll}
|
||||||
scrollEventThrottle={1}
|
scrollEventThrottle={1}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
paddingTop: headerHeight,
|
paddingTop: headerHeight,
|
||||||
minHeight: Dimensions.get('window').height * 1.5,
|
minHeight: Dimensions.get('window').height * 1.5,
|
||||||
}}
|
}}>
|
||||||
onScroll={scrollHandler}>
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import React, {useCallback, useMemo} from 'react'
|
import React, {useCallback, useMemo} from 'react'
|
||||||
import {
|
import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native'
|
||||||
ActivityIndicator,
|
|
||||||
FlatList,
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
@ -22,6 +16,7 @@ import {EmptyState} from 'view/com/util/EmptyState'
|
||||||
import {RichText} from 'view/com/util/text/RichText'
|
import {RichText} from 'view/com/util/text/RichText'
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
import {Button} from 'view/com/util/forms/Button'
|
||||||
import {TextLink} from 'view/com/util/Link'
|
import {TextLink} from 'view/com/util/Link'
|
||||||
|
import {ListRef} from 'view/com/util/List'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
import * as Toast from 'view/com/util/Toast'
|
||||||
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {FAB} from 'view/com/util/fab/FAB'
|
import {FAB} from 'view/com/util/fab/FAB'
|
||||||
|
@ -31,7 +26,6 @@ import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||||
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {toShareUrl} from 'lib/strings/url-helpers'
|
import {toShareUrl} from 'lib/strings/url-helpers'
|
||||||
import {shareUrl} from 'lib/sharing'
|
import {shareUrl} from 'lib/sharing'
|
||||||
|
@ -165,36 +159,22 @@ function ProfileListScreenLoaded({
|
||||||
isHeaderReady={true}
|
isHeaderReady={true}
|
||||||
renderHeader={renderHeader}
|
renderHeader={renderHeader}
|
||||||
onCurrentPageSelected={onCurrentPageSelected}>
|
onCurrentPageSelected={onCurrentPageSelected}>
|
||||||
{({
|
{({headerHeight, scrollElRef, isFocused}) => (
|
||||||
onScroll,
|
|
||||||
headerHeight,
|
|
||||||
isScrolledDown,
|
|
||||||
scrollElRef,
|
|
||||||
isFocused,
|
|
||||||
}) => (
|
|
||||||
<FeedSection
|
<FeedSection
|
||||||
ref={feedSectionRef}
|
ref={feedSectionRef}
|
||||||
feed={`list|${uri}`}
|
feed={`list|${uri}`}
|
||||||
scrollElRef={
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isScrolledDown={isScrolledDown}
|
|
||||||
isFocused={isFocused}
|
isFocused={isFocused}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
|
{({headerHeight, scrollElRef}) => (
|
||||||
<AboutSection
|
<AboutSection
|
||||||
ref={aboutSectionRef}
|
ref={aboutSectionRef}
|
||||||
scrollElRef={
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
list={list}
|
list={list}
|
||||||
onPressAddUser={onPressAddUser}
|
onPressAddUser={onPressAddUser}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isScrolledDown={isScrolledDown}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</PagerWithHeader>
|
</PagerWithHeader>
|
||||||
|
@ -221,16 +201,12 @@ function ProfileListScreenLoaded({
|
||||||
items={SECTION_TITLES_MOD}
|
items={SECTION_TITLES_MOD}
|
||||||
isHeaderReady={true}
|
isHeaderReady={true}
|
||||||
renderHeader={renderHeader}>
|
renderHeader={renderHeader}>
|
||||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
|
{({headerHeight, scrollElRef}) => (
|
||||||
<AboutSection
|
<AboutSection
|
||||||
list={list}
|
list={list}
|
||||||
scrollElRef={
|
scrollElRef={scrollElRef as ListRef}
|
||||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
|
||||||
onPressAddUser={onPressAddUser}
|
onPressAddUser={onPressAddUser}
|
||||||
onScroll={onScroll}
|
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
isScrolledDown={isScrolledDown}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</PagerWithHeader>
|
</PagerWithHeader>
|
||||||
|
@ -615,19 +591,15 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
||||||
|
|
||||||
interface FeedSectionProps {
|
interface FeedSectionProps {
|
||||||
feed: FeedDescriptor
|
feed: FeedDescriptor
|
||||||
onScroll: OnScrollHandler
|
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
isScrolledDown: boolean
|
scrollElRef: ListRef
|
||||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
|
||||||
isFocused: boolean
|
isFocused: boolean
|
||||||
}
|
}
|
||||||
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
function FeedSectionImpl(
|
function FeedSectionImpl({feed, scrollElRef, headerHeight, isFocused}, ref) {
|
||||||
{feed, scrollElRef, onScroll, headerHeight, isScrolledDown, isFocused},
|
|
||||||
ref,
|
|
||||||
) {
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const [hasNew, setHasNew] = React.useState(false)
|
const [hasNew, setHasNew] = React.useState(false)
|
||||||
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
|
|
||||||
const onScrollToTop = useCallback(() => {
|
const onScrollToTop = useCallback(() => {
|
||||||
scrollElRef.current?.scrollToOffset({
|
scrollElRef.current?.scrollToOffset({
|
||||||
|
@ -654,8 +626,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
pollInterval={30e3}
|
pollInterval={30e3}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
onHasNew={setHasNew}
|
onHasNew={setHasNew}
|
||||||
onScroll={onScroll}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
scrollEventThrottle={1}
|
|
||||||
renderEmptyState={renderPostsEmpty}
|
renderEmptyState={renderPostsEmpty}
|
||||||
headerOffset={headerHeight}
|
headerOffset={headerHeight}
|
||||||
/>
|
/>
|
||||||
|
@ -674,20 +645,19 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||||
interface AboutSectionProps {
|
interface AboutSectionProps {
|
||||||
list: AppBskyGraphDefs.ListView
|
list: AppBskyGraphDefs.ListView
|
||||||
onPressAddUser: () => void
|
onPressAddUser: () => void
|
||||||
onScroll: OnScrollHandler
|
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
isScrolledDown: boolean
|
scrollElRef: ListRef
|
||||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
|
||||||
}
|
}
|
||||||
const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
|
const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
|
||||||
function AboutSectionImpl(
|
function AboutSectionImpl(
|
||||||
{list, onPressAddUser, onScroll, headerHeight, isScrolledDown, scrollElRef},
|
{list, onPressAddUser, headerHeight, scrollElRef},
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
|
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
|
||||||
const isOwner = list.creator.did === currentAccount?.did
|
const isOwner = list.creator.did === currentAccount?.did
|
||||||
|
|
||||||
|
@ -817,8 +787,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
|
||||||
renderHeader={renderHeader}
|
renderHeader={renderHeader}
|
||||||
renderEmptyState={renderEmptyState}
|
renderEmptyState={renderEmptyState}
|
||||||
headerOffset={headerHeight}
|
headerOffset={headerHeight}
|
||||||
onScroll={onScroll}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
scrollEventThrottle={1}
|
|
||||||
/>
|
/>
|
||||||
{isScrolledDown && (
|
{isScrolledDown && (
|
||||||
<LoadLatestBtn
|
<LoadLatestBtn
|
||||||
|
|
|
@ -8,7 +8,8 @@ import {
|
||||||
Pressable,
|
Pressable,
|
||||||
Platform,
|
Platform,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {FlatList, ScrollView, CenteredView} from '#/view/com/util/Views'
|
import {ScrollView, CenteredView} from '#/view/com/util/Views'
|
||||||
|
import {List} from '#/view/com/util/List'
|
||||||
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
|
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
@ -155,7 +156,7 @@ function SearchScreenSuggestedFollows() {
|
||||||
}, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
|
}, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
|
||||||
|
|
||||||
return suggestions.length ? (
|
return suggestions.length ? (
|
||||||
<FlatList
|
<List
|
||||||
data={suggestions}
|
data={suggestions}
|
||||||
renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />}
|
renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />}
|
||||||
keyExtractor={item => item.did}
|
keyExtractor={item => item.did}
|
||||||
|
@ -243,7 +244,7 @@ function SearchScreenPostResults({query}: {query: string}) {
|
||||||
{isFetched ? (
|
{isFetched ? (
|
||||||
<>
|
<>
|
||||||
{posts.length ? (
|
{posts.length ? (
|
||||||
<FlatList
|
<List
|
||||||
data={items}
|
data={items}
|
||||||
renderItem={({item}) => {
|
renderItem={({item}) => {
|
||||||
if (item.type === 'post') {
|
if (item.type === 'post') {
|
||||||
|
@ -284,7 +285,7 @@ function SearchScreenUserResults({query}: {query: string}) {
|
||||||
return isFetched && results ? (
|
return isFetched && results ? (
|
||||||
<>
|
<>
|
||||||
{results.length ? (
|
{results.length ? (
|
||||||
<FlatList
|
<List
|
||||||
data={results}
|
data={results}
|
||||||
renderItem={({item}) => (
|
renderItem={({item}) => (
|
||||||
<ProfileCardWithFollowBtn profile={item} noBg />
|
<ProfileCardWithFollowBtn profile={item} noBg />
|
||||||
|
|
Loading…
Reference in New Issue