refactor(mobile): simplify scroll-to-top implementation

Remove scroll-to-top atom and consolidate scroll-to-top logic into a new hook, improving component integration and reducing complexity

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-02-21 20:32:50 +08:00
parent 15e3e1c27f
commit c4c9ceb385
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
5 changed files with 57 additions and 60 deletions

View File

@ -1,29 +0,0 @@
import { jotaiStore } from "@follow/utils"
import type { FlashList } from "@shopify/flash-list"
import { atom } from "jotai"
import { useEffect, useRef } from "react"
const defaultScrollToTop = { scrollToTop: () => {} }
const scrollToTopAtom = atom<{ scrollToTop: () => void }>(defaultScrollToTop)
export const scrollToTop = () => {
const { scrollToTop } = jotaiStore.get(scrollToTopAtom)
scrollToTop()
}
export const useScrollToTopRef = <T extends FlashList<unknown>>(enabled = true) => {
const ref = useRef<T>(null)
useEffect(() => {
if (!enabled) return
const scrollToTop = () => {
ref.current?.scrollToOffset({ animated: true, offset: 0 })
}
jotaiStore.set(scrollToTopAtom, { scrollToTop })
return () => {
if (jotaiStore.get(scrollToTopAtom).scrollToTop !== scrollToTop) return
jotaiStore.set(scrollToTopAtom, defaultScrollToTop)
}
}, [enabled, ref])
return ref
}

View File

@ -6,6 +6,7 @@ import { createContext, useContext, useEffect, useMemo, useRef } from "react"
import type { ScrollView, ScrollViewProps } from "react-native"
import {
Animated as RNAnimated,
Pressable,
StyleSheet,
TouchableOpacity,
useAnimatedValue,
@ -16,12 +17,11 @@ import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescri
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useColor } from "react-native-uikit-colors"
import { scrollToTop } from "@/src/atoms/scroll-to-top"
import {
AttachNavigationScrollViewContext,
SetAttachNavigationScrollViewContext,
} from "@/src/components/ui/tabbar/contexts/AttachNavigationScrollViewContext"
import { useBottomTabBarHeight } from "@/src/components/ui/tabbar/hooks"
import { useBottomTabBarHeight, useNavigationScrollToTop } from "@/src/components/ui/tabbar/hooks"
import { useDefaultHeaderHeight } from "@/src/hooks/useDefaultHeaderHeight"
import { MingcuteLeftLineIcon } from "@/src/icons/mingcute_left_line"
@ -147,6 +147,7 @@ export const NavigationBlurEffectHeader = ({
const hideableBottom = headerHideableBottom?.()
const { headerLeft, ...rest } = props
const scrollToTop = useNavigationScrollToTop()
return (
<Stack.Screen
@ -175,13 +176,11 @@ export const NavigationBlurEffectHeader = ({
<View pointerEvents="box-none" style={[StyleSheet.absoluteFill]}>
{options.headerBackground?.()}
</View>
<TouchableOpacity onPress={scrollToTop}>
<Header
title={options.title ?? ""}
{...options}
headerBackground={() => null}
/>
</TouchableOpacity>
<Pressable className="absolute inset-x-0 top-0 h-28" onPress={scrollToTop} />
<Header title={options.title ?? ""} {...options} headerBackground={() => null} />
{hideableBottom}
</Animated.View>
)

View File

@ -2,7 +2,7 @@ import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"
import { Tabs } from "expo-router"
import type { ForwardRefExoticComponent } from "react"
import { forwardRef, useMemo, useRef, useState } from "react"
import type { FlatList, ScrollView } from "react-native"
import type { ScrollView } from "react-native"
import { useSharedValue } from "react-native-reanimated"
import { BottomTabHeightProvider } from "./BottomTabHeightProvider"
@ -15,6 +15,7 @@ import {
BottomTabBarVisibleContext,
SetBottomTabBarVisibleContext,
} from "./contexts/BottomTabBarVisibleContext"
import { useNavigationScrollToTop } from "./hooks"
import { Tabbar } from "./Tabbar"
type ExtractReactForwardRefExoticComponent<T> =
@ -29,6 +30,7 @@ export const BottomTabs: ForwardRefExoticComponent<
useState<React.RefObject<ScrollView> | null>(null)
const currentTarget = useRef<string | undefined>(undefined)
const scrollToTop = useNavigationScrollToTop(attachNavigationScrollViewRef)
return (
<AttachNavigationScrollViewContext.Provider value={attachNavigationScrollViewRef}>
<SetAttachNavigationScrollViewContext.Provider value={setAttachNavigationScrollViewRef}>
@ -47,24 +49,7 @@ export const BottomTabs: ForwardRefExoticComponent<
}
if (currentTarget.current === e.target) {
const $scroller = attachNavigationScrollViewRef?.current as any
if ("scrollTo" in $scroller) {
;($scroller as ScrollView).scrollTo({
y: 0,
animated: true,
})
} else if ("scrollToIndex" in $scroller) {
;($scroller as FlatList<any>).scrollToIndex({
index: 0,
animated: true,
})
} else if ("scrollToOffset" in $scroller) {
;($scroller as FlatList<any>).scrollToOffset({
offset: 0,
animated: true,
})
}
scrollToTop()
return
}

View File

@ -1,8 +1,38 @@
import { useContext } from "react"
import { useCallback, useContext } from "react"
import type { FlatList, ScrollView } from "react-native"
import { AttachNavigationScrollViewContext } from "./contexts/AttachNavigationScrollViewContext"
import { BottomTabBarHeightContext } from "./contexts/BottomTabBarHeightContext"
export const useBottomTabBarHeight = () => {
const height = useContext(BottomTabBarHeightContext)
return height
}
export const useNavigationScrollToTop = (
overrideScrollerRef?: React.RefObject<ScrollView> | React.RefObject<FlatList<any>> | null,
) => {
const attachNavigationScrollViewRef = useContext(AttachNavigationScrollViewContext)
return useCallback(() => {
const $scroller = overrideScrollerRef?.current ?? attachNavigationScrollViewRef?.current
if (!$scroller) return
if ("scrollTo" in $scroller) {
;($scroller as ScrollView).scrollTo({
y: 0,
animated: true,
})
} else if ("scrollToIndex" in $scroller) {
;($scroller as FlatList<any>).scrollToIndex({
index: 0,
animated: true,
})
} else if ("scrollToOffset" in $scroller) {
;($scroller as FlatList<any>).scrollToOffset({
offset: 0,
animated: true,
})
}
return
}, [attachNavigationScrollViewRef, overrideScrollerRef])
}

View File

@ -1,6 +1,10 @@
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 { useScrollToTopRef } from "@/src/atoms/scroll-to-top"
import { SetAttachNavigationScrollViewContext } from "@/src/components/ui/tabbar/contexts/AttachNavigationScrollViewContext"
import { EntryListContentGrid } from "@/src/modules/entry-list/EntryListContentGrid"
import { EntryListContentArticle } from "./EntryListContentArticle"
@ -15,7 +19,15 @@ export function EntryListSelector({
viewId: FeedViewType
active?: boolean
}) {
const ref = useScrollToTopRef(active)
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])
let ContentComponent: typeof EntryListContentSocial | typeof EntryListContentGrid =
EntryListContentArticle