refactor(rn): subscription drawer layout
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
fe44c38b49
commit
2edebdfcdb
|
|
@ -0,0 +1,3 @@
|
|||
import { Platform } from "react-native"
|
||||
|
||||
export const isIOS = Platform.OS === "ios"
|
||||
|
|
@ -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 },
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue