Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3348aca15 | |||
| cb14cbb153 | |||
| 9beed0ee9a | |||
| 2b1cbf376a | |||
| 0a67e83b05 | |||
| 79774e67d5 | |||
| ace173c518 | |||
| a3c14b8108 | |||
| 03789cd77c | |||
| 8d60937a25 | |||
| 53aa7d14c0 | |||
| 342d18949f |
+29
-114
@@ -1,122 +1,37 @@
|
||||
import 'react-native-url-polyfill/auto'
|
||||
import 'lib/sentry' // must be near top
|
||||
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {QueryClientProvider} from '@tanstack/react-query'
|
||||
|
||||
import 'view/icons'
|
||||
|
||||
import {init as initPersistedState} from '#/state/persisted'
|
||||
import {useColorMode} from 'state/shell'
|
||||
import {ThemeProvider} from 'lib/ThemeContext'
|
||||
import {s} from 'lib/styles'
|
||||
import {RootStoreModel, setupState, RootStoreProvider} from './state'
|
||||
import {Shell} from 'view/shell'
|
||||
import * as notifications from 'lib/notifications/notifications'
|
||||
import * as analytics from 'lib/analytics/analytics'
|
||||
import * as Toast from 'view/com/util/Toast'
|
||||
import {queryClient} from 'lib/react-query'
|
||||
import {TestCtrls} from 'view/com/testing/TestCtrls'
|
||||
import {Provider as ShellStateProvider} from 'state/shell'
|
||||
import {Provider as ModalStateProvider} from 'state/modals'
|
||||
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
|
||||
import {Provider as InvitesStateProvider} from 'state/invites'
|
||||
import {Provider as PrefsStateProvider} from 'state/preferences'
|
||||
import {
|
||||
Provider as SessionProvider,
|
||||
useSession,
|
||||
useSessionApi,
|
||||
} from 'state/session'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {i18n} from '@lingui/core'
|
||||
import {I18nProvider} from '@lingui/react'
|
||||
import {messages} from './locale/locales/en/messages'
|
||||
i18n.load('en', messages)
|
||||
i18n.activate('en')
|
||||
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
const InnerApp = observer(function AppImpl() {
|
||||
const colorMode = useColorMode()
|
||||
const {isInitialLoad} = useSession()
|
||||
const {resumeSession} = useSessionApi()
|
||||
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
// init
|
||||
useEffect(() => {
|
||||
setupState().then(store => {
|
||||
setRootStore(store)
|
||||
analytics.init(store)
|
||||
notifications.init(store)
|
||||
store.onSessionDropped(() => {
|
||||
Toast.show('Sorry! Your session expired. Please log in again.')
|
||||
})
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const account = persisted.get('session').currentAccount
|
||||
resumeSession(account)
|
||||
}, [resumeSession])
|
||||
|
||||
// show nothing prior to init
|
||||
if (!rootStore || isInitialLoad) {
|
||||
// TODO add a loading state
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider theme={colorMode}>
|
||||
<RootSiblingParent>
|
||||
<analytics.Provider>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<I18nProvider i18n={i18n}>
|
||||
<GestureHandlerRootView style={s.h100pct}>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
</GestureHandlerRootView>
|
||||
</I18nProvider>
|
||||
</RootStoreProvider>
|
||||
</analytics.Provider>
|
||||
</RootSiblingParent>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
})
|
||||
|
||||
function App() {
|
||||
const [isReady, setReady] = useState(false)
|
||||
import * as React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import Animated, {useAnimatedScrollHandler} from 'react-native-reanimated'
|
||||
|
||||
export default function App() {
|
||||
const [x, setX] = React.useState(0)
|
||||
React.useEffect(() => {
|
||||
initPersistedState().then(() => setReady(true))
|
||||
let id = setInterval(() => setX(x => x + 1), 1000)
|
||||
return () => clearInterval(id)
|
||||
}, [])
|
||||
|
||||
if (!isReady) {
|
||||
return null
|
||||
}
|
||||
// props to pass into children render functions
|
||||
const onScroll = useAnimatedScrollHandler({
|
||||
onScroll(e) {
|
||||
console.log(x)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<SessionProvider>
|
||||
<ShellStateProvider>
|
||||
<PrefsStateProvider>
|
||||
<MutedThreadsProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<InnerApp />
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</MutedThreadsProvider>
|
||||
</PrefsStateProvider>
|
||||
</ShellStateProvider>
|
||||
</SessionProvider>
|
||||
<View style={{flex: 1, flexDirection: 'row'}}>
|
||||
<Animated.FlatList
|
||||
onScroll={onScroll}
|
||||
style={{flex: 1, backgroundColor: 'red'}}
|
||||
contentContainerStyle={{
|
||||
paddingTop: 1000,
|
||||
}}
|
||||
/>
|
||||
<Animated.FlatList
|
||||
onScroll={onScroll}
|
||||
style={{flex: 1, backgroundColor: 'blue'}}
|
||||
contentContainerStyle={{
|
||||
paddingTop: 1000,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {MutableRefObject} from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
RefreshControl,
|
||||
StyleProp,
|
||||
View,
|
||||
@@ -18,7 +19,6 @@ import {ListModel} from 'state/models/content/list'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {s} from 'lib/styles'
|
||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||
import {logger} from '#/logger'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
@@ -224,7 +224,9 @@ export const ListItems = observer(function ListItemsImpl({
|
||||
progressViewOffset={headerOffset}
|
||||
/>
|
||||
}
|
||||
contentContainerStyle={s.contentContainer}
|
||||
contentContainerStyle={{
|
||||
paddingBottom: Dimensions.get('window').height - headerOffset,
|
||||
}}
|
||||
style={{paddingTop: headerOffset}}
|
||||
onScroll={onScroll}
|
||||
onEndReached={onEndReached}
|
||||
|
||||
@@ -49,7 +49,18 @@ export const Pager = React.forwardRef(function PagerImpl(
|
||||
onSelect: onTabBarSelect,
|
||||
})}
|
||||
{React.Children.map(children, (child, i) => (
|
||||
<View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
|
||||
<View
|
||||
style={
|
||||
selectedPage === i
|
||||
? s.flex1
|
||||
: {
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
// @ts-ignore web-only
|
||||
visibility: 'hidden',
|
||||
}
|
||||
}
|
||||
key={`page-${i}`}>
|
||||
{child}
|
||||
</View>
|
||||
))}
|
||||
|
||||
@@ -1,235 +1,37 @@
|
||||
import * as React from 'react'
|
||||
import {LayoutChangeEvent, StyleSheet, View} from 'react-native'
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedReaction,
|
||||
useAnimatedScrollHandler,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
runOnJS,
|
||||
} from 'react-native-reanimated'
|
||||
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||
import {TabBar} from './TabBar'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||
import {View} from 'react-native'
|
||||
import Animated, {useAnimatedScrollHandler} from 'react-native-reanimated'
|
||||
|
||||
const SCROLLED_DOWN_LIMIT = 200
|
||||
export function PagerWithHeader() {
|
||||
const [x, setX] = React.useState(0)
|
||||
React.useEffect(() => {
|
||||
let id = setInterval(() => setX(x => x + 1), 1000)
|
||||
return () => clearInterval(id)
|
||||
}, [])
|
||||
|
||||
interface PagerWithHeaderChildParams {
|
||||
headerHeight: number
|
||||
onScroll: OnScrollCb
|
||||
isScrolledDown: boolean
|
||||
}
|
||||
|
||||
export interface PagerWithHeaderProps {
|
||||
testID?: string
|
||||
children:
|
||||
| (((props: PagerWithHeaderChildParams) => JSX.Element) | null)[]
|
||||
| ((props: PagerWithHeaderChildParams) => JSX.Element)
|
||||
items: string[]
|
||||
isHeaderReady: boolean
|
||||
renderHeader?: () => JSX.Element
|
||||
initialPage?: number
|
||||
onPageSelected?: (index: number) => void
|
||||
onCurrentPageSelected?: (index: number) => void
|
||||
}
|
||||
export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
function PageWithHeaderImpl(
|
||||
{
|
||||
children,
|
||||
testID,
|
||||
items,
|
||||
isHeaderReady,
|
||||
renderHeader,
|
||||
initialPage,
|
||||
onPageSelected,
|
||||
onCurrentPageSelected,
|
||||
}: PagerWithHeaderProps,
|
||||
ref,
|
||||
) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const [currentPage, setCurrentPage] = React.useState(0)
|
||||
const scrollYs = React.useRef<Record<number, number>>({})
|
||||
const scrollY = useSharedValue(scrollYs.current[currentPage] || 0)
|
||||
const [tabBarHeight, setTabBarHeight] = React.useState(0)
|
||||
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
||||
const [isScrolledDown, setIsScrolledDown] = React.useState(
|
||||
scrollYs.current[currentPage] > SCROLLED_DOWN_LIMIT,
|
||||
)
|
||||
|
||||
const headerHeight = headerOnlyHeight + tabBarHeight
|
||||
|
||||
// react to scroll updates
|
||||
function onScrollUpdate(v: number) {
|
||||
// track each page's current scroll position
|
||||
scrollYs.current[currentPage] = Math.min(v, headerOnlyHeight)
|
||||
// update the 'is scrolled down' value
|
||||
setIsScrolledDown(v > SCROLLED_DOWN_LIMIT)
|
||||
}
|
||||
useAnimatedReaction(
|
||||
() => scrollY.value,
|
||||
v => runOnJS(onScrollUpdate)(v),
|
||||
)
|
||||
|
||||
// capture the header bar sizing
|
||||
const onTabBarLayout = React.useCallback(
|
||||
(evt: LayoutChangeEvent) => {
|
||||
setTabBarHeight(evt.nativeEvent.layout.height)
|
||||
},
|
||||
[setTabBarHeight],
|
||||
)
|
||||
const onHeaderOnlyLayout = React.useCallback(
|
||||
(evt: LayoutChangeEvent) => {
|
||||
setHeaderOnlyHeight(evt.nativeEvent.layout.height)
|
||||
},
|
||||
[setHeaderOnlyHeight],
|
||||
)
|
||||
|
||||
// render the the header and tab bar
|
||||
const headerTransform = useAnimatedStyle(
|
||||
() => ({
|
||||
transform: [
|
||||
{
|
||||
translateY: Math.min(
|
||||
Math.min(scrollY.value, headerOnlyHeight) * -1,
|
||||
0,
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
[scrollY, headerHeight, tabBarHeight],
|
||||
)
|
||||
const renderTabBar = React.useCallback(
|
||||
(props: RenderTabBarFnProps) => {
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
isMobile ? styles.tabBarMobile : styles.tabBarDesktop,
|
||||
headerTransform,
|
||||
]}>
|
||||
<View onLayout={onHeaderOnlyLayout}>{renderHeader?.()}</View>
|
||||
<View
|
||||
onLayout={onTabBarLayout}
|
||||
style={{
|
||||
// Render it immediately to measure it early since its size doesn't depend on the content.
|
||||
// However, keep it invisible until the header above stabilizes in order to prevent jumps.
|
||||
opacity: isHeaderReady ? 1 : 0,
|
||||
pointerEvents: isHeaderReady ? 'auto' : 'none',
|
||||
}}>
|
||||
<TabBar
|
||||
items={items}
|
||||
selectedPage={currentPage}
|
||||
onSelect={props.onSelect}
|
||||
onPressSelected={onCurrentPageSelected}
|
||||
/>
|
||||
</View>
|
||||
</Animated.View>
|
||||
)
|
||||
},
|
||||
[
|
||||
items,
|
||||
isHeaderReady,
|
||||
renderHeader,
|
||||
headerTransform,
|
||||
currentPage,
|
||||
onCurrentPageSelected,
|
||||
isMobile,
|
||||
onTabBarLayout,
|
||||
onHeaderOnlyLayout,
|
||||
],
|
||||
)
|
||||
|
||||
// props to pass into children render functions
|
||||
const onScroll = useAnimatedScrollHandler({
|
||||
onScroll(e) {
|
||||
scrollY.value = e.contentOffset.y
|
||||
},
|
||||
})
|
||||
|
||||
const onPageSelectedInner = React.useCallback(
|
||||
(index: number) => {
|
||||
setCurrentPage(index)
|
||||
onPageSelected?.(index)
|
||||
},
|
||||
[onPageSelected, setCurrentPage],
|
||||
)
|
||||
|
||||
const onPageSelecting = React.useCallback(
|
||||
(index: number) => {
|
||||
setCurrentPage(index)
|
||||
if (scrollY.value > headerHeight) {
|
||||
scrollY.value = headerHeight
|
||||
}
|
||||
scrollY.value = withTiming(scrollYs.current[index] || 0, {
|
||||
duration: 170,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
})
|
||||
},
|
||||
[scrollY, setCurrentPage, scrollYs, headerHeight],
|
||||
)
|
||||
|
||||
return (
|
||||
<Pager
|
||||
ref={ref}
|
||||
testID={testID}
|
||||
initialPage={initialPage}
|
||||
onPageSelected={onPageSelectedInner}
|
||||
onPageSelecting={onPageSelecting}
|
||||
renderTabBar={renderTabBar}
|
||||
tabBarPosition="top">
|
||||
{toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, i) => {
|
||||
let output = null
|
||||
if (
|
||||
child != null &&
|
||||
// Defer showing content until we know it won't jump.
|
||||
isHeaderReady &&
|
||||
headerOnlyHeight > 0 &&
|
||||
tabBarHeight > 0
|
||||
) {
|
||||
output = child({
|
||||
headerHeight,
|
||||
isScrolledDown,
|
||||
onScroll: i === currentPage ? onScroll : noop,
|
||||
})
|
||||
}
|
||||
// Pager children must be noncollapsible plain <View>s.
|
||||
return (
|
||||
<View key={i} collapsable={false}>
|
||||
{output}
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</Pager>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBarMobile: {
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
},
|
||||
tabBarDesktop: {
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 0,
|
||||
// @ts-ignore Web only -prf
|
||||
left: 'calc(50% - 299px)',
|
||||
width: 598,
|
||||
},
|
||||
})
|
||||
|
||||
function noop() {}
|
||||
|
||||
function toArray<T>(v: T | T[]): T[] {
|
||||
if (Array.isArray(v)) {
|
||||
return v
|
||||
}
|
||||
return [v]
|
||||
// props to pass into children render functions
|
||||
const onScroll = useAnimatedScrollHandler({
|
||||
onScroll(e) {
|
||||
console.log(x)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, flexDirection: 'row'}}>
|
||||
<Animated.FlatList
|
||||
onScroll={onScroll}
|
||||
style={{flex: 1, backgroundColor: 'red'}}
|
||||
contentContainerStyle={{
|
||||
paddingTop: 1000,
|
||||
}}
|
||||
/>
|
||||
<Animated.FlatList
|
||||
onScroll={onScroll}
|
||||
style={{flex: 1, backgroundColor: 'blue'}}
|
||||
contentContainerStyle={{
|
||||
paddingTop: 1000,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, {MutableRefObject} from 'react'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
RefreshControl,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
@@ -15,7 +16,6 @@ import {PostsFeedModel} from 'state/models/feeds/posts'
|
||||
import {FeedSlice} from './FeedSlice'
|
||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
|
||||
import {s} from 'lib/styles'
|
||||
import {useAnalytics} from 'lib/analytics/analytics'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useTheme} from 'lib/ThemeContext'
|
||||
@@ -176,7 +176,9 @@ export const Feed = observer(function Feed({
|
||||
progressViewOffset={headerOffset}
|
||||
/>
|
||||
}
|
||||
contentContainerStyle={s.contentContainer}
|
||||
contentContainerStyle={{
|
||||
paddingBottom: Dimensions.get('window').height - headerOffset,
|
||||
}}
|
||||
style={{paddingTop: headerOffset}}
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={scrollEventThrottle}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React, {useMemo, useCallback} from 'react'
|
||||
import {FlatList, StyleSheet, View, ActivityIndicator} from 'react-native'
|
||||
import {
|
||||
Dimensions,
|
||||
StyleSheet,
|
||||
View,
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
} from 'react-native'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
@@ -342,16 +348,19 @@ export const ProfileFeedScreenInner = observer(
|
||||
isHeaderReady={feedInfo?.hasLoaded ?? false}
|
||||
renderHeader={renderHeader}
|
||||
onCurrentPageSelected={onCurrentPageSelected}>
|
||||
{({onScroll, headerHeight, isScrolledDown}) => (
|
||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
|
||||
<FeedSection
|
||||
ref={feedSectionRef}
|
||||
feed={feed}
|
||||
onScroll={onScroll}
|
||||
headerHeight={headerHeight}
|
||||
isScrolledDown={isScrolledDown}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{({onScroll, headerHeight}) => (
|
||||
{({onScroll, headerHeight, scrollElRef}) => (
|
||||
<AboutSection
|
||||
feedOwnerDid={feedOwnerDid}
|
||||
feedRkey={rkey}
|
||||
@@ -359,6 +368,9 @@ export const ProfileFeedScreenInner = observer(
|
||||
headerHeight={headerHeight}
|
||||
onToggleLiked={onToggleLiked}
|
||||
onScroll={onScroll}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<ScrollView | null>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PagerWithHeader>
|
||||
@@ -386,14 +398,14 @@ interface FeedSectionProps {
|
||||
onScroll: OnScrollCb
|
||||
headerHeight: number
|
||||
isScrolledDown: boolean
|
||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||
function FeedSectionImpl(
|
||||
{feed, onScroll, headerHeight, isScrolledDown},
|
||||
{feed, onScroll, headerHeight, isScrolledDown, scrollElRef},
|
||||
ref,
|
||||
) {
|
||||
const hasNew = feed.hasNewLatest && !feed.isRefreshing
|
||||
const scrollElRef = React.useRef<FlatList>(null)
|
||||
|
||||
const onScrollToTop = useCallback(() => {
|
||||
scrollElRef.current?.scrollToOffset({offset: -headerHeight})
|
||||
@@ -437,6 +449,7 @@ const AboutSection = observer(function AboutPageImpl({
|
||||
headerHeight,
|
||||
onToggleLiked,
|
||||
onScroll,
|
||||
scrollElRef,
|
||||
}: {
|
||||
feedOwnerDid: string
|
||||
feedRkey: string
|
||||
@@ -444,6 +457,7 @@ const AboutSection = observer(function AboutPageImpl({
|
||||
headerHeight: number
|
||||
onToggleLiked: () => void
|
||||
onScroll: OnScrollCb
|
||||
scrollElRef: React.MutableRefObject<ScrollView | null>
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
@@ -454,8 +468,12 @@ const AboutSection = observer(function AboutPageImpl({
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
ref={scrollElRef}
|
||||
scrollEventThrottle={1}
|
||||
contentContainerStyle={{paddingTop: headerHeight}}
|
||||
contentContainerStyle={{
|
||||
paddingTop: headerHeight,
|
||||
paddingBottom: Dimensions.get('window').height - headerHeight,
|
||||
}}
|
||||
onScroll={onScroll}>
|
||||
<View
|
||||
style={[
|
||||
|
||||
@@ -175,18 +175,24 @@ export const ProfileListScreenInner = observer(
|
||||
isHeaderReady={list.hasLoaded}
|
||||
renderHeader={renderHeader}
|
||||
onCurrentPageSelected={onCurrentPageSelected}>
|
||||
{({onScroll, headerHeight, isScrolledDown}) => (
|
||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
|
||||
<FeedSection
|
||||
ref={feedSectionRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
feed={feed}
|
||||
onScroll={onScroll}
|
||||
headerHeight={headerHeight}
|
||||
isScrolledDown={isScrolledDown}
|
||||
/>
|
||||
)}
|
||||
{({onScroll, headerHeight, isScrolledDown}) => (
|
||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
|
||||
<AboutSection
|
||||
ref={aboutSectionRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
list={list}
|
||||
descriptionRT={list.descriptionRT}
|
||||
creator={list.data ? list.data.creator : undefined}
|
||||
@@ -223,9 +229,12 @@ export const ProfileListScreenInner = observer(
|
||||
items={SECTION_TITLES_MOD}
|
||||
isHeaderReady={list.hasLoaded}
|
||||
renderHeader={renderHeader}>
|
||||
{({onScroll, headerHeight, isScrolledDown}) => (
|
||||
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => (
|
||||
<AboutSection
|
||||
list={list}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
descriptionRT={list.descriptionRT}
|
||||
creator={list.data ? list.data.creator : undefined}
|
||||
isCurateList={list.isCuratelist}
|
||||
@@ -557,14 +566,14 @@ interface FeedSectionProps {
|
||||
onScroll: OnScrollCb
|
||||
headerHeight: number
|
||||
isScrolledDown: boolean
|
||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||
function FeedSectionImpl(
|
||||
{feed, onScroll, headerHeight, isScrolledDown},
|
||||
{feed, scrollElRef, onScroll, headerHeight, isScrolledDown},
|
||||
ref,
|
||||
) {
|
||||
const hasNew = feed.hasNewLatest && !feed.isRefreshing
|
||||
const scrollElRef = React.useRef<FlatList>(null)
|
||||
|
||||
const onScrollToTop = useCallback(() => {
|
||||
scrollElRef.current?.scrollToOffset({offset: -headerHeight})
|
||||
@@ -611,6 +620,7 @@ interface AboutSectionProps {
|
||||
onScroll: OnScrollCb
|
||||
headerHeight: number
|
||||
isScrolledDown: boolean
|
||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
|
||||
function AboutSectionImpl(
|
||||
@@ -624,13 +634,13 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
|
||||
onScroll,
|
||||
headerHeight,
|
||||
isScrolledDown,
|
||||
scrollElRef,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const scrollElRef = React.useRef<FlatList>(null)
|
||||
|
||||
const onScrollToTop = useCallback(() => {
|
||||
scrollElRef.current?.scrollToOffset({offset: -headerHeight})
|
||||
|
||||
Reference in New Issue
Block a user