Allow profile header to overscroll (#5457)

* add allowoverscroll prop

* ensure spinner is visible

* more generic prop for `<List>`

* rename to allowHeaderOverScroll
This commit is contained in:
Samuel Newman
2024-09-25 14:58:53 +01:00
committed by GitHub
parent 850cfc1cd5
commit bd393b1b38
10 changed files with 72 additions and 47 deletions
@@ -12,15 +12,15 @@ import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
import {useGenerateStarterPackMutation} from '#/lib/generate-starterpack'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types'
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useGenerateStarterPackMutation} from 'lib/generate-starterpack' import {List, ListRef} from '#/view/com/util/List'
import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset' import {Text} from '#/view/com/util/text/Text'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {atoms as a, ios, useTheme} from '#/alf'
import {NavigationProp} from 'lib/routes/types'
import {parseStarterPackUri} from 'lib/strings/starter-pack'
import {List, ListRef} from 'view/com/util/List'
import {Text} from 'view/com/util/text/Text'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog' import {useDialogControl} from '#/components/Dialog'
import {LinearGradientBackground} from '#/components/LinearGradientBackground' import {LinearGradientBackground} from '#/components/LinearGradientBackground'
@@ -132,6 +132,7 @@ export const ProfileStarterPacks = React.forwardRef<
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
refreshing={isPTRing} refreshing={isPTRing}
headerOffset={headerOffset} headerOffset={headerOffset}
progressViewOffset={ios(0)}
contentContainerStyle={{paddingBottom: headerOffset + bottomBarOffset}} contentContainerStyle={{paddingBottom: headerOffset + bottomBarOffset}}
indicatorStyle={t.name === 'light' ? 'black' : 'white'} indicatorStyle={t.name === 'light' ? 'black' : 'white'}
removeClippedSubviews={true} removeClippedSubviews={true}
+9 -5
View File
@@ -7,18 +7,22 @@ import {
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {usePalette} from 'lib/hooks/usePalette' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' import {useTheme} from '#/alf'
import {ProfileHeaderLabeler} from './ProfileHeaderLabeler' import {ProfileHeaderLabeler} from './ProfileHeaderLabeler'
import {ProfileHeaderStandard} from './ProfileHeaderStandard' import {ProfileHeaderStandard} from './ProfileHeaderStandard'
let ProfileHeaderLoading = (_props: {}): React.ReactNode => { let ProfileHeaderLoading = (_props: {}): React.ReactNode => {
const pal = usePalette('default') const t = useTheme()
return ( return (
<View style={pal.view}> <View style={t.atoms.bg}>
<LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} /> <LoadingPlaceholder width="100%" height={150} style={{borderRadius: 0}} />
<View <View
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> style={[
t.atoms.bg,
{borderColor: t.atoms.bg.backgroundColor},
styles.avi,
]}>
<LoadingPlaceholder width={90} height={90} style={styles.br45} /> <LoadingPlaceholder width={90} height={90} style={styles.br45} />
</View> </View>
<View style={styles.content}> <View style={styles.content}>
+8 -6
View File
@@ -4,17 +4,18 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {usePalette} from '#/lib/hooks/usePalette'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {FeedDescriptor} from '#/state/queries/post-feed' import {FeedDescriptor} from '#/state/queries/post-feed'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {truncateAndInvalidate} from '#/state/queries/util' import {truncateAndInvalidate} from '#/state/queries/util'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' import {Feed} from '#/view/com/posts/Feed'
import {usePalette} from 'lib/hooks/usePalette' import {EmptyState} from '#/view/com/util/EmptyState'
import {ListRef} from '#/view/com/util/List'
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
import {Text} from '#/view/com/util/text/Text' import {Text} from '#/view/com/util/text/Text'
import {Feed} from 'view/com/posts/Feed' import {ios} from '#/alf'
import {EmptyState} from 'view/com/util/EmptyState'
import {ListRef} from 'view/com/util/List'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {SectionRef} from './types' import {SectionRef} from './types'
interface FeedSectionProps { interface FeedSectionProps {
@@ -82,6 +83,7 @@ export const ProfileFeedSection = React.forwardRef<
onScrolledDownChange={setIsScrolledDown} onScrolledDownChange={setIsScrolledDown}
renderEmptyState={renderPostsEmpty} renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight} headerOffset={headerHeight}
progressViewOffset={ios(0)}
renderEndOfFeed={ProfileEndOfFeed} renderEndOfFeed={ProfileEndOfFeed}
ignoreFilterFor={ignoreFilterFor} ignoreFilterFor={ignoreFilterFor}
initialNumToRender={ initialNumToRender={
+3 -2
View File
@@ -15,9 +15,9 @@ import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection' import {isNative, isWeb} from '#/platform/detection'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens'
import {EmptyState} from '#/view/com/util/EmptyState'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {EmptyState} from 'view/com/util/EmptyState' import {atoms as a, ios, useTheme} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import * as FeedCard from '#/components/FeedCard' import * as FeedCard from '#/components/FeedCard'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List' import {List, ListRef} from '../util/List'
@@ -191,6 +191,7 @@ export const ProfileFeedgens = React.forwardRef<
refreshing={isPTRing} refreshing={isPTRing}
onRefresh={onRefresh} onRefresh={onRefresh}
headerOffset={headerOffset} headerOffset={headerOffset}
progressViewOffset={ios(0)}
contentContainerStyle={isNative && {paddingBottom: headerOffset + 100}} contentContainerStyle={isNative && {paddingBottom: headerOffset + 100}}
indicatorStyle={t.name === 'light' ? 'black' : 'white'} indicatorStyle={t.name === 'light' ? 'black' : 'white'}
removeClippedSubviews={true} removeClippedSubviews={true}
+4 -3
View File
@@ -10,14 +10,14 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {useAnalytics} from '#/lib/analytics/analytics'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection' import {isNative, isWeb} from '#/platform/detection'
import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists'
import {useAnalytics} from 'lib/analytics/analytics' import {EmptyState} from '#/view/com/util/EmptyState'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {EmptyState} from 'view/com/util/EmptyState' import {atoms as a, ios, useTheme} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import * as ListCard from '#/components/ListCard' import * as ListCard from '#/components/ListCard'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {List, ListRef} from '../util/List' import {List, ListRef} from '../util/List'
@@ -192,6 +192,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
refreshing={isPTRing} refreshing={isPTRing}
onRefresh={onRefresh} onRefresh={onRefresh}
headerOffset={headerOffset} headerOffset={headerOffset}
progressViewOffset={ios(0)}
contentContainerStyle={ contentContainerStyle={
isNative && {paddingBottom: headerOffset + 100} isNative && {paddingBottom: headerOffset + 100}
} }
+20 -9
View File
@@ -19,8 +19,8 @@ import Animated, {
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {ScrollProvider} from '#/lib/ScrollContext' import {ScrollProvider} from '#/lib/ScrollContext'
import {isIOS} from 'platform/detection' import {isIOS} from '#/platform/detection'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {ListMethods} from '../util/List' import {ListMethods} from '../util/List'
import {TabBar} from './TabBar' import {TabBar} from './TabBar'
@@ -41,6 +41,7 @@ export interface PagerWithHeaderProps {
initialPage?: number initialPage?: number
onPageSelected?: (index: number) => void onPageSelected?: (index: number) => void
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
allowHeaderOverScroll?: boolean
} }
export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>( export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
function PageWithHeaderImpl( function PageWithHeaderImpl(
@@ -53,6 +54,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
initialPage, initialPage,
onPageSelected, onPageSelected,
onCurrentPageSelected, onCurrentPageSelected,
allowHeaderOverScroll,
}: PagerWithHeaderProps, }: PagerWithHeaderProps,
ref, ref,
) { ) {
@@ -92,6 +94,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
onSelect={props.onSelect} onSelect={props.onSelect}
scrollY={scrollY} scrollY={scrollY}
testID={testID} testID={testID}
allowHeaderOverScroll={allowHeaderOverScroll}
/> />
) )
}, },
@@ -106,6 +109,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
onHeaderOnlyLayout, onHeaderOnlyLayout,
scrollY, scrollY,
testID, testID,
allowHeaderOverScroll,
], ],
) )
@@ -216,6 +220,7 @@ let PagerTabBar = ({
onTabBarLayout, onTabBarLayout,
onCurrentPageSelected, onCurrentPageSelected,
onSelect, onSelect,
allowHeaderOverScroll,
}: { }: {
currentPage: number currentPage: number
headerOnlyHeight: number headerOnlyHeight: number
@@ -228,14 +233,20 @@ let PagerTabBar = ({
onTabBarLayout: (e: LayoutChangeEvent) => void onTabBarLayout: (e: LayoutChangeEvent) => void
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
onSelect?: (index: number) => void onSelect?: (index: number) => void
allowHeaderOverScroll?: boolean
}): React.ReactNode => { }): React.ReactNode => {
const headerTransform = useAnimatedStyle(() => ({ const headerTransform = useAnimatedStyle(() => {
transform: [ const translateY = Math.min(scrollY.value, headerOnlyHeight) * -1
{ return {
translateY: Math.min(Math.min(scrollY.value, headerOnlyHeight) * -1, 0), transform: [
}, {
], translateY: allowHeaderOverScroll
})) ? translateY
: Math.min(translateY, 0),
},
],
}
})
const headerRef = React.useRef(null) const headerRef = React.useRef(null)
return ( return (
<Animated.View <Animated.View
+1 -1
View File
@@ -4,7 +4,7 @@ import {useAnimatedRef} from 'react-native-reanimated'
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 {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
import {ListMethods} from '../util/List' import {ListMethods} from '../util/List'
import {TabBar} from './TabBar' import {TabBar} from './TabBar'
+6 -3
View File
@@ -14,8 +14,11 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {useAnalytics} from '#/lib/analytics/analytics'
import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants' import {DISCOVER_FEED_URI, KNOWN_SHUTDOWN_FEEDS} from '#/lib/constants'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {logEvent, useGate} from '#/lib/statsig/statsig' import {logEvent, useGate} from '#/lib/statsig/statsig'
import {useTheme} from '#/lib/ThemeContext'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {listenPostCreated} from '#/state/events' import {listenPostCreated} from '#/state/events'
@@ -30,9 +33,6 @@ import {
usePostFeedQuery, usePostFeedQuery,
} from '#/state/queries/post-feed' } from '#/state/queries/post-feed'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useAnalytics} from 'lib/analytics/analytics'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {useTheme} from 'lib/ThemeContext'
import { import {
ProgressGuide, ProgressGuide,
SuggestedFeeds, SuggestedFeeds,
@@ -167,6 +167,7 @@ let Feed = ({
renderEndOfFeed, renderEndOfFeed,
testID, testID,
headerOffset = 0, headerOffset = 0,
progressViewOffset,
desktopFixedHeightOffset, desktopFixedHeightOffset,
ListHeaderComponent, ListHeaderComponent,
extraData, extraData,
@@ -187,6 +188,7 @@ let Feed = ({
renderEndOfFeed?: () => JSX.Element renderEndOfFeed?: () => JSX.Element
testID?: string testID?: string
headerOffset?: number headerOffset?: number
progressViewOffset?: number
desktopFixedHeightOffset?: number desktopFixedHeightOffset?: number
ListHeaderComponent?: () => JSX.Element ListHeaderComponent?: () => JSX.Element
extraData?: any extraData?: any
@@ -548,6 +550,7 @@ let Feed = ({
refreshing={isPTRing} refreshing={isPTRing}
onRefresh={onRefresh} onRefresh={onRefresh}
headerOffset={headerOffset} headerOffset={headerOffset}
progressViewOffset={progressViewOffset}
contentContainerStyle={{ contentContainerStyle={{
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}} }}
+9 -8
View File
@@ -4,11 +4,11 @@ import {runOnJS, useSharedValue} from 'react-native-reanimated'
import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video' import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {usePalette} from '#/lib/hooks/usePalette' import {useDedupe} from '#/lib/hooks/useDedupe'
import {useScrollHandlers} from '#/lib/ScrollContext' import {useScrollHandlers} from '#/lib/ScrollContext'
import {useDedupe} from 'lib/hooks/useDedupe' import {addStyle} from '#/lib/styles'
import {addStyle} from 'lib/styles' import {isIOS} from '#/platform/detection'
import {isIOS} from 'platform/detection' import {useTheme} from '#/alf'
import {FlatList_INTERNAL} from './Views' import {FlatList_INTERNAL} from './Views'
export type ListMethods = FlatList_INTERNAL export type ListMethods = FlatList_INTERNAL
@@ -44,12 +44,13 @@ function ListImpl<ItemT>(
onItemSeen, onItemSeen,
headerOffset, headerOffset,
style, style,
progressViewOffset,
...props ...props
}: ListProps<ItemT>, }: ListProps<ItemT>,
ref: React.Ref<ListMethods>, ref: React.Ref<ListMethods>,
) { ) {
const isScrolledDown = useSharedValue(false) const isScrolledDown = useSharedValue(false)
const pal = usePalette('default') const t = useTheme()
const dedupe = useDedupe(400) const dedupe = useDedupe(400)
function handleScrolledDownChange(didScrollDown: boolean) { function handleScrolledDownChange(didScrollDown: boolean) {
@@ -120,9 +121,9 @@ function ListImpl<ItemT>(
<RefreshControl <RefreshControl
refreshing={refreshing ?? false} refreshing={refreshing ?? false}
onRefresh={onRefresh} onRefresh={onRefresh}
tintColor={pal.colors.text} tintColor={t.atoms.text.color}
titleColor={pal.colors.text} titleColor={t.atoms.text.color}
progressViewOffset={headerOffset} progressViewOffset={progressViewOffset ?? headerOffset}
/> />
) )
} }
+3 -2
View File
@@ -37,11 +37,11 @@ import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens' import {ProfileFeedgens} from '#/view/com/feeds/ProfileFeedgens'
import {ProfileLists} from '#/view/com/lists/ProfileLists' import {ProfileLists} from '#/view/com/lists/ProfileLists'
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {FAB} from '#/view/com/util/fab/FAB' import {FAB} from '#/view/com/util/fab/FAB'
import {ListRef} from '#/view/com/util/List' import {ListRef} from '#/view/com/util/List'
import {CenteredView} from '#/view/com/util/Views' import {CenteredView} from '#/view/com/util/Views'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header' import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
@@ -363,7 +363,8 @@ function ProfileScreenLoaded({
items={sectionTitles} items={sectionTitles}
onPageSelected={onPageSelected} onPageSelected={onPageSelected}
onCurrentPageSelected={onCurrentPageSelected} onCurrentPageSelected={onCurrentPageSelected}
renderHeader={renderHeader}> renderHeader={renderHeader}
allowHeaderOverScroll>
{showFiltersTab {showFiltersTab
? ({headerHeight, isFocused, scrollElRef}) => ( ? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileLabelsSection <ProfileLabelsSection