From ed86f7ef012fb558fe8a8974e1e162ce75cbfd15 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Tue, 26 Aug 2025 15:47:05 +0300 Subject: feat(mobile): Retheme the mobile app (#1872) * Add nativewindui * migrate to nativewindui text * Replace buttons with nativewindui buttons * Use nativewindui search input * fix the divider color * More changes * fix manage tag icon * fix styling of bookmark card * fix ios compilation * fix search clear * fix tag pill border color * Store theme setting in app settings * fix setting color appearance * fix coloring of search input * fix following system theme * add a save button to info * fix the grey colors on android * fix icon active tint color * drop the use of TextField --- .../app/dashboard/bookmarks/[slug]/index.tsx | 11 +- .../mobile/app/dashboard/bookmarks/[slug]/info.tsx | 287 +++++++++++++-------- .../dashboard/bookmarks/[slug]/manage_lists.tsx | 7 +- .../app/dashboard/bookmarks/[slug]/manage_tags.tsx | 26 +- apps/mobile/app/dashboard/bookmarks/new.tsx | 8 +- 5 files changed, 198 insertions(+), 141 deletions(-) (limited to 'apps/mobile/app/dashboard/bookmarks') diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx index eafcfc19..3b1300ca 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/index.tsx @@ -25,6 +25,7 @@ import { Button } from "@/components/ui/Button"; import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView"; import FullPageSpinner from "@/components/ui/FullPageSpinner"; import { Input } from "@/components/ui/Input"; +import { Text } from "@/components/ui/Text"; import { useToast } from "@/components/ui/Toast"; import { useAssetUrl } from "@/lib/hooks"; import useAppSettings from "@/lib/settings"; @@ -296,15 +297,17 @@ function BookmarkTextView({ bookmark }: { bookmark: ZBookmark }) { {isEditing && ( - )} diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx index af124160..1781ec74 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx @@ -1,26 +1,22 @@ import React from "react"; +import { Alert, Pressable, View } from "react-native"; import { - Alert, - Keyboard, - Pressable, - Text, - TouchableWithoutFeedback, - View, -} from "react-native"; -import Animated, { - useAnimatedKeyboard, - useAnimatedStyle, -} from "react-native-reanimated"; + KeyboardAwareScrollView, + KeyboardGestureArea, +} from "react-native-keyboard-controller"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; import { router, Stack, useLocalSearchParams } from "expo-router"; import TagPill from "@/components/bookmarks/TagPill"; import FullPageError from "@/components/FullPageError"; import { Button } from "@/components/ui/Button"; +import ChevronRight from "@/components/ui/ChevronRight"; import { Divider } from "@/components/ui/Divider"; import FullPageSpinner from "@/components/ui/FullPageSpinner"; import { Input } from "@/components/ui/Input"; import { Skeleton } from "@/components/ui/Skeleton"; +import { Text } from "@/components/ui/Text"; import { useToast } from "@/components/ui/Toast"; -import { ChevronRight } from "lucide-react-native"; +import { cn } from "@/lib/utils"; import { useAutoRefreshingBookmarkQuery, @@ -30,9 +26,21 @@ import { import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; import { isBookmarkStillTagging } from "@karakeep/shared/utils/bookmarkUtils"; +function InfoSection({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + function TagList({ bookmark }: { bookmark: ZBookmark }) { return ( - + {isBookmarkStillTagging(bookmark) ? ( @@ -41,7 +49,7 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) { ) : ( bookmark.tags.length > 0 && ( <> - + {bookmark.tags.map((t) => ( ))} @@ -50,94 +58,104 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) { ) )} - - router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`) - } - className="flex w-full flex-row justify-between gap-3 px-4" - > - Manage Tags - - - + + + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`) + } + className="flex w-full flex-row justify-between gap-3" + > + Manage Tags + + + + ); } function ManageLists({ bookmark }: { bookmark: ZBookmark }) { return ( - - - router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`) - } - className="flex w-full flex-row justify-between gap-3 rounded-lg bg-white px-4 py-2 dark:bg-accent" - > - Manage Lists - - - + + + + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`) + } + className="flex w-full flex-row justify-between gap-3 rounded-lg" + > + Manage Lists + + + + ); } function TitleEditor({ - bookmarkId, title, + setTitle, + isPending, }: { - bookmarkId: string; - title: string; + title: string | null | undefined; + setTitle: (title: string | null) => void; + isPending: boolean; }) { - const { mutate, isPending } = useUpdateBookmark(); return ( - + - mutate({ - bookmarkId, - title: ev.nativeEvent.text ? ev.nativeEvent.text : null, - }) - } + onChangeText={(text) => setTitle(text)} defaultValue={title ?? ""} /> - + ); } -function NotesEditor({ bookmark }: { bookmark: ZBookmark }) { - const { mutate, isPending } = useUpdateBookmark(); +function NotesEditor({ + notes, + setNotes, + isPending, +}: { + notes: string | null | undefined; + setNotes: (title: string | null) => void; + isPending: boolean; +}) { return ( - + setNotes(text)} textAlignVertical="top" - onEndEditing={(ev) => - mutate({ - bookmarkId: bookmark.id, - note: ev.nativeEvent.text, - }) - } - defaultValue={bookmark.note ?? ""} + defaultValue={notes ?? ""} /> - + ); } const ViewBookmarkPage = () => { + const insets = useSafeAreaInsets(); const { slug } = useLocalSearchParams(); const { toast } = useToast(); if (typeof slug !== "string") { throw new Error("Unexpected param type"); } + const { mutate: editBookmark, isPending: isEditPending } = useUpdateBookmark({ + onSuccess: () => { + toast({ + message: "The bookmark has been updated!", + showProgress: false, + }); + setEditedBookmark({}); + }, + }); + const { mutate: deleteBookmark, isPending: isDeletionPending } = useDeleteBookmark({ onSuccess: () => { @@ -149,12 +167,6 @@ const ViewBookmarkPage = () => { }, }); - const keyboard = useAnimatedKeyboard(); - - const animatedStyles = useAnimatedStyle(() => ({ - marginBottom: keyboard.height.value, - })); - const { data: bookmark, isPending, @@ -163,6 +175,11 @@ const ViewBookmarkPage = () => { bookmarkId: slug, }); + const [editedBookmark, setEditedBookmark] = React.useState<{ + title?: string | null; + note?: string; + }>({}); + if (isPending) { return ; } @@ -188,6 +205,27 @@ const ViewBookmarkPage = () => { ); }; + const onDone = () => { + const doDone = () => { + if (router.canGoBack()) { + router.back(); + } else { + router.replace("dashboard"); + } + }; + if (Object.keys(editedBookmark).length === 0) { + doDone(); + return; + } + Alert.alert("You have unsaved changes", "Do you still want to leave?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Leave", + onPress: doDone, + }, + ]); + }; + let title = null; switch (bookmark.content.type) { case BookmarkTypes.LINK: @@ -201,56 +239,77 @@ const ViewBookmarkPage = () => { break; } return ( - - ( - { - if (router.canGoBack()) { - router.back(); - } else { - router.replace("dashboard"); - } - }} + + + ( + + Done + + ), + }} + /> + + + setEditedBookmark((prev) => ({ ...prev, title })) + } + isPending={isEditPending} + /> + + + + setEditedBookmark((prev) => ({ ...prev, note: note ?? "" })) + } + isPending={isEditPending} + /> + + + + + + Created {bookmark.createdAt.toLocaleString()} + + {bookmark.modifiedAt && + bookmark.modifiedAt.getTime() !== + bookmark.createdAt.getTime() && ( + + Modified {bookmark.modifiedAt.toLocaleString()} + + )} - - - + + + ); }; diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx index 9f2149ae..7250d06b 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_lists.tsx @@ -1,8 +1,9 @@ import React from "react"; -import { FlatList, Pressable, Text, View } from "react-native"; +import { FlatList, Pressable, View } from "react-native"; import Checkbox from "expo-checkbox"; import { useLocalSearchParams } from "expo-router"; import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView"; +import { Text } from "@/components/ui/Text"; import { useToast } from "@/components/ui/Toast"; import { @@ -75,13 +76,13 @@ const ListPickerPage = () => { gap: 5, }} renderItem={(l) => ( - + toggleList(l.item[l.item.length - 1].id)} className="flex w-full flex-row justify-between" > - + {l.item.map((item) => `${item.icon} ${item.name}`).join(" / ")} { + const { colors } = useColorScheme(); const { slug: bookmarkId } = useLocalSearchParams(); const [search, setSearch] = React.useState(""); @@ -211,20 +207,14 @@ const ListPickerPage = () => { }) } > - + {t.section.title == "Existing Tags" && ( - } - /> + )} {t.section.title == "All Tags" && t.item.id == NEW_TAG_ID && ( - } - /> + )} - + {t.item.id == NEW_TAG_ID ? `Create new tag '${t.item.name}'` : t.item.name} diff --git a/apps/mobile/app/dashboard/bookmarks/new.tsx b/apps/mobile/app/dashboard/bookmarks/new.tsx index d24c1597..50f8f2a7 100644 --- a/apps/mobile/app/dashboard/bookmarks/new.tsx +++ b/apps/mobile/app/dashboard/bookmarks/new.tsx @@ -1,9 +1,10 @@ import React, { useState } from "react"; -import { Text, View } from "react-native"; +import { View } from "react-native"; import { router } from "expo-router"; import { Button } from "@/components/ui/Button"; import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView"; import { Input } from "@/components/ui/Input"; +import { Text } from "@/components/ui/Text"; import { useToast } from "@/components/ui/Toast"; import { useCreateBookmark } from "@karakeep/shared-react/hooks/bookmarks"; @@ -61,13 +62,16 @@ const NoteEditorPage = () => { )} - ); -- cgit v1.2.3-70-g09d2