From c4c9ceb385d2ca5947dcf7742cc1764ad3cebe56 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 21 Feb 2025 20:32:50 +0800 Subject: [PATCH] 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 --- apps/mobile/src/atoms/scroll-to-top.ts | 29 ----------------- .../common/SafeNavigationScrollView.tsx | 17 +++++----- .../src/components/ui/tabbar/BottomTabs.tsx | 23 +++---------- apps/mobile/src/components/ui/tabbar/hooks.ts | 32 ++++++++++++++++++- .../modules/entry-list/EntryListSelector.tsx | 16 ++++++++-- 5 files changed, 57 insertions(+), 60 deletions(-) delete mode 100644 apps/mobile/src/atoms/scroll-to-top.ts diff --git a/apps/mobile/src/atoms/scroll-to-top.ts b/apps/mobile/src/atoms/scroll-to-top.ts deleted file mode 100644 index 50cb6ee49..000000000 --- a/apps/mobile/src/atoms/scroll-to-top.ts +++ /dev/null @@ -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 = >(enabled = true) => { - const ref = useRef(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 -} diff --git a/apps/mobile/src/components/common/SafeNavigationScrollView.tsx b/apps/mobile/src/components/common/SafeNavigationScrollView.tsx index 1cc18f444..a4b8b9e95 100644 --- a/apps/mobile/src/components/common/SafeNavigationScrollView.tsx +++ b/apps/mobile/src/components/common/SafeNavigationScrollView.tsx @@ -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 ( {options.headerBackground?.()} - -
null} - /> - + + + +
null} /> + {hideableBottom} ) diff --git a/apps/mobile/src/components/ui/tabbar/BottomTabs.tsx b/apps/mobile/src/components/ui/tabbar/BottomTabs.tsx index d32ab2a5b..58c40d6dd 100644 --- a/apps/mobile/src/components/ui/tabbar/BottomTabs.tsx +++ b/apps/mobile/src/components/ui/tabbar/BottomTabs.tsx @@ -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 = @@ -29,6 +30,7 @@ export const BottomTabs: ForwardRefExoticComponent< useState | null>(null) const currentTarget = useRef(undefined) + const scrollToTop = useNavigationScrollToTop(attachNavigationScrollViewRef) return ( @@ -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).scrollToIndex({ - index: 0, - animated: true, - }) - } else if ("scrollToOffset" in $scroller) { - ;($scroller as FlatList).scrollToOffset({ - offset: 0, - animated: true, - }) - } + scrollToTop() return } diff --git a/apps/mobile/src/components/ui/tabbar/hooks.ts b/apps/mobile/src/components/ui/tabbar/hooks.ts index d7144e3d2..dd0a9b02d 100644 --- a/apps/mobile/src/components/ui/tabbar/hooks.ts +++ b/apps/mobile/src/components/ui/tabbar/hooks.ts @@ -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 | React.RefObject> | 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).scrollToIndex({ + index: 0, + animated: true, + }) + } else if ("scrollToOffset" in $scroller) { + ;($scroller as FlatList).scrollToOffset({ + offset: 0, + animated: true, + }) + } + return + }, [attachNavigationScrollViewRef, overrideScrollerRef]) +} diff --git a/apps/mobile/src/modules/entry-list/EntryListSelector.tsx b/apps/mobile/src/modules/entry-list/EntryListSelector.tsx index 1d784891a..20718c0da 100644 --- a/apps/mobile/src/modules/entry-list/EntryListSelector.tsx +++ b/apps/mobile/src/modules/entry-list/EntryListSelector.tsx @@ -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>(null) + useEffect(() => { + if (!active) return + if (setAttachNavigationScrollViewRef) { + setAttachNavigationScrollViewRef(ref as unknown as RefObject) + } + }, [setAttachNavigationScrollViewRef, ref, active]) let ContentComponent: typeof EntryListContentSocial | typeof EntryListContentGrid = EntryListContentArticle