refactor(rn): subscription drawer layout

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-01-14 20:25:18 +08:00
parent fe44c38b49
commit 2edebdfcdb
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
9 changed files with 89 additions and 68 deletions

View File

@ -0,0 +1,3 @@
import { Platform } from "react-native"
export const isIOS = Platform.OS === "ios"

View File

@ -1,11 +1,11 @@
export enum SearchType {
Feed = "feed",
List = "list",
User = "user",
// User = "user",
}
export const SearchTabs = [
{ name: "Feed", value: SearchType.Feed },
{ name: "List", value: SearchType.List },
{ name: "User", value: SearchType.User },
// { name: "User", value: SearchType.User },
]

View File

@ -1,12 +1,13 @@
import { cn } from "@follow/utils"
import {
Image,
SafeAreaView,
ScrollView,
StyleSheet,
TouchableOpacity,
useWindowDimensions,
View,
} from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
import type { ViewDefinition } from "@/src/constants/views"
@ -20,23 +21,34 @@ export const CollectionPanel = () => {
const winDim = useWindowDimensions()
const lists = useAllListSubscription()
const insets = useSafeAreaInsets()
return (
<SafeAreaView
<View
className="bg-secondary-system-background"
style={{ width: Math.max(50, winDim.width * 0.15) }}
>
<ScrollView contentContainerClassName="flex py-3 gap-3">
<ScrollView
contentContainerClassName="flex py-3 gap-3"
contentContainerStyle={{ paddingTop: insets.top, paddingBottom: insets.bottom }}
>
{views.map((viewDef) => (
<ViewButton key={viewDef.name} viewDef={viewDef} />
))}
<View style={styles.hairline} className="bg-opaque-separator mx-4" />
{lists.map((listId) => (
<ListButton key={listId} listId={listId} />
))}
</ScrollView>
</SafeAreaView>
</View>
)
}
const styles = StyleSheet.create({
hairline: {
height: StyleSheet.hairlineWidth,
},
})
const ViewButton = ({ viewDef }: { viewDef: ViewDefinition }) => {
const selectedCollection = useSelectedCollection()
const isActive = selectedCollection.type === "view" && selectedCollection.viewId === viewDef.view
@ -45,7 +57,7 @@ const ViewButton = ({ viewDef }: { viewDef: ViewDefinition }) => {
<TouchableOpacity
className={cn(
"mx-3 flex aspect-square items-center justify-center rounded-lg p-3",
isActive ? "bg-system-fill" : "bg-system-background",
isActive ? "bg-secondary-system-fill" : "bg-system-background",
)}
onPress={() =>
selectCollection({

View File

@ -63,7 +63,7 @@ export const FeedDrawer = ({ children }: PropsWithChildren) => {
const DrawerContent = () => {
return (
<View className="bg-system-background flex h-[500px] flex-1 flex-row overflow-hidden">
<View className="bg-system-background flex-1 flex-row overflow-hidden">
<CollectionPanel />
<FeedPanel />
</View>

View File

@ -1,12 +1,12 @@
import type { FeedViewType } from "@follow/constants"
import { cn } from "@follow/utils"
import { BottomTabBarHeightContext } from "@react-navigation/bottom-tabs"
import { HeaderHeightContext } from "@react-navigation/elements"
import { router } from "expo-router"
import type { FC } from "react"
import { createContext, memo, useContext, useMemo } from "react"
import { createContext, memo, useContext, useState } from "react"
import {
Animated,
Easing,
SafeAreaView,
ScrollView,
StyleSheet,
Text,
@ -23,13 +23,7 @@ import { ItemPressable } from "@/src/components/ui/pressable/item-pressable"
import { MingcuteRightLine } from "@/src/icons/mingcute_right_line"
import { useFeed, usePrefetchFeed } from "@/src/store/feed/hooks"
import { useList } from "@/src/store/list/hooks"
import {
useGroupedSubscription,
usePrefetchSubscription,
useSortedGroupedSubscription,
useSortedUngroupedSubscription,
useSubscription,
} from "@/src/store/subscription/hooks"
import { useSortedUngroupedSubscription, useSubscription } from "@/src/store/subscription/hooks"
import { useUnreadCount, useUnreadCounts } from "@/src/store/unread/hooks"
import {
@ -37,40 +31,44 @@ import {
SubscriptionFeedItemContextMenu,
} from "../context-menu/feeds"
import { useCurrentView, useFeedListSortMethod, useFeedListSortOrder } from "../subscription/atoms"
import { ViewPageCurrentViewProvider } from "../subscription/ctx"
import { SubscriptionList } from "../subscription/SubscriptionLists"
import { useSelectedCollection } from "./atoms"
import { ListHeaderComponent, ViewHeaderComponent } from "./header"
const useSortedSubscription = (view: FeedViewType) => {
usePrefetchSubscription(view)
const { grouped, unGrouped } = useGroupedSubscription(view)
const sortBy = useFeedListSortMethod()
const sortOrder = useFeedListSortOrder()
const sortedGrouped = useSortedGroupedSubscription(grouped, sortBy, sortOrder)
const sortedUnGrouped = useSortedUngroupedSubscription(unGrouped, sortBy, sortOrder)
const data = useMemo(
() => [...sortedGrouped, ...sortedUnGrouped],
[sortedGrouped, sortedUnGrouped],
)
return data
}
export const FeedPanel = () => {
const selectedCollection = useSelectedCollection()
const [headerHeight, setHeaderHeight] = useState(0)
if (selectedCollection.type === "view") {
return (
<SafeAreaView className="flex flex-1 overflow-hidden">
<ViewHeaderComponent view={selectedCollection.viewId} />
<FeedListView view={selectedCollection.viewId} />
</SafeAreaView>
<View className="flex flex-1 overflow-hidden">
<ViewHeaderComponent
view={selectedCollection.viewId}
onLayout={(e) => {
setHeaderHeight(e.nativeEvent.layout.height)
}}
/>
<HeaderHeightContext.Provider value={headerHeight}>
<BottomTabBarHeightContext.Provider value={0}>
<ViewPageCurrentViewProvider
key={selectedCollection.viewId}
value={selectedCollection.viewId}
>
<SubscriptionList view={selectedCollection.viewId} />
</ViewPageCurrentViewProvider>
</BottomTabBarHeightContext.Provider>
</HeaderHeightContext.Provider>
</View>
)
}
return (
<SafeAreaView className="flex flex-1 overflow-hidden">
<View className="flex flex-1 overflow-hidden">
<ListHeaderComponent listId={selectedCollection.listId} />
<ListView listId={selectedCollection.listId} />
</SafeAreaView>
</View>
)
}
@ -93,23 +91,6 @@ const ListView = ({ listId }: { listId: string }) => {
)
}
const FeedListView = ({ view }: { view: FeedViewType }) => {
const data = useSortedSubscription(view)
return (
<ScrollView>
{data.map((item, index) => (
<ItemRender
key={typeof item === "string" ? item : item.category}
item={item}
index={index}
/>
))}
{/* Just a placeholder */}
<View className="h-10" />
</ScrollView>
)
}
const ItemRender = ({
item,
}: {

View File

@ -1,16 +1,32 @@
import type { FeedViewType } from "@follow/constants"
import type { LayoutChangeEvent } from "react-native"
import { Text, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { BlurEffect } from "@/src/components/common/HeaderBlur"
import { useList } from "@/src/store/list/hooks"
import { SortActionButton } from "../subscription/header-actions"
import { useViewDefinition } from "./atoms"
export const ViewHeaderComponent = ({ view }: { view: FeedViewType }) => {
export const ViewHeaderComponent = ({
view,
onLayout,
}: {
view: FeedViewType
onLayout?: (event: LayoutChangeEvent) => void
}) => {
const viewDef = useViewDefinition(view)
const insets = useSafeAreaInsets()
return (
<View className="border-b-separator border-b-hairline flex flex-row items-center gap-2">
<Text className="text-text my-4 ml-4 text-2xl font-bold">{viewDef.name}</Text>
<View
onLayout={onLayout}
className="border-b-opaque-separator bg-system-background/90 border-b-hairline absolute inset-x-0 top-0 z-[999] flex flex-row items-center justify-between gap-2 px-4"
style={{ paddingTop: insets.top }}
>
<BlurEffect />
<Text className="text-text my-4 text-2xl font-bold">{viewDef.name}</Text>
<SortActionButton />
</View>
)
@ -18,17 +34,21 @@ export const ViewHeaderComponent = ({ view }: { view: FeedViewType }) => {
export const ListHeaderComponent = ({ listId }: { listId: string }) => {
const list = useList(listId)
const insets = useSafeAreaInsets()
if (!list) {
console.warn("list not found:", listId)
return null
}
return (
<View className="border-b-separator border-b-hairline flex flex-col gap-2 py-4">
<View
className="border-b-opaque-separator border-b-hairline flex flex-col gap-2 py-4"
style={{ paddingTop: insets.top }}
>
<View className="flex flex-row items-center gap-2">
<Text className="text-text ml-4 text-2xl font-bold">{list.title}</Text>
</View>
{list.description && (
<Text className="text-tertiary-label ml-4 text-sm font-medium">{list.description}</Text>
<Text className="text-secondary-label ml-4 text-sm font-medium">{list.description}</Text>
)}
</View>
)

View File

@ -1,7 +1,7 @@
import { StyleSheet, View } from "react-native"
const el = (
<View className="bg-opaque-separator w-full" style={{ height: StyleSheet.hairlineWidth }} />
<View className="bg-opaque-separator ml-12 flex-1" style={{ height: StyleSheet.hairlineWidth }} />
)
export const ItemSeparator = () => {
return el

View File

@ -62,7 +62,7 @@ export const SubscriptionLists = memo(() => {
].map((view) => {
return (
<ViewPageCurrentViewProvider key={view} value={view}>
<SubscriptionList view={view} />
<SubscriptionList view={view} additionalOffsetTop={ViewTabHeight * 2 + 23} />
</ViewPageCurrentViewProvider>
)
})}
@ -75,7 +75,13 @@ const keyExtractor = (item: string | { category: string; subscriptionIds: string
}
return item.category
}
const SubscriptionList = ({ view }: { view: FeedViewType }) => {
export const SubscriptionList = ({
view,
additionalOffsetTop,
}: {
view: FeedViewType
additionalOffsetTop?: number
}) => {
const headerHeight = useHeaderHeight()
const insets = useSafeAreaInsets()
const tabHeight = useBottomTabBarHeight()
@ -97,13 +103,13 @@ const SubscriptionList = ({ view }: { view: FeedViewType }) => {
return subscriptionSyncService.fetch(view)
})
const offsetTop = headerHeight - insets.top + ViewTabHeight * 2 + 23
const offsetTop = headerHeight - insets.top + (additionalOffsetTop || 0)
return (
<ReAnimated.FlatList
refreshControl={
<RefreshControl
progressViewOffset={offsetTop}
progressViewOffset={headerHeight}
onRefresh={() => {
setRefreshing(true)
onRefresh().finally(() => {

View File

@ -19,7 +19,6 @@ import {
import { SearchHeader } from "@/src/modules/discover/search"
import { SearchFeed } from "@/src/modules/discover/search-tabs/SearchFeed"
import { SearchList } from "@/src/modules/discover/search-tabs/SearchList"
import { SearchUser } from "@/src/modules/discover/search-tabs/SearchUser"
const Search = () => {
return (
@ -39,7 +38,7 @@ const Search = () => {
const SearchType2RenderContent: Record<SearchType, React.FC> = {
[SearchType.Feed]: SearchFeed,
[SearchType.List]: SearchList,
[SearchType.User]: SearchUser,
// [SearchType.User]: SearchUser,
}
const PlaceholderLazyView = () => {
const windowWidth = Dimensions.get("window").width