Skip to content

Commit

Permalink
feat: enhance collect select state
Browse files Browse the repository at this point in the history
  • Loading branch information
lawvs committed Jan 8, 2025
1 parent 702147c commit 5223c65
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 25 deletions.
42 changes: 41 additions & 1 deletion apps/mobile/src/modules/feed-drawer/atoms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { FeedViewType } from "@follow/constants"
import { jotaiStore } from "@follow/utils"
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai"
import { useCallback } from "react"
import { useCallback, useMemo } from "react"

import { views } from "@/src/constants/views"

// drawer open state

const drawerOpenAtom = atom<boolean>(false)

Expand All @@ -14,6 +20,8 @@ export function useFeedDrawer() {
}
}

// is drawer swipe disabled

const isDrawerSwipeDisabledAtom = atom<boolean>(false)

export function useIsDrawerSwipeDisabled() {
Expand All @@ -23,3 +31,35 @@ export function useIsDrawerSwipeDisabled() {
export function useSetDrawerSwipeDisabled() {
return useSetAtom(isDrawerSwipeDisabledAtom)
}

// collection panel selected state

type CollectionPanelState =
| {
type: "view"
viewId: FeedViewType
}
| {
type: "list"
listId: string
}

const collectionPanelStateAtom = atom<CollectionPanelState>({
type: "view",
viewId: FeedViewType.Articles,
})

export function useSelectedCollection() {
return useAtomValue(collectionPanelStateAtom)
}
export const selectCollection = (state: CollectionPanelState) => {
jotaiStore.set(collectionPanelStateAtom, state)
}

export const useViewDefinition = (view: FeedViewType) => {
const viewDef = useMemo(() => views.find((v) => v.view === view), [view])
if (!viewDef) {
throw new Error(`View ${view} not found`)
}
return viewDef
}
67 changes: 58 additions & 9 deletions apps/mobile/src/modules/feed-drawer/collection-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,90 @@
import { cn } from "@follow/utils"
import { SafeAreaView, ScrollView, TouchableOpacity, useWindowDimensions } from "react-native"
import {
Image,
SafeAreaView,
ScrollView,
TouchableOpacity,
useWindowDimensions,
View,
} from "react-native"

import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
import type { ViewDefinition } from "@/src/constants/views"
import { views } from "@/src/constants/views"
import { useList } from "@/src/store/list/hooks"
import { useAllListSubscription } from "@/src/store/subscription/hooks"

import { setCurrentView, useCurrentView } from "../subscription/atoms"
import { selectCollection, useSelectedCollection } from "./atoms"

export const CollectionPanel = () => {
const winDim = useWindowDimensions()
const lists = useAllListSubscription()

return (
<SafeAreaView
className="bg-secondary-system-background"
style={{ width: Math.max(50, winDim.width * 0.15) }}
>
<ScrollView contentContainerClassName="flex py-3 gap-3">
{views.map((view) => (
<CollectionButton key={view.name} view={view} />
{views.map((viewDef) => (
<ViewButton key={viewDef.name} viewDef={viewDef} />
))}
{lists.map((listId) => (
<ListButton key={listId} listId={listId} />
))}
</ScrollView>
</SafeAreaView>
)
}

const CollectionButton = ({ view }: { view: ViewDefinition }) => {
const selectedView = useCurrentView()
const isActive = selectedView === view.view
const ViewButton = ({ viewDef }: { viewDef: ViewDefinition }) => {
const selectedCollection = useSelectedCollection()
const isActive = selectedCollection.type === "view" && selectedCollection.viewId === viewDef.view

return (
<TouchableOpacity
className={cn(
"mx-3 flex aspect-square items-center justify-center rounded-lg p-3",
isActive ? "bg-system-fill" : "bg-system-background",
)}
onPress={() =>
selectCollection({
type: "view",
viewId: viewDef.view,
})
}
>
<viewDef.icon key={viewDef.name} color={viewDef.activeColor} />
</TouchableOpacity>
)
}

const ListButton = ({ listId }: { listId: string }) => {
const list = useList(listId)
const selectedCollection = useSelectedCollection()
const isActive = selectedCollection.type === "list" && selectedCollection.listId === listId
if (!list) return null

return (
<TouchableOpacity
className={cn(
"mx-3 flex aspect-square items-center justify-center rounded-lg p-3",
isActive ? "bg-system-fill" : "bg-system-background",
)}
onPress={() => setCurrentView(view.view)}
onPress={() =>
selectCollection({
type: "list",
listId,
})
}
>
<view.icon key={view.name} color={view.activeColor} />
<View className="overflow-hidden rounded">
{list.image ? (
<Image source={{ uri: list.image, width: 24, height: 24 }} resizeMode="cover" />
) : (
<FallbackIcon title={list.title} size={24} />
)}
</View>
</TouchableOpacity>
)
}
30 changes: 15 additions & 15 deletions apps/mobile/src/modules/feed-drawer/feed-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ import {
SubscriptionFeedCategoryContextMenu,
SubscriptionFeedItemContextMenu,
} from "../context-menu/feeds"
import {
useCurrentView,
useCurrentViewDefinition,
useFeedListSortMethod,
useFeedListSortOrder,
} from "../subscription/atoms"
import { useCurrentView, useFeedListSortMethod, useFeedListSortOrder } from "../subscription/atoms"
import { SortActionButton } from "../subscription/header-actions"
import { useSelectedCollection, useViewDefinition } from "./atoms"

export const FeedPanel = () => {
const selectedView = useCurrentView()
return (
<SafeAreaView className="flex flex-1 overflow-hidden">
<ListHeaderComponent />
<RecycleList view={selectedView} />
</SafeAreaView>
)
const selectedCollection = useSelectedCollection()
if (selectedCollection.type === "view") {
return (
<SafeAreaView className="flex flex-1 overflow-hidden">
<ViewHeaderComponent view={selectedCollection.viewId} />
<RecycleList view={selectedCollection.viewId} />
</SafeAreaView>
)
}

return
}

const ListHeaderComponent = () => {
const viewDef = useCurrentViewDefinition()
const ViewHeaderComponent = ({ view }: { view: FeedViewType }) => {
const viewDef = useViewDefinition(view)
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>
Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/src/store/subscription/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ export const useSubscription = (id: string) => {
})
}

export const useAllListSubscription = () => {
return useSubscriptionStore(
useCallback((state) => {
return Object.values(state.listIdByView).flatMap((list) => Array.from(list))
}, []),
)
}

export const useListSubscription = (view: FeedViewType) => {
return useSubscriptionStore(
useCallback(
Expand Down

0 comments on commit 5223c65

Please sign in to comment.