refactor(mobile): improve scroll-to-top handling (#2910)
* refactor(mobile): enhance navigation scroll view handling and type safety * fix(mobile): update contentScrollerRef in discover screen * fix(mobile): integrate navigation listener for scroll view reference attachment * fix: types
This commit is contained in:
parent
30aef70849
commit
34540a1651
|
|
@ -1,3 +1,4 @@
|
|||
import { useNavigation } from "expo-router"
|
||||
import type { RefObject } from "react"
|
||||
import { useCallback, useContext, useEffect, useRef } from "react"
|
||||
import type { FlatList, ScrollView } from "react-native"
|
||||
|
|
@ -6,8 +7,8 @@ import { findNodeHandle, Platform } from "react-native"
|
|||
import { performNativeScrollToTop } from "@/src/lib/native"
|
||||
|
||||
import {
|
||||
AttachNavigationScrollViewContext,
|
||||
SetAttachNavigationScrollViewContext,
|
||||
useAttachNavigationScrollView,
|
||||
} from "./contexts/AttachNavigationScrollViewContext"
|
||||
import { BottomTabBarHeightContext } from "./contexts/BottomTabBarHeightContext"
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ export const useBottomTabBarHeight = () => {
|
|||
export const useNavigationScrollToTop = (
|
||||
overrideScrollerRef?: React.RefObject<ScrollView> | React.RefObject<FlatList<any>> | null,
|
||||
) => {
|
||||
const attachNavigationScrollViewRef = useContext(AttachNavigationScrollViewContext)
|
||||
const attachNavigationScrollViewRef = useAttachNavigationScrollView()
|
||||
return useCallback(() => {
|
||||
const $scroller = overrideScrollerRef?.current ?? attachNavigationScrollViewRef?.current
|
||||
if (!$scroller) return
|
||||
|
|
@ -33,17 +34,17 @@ export const useNavigationScrollToTop = (
|
|||
}
|
||||
|
||||
if ("scrollTo" in $scroller) {
|
||||
;($scroller as ScrollView).scrollTo({
|
||||
void ($scroller as ScrollView).scrollTo({
|
||||
y: 0,
|
||||
animated: true,
|
||||
})
|
||||
} else if ("scrollToIndex" in $scroller) {
|
||||
;($scroller as FlatList<any>).scrollToIndex({
|
||||
void ($scroller as FlatList<any>).scrollToIndex({
|
||||
index: 0,
|
||||
animated: true,
|
||||
})
|
||||
} else if ("scrollToOffset" in $scroller) {
|
||||
;($scroller as FlatList<any>).scrollToOffset({
|
||||
void ($scroller as FlatList<any>).scrollToOffset({
|
||||
offset: 0,
|
||||
animated: true,
|
||||
})
|
||||
|
|
@ -52,13 +53,19 @@ export const useNavigationScrollToTop = (
|
|||
}, [attachNavigationScrollViewRef, overrideScrollerRef])
|
||||
}
|
||||
|
||||
export const useRegisterNavigationScrollView = <T = any>() => {
|
||||
export const useRegisterNavigationScrollView = <T = unknown>(active = true) => {
|
||||
const scrollViewRef = useRef<T>(null)
|
||||
const navigation = useNavigation()
|
||||
const setAttachNavigationScrollViewRef = useContext(SetAttachNavigationScrollViewContext)
|
||||
useEffect(() => {
|
||||
if (setAttachNavigationScrollViewRef) {
|
||||
if (!active) return
|
||||
if (!setAttachNavigationScrollViewRef) return
|
||||
|
||||
setAttachNavigationScrollViewRef(scrollViewRef as unknown as RefObject<ScrollView>)
|
||||
const unsubscribe = navigation.addListener("focus", () => {
|
||||
setAttachNavigationScrollViewRef(scrollViewRef as unknown as RefObject<ScrollView>)
|
||||
}
|
||||
}, [setAttachNavigationScrollViewRef, scrollViewRef])
|
||||
})
|
||||
return unsubscribe
|
||||
}, [setAttachNavigationScrollViewRef, scrollViewRef, active, navigation])
|
||||
return scrollViewRef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ export const Recommendations = () => {
|
|||
const currentTab = useAtomValue(currentTabAtom)
|
||||
|
||||
const windowWidth = useWindowDimensions().width
|
||||
const contentScrollerRef = useRegisterNavigationScrollView<ScrollView>()
|
||||
const ref = useRef<ScrollView>(null)
|
||||
|
||||
useEffect(() => {
|
||||
contentScrollerRef.current?.scrollTo({ x: currentTab * windowWidth, y: 0, animated: true })
|
||||
}, [contentScrollerRef, currentTab, windowWidth])
|
||||
ref.current?.scrollTo({ x: currentTab * windowWidth, y: 0, animated: true })
|
||||
}, [ref, currentTab, windowWidth])
|
||||
|
||||
const [loadedTabIndex, setLoadedTabIndex] = useState(() => new Set())
|
||||
useEffect(() => {
|
||||
|
|
@ -55,7 +55,7 @@ export const Recommendations = () => {
|
|||
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: animatedX } } }], {
|
||||
useNativeDriver: true,
|
||||
})}
|
||||
ref={contentScrollerRef}
|
||||
ref={ref}
|
||||
horizontal
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
|
|
@ -176,7 +176,10 @@ const Tab: TabComponent = ({ tab, isSelected, ...rest }) => {
|
|||
}, [data, keys])
|
||||
|
||||
// Add ref for FlashList
|
||||
const listRef = useRef<FlashList<{ key: string; data: RSSHubRouteDeclaration } | string>>(null)
|
||||
const listRef =
|
||||
useRegisterNavigationScrollView<
|
||||
FlashList<{ key: string; data: RSSHubRouteDeclaration } | string>
|
||||
>(isSelected)
|
||||
|
||||
const getItemType = useCallback((item: string | { key: string }) => {
|
||||
return typeof item === "string" ? "sectionHeader" : "row"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import type { FlashList } from "@shopify/flash-list"
|
||||
import type { RefObject } from "react"
|
||||
import { useContext, useEffect, useRef } from "react"
|
||||
import type { ScrollView } from "react-native"
|
||||
|
||||
import { SetAttachNavigationScrollViewContext } from "@/src/components/layouts/tabbar/contexts/AttachNavigationScrollViewContext"
|
||||
import { useRegisterNavigationScrollView } from "@/src/components/layouts/tabbar/hooks"
|
||||
import { EntryListContentPicture } from "@/src/modules/entry-list/EntryListContentPicture"
|
||||
|
||||
import { EntryListContentArticle } from "./EntryListContentArticle"
|
||||
|
|
@ -21,15 +18,7 @@ export function EntryListSelector({
|
|||
viewId: FeedViewType
|
||||
active?: boolean
|
||||
}) {
|
||||
const setAttachNavigationScrollViewRef = useContext(SetAttachNavigationScrollViewContext)
|
||||
|
||||
const ref = useRef<FlashList<any>>(null)
|
||||
useEffect(() => {
|
||||
if (!active) return
|
||||
if (setAttachNavigationScrollViewRef) {
|
||||
setAttachNavigationScrollViewRef(ref as unknown as RefObject<ScrollView>)
|
||||
}
|
||||
}, [setAttachNavigationScrollViewRef, ref, active])
|
||||
const ref = useRegisterNavigationScrollView<FlashList<any>>(active)
|
||||
|
||||
let ContentComponent: typeof EntryListContentSocial | typeof EntryListContentPicture =
|
||||
EntryListContentArticle
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type {
|
|||
} from "@shopify/flash-list"
|
||||
import { FlashList, MasonryFlashList } from "@shopify/flash-list"
|
||||
import type { ElementRef, RefObject } from "react"
|
||||
import { forwardRef, useCallback, useContext, useImperativeHandle, useRef } from "react"
|
||||
import { forwardRef, useCallback, useContext } from "react"
|
||||
import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native"
|
||||
import { RefreshControl } from "react-native"
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context"
|
||||
|
|
@ -45,15 +45,15 @@ export const TimelineSelectorList = forwardRef<
|
|||
|
||||
const systemFill = useColor("secondaryLabel")
|
||||
|
||||
const listRef = useRef<FlashList<any>>(null)
|
||||
// const listRef = useRef<FlashList<any>>(null)
|
||||
|
||||
useImperativeHandle(ref, () => listRef.current!)
|
||||
// useImperativeHandle(ref, () => listRef.current!)
|
||||
|
||||
return (
|
||||
<FlashList
|
||||
automaticallyAdjustsScrollIndicatorInsets={false}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
ref={listRef}
|
||||
ref={ref}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
progressViewOffset={headerHeight}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { FeedViewType } from "@follow/constants"
|
||||
import type { FlashList } from "@shopify/flash-list"
|
||||
import { router } from "expo-router"
|
||||
import { useMemo, useState } from "react"
|
||||
import { Text } from "react-native"
|
||||
|
|
@ -46,7 +47,7 @@ export const SubscriptionList = ({ view }: { view: FeedViewType }) => {
|
|||
return subscriptionSyncService.fetch(view)
|
||||
})
|
||||
|
||||
const scrollViewRef = useRegisterNavigationScrollView()
|
||||
const scrollViewRef = useRegisterNavigationScrollView<FlashList<any>>()
|
||||
|
||||
return (
|
||||
<TimelineSelectorList
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { getDefaultHeaderHeight } from "@react-navigation/elements"
|
|||
import { useIsFocused } from "@react-navigation/native"
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack"
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react"
|
||||
import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native"
|
||||
import type { NativeScrollEvent, NativeSyntheticEvent, ScrollView } from "react-native"
|
||||
import { findNodeHandle, Text, UIManager } from "react-native"
|
||||
import type { SharedValue } from "react-native-reanimated"
|
||||
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated"
|
||||
|
|
@ -69,7 +69,7 @@ function Settings() {
|
|||
[opacity],
|
||||
)
|
||||
const [contentSize, setContentSize] = useState({ height: 0, width: 0 })
|
||||
const registerNavigationScrollView = useRegisterNavigationScrollView()
|
||||
const registerNavigationScrollView = useRegisterNavigationScrollView<ScrollView>()
|
||||
useEffect(() => {
|
||||
if (!isFocused) return
|
||||
const scrollView = registerNavigationScrollView.current
|
||||
|
|
|
|||
Loading…
Reference in New Issue