From 5ae6694da365c3c22b7dc00a999182e0310f6c38 Mon Sep 17 00:00:00 2001
From: MohamedBassem <me@mbassem.com>
Date: Fri, 19 Apr 2024 12:29:19 +0100
Subject: feature(mobile): Add support for nested listed on mobile

---
 apps/mobile/app/dashboard/(tabs)/lists.tsx | 107 ++++++++++++++++++++++++-----
 1 file changed, 89 insertions(+), 18 deletions(-)

(limited to 'apps/mobile/app/dashboard/(tabs)/lists.tsx')

diff --git a/apps/mobile/app/dashboard/(tabs)/lists.tsx b/apps/mobile/app/dashboard/(tabs)/lists.tsx
index 53817bf2..ed085f3d 100644
--- a/apps/mobile/app/dashboard/(tabs)/lists.tsx
+++ b/apps/mobile/app/dashboard/(tabs)/lists.tsx
@@ -4,11 +4,53 @@ import { Link } from "expo-router";
 import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
 import PageTitle from "@/components/ui/PageTitle";
 import { api } from "@/lib/trpc";
-import { ChevronRight } from "lucide-react-native";
+import { ChevronRight, Triangle } from "lucide-react-native";
+
+import { useBookmarkLists } from "@hoarder/shared-react/hooks/lists";
+import { ZBookmarkListTreeNode } from "@hoarder/shared/utils/listUtils";
+
+interface ListLink {
+  id: string;
+  logo: string;
+  name: string;
+  href: string;
+  level: number;
+  parent?: string;
+  numChildren: number;
+  collapsed: boolean;
+}
+
+function traverseTree(
+  node: ZBookmarkListTreeNode,
+  links: ListLink[],
+  showChildrenOf: Record<string, boolean>,
+  parent?: string,
+  level = 0,
+) {
+  links.push({
+    id: node.item.id,
+    logo: node.item.icon,
+    name: node.item.name,
+    href: `/dashboard/lists/${node.item.id}`,
+    level,
+    parent,
+    numChildren: node.children?.length ?? 0,
+    collapsed: !showChildrenOf[node.item.id],
+  });
+
+  if (node.children && showChildrenOf[node.item.id]) {
+    node.children.forEach((child) =>
+      traverseTree(child, links, showChildrenOf, node.item.id, level + 1),
+    );
+  }
+}
 
 export default function Lists() {
   const [refreshing, setRefreshing] = useState(false);
-  const { data: lists, isPending } = api.lists.list.useQuery();
+  const { data: lists, isPending } = useBookmarkLists();
+  const [showChildrenOf, setShowChildrenOf] = useState<Record<string, boolean>>(
+    {},
+  );
   const apiUtils = api.useUtils();
 
   useEffect(() => {
@@ -24,46 +66,75 @@ export default function Lists() {
     apiUtils.lists.list.invalidate();
   };
 
-  const links = [
+  const links: ListLink[] = [
     {
       id: "fav",
       logo: "⭐️",
       name: "Favourites",
       href: "/dashboard/favourites",
+      level: 0,
+      numChildren: 0,
+      collapsed: false,
     },
     {
       id: "arch",
       logo: "🗄️",
       name: "Archive",
       href: "/dashboard/archive",
+      level: 0,
+      numChildren: 0,
+      collapsed: false,
     },
   ];
 
-  links.push(
-    ...lists.lists.map((l) => ({
-      id: l.id,
-      logo: l.icon,
-      name: l.name,
-      href: `/dashboard/lists/${l.id}`,
-    })),
+  Object.values(lists.root).forEach((list) =>
+    traverseTree(list, links, showChildrenOf),
   );
 
   return (
     <CustomSafeAreaView>
       <FlatList
+        className="h-full"
         ListHeaderComponent={<PageTitle title="Lists" />}
         contentContainerStyle={{
           gap: 5,
         }}
         renderItem={(l) => (
-          <Link asChild key={l.item.id} href={l.item.href}>
-            <Pressable className="mx-2 flex flex-row justify-between rounded-xl border border-input bg-white px-4 py-2 dark:bg-accent">
-              <Text className="text-lg text-accent-foreground">
-                {l.item.logo} {l.item.name}
-              </Text>
-              <ChevronRight color="rgb(0, 122, 255)" />
-            </Pressable>
-          </Link>
+          <View
+            className="mx-2 flex flex-row items-center gap-x-2 rounded-xl border border-input bg-white px-4 py-2 dark:bg-accent"
+            style={{ marginLeft: l.item.level * 20 }}
+          >
+            {l.item.numChildren > 0 && (
+              <Pressable
+                onPress={() => {
+                  setShowChildrenOf((prev) => ({
+                    ...prev,
+                    [l.item.id]: !prev[l.item.id],
+                  }));
+                }}
+              >
+                <Triangle
+                  fill="black"
+                  color="black"
+                  size={10}
+                  style={{
+                    transform: [
+                      { rotate: l.item.collapsed ? "90deg" : "180deg" },
+                    ],
+                  }}
+                />
+              </Pressable>
+            )}
+
+            <Link asChild key={l.item.id} href={l.item.href} className="flex-1">
+              <Pressable className="flex flex-row justify-between">
+                <Text className="text-lg text-accent-foreground">
+                  {l.item.logo} {l.item.name}
+                </Text>
+                <ChevronRight color="rgb(0, 122, 255)" />
+              </Pressable>
+            </Link>
+          </View>
         )}
         data={links}
         refreshing={refreshing}
-- 
cgit v1.2.3-70-g09d2

