abstract into a nice hook
This commit is contained in:
@@ -7,11 +7,6 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {
|
||||
type ScrollHandler,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {type AppBskyGraphDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -21,20 +16,19 @@ import {useNavigation} from '@react-navigation/native'
|
||||
import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack'
|
||||
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
|
||||
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {type NavigationProp} from '#/lib/routes/types'
|
||||
import {ScrollProvider, useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
|
||||
import {logger} from '#/logger'
|
||||
import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {
|
||||
EmptyState,
|
||||
type EmptyStateButtonProps,
|
||||
} from '#/view/com/util/EmptyState'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {atoms as a, ios, useTheme} from '#/alf'
|
||||
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {useProfileScrollbarAdjustment} from '#/screens/Profile/useProfileScrollbarAdjustment'
|
||||
import {atoms as a, ios, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||
@@ -88,39 +82,8 @@ export function ProfileStarterPacks({
|
||||
const bottomBarOffset = useBottomBarOffset(100)
|
||||
const {height} = useWindowDimensions()
|
||||
const [isPTRing, setIsPTRing] = useState(false)
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const onScrollWorklet = useCallback<ScrollHandler<any>>(
|
||||
(e, ctx) => {
|
||||
'worklet'
|
||||
onScrollFromContext?.(e, ctx)
|
||||
scrollY.set(e.contentOffset.y)
|
||||
},
|
||||
[onScrollFromContext, scrollY],
|
||||
)
|
||||
|
||||
const {footerHeight} = useShellLayout()
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
if (IS_IOS) {
|
||||
return {
|
||||
scrollIndicatorInsets: {
|
||||
top: Math.max(headerOffset - scrollY.get(), collapsedHeaderHeight),
|
||||
right: 1,
|
||||
left: 0,
|
||||
bottom: footerHeight.get(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
const {
|
||||
data,
|
||||
refetch,
|
||||
@@ -129,7 +92,6 @@ export function ProfileStarterPacks({
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
} = useActorStarterPacksQuery({did, enabled})
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
|
||||
const items = data?.pages.flatMap(page => page.starterPacks)
|
||||
const {_} = useLingui()
|
||||
@@ -191,58 +153,54 @@ export function ProfileStarterPacks({
|
||||
<View
|
||||
style={[
|
||||
a.p_lg,
|
||||
(isTabletOrDesktop || index !== 0) && a.border_t,
|
||||
(gtMobile || index !== 0) && a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<StarterPackCard starterPack={item} />
|
||||
</View>
|
||||
)
|
||||
},
|
||||
[isTabletOrDesktop, t.atoms.border_contrast_low],
|
||||
[gtMobile, t.atoms.border_contrast_low],
|
||||
)
|
||||
|
||||
const list = (
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={items}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={ios(0)}
|
||||
contentContainerStyle={{
|
||||
minHeight: height + headerOffset,
|
||||
paddingBottom: bottomBarOffset,
|
||||
}}
|
||||
removeClippedSubviews={true}
|
||||
desktopFixedHeight
|
||||
onEndReached={onEndReached}
|
||||
onRefresh={onRefresh}
|
||||
ListEmptyComponent={
|
||||
data ? (isMe ? EmptyComponent : undefined) : FeedLoadingPlaceholder
|
||||
}
|
||||
ListFooterComponent={
|
||||
!!data && items?.length !== 0 && isMe ? CreateAnother : undefined
|
||||
}
|
||||
animatedProps={IS_IOS ? animatedProps : undefined}
|
||||
/>
|
||||
)
|
||||
const {scrollHandlers, animatedProps} = useProfileScrollbarAdjustment({
|
||||
headerOffset,
|
||||
collapsedHeaderHeight,
|
||||
})
|
||||
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{IS_IOS ? (
|
||||
<ScrollProvider
|
||||
onScroll={onScrollWorklet}
|
||||
onBeginDrag={onBeginDragFromContext}
|
||||
onEndDrag={onEndDragFromContext}
|
||||
onMomentumBegin={onMomentumEndFromContext}
|
||||
onMomentumEnd={onMomentumEndFromContext}>
|
||||
{list}
|
||||
</ScrollProvider>
|
||||
) : (
|
||||
list
|
||||
)}
|
||||
<ScrollProvider {...scrollHandlers}>
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={items}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
refreshing={isPTRing}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={ios(0)}
|
||||
contentContainerStyle={{
|
||||
minHeight: height + headerOffset,
|
||||
paddingBottom: bottomBarOffset,
|
||||
}}
|
||||
removeClippedSubviews={true}
|
||||
desktopFixedHeight
|
||||
onEndReached={onEndReached}
|
||||
onRefresh={onRefresh}
|
||||
ListEmptyComponent={
|
||||
data
|
||||
? isMe
|
||||
? EmptyComponent
|
||||
: undefined
|
||||
: FeedFeedLoadingPlaceholder
|
||||
}
|
||||
ListFooterComponent={
|
||||
!!data && items?.length !== 0 && isMe ? CreateAnother : undefined
|
||||
}
|
||||
animatedProps={animatedProps}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react'
|
||||
import {findNodeHandle, type ListRenderItemInfo, View} from 'react-native'
|
||||
import {
|
||||
type ScrollHandler,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {
|
||||
type AppBskyLabelerDefs,
|
||||
type InterpretedLabelValueDefinition,
|
||||
@@ -16,9 +11,9 @@ import {useLingui} from '@lingui/react'
|
||||
import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation'
|
||||
import {ScrollProvider, useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {useProfileScrollbarAdjustment} from '#/screens/Profile/useProfileScrollbarAdjustment'
|
||||
import {atoms as a, ios, tokens, useTheme} from '#/alf'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||
@@ -57,39 +52,6 @@ export function ProfileLabelsSection({
|
||||
}: LabelsSectionProps) {
|
||||
const t = useTheme()
|
||||
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const onScrollWorklet = useCallback<ScrollHandler<any>>(
|
||||
(e, ctx) => {
|
||||
'worklet'
|
||||
onScrollFromContext?.(e, ctx)
|
||||
scrollY.set(e.contentOffset.y)
|
||||
},
|
||||
[onScrollFromContext, scrollY],
|
||||
)
|
||||
|
||||
const {footerHeight} = useShellLayout()
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
if (IS_IOS) {
|
||||
return {
|
||||
scrollIndicatorInsets: {
|
||||
top: Math.max(headerHeight - scrollY.get(), collapsedHeaderHeight),
|
||||
right: 1,
|
||||
left: 0,
|
||||
bottom: footerHeight.get(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const onScrollToTop = useCallback(() => {
|
||||
scrollElRef.current?.scrollToOffset({
|
||||
animated: IS_NATIVE,
|
||||
@@ -159,45 +121,40 @@ export function ProfileLabelsSection({
|
||||
[labelerInfo, isSubscribed, numItems, t],
|
||||
)
|
||||
|
||||
const list = (
|
||||
<List
|
||||
ref={scrollElRef}
|
||||
data={labelValues}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
contentContainerStyle={a.px_xl}
|
||||
headerOffset={headerHeight}
|
||||
progressViewOffset={ios(0)}
|
||||
ListHeaderComponent={
|
||||
<LabelerListHeader
|
||||
isLabelerLoading={isLabelerLoading}
|
||||
labelerInfo={labelerInfo}
|
||||
labelerError={labelerError}
|
||||
hasValues={labelValues.length !== 0}
|
||||
isSubscribed={isSubscribed}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<ListFooter height={headerHeight + 180} style={a.border_transparent} />
|
||||
}
|
||||
animatedProps={IS_IOS ? animatedProps : undefined}
|
||||
/>
|
||||
)
|
||||
const {scrollHandlers, animatedProps} = useProfileScrollbarAdjustment({
|
||||
headerOffset: headerHeight,
|
||||
collapsedHeaderHeight,
|
||||
})
|
||||
|
||||
return (
|
||||
<View>
|
||||
{IS_IOS ? (
|
||||
<ScrollProvider
|
||||
onScroll={onScrollWorklet}
|
||||
onBeginDrag={onBeginDragFromContext}
|
||||
onEndDrag={onEndDragFromContext}
|
||||
onMomentumBegin={onMomentumEndFromContext}
|
||||
onMomentumEnd={onMomentumEndFromContext}>
|
||||
{list}
|
||||
</ScrollProvider>
|
||||
) : (
|
||||
list
|
||||
)}
|
||||
<ScrollProvider {...scrollHandlers}>
|
||||
<List
|
||||
ref={scrollElRef}
|
||||
data={labelValues}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={keyExtractor}
|
||||
contentContainerStyle={a.px_xl}
|
||||
headerOffset={headerHeight}
|
||||
progressViewOffset={ios(0)}
|
||||
ListHeaderComponent={
|
||||
<LabelerListHeader
|
||||
isLabelerLoading={isLabelerLoading}
|
||||
labelerInfo={labelerInfo}
|
||||
labelerError={labelerError}
|
||||
hasValues={labelValues.length !== 0}
|
||||
isSubscribed={isSubscribed}
|
||||
/>
|
||||
}
|
||||
ListFooterComponent={
|
||||
<ListFooter
|
||||
height={headerHeight + 180}
|
||||
style={a.border_transparent}
|
||||
/>
|
||||
}
|
||||
animatedProps={animatedProps}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {
|
||||
type ScrollHandler,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
|
||||
/**
|
||||
* Adjusts the scroll indicator insets on iOS to account for the pager header.
|
||||
* Adds another scroll offset listener, so use sparingly.
|
||||
*
|
||||
* HOW TO USE:
|
||||
*
|
||||
* ```tsx
|
||||
* const { scrollHandlers, animatedProps } = useProfileScrollbarAdjustment({
|
||||
* headerHeight: 600, // full size of the header
|
||||
* collapsedHeaderHeight: 200, // height of the header when collapsed
|
||||
* })
|
||||
*
|
||||
* return (
|
||||
* <ScrollContext {...scrollHandlers}>
|
||||
* <List animatedProps={animatedProps} />
|
||||
* </ScrollContext>
|
||||
* )
|
||||
* ````
|
||||
*/
|
||||
export function useProfileScrollbarAdjustment({
|
||||
enabled = true,
|
||||
headerOffset,
|
||||
collapsedHeaderHeight,
|
||||
}: {
|
||||
enabled?: boolean
|
||||
headerOffset: number
|
||||
collapsedHeaderHeight: number
|
||||
}) {
|
||||
const {onScroll: onScrollFromContext, ...otherScrollHandlers} =
|
||||
useScrollHandlers()
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const onScroll = useCallback<ScrollHandler<any>>(
|
||||
(e, ctx) => {
|
||||
'worklet'
|
||||
onScrollFromContext?.(e, ctx)
|
||||
if (enabled) {
|
||||
scrollY.set(e.contentOffset.y)
|
||||
}
|
||||
},
|
||||
[onScrollFromContext, scrollY, enabled],
|
||||
)
|
||||
|
||||
const {footerHeight} = useShellLayout()
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
return {
|
||||
scrollIndicatorInsets: {
|
||||
top: enabled
|
||||
? Math.max(headerOffset - scrollY.get(), collapsedHeaderHeight)
|
||||
: headerOffset,
|
||||
right: 1,
|
||||
left: 0,
|
||||
bottom: footerHeight.get(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const scrollHandlers = useMemo(
|
||||
() => ({
|
||||
onScroll,
|
||||
...otherScrollHandlers,
|
||||
}),
|
||||
[onScroll, otherScrollHandlers],
|
||||
)
|
||||
|
||||
return {
|
||||
scrollHandlers,
|
||||
animatedProps,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||
|
||||
/**
|
||||
* Adjusts the scroll indicator insets on iOS to account for the pager header. Adds another scroll offset
|
||||
*
|
||||
* HOW TO USE:
|
||||
*
|
||||
* ```tsx
|
||||
* const { scrollHandlers, animatedProps } = useScrollbarAdjustment({
|
||||
* headerHeight: 600, // full size of the header
|
||||
* collapsedHeaderHeight: 200, // height of the header when collapsed
|
||||
* })
|
||||
*
|
||||
* return (
|
||||
* <ScrollContext {...scrollHandlers}>
|
||||
* <List animatedProps={animatedProps} />
|
||||
* </ScrollContext>
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @platform ios
|
||||
*/
|
||||
export function useProfileScrollbarAdjustment({}: {
|
||||
enabled?: boolean
|
||||
headerOffset: number
|
||||
collapsedHeaderHeight: number
|
||||
}): {
|
||||
scrollHandlers: ReturnType<typeof useScrollHandlers>
|
||||
animatedProps:
|
||||
| undefined
|
||||
| Partial<{
|
||||
scrollIndicatorInsets: {
|
||||
top: number
|
||||
right: number
|
||||
left: number
|
||||
bottom: number
|
||||
}
|
||||
}>
|
||||
} {
|
||||
// this is a no-op version for android/web
|
||||
|
||||
const scrollHandlers = useScrollHandlers()
|
||||
|
||||
const animatedProps = undefined
|
||||
|
||||
return {
|
||||
scrollHandlers,
|
||||
animatedProps,
|
||||
}
|
||||
}
|
||||
@@ -13,28 +13,23 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {
|
||||
type ScrollHandler,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {ScrollProvider, useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||
import {useProfileScrollbarAdjustment} from '#/screens/Profile/useProfileScrollbarAdjustment'
|
||||
import {atoms as a, ios, useTheme} from '#/alf'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag'
|
||||
@@ -77,38 +72,6 @@ export function ProfileFeedgens({
|
||||
const t = useTheme()
|
||||
const [isPTRing, setIsPTRing] = useState(false)
|
||||
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const onScrollWorklet = useCallback<ScrollHandler<any>>(
|
||||
(e, ctx) => {
|
||||
'worklet'
|
||||
onScrollFromContext?.(e, ctx)
|
||||
scrollY.set(e.contentOffset.y)
|
||||
},
|
||||
[onScrollFromContext, scrollY],
|
||||
)
|
||||
|
||||
const {footerHeight} = useShellLayout()
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
if (IS_IOS) {
|
||||
return {
|
||||
scrollIndicatorInsets: {
|
||||
top: Math.max(headerOffset - scrollY.get(), collapsedHeaderHeight),
|
||||
right: 1,
|
||||
left: 0,
|
||||
bottom: footerHeight.get(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
const {height} = useWindowDimensions()
|
||||
const opts = useMemo(() => ({enabled}), [enabled])
|
||||
const {
|
||||
@@ -230,7 +193,7 @@ export function ProfileFeedgens({
|
||||
/>
|
||||
)
|
||||
} else if (item === LOADING) {
|
||||
return <FeedLoadingPlaceholder />
|
||||
return <FeedFeedLoadingPlaceholder />
|
||||
}
|
||||
if (preferences) {
|
||||
return (
|
||||
@@ -286,40 +249,32 @@ export function ProfileFeedgens({
|
||||
isEmpty,
|
||||
])
|
||||
|
||||
const list = (
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={items}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={ProfileFeedgensFooter}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={ios(0)}
|
||||
removeClippedSubviews={true}
|
||||
desktopFixedHeight
|
||||
onEndReached={onEndReached}
|
||||
contentContainerStyle={{minHeight: height + headerOffset}}
|
||||
animatedProps={IS_IOS ? animatedProps : undefined}
|
||||
/>
|
||||
)
|
||||
const {scrollHandlers, animatedProps} = useProfileScrollbarAdjustment({
|
||||
headerOffset,
|
||||
collapsedHeaderHeight,
|
||||
})
|
||||
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{IS_IOS ? (
|
||||
<ScrollProvider
|
||||
onScroll={onScrollWorklet}
|
||||
onBeginDrag={onBeginDragFromContext}
|
||||
onEndDrag={onEndDragFromContext}
|
||||
onMomentumBegin={onMomentumEndFromContext}
|
||||
onMomentumEnd={onMomentumEndFromContext}>
|
||||
{list}
|
||||
</ScrollProvider>
|
||||
) : (
|
||||
list
|
||||
)}
|
||||
<ScrollProvider {...scrollHandlers}>
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={items}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={ProfileFeedgensFooter}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={ios(0)}
|
||||
removeClippedSubviews={true}
|
||||
desktopFixedHeight
|
||||
onEndReached={onEndReached}
|
||||
contentContainerStyle={{minHeight: height + headerOffset}}
|
||||
animatedProps={animatedProps}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,28 +13,23 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {
|
||||
type ScrollHandler,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {ScrollProvider, useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||
import {useProfileScrollbarAdjustment} from '#/screens/Profile/useProfileScrollbarAdjustment'
|
||||
import {atoms as a, ios, useTheme} from '#/alf'
|
||||
import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList'
|
||||
import * as ListCard from '#/components/ListCard'
|
||||
@@ -77,38 +72,6 @@ export function ProfileLists({
|
||||
const t = useTheme()
|
||||
const [isPTRing, setIsPTRing] = useState(false)
|
||||
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const onScrollWorklet = useCallback<ScrollHandler<any>>(
|
||||
(e, ctx) => {
|
||||
'worklet'
|
||||
onScrollFromContext?.(e, ctx)
|
||||
scrollY.set(e.contentOffset.y)
|
||||
},
|
||||
[onScrollFromContext, scrollY],
|
||||
)
|
||||
|
||||
const {footerHeight} = useShellLayout()
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
if (IS_IOS) {
|
||||
return {
|
||||
scrollIndicatorInsets: {
|
||||
top: Math.max(headerOffset - scrollY.get(), collapsedHeaderHeight),
|
||||
right: 1,
|
||||
left: 0,
|
||||
bottom: footerHeight.get(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
const {height} = useWindowDimensions()
|
||||
const opts = useMemo(() => ({enabled}), [enabled])
|
||||
const {
|
||||
@@ -229,7 +192,7 @@ export function ProfileLists({
|
||||
/>
|
||||
)
|
||||
} else if (item === LOADING) {
|
||||
return <FeedLoadingPlaceholder />
|
||||
return <FeedFeedLoadingPlaceholder />
|
||||
}
|
||||
if (preferences) {
|
||||
return (
|
||||
@@ -285,40 +248,32 @@ export function ProfileLists({
|
||||
isEmpty,
|
||||
])
|
||||
|
||||
const list = (
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={items}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={ProfileListsFooter}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={ios(0)}
|
||||
removeClippedSubviews={true}
|
||||
desktopFixedHeight
|
||||
onEndReached={onEndReached}
|
||||
contentContainerStyle={{minHeight: height + headerOffset}}
|
||||
animatedProps={IS_IOS ? animatedProps : undefined}
|
||||
/>
|
||||
)
|
||||
const {scrollHandlers, animatedProps} = useProfileScrollbarAdjustment({
|
||||
headerOffset,
|
||||
collapsedHeaderHeight,
|
||||
})
|
||||
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{IS_IOS ? (
|
||||
<ScrollProvider
|
||||
onScroll={onScrollWorklet}
|
||||
onBeginDrag={onBeginDragFromContext}
|
||||
onEndDrag={onEndDragFromContext}
|
||||
onMomentumBegin={onMomentumEndFromContext}
|
||||
onMomentumEnd={onMomentumEndFromContext}>
|
||||
{list}
|
||||
</ScrollProvider>
|
||||
) : (
|
||||
list
|
||||
)}
|
||||
<ScrollProvider {...scrollHandlers}>
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={items}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={ProfileListsFooter}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={ios(0)}
|
||||
removeClippedSubviews={true}
|
||||
desktopFixedHeight
|
||||
onEndReached={onEndReached}
|
||||
contentContainerStyle={{minHeight: height + headerOffset}}
|
||||
animatedProps={animatedProps}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,6 @@ import {
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {
|
||||
type ScrollHandler,
|
||||
useAnimatedProps,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
AppBskyEmbedVideo,
|
||||
@@ -26,7 +21,7 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {ScrollProvider, useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow'
|
||||
@@ -48,10 +43,10 @@ import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useProgressGuide} from '#/state/shell/progress-guide'
|
||||
import {useSelectedFeed} from '#/state/shell/selected-feed'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {List, type ListRef} from '#/view/com/util/List'
|
||||
import {PostFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||
import {useProfileScrollbarAdjustment} from '#/screens/Profile/useProfileScrollbarAdjustment'
|
||||
import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types'
|
||||
import {useBreakpoints, useLayoutBreakpoints} from '#/alf'
|
||||
import {
|
||||
@@ -211,7 +206,7 @@ let PostFeed = ({
|
||||
savedFeedConfig,
|
||||
initialNumToRender: initialNumToRenderOverride,
|
||||
isVideoFeed = false,
|
||||
adjustScrollIndicators,
|
||||
adjustScrollIndicators = false,
|
||||
collapsedHeaderHeight = 0,
|
||||
}: {
|
||||
feed: FeedDescriptor
|
||||
@@ -1010,88 +1005,46 @@ let PostFeed = ({
|
||||
[feedFeedback, feed, liveNowConfig, getPostPosition, ax],
|
||||
)
|
||||
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
|
||||
const scrollY = useSharedValue(0)
|
||||
const onScrollWorklet = useCallback<ScrollHandler<any>>(
|
||||
(e, ctx) => {
|
||||
'worklet'
|
||||
onScrollFromContext?.(e, ctx)
|
||||
scrollY.set(e.contentOffset.y)
|
||||
},
|
||||
[onScrollFromContext, scrollY],
|
||||
)
|
||||
|
||||
const {footerHeight} = useShellLayout()
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
if (IS_IOS) {
|
||||
return {
|
||||
scrollIndicatorInsets: {
|
||||
top: adjustScrollIndicators
|
||||
? Math.max(headerOffset - scrollY.get(), collapsedHeaderHeight)
|
||||
: headerOffset,
|
||||
right: 1,
|
||||
left: 0,
|
||||
bottom: footerHeight.get(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
const {scrollHandlers, animatedProps} = useProfileScrollbarAdjustment({
|
||||
enabled: adjustScrollIndicators,
|
||||
headerOffset,
|
||||
collapsedHeaderHeight,
|
||||
})
|
||||
|
||||
const list = (
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={feedItems}
|
||||
keyExtractor={(item: FeedRow) => item.key}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={FeedFooter}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={() => void onRefresh()}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={progressViewOffset}
|
||||
contentContainerStyle={{
|
||||
minHeight: Dimensions.get('window').height * 1.5,
|
||||
}}
|
||||
onScrolledDownChange={handleScrolledDownChange}
|
||||
onEndReached={() => void onEndReached()}
|
||||
onEndReachedThreshold={2} // number of posts left to trigger load more
|
||||
removeClippedSubviews={true}
|
||||
extraData={extraData}
|
||||
desktopFixedHeight={
|
||||
desktopFixedHeightOffset ? desktopFixedHeightOffset : true
|
||||
}
|
||||
initialNumToRender={initialNumToRenderOverride ?? initialNumToRender}
|
||||
windowSize={9}
|
||||
maxToRenderPerBatch={IS_IOS ? 5 : 1}
|
||||
updateCellsBatchingPeriod={40}
|
||||
onItemSeen={onItemSeen}
|
||||
animatedProps={IS_IOS ? animatedProps : undefined}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{IS_IOS ? (
|
||||
<ScrollProvider
|
||||
onScroll={onScrollWorklet}
|
||||
onBeginDrag={onBeginDragFromContext}
|
||||
onEndDrag={onEndDragFromContext}
|
||||
onMomentumBegin={onMomentumEndFromContext}
|
||||
onMomentumEnd={onMomentumEndFromContext}>
|
||||
{list}
|
||||
</ScrollProvider>
|
||||
) : (
|
||||
list
|
||||
)}
|
||||
<ScrollProvider {...scrollHandlers}>
|
||||
<List
|
||||
testID={testID ? `${testID}-flatlist` : undefined}
|
||||
ref={scrollElRef}
|
||||
data={feedItems}
|
||||
keyExtractor={(item: FeedRow) => item.key}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={FeedFooter}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
refreshing={isPTRing}
|
||||
onRefresh={() => void onRefresh()}
|
||||
headerOffset={headerOffset}
|
||||
progressViewOffset={progressViewOffset}
|
||||
contentContainerStyle={{
|
||||
minHeight: Dimensions.get('window').height * 1.5,
|
||||
}}
|
||||
onScrolledDownChange={handleScrolledDownChange}
|
||||
onEndReached={() => void onEndReached()}
|
||||
onEndReachedThreshold={2} // number of posts left to trigger load more
|
||||
removeClippedSubviews={true}
|
||||
extraData={extraData}
|
||||
desktopFixedHeight={
|
||||
desktopFixedHeightOffset ? desktopFixedHeightOffset : true
|
||||
}
|
||||
initialNumToRender={initialNumToRenderOverride ?? initialNumToRender}
|
||||
windowSize={9}
|
||||
maxToRenderPerBatch={IS_IOS ? 5 : 1}
|
||||
updateCellsBatchingPeriod={40}
|
||||
onItemSeen={onItemSeen}
|
||||
animatedProps={animatedProps}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user