refactor(mobile): Replace TimelineSelectorHeader with TimelineSelectorProvider
- Remove TimelineSelectorHeader component - Introduce TimelineSelectorProvider to manage timeline context - Update entry list, subscriptions, and feed screens to use new provider - Simplify component structure and improve context management Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
bdff613867
commit
ba0c9119dc
|
|
@ -1,7 +1,7 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { useIsFocused } from "@react-navigation/native"
|
||||
import { useEffect, useMemo } from "react"
|
||||
import { Animated, StyleSheet, View } from "react-native"
|
||||
import { Animated, StyleSheet } from "react-native"
|
||||
import PagerView from "react-native-pager-view"
|
||||
|
||||
import { views } from "@/src/constants/views"
|
||||
|
|
@ -19,7 +19,7 @@ import {
|
|||
} from "@/src/store/entry/hooks"
|
||||
import { useListEntryIds } from "@/src/store/list/hooks"
|
||||
|
||||
import { TimelineSelectorHeader } from "../screen/TimelineSelectorHeader"
|
||||
import { TimelineSelectorProvider } from "../screen/TimelineSelectorProvider"
|
||||
import { EntryListSelector } from "./EntryListSelector"
|
||||
import { usePagerView } from "./usePagerView"
|
||||
|
||||
|
|
@ -35,26 +35,30 @@ export function EntryList() {
|
|||
}, [setDrawerSwipeDisabled, isFocused])
|
||||
|
||||
const selectedFeed = useSelectedFeed()
|
||||
if (!selectedFeed) return null
|
||||
|
||||
switch (selectedFeed.type) {
|
||||
case "view": {
|
||||
return <ViewPagerList viewId={selectedFeed.viewId} />
|
||||
const Content = useMemo(() => {
|
||||
if (!selectedFeed) return null
|
||||
switch (selectedFeed.type) {
|
||||
case "view": {
|
||||
return <ViewPagerList viewId={selectedFeed.viewId} />
|
||||
}
|
||||
case "feed": {
|
||||
return <FeedEntryList feedId={selectedFeed.feedId} />
|
||||
}
|
||||
case "category": {
|
||||
return <CategoryEntryList categoryName={selectedFeed.categoryName} />
|
||||
}
|
||||
case "list": {
|
||||
return <ListEntryList listId={selectedFeed.listId} />
|
||||
}
|
||||
case "inbox": {
|
||||
return <InboxEntryList inboxId={selectedFeed.inboxId} />
|
||||
}
|
||||
}
|
||||
case "feed": {
|
||||
return <FeedEntryList feedId={selectedFeed.feedId} />
|
||||
}
|
||||
case "category": {
|
||||
return <CategoryEntryList categoryName={selectedFeed.categoryName} />
|
||||
}
|
||||
case "list": {
|
||||
return <ListEntryList listId={selectedFeed.listId} />
|
||||
}
|
||||
case "inbox": {
|
||||
return <InboxEntryList inboxId={selectedFeed.inboxId} />
|
||||
}
|
||||
// No default
|
||||
}
|
||||
}, [selectedFeed])
|
||||
if (!Content) return null
|
||||
|
||||
return <TimelineSelectorProvider>{Content}</TimelineSelectorProvider>
|
||||
}
|
||||
|
||||
const AnimatedPagerView = Animated.createAnimatedComponent<typeof PagerView>(PagerView)
|
||||
|
|
@ -73,33 +77,21 @@ function ViewPagerList({ viewId }: { viewId: FeedViewType }) {
|
|||
}, [page, pagerRef, viewId])
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TimelineSelectorHeader>
|
||||
<AnimatedPagerView
|
||||
testID="pager-view"
|
||||
ref={pagerRef}
|
||||
style={styles.PagerView}
|
||||
initialPage={page}
|
||||
layoutDirection="ltr"
|
||||
overdrag
|
||||
onPageScroll={rest.onPageScroll}
|
||||
onPageSelected={rest.onPageSelected}
|
||||
onPageScrollStateChanged={rest.onPageScrollStateChanged}
|
||||
pageMargin={10}
|
||||
orientation="horizontal"
|
||||
>
|
||||
{useMemo(
|
||||
() =>
|
||||
views.map((view) => (
|
||||
<View testID="pager-view-content" key={view.view}>
|
||||
<ViewEntryList viewId={view.view} />
|
||||
</View>
|
||||
)),
|
||||
[],
|
||||
)}
|
||||
</AnimatedPagerView>
|
||||
</TimelineSelectorHeader>
|
||||
</View>
|
||||
<AnimatedPagerView
|
||||
testID="pager-view"
|
||||
ref={pagerRef}
|
||||
style={styles.PagerView}
|
||||
initialPage={page}
|
||||
layoutDirection="ltr"
|
||||
overdrag
|
||||
onPageScroll={rest.onPageScroll}
|
||||
onPageSelected={rest.onPageSelected}
|
||||
onPageScrollStateChanged={rest.onPageScrollStateChanged}
|
||||
pageMargin={10}
|
||||
orientation="horizontal"
|
||||
>
|
||||
{useMemo(() => views.map((view) => <ViewEntryList viewId={view.view} key={view.view} />), [])}
|
||||
</AnimatedPagerView>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export function EntryGridItem(props: MasonryItem) {
|
|||
}
|
||||
case "video": {
|
||||
const { videoPreviewImageUrl } = props
|
||||
|
||||
return (
|
||||
<>
|
||||
{videoPreviewImageUrl ? (
|
||||
|
|
|
|||
|
|
@ -27,13 +27,15 @@ export const TimelineSelectorList = forwardRef<
|
|||
const insets = useSafeAreaInsets()
|
||||
|
||||
const headerHeight = useHeaderHeight()
|
||||
const scrollY = useContext(NavigationContext)?.scrollY
|
||||
const { scrollY } = useContext(NavigationContext)!
|
||||
|
||||
const onScroll = useCallback(
|
||||
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
props.onScroll?.(e)
|
||||
|
||||
scrollY?.setValue(e.nativeEvent.contentOffset.y)
|
||||
},
|
||||
[scrollY],
|
||||
[scrollY, props.onScroll],
|
||||
)
|
||||
|
||||
const tabBarHeight = useBottomTabBarHeight()
|
||||
|
|
@ -56,7 +58,6 @@ export const TimelineSelectorList = forwardRef<
|
|||
refreshing={isRefetching}
|
||||
/>
|
||||
}
|
||||
onScroll={onScroll}
|
||||
scrollIndicatorInsets={{
|
||||
top: headerHeight - insets.top,
|
||||
bottom: tabBarHeight ? tabBarHeight - insets.bottom : undefined,
|
||||
|
|
@ -66,6 +67,7 @@ export const TimelineSelectorList = forwardRef<
|
|||
paddingBottom: tabBarHeight,
|
||||
}}
|
||||
{...props}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
|
@ -85,9 +87,10 @@ export const TimelineSelectorMasonryList = ({
|
|||
|
||||
const onScroll = useCallback(
|
||||
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
props.onScroll?.(e)
|
||||
scrollY?.setValue(e.nativeEvent.contentOffset.y)
|
||||
},
|
||||
[scrollY],
|
||||
[scrollY, props.onScroll],
|
||||
)
|
||||
|
||||
const tabBarHeight = useBottomTabBarHeight()
|
||||
|
|
@ -109,7 +112,6 @@ export const TimelineSelectorMasonryList = ({
|
|||
refreshing={isRefetching}
|
||||
/>
|
||||
}
|
||||
onScroll={onScroll}
|
||||
scrollIndicatorInsets={{
|
||||
top: headerHeight - insets.top,
|
||||
bottom: tabBarHeight ? tabBarHeight - insets.bottom : undefined,
|
||||
|
|
@ -119,6 +121,7 @@ export const TimelineSelectorMasonryList = ({
|
|||
paddingBottom: tabBarHeight,
|
||||
}}
|
||||
{...props}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { getFeed } from "@/src/store/feed/getter"
|
|||
|
||||
import { useEntryListContext, useSelectedFeedTitle, useSelectedView } from "./atoms"
|
||||
|
||||
export function TimelineSelectorHeader({ children }: { children: React.ReactNode }) {
|
||||
export function TimelineSelectorProvider({ children }: { children: React.ReactNode }) {
|
||||
const scrollY = useAnimatedValue(0)
|
||||
const viewTitle = useSelectedFeedTitle()
|
||||
const screenType = useEntryListContext().type
|
||||
|
|
@ -22,7 +22,6 @@ import { Search3CuteFiIcon } from "@/src/icons/search_3_cute_fi"
|
|||
import { Search3CuteReIcon } from "@/src/icons/search_3_cute_re"
|
||||
import { Settings1CuteFiIcon } from "@/src/icons/settings_1_cute_fi"
|
||||
import { Settings1CuteReIcon } from "@/src/icons/settings_1_cute_re"
|
||||
import { FeedDrawer } from "@/src/modules/feed-drawer/drawer"
|
||||
import { useColor } from "@/src/theme/colors"
|
||||
|
||||
const fifthTap = Gesture.Tap()
|
||||
|
|
@ -46,112 +45,110 @@ export default function TabLayout() {
|
|||
}, [animatedTransformY, tabBarVisible])
|
||||
|
||||
return (
|
||||
<FeedDrawer>
|
||||
<BottomTabBarBackgroundContext.Provider value={useMemo(() => ({ opacity }), [opacity])}>
|
||||
<SetBottomTabBarVisibleContext.Provider value={setTabBarVisible}>
|
||||
<Tabs
|
||||
screenListeners={{
|
||||
tabPress: () => {
|
||||
opacity.value = 1
|
||||
<BottomTabBarBackgroundContext.Provider value={useMemo(() => ({ opacity }), [opacity])}>
|
||||
<SetBottomTabBarVisibleContext.Provider value={setTabBarVisible}>
|
||||
<Tabs
|
||||
screenListeners={{
|
||||
tabPress: () => {
|
||||
opacity.value = 1
|
||||
},
|
||||
transitionStart: () => {
|
||||
opacity.value = 1
|
||||
},
|
||||
}}
|
||||
screenOptions={{
|
||||
tabBarBackground: TabBarBackground,
|
||||
tabBarStyle: {
|
||||
position: "absolute",
|
||||
borderTopWidth: 0,
|
||||
transform: [
|
||||
{
|
||||
translateY: animatedTransformY.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
animation: "fade",
|
||||
transitionSpec: {
|
||||
animation: "timing",
|
||||
config: {
|
||||
duration: 50,
|
||||
easing: Easing.ease,
|
||||
},
|
||||
transitionStart: () => {
|
||||
opacity.value = 1
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Home",
|
||||
headerShown: false,
|
||||
tabBarLabel: "Timeline",
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? Home5CuteReIcon : Home5CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
tabBarButton(props) {
|
||||
return <Pressable {...props} />
|
||||
},
|
||||
}}
|
||||
screenOptions={{
|
||||
tabBarBackground: TabBarBackground,
|
||||
tabBarStyle: {
|
||||
position: "absolute",
|
||||
borderTopWidth: 0,
|
||||
transform: [
|
||||
{
|
||||
translateY: animatedTransformY.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="subscriptions"
|
||||
options={{
|
||||
title: "Subscriptions",
|
||||
headerShown: false,
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? BlackBoard2CuteReIcon : BlackBoard2CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
animation: "fade",
|
||||
transitionSpec: {
|
||||
animation: "timing",
|
||||
config: {
|
||||
duration: 50,
|
||||
easing: Easing.ease,
|
||||
},
|
||||
tabBarLabel: "Subscriptions",
|
||||
|
||||
tabBarButton(props) {
|
||||
return <Pressable {...props} />
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Home",
|
||||
headerShown: false,
|
||||
tabBarLabel: "Timeline",
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? Home5CuteReIcon : Home5CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
tabBarButton(props) {
|
||||
return <Pressable {...props} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="subscriptions"
|
||||
options={{
|
||||
title: "Subscriptions",
|
||||
headerShown: false,
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? BlackBoard2CuteReIcon : BlackBoard2CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
tabBarLabel: "Subscriptions",
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="discover"
|
||||
options={{
|
||||
title: "Discover",
|
||||
headerShown: false,
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? Search3CuteReIcon : Search3CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
tabBarButton(props) {
|
||||
return <Pressable {...props} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
tabBarButton(props) {
|
||||
return <Pressable {...props} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="discover"
|
||||
options={{
|
||||
title: "Discover",
|
||||
headerShown: false,
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? Search3CuteReIcon : Search3CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
tabBarButton(props) {
|
||||
return <Pressable {...props} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tabs.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
title: "Settings",
|
||||
headerShown: false,
|
||||
tabBarButton(props) {
|
||||
return (
|
||||
<GestureDetector gesture={fifthTap}>
|
||||
<View className="flex-1">
|
||||
<Pressable {...props} />
|
||||
</View>
|
||||
</GestureDetector>
|
||||
)
|
||||
},
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? Settings1CuteReIcon : Settings1CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</SetBottomTabBarVisibleContext.Provider>
|
||||
</BottomTabBarBackgroundContext.Provider>
|
||||
</FeedDrawer>
|
||||
<Tabs.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
title: "Settings",
|
||||
headerShown: false,
|
||||
tabBarButton(props) {
|
||||
return (
|
||||
<GestureDetector gesture={fifthTap}>
|
||||
<View className="flex-1">
|
||||
<Pressable {...props} />
|
||||
</View>
|
||||
</GestureDetector>
|
||||
)
|
||||
},
|
||||
tabBarIcon: ({ color, focused }) => {
|
||||
const Icon = !focused ? Settings1CuteReIcon : Settings1CuteFiIcon
|
||||
return <Icon color={color} width={24} height={24} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</SetBottomTabBarVisibleContext.Provider>
|
||||
</BottomTabBarBackgroundContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useSelectedFeed } from "@/src/modules/screen/atoms"
|
||||
import { TimelineSelectorHeader } from "@/src/modules/screen/TimelineSelectorHeader"
|
||||
import { TimelineSelectorProvider } from "@/src/modules/screen/TimelineSelectorProvider"
|
||||
import { SubscriptionList } from "@/src/modules/subscription/SubscriptionLists"
|
||||
|
||||
export default function Subscriptions() {
|
||||
|
|
@ -7,8 +7,8 @@ export default function Subscriptions() {
|
|||
const view = selectedFeed?.type === "view" ? selectedFeed.viewId : undefined
|
||||
|
||||
return (
|
||||
<TimelineSelectorHeader>
|
||||
<TimelineSelectorProvider>
|
||||
<SubscriptionList view={view ?? 0} />
|
||||
</TimelineSelectorHeader>
|
||||
</TimelineSelectorProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"
|
|||
|
||||
import { EntryListSelector } from "@/src/modules/entry-list/EntryListSelector"
|
||||
import { EntryListContext, useSelectedView } from "@/src/modules/screen/atoms"
|
||||
import { TimelineSelectorHeader } from "@/src/modules/screen/TimelineSelectorHeader"
|
||||
import { TimelineSelectorProvider } from "@/src/modules/screen/TimelineSelectorProvider"
|
||||
import { useCollectionEntryList } from "@/src/store/collection/hooks"
|
||||
import { useEntryIdsByCategory, useEntryIdsByFeedId } from "@/src/store/entry/hooks"
|
||||
import { FEED_COLLECTION_LIST } from "@/src/store/entry/utils"
|
||||
|
|
@ -29,9 +29,9 @@ export default function Feed() {
|
|||
return (
|
||||
<EntryListContext.Provider value={useMemo(() => ({ type: "feed" }), [])}>
|
||||
<BottomTabBarHeightContext.Provider value={insets.bottom}>
|
||||
<TimelineSelectorHeader>
|
||||
<TimelineSelectorProvider>
|
||||
<EntryListSelector entryIds={entryIds} viewId={view} />
|
||||
</TimelineSelectorHeader>
|
||||
</TimelineSelectorProvider>
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</EntryListContext.Provider>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue