diff options
| author | MohamedBassem <me@mbassem.com> | 2024-04-17 17:56:21 +0100 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-04-17 18:13:31 +0100 |
| commit | c46482cdaaf883971736488750513663dd023076 (patch) | |
| tree | 9e3d70fd9e7ae39f8ef21e0651049558e5c5fa5b | |
| parent | bb44ebcb9967bde81d15e2f7858d515777681c10 (diff) | |
| download | karakeep-c46482cdaaf883971736488750513663dd023076.tar.zst | |
mobile: Add dark mode support
22 files changed, 188 insertions, 56 deletions
diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 592824b7..0cba2b94 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -6,16 +6,21 @@ "version": "1.3.2", "orientation": "portrait", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "splash": { - "image": "./assets/splash.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, + "userInterfaceStyle": "automatic", "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "bundleIdentifier": "app.hoarder.hoardermobile", + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "image": "./assets/splash-white.png", + "resizeMode": "contain", + "backgroundColor": "#000000" + } + }, "config": { "usesNonExemptEncryption": false }, @@ -31,6 +36,16 @@ "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#000000" }, + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff", + "dark": { + "image": "./assets/splash-white.png", + "resizeMode": "contain", + "backgroundColor": "#000000" + } + }, "package": "app.hoarder.hoardermobile", "versionCode": 2 }, diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx index f36c9eec..a5aafb8c 100644 --- a/apps/mobile/app/_layout.tsx +++ b/apps/mobile/app/_layout.tsx @@ -7,11 +7,15 @@ import { useRouter } from "expo-router"; import { Stack } from "expo-router/stack"; import { ShareIntentProvider, useShareIntent } from "expo-share-intent"; import { StatusBar } from "expo-status-bar"; +import { StyledStack } from "@/components/navigation/stack"; import { Providers } from "@/lib/providers"; +import { cn } from "@/lib/utils"; +import { useColorScheme } from "nativewind"; export default function RootLayout() { const router = useRouter(); const { hasShareIntent } = useShareIntent(); + const { colorScheme } = useColorScheme(); useEffect(() => { if (hasShareIntent) { @@ -24,8 +28,14 @@ export default function RootLayout() { return ( <ShareIntentProvider> <Providers> - <View className="w-full flex-1 bg-background"> - <Stack + <View + className={cn( + "w-full flex-1 bg-gray-100 text-foreground dark:bg-background", + colorScheme == "dark" ? "dark" : "light", + )} + > + <StyledStack + contentClassName="bg-gray-100 dark:bg-background" screenOptions={{ headerShown: false, }} @@ -37,7 +47,7 @@ export default function RootLayout() { presentation: "modal", }} /> - </Stack> + </StyledStack> <StatusBar style="auto" /> </View> </Providers> diff --git a/apps/mobile/app/dashboard/(tabs)/_layout.tsx b/apps/mobile/app/dashboard/(tabs)/_layout.tsx index 7967b5c6..cf7e473f 100644 --- a/apps/mobile/app/dashboard/(tabs)/_layout.tsx +++ b/apps/mobile/app/dashboard/(tabs)/_layout.tsx @@ -1,17 +1,13 @@ -import React, { useEffect } from "react"; -import { Platform } from "react-native"; -import * as NavigationBar from "expo-navigation-bar"; +import React from "react"; import { Tabs } from "expo-router"; +import { StyledTabs } from "@/components/navigation/tabs"; import { ClipboardList, Home, Search, Settings } from "lucide-react-native"; export default function TabLayout() { - useEffect(() => { - if (Platform.OS == "android") { - NavigationBar.setBackgroundColorAsync("white"); - } - }, []); return ( - <Tabs + <StyledTabs + tabBarClassName="bg-gray-100 dark:bg-background pt-3" + sceneContainerClassName="bg-gray-100 dark:bg-background" screenOptions={{ headerShown: false, }} @@ -44,6 +40,6 @@ export default function TabLayout() { tabBarIcon: ({ color }) => <Settings color={color} />, }} /> - </Tabs> + </StyledTabs> ); } diff --git a/apps/mobile/app/dashboard/(tabs)/lists.tsx b/apps/mobile/app/dashboard/(tabs)/lists.tsx index 767b9256..53817bf2 100644 --- a/apps/mobile/app/dashboard/(tabs)/lists.tsx +++ b/apps/mobile/app/dashboard/(tabs)/lists.tsx @@ -57,8 +57,8 @@ export default function Lists() { }} 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-gray-100 bg-white px-4 py-2"> - <Text className="text-lg"> + <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)" /> diff --git a/apps/mobile/app/dashboard/(tabs)/search.tsx b/apps/mobile/app/dashboard/(tabs)/search.tsx index 0a4dcbfd..1a79b921 100644 --- a/apps/mobile/app/dashboard/(tabs)/search.tsx +++ b/apps/mobile/app/dashboard/(tabs)/search.tsx @@ -34,7 +34,7 @@ export default function Search() { <PageTitle title="Search" /> <Input placeholder="Search" - className="mx-4 bg-white" + className="mx-4" value={search} onChangeText={setSearch} autoFocus diff --git a/apps/mobile/app/dashboard/(tabs)/settings.tsx b/apps/mobile/app/dashboard/(tabs)/settings.tsx index c2db2846..93033926 100644 --- a/apps/mobile/app/dashboard/(tabs)/settings.tsx +++ b/apps/mobile/app/dashboard/(tabs)/settings.tsx @@ -18,8 +18,8 @@ export default function Dashboard() { <CustomSafeAreaView> <PageTitle title="Settings" /> <View className="flex h-full w-full items-center gap-4 px-4 py-2"> - <View className="w-full rounded-lg bg-white px-4 py-2"> - <Text className="text-lg"> + <View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent"> + <Text className="text-lg text-accent-foreground"> {isLoading ? "Loading ..." : data?.email} </Text> </View> diff --git a/apps/mobile/app/dashboard/_layout.tsx b/apps/mobile/app/dashboard/_layout.tsx index c9f7355b..7548a6db 100644 --- a/apps/mobile/app/dashboard/_layout.tsx +++ b/apps/mobile/app/dashboard/_layout.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react"; import { AppState, Platform } from "react-native"; import { useRouter } from "expo-router"; import { Stack } from "expo-router/stack"; +import { StyledStack } from "@/components/navigation/stack"; import { useIsLoggedIn } from "@/lib/session"; import { focusManager } from "@tanstack/react-query"; @@ -29,7 +30,10 @@ export default function Dashboard() { }, []); return ( - <Stack> + <StyledStack + contentClassName="bg-gray-100 dark:bg-background" + headerClassName="bg-gray-100 dark:bg-background text-foreground" + > <Stack.Screen name="(tabs)" options={{ headerShown: false, title: "Home" }} @@ -64,6 +68,6 @@ export default function Dashboard() { presentation: "modal", }} /> - </Stack> + </StyledStack> ); } diff --git a/apps/mobile/app/dashboard/add-link.tsx b/apps/mobile/app/dashboard/add-link.tsx index 568a36b6..d913ac01 100644 --- a/apps/mobile/app/dashboard/add-link.tsx +++ b/apps/mobile/app/dashboard/add-link.tsx @@ -39,7 +39,6 @@ export default function AddNote() { <Text className="w-full text-center text-red-500">{error}</Text> )} <Input - className="bg-white" value={text} onChangeText={setText} placeholder="Link" diff --git a/apps/mobile/app/dashboard/add-note.tsx b/apps/mobile/app/dashboard/add-note.tsx index 1f903e94..40c97456 100644 --- a/apps/mobile/app/dashboard/add-note.tsx +++ b/apps/mobile/app/dashboard/add-note.tsx @@ -39,7 +39,6 @@ export default function AddNote() { <Text className="w-full text-center text-red-500">{error}</Text> )} <Input - className="bg-white" value={text} onChangeText={setText} multiline diff --git a/apps/mobile/app/sharing.tsx b/apps/mobile/app/sharing.tsx index c178d69e..bbeaeeee 100644 --- a/apps/mobile/app/sharing.tsx +++ b/apps/mobile/app/sharing.tsx @@ -66,7 +66,7 @@ function SaveBookmark({ setMode }: { setMode: (mode: Mode) => void }) { return ( <View className="flex flex-row gap-3"> - <Text className="text-4xl">Hoarding</Text> + <Text className="text-4xl text-foreground">Hoarding</Text> <ActivityIndicator /> </View> ); @@ -83,11 +83,11 @@ export default function Sharing() { break; } case "success": { - comp = <Text className="text-4xl">Hoarded!</Text>; + comp = <Text className="text-4xl text-foreground">Hoarded!</Text>; break; } case "error": { - comp = <Text className="text-4xl">Error!</Text>; + comp = <Text className="text-4xl text-foreground">Error!</Text>; break; } } diff --git a/apps/mobile/app/signin.tsx b/apps/mobile/app/signin.tsx index 80a5f219..c524d1e0 100644 --- a/apps/mobile/app/signin.tsx +++ b/apps/mobile/app/signin.tsx @@ -9,6 +9,7 @@ import { } from "react-native"; import { Redirect } from "expo-router"; import Logo from "@/components/Logo"; +import { TailwindResolver } from "@/components/TailwindResolver"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import useAppSettings from "@/lib/settings"; @@ -57,7 +58,16 @@ export default function Signin() { <TouchableWithoutFeedback onPress={Keyboard.dismiss}> <View className="flex h-full flex-col justify-center gap-2 px-4"> <View className="items-center"> - <Logo height={150} width={200} /> + <TailwindResolver + className="color-foreground" + comp={(styles) => ( + <Logo + height={150} + width={200} + fill={styles?.color?.toString()} + /> + )} + /> </View> {error && ( <Text className="w-full text-center text-red-500">{error}</Text> diff --git a/apps/mobile/assets/splash-white.png b/apps/mobile/assets/splash-white.png Binary files differnew file mode 100644 index 00000000..23df57d7 --- /dev/null +++ b/apps/mobile/assets/splash-white.png diff --git a/apps/mobile/components/TailwindResolver.tsx b/apps/mobile/components/TailwindResolver.tsx new file mode 100644 index 00000000..d5e084cd --- /dev/null +++ b/apps/mobile/components/TailwindResolver.tsx @@ -0,0 +1,16 @@ +import { TextStyle, ViewStyle } from "react-native"; +import { cssInterop } from "nativewind"; + +function TailwindResolverImpl({ + comp, + style, +}: { + comp: (style?: ViewStyle & TextStyle) => React.ReactNode; + style?: ViewStyle & TextStyle; +}) { + return comp(style); +} + +export const TailwindResolver = cssInterop(TailwindResolverImpl, { + className: "style", +}); diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index 76a05aef..9e5febe3 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -22,6 +22,7 @@ import { useUpdateBookmark, } from "@hoarder/shared-react/hooks/bookmarks"; +import { TailwindResolver } from "../TailwindResolver"; import { Divider } from "../ui/Divider"; import { Skeleton } from "../ui/Skeleton"; import { useToast } from "../ui/Toast"; @@ -162,9 +163,11 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) { {tags.map((t) => ( <View key={t.id} - className="rounded-full border border-gray-200 px-2.5 py-0.5 text-xs font-semibold" + className="rounded-full border border-accent px-2.5 py-0.5 text-xs font-semibold" > - <Link href={`dashboard/tags/${t.id}`}>{t.name}</Link> + <Link className="text-foreground" href={`dashboard/tags/${t.id}`}> + {t.name} + </Link> </View> ))} </View> @@ -198,7 +201,7 @@ function LinkCard({ bookmark }: { bookmark: ZBookmark }) { {imageComp} <View className="flex gap-2 p-2"> <Text - className="line-clamp-2 text-xl font-bold" + className="line-clamp-2 text-xl font-bold text-foreground" onPress={() => WebBrowser.openBrowserAsync(url)} > {bookmark.title ?? bookmark.content.title ?? parsedUrl.host} @@ -206,7 +209,9 @@ function LinkCard({ bookmark }: { bookmark: ZBookmark }) { <TagList bookmark={bookmark} /> <Divider orientation="vertical" className="mt-2 h-0.5 w-full" /> <View className="mt-2 flex flex-row justify-between px-2 pb-2"> - <Text className="my-auto line-clamp-1">{parsedUrl.host}</Text> + <Text className="my-auto line-clamp-1 text-foreground"> + {parsedUrl.host} + </Text> <ActionBar bookmark={bookmark} /> </View> </View> @@ -218,13 +223,29 @@ function TextCard({ bookmark }: { bookmark: ZBookmark }) { if (bookmark.content.type !== "text") { throw new Error("Wrong content type rendered"); } + const content = bookmark.content.text; return ( <View className="flex max-h-96 gap-2 p-2"> {bookmark.title && ( - <Text className="line-clamp-2 text-xl font-bold">{bookmark.title}</Text> + <Text className="line-clamp-2 text-xl font-bold text-foreground"> + {bookmark.title} + </Text> )} - <View className="max-h-56 overflow-hidden p-2"> - <Markdown>{bookmark.content.text}</Markdown> + <View className="max-h-56 overflow-hidden p-2 text-foreground"> + <TailwindResolver + className="text-foreground" + comp={(styles) => ( + <Markdown + style={{ + text: { + color: styles?.color?.toString(), + }, + }} + > + {content} + </Markdown> + )} + /> </View> <TagList bookmark={bookmark} /> <Divider orientation="vertical" className="mt-2 h-0.5 w-full" /> @@ -256,7 +277,9 @@ function AssetCard({ bookmark }: { bookmark: ZBookmark }) { /> <View className="flex gap-2 p-2"> {title && ( - <Text className="line-clamp-2 text-xl font-bold">{title}</Text> + <Text className="line-clamp-2 text-xl font-bold text-foreground"> + {title} + </Text> )} <TagList bookmark={bookmark} /> <Divider orientation="vertical" className="mt-2 h-0.5 w-full" /> @@ -307,5 +330,5 @@ export default function BookmarkCard({ break; } - return <View className="border-b border-gray-300 bg-white">{comp}</View>; + return <View className="border-b border-accent bg-background">{comp}</View>; } diff --git a/apps/mobile/components/bookmarks/BookmarkList.tsx b/apps/mobile/components/bookmarks/BookmarkList.tsx index 7477992d..3ad23072 100644 --- a/apps/mobile/components/bookmarks/BookmarkList.tsx +++ b/apps/mobile/components/bookmarks/BookmarkList.tsx @@ -37,7 +37,7 @@ export default function BookmarkList({ renderItem={(b) => <BookmarkCard bookmark={b.item} />} ListEmptyComponent={ <View className="items-center justify-center pt-4"> - <Text className="text-xl">No Bookmarks</Text> + <Text className="text-xl text-foreground">No Bookmarks</Text> </View> } data={bookmarks} diff --git a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx index 8495ee22..efc0d5e7 100644 --- a/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx +++ b/apps/mobile/components/bookmarks/UpdatingBookmarkList.tsx @@ -4,7 +4,7 @@ import { api } from "@/lib/trpc"; import type { ZGetBookmarksRequest } from "@hoarder/trpc/types/bookmarks"; import FullPageSpinner from "../ui/FullPageSpinner"; -import BookmarkList2 from "./BookmarkList"; +import BookmarkList from "./BookmarkList"; export default function UpdatingBookmarkList({ query, @@ -40,7 +40,7 @@ export default function UpdatingBookmarkList({ }; return ( - <BookmarkList2 + <BookmarkList bookmarks={data.pages.flatMap((p) => p.bookmarks)} header={header} onRefresh={onRefresh} diff --git a/apps/mobile/components/navigation/stack.tsx b/apps/mobile/components/navigation/stack.tsx new file mode 100644 index 00000000..f53b3652 --- /dev/null +++ b/apps/mobile/components/navigation/stack.tsx @@ -0,0 +1,27 @@ +import { TextStyle, ViewStyle } from "react-native"; +import { Stack } from "expo-router/stack"; +import { cssInterop } from "nativewind"; + +interface StackProps extends React.ComponentProps<typeof Stack> { + contentStyle?: ViewStyle; + headerStyle?: TextStyle; +} + +function StackImpl({ contentStyle, headerStyle, ...props }: StackProps) { + props.screenOptions = { + ...props.screenOptions, + contentStyle, + headerStyle: { + backgroundColor: headerStyle?.backgroundColor?.toString(), + }, + navigationBarColor: contentStyle?.backgroundColor?.toString(), + headerTintColor: headerStyle?.color?.toString(), + }; + return <Stack {...props} />; +} + +// Changing this requires reloading the app +export const StyledStack = cssInterop(StackImpl, { + contentClassName: "contentStyle", + headerClassName: "headerStyle", +}); diff --git a/apps/mobile/components/navigation/tabs.tsx b/apps/mobile/components/navigation/tabs.tsx new file mode 100644 index 00000000..976731bc --- /dev/null +++ b/apps/mobile/components/navigation/tabs.tsx @@ -0,0 +1,25 @@ +import { ViewStyle } from "react-native"; +import { Tabs } from "expo-router"; +import { cssInterop } from "nativewind"; + +function StyledTabsImpl({ + tabBarStyle, + headerStyle, + ...props +}: React.ComponentProps<typeof Tabs> & { + tabBarStyle?: ViewStyle; + headerStyle?: ViewStyle; +}) { + props.screenOptions = { + ...props.screenOptions, + tabBarStyle, + headerStyle, + }; + return <Tabs {...props} />; +} + +export const StyledTabs = cssInterop(StyledTabsImpl, { + tabBarClassName: "tabBarStyle", + headerClassName: "headerStyle", + sceneContainerClassName: "sceneContainerStyle", +}); diff --git a/apps/mobile/components/ui/Divider.tsx b/apps/mobile/components/ui/Divider.tsx index cf1b4624..fbc5cf64 100644 --- a/apps/mobile/components/ui/Divider.tsx +++ b/apps/mobile/components/ui/Divider.tsx @@ -2,7 +2,6 @@ import { View } from "react-native"; import { cn } from "@/lib/utils"; function Divider({ - color = "#DFE4EA", className, orientation, ...props @@ -10,15 +9,13 @@ function Divider({ color?: string; orientation: "horizontal" | "vertical"; } & React.ComponentPropsWithoutRef<typeof View>) { - const dividerStyles = [{ backgroundColor: color }]; - return ( <View className={cn( + "bg-accent", orientation === "horizontal" ? "h-0.5" : "w-0.5", className, )} - style={dividerStyles} {...props} /> ); diff --git a/apps/mobile/components/ui/FullPageSpinner.tsx b/apps/mobile/components/ui/FullPageSpinner.tsx index 89b66090..5436937a 100644 --- a/apps/mobile/components/ui/FullPageSpinner.tsx +++ b/apps/mobile/components/ui/FullPageSpinner.tsx @@ -2,7 +2,7 @@ import { ActivityIndicator, View } from "react-native"; export default function FullPageSpinner() { return ( - <View className="h-full w-full items-center justify-center"> + <View className="h-full w-full items-center justify-center bg-gray-100 dark:bg-background"> <ActivityIndicator /> </View> ); diff --git a/apps/mobile/components/ui/Input.tsx b/apps/mobile/components/ui/Input.tsx index 01c9fb2f..57d16f5d 100644 --- a/apps/mobile/components/ui/Input.tsx +++ b/apps/mobile/components/ui/Input.tsx @@ -2,6 +2,8 @@ import { forwardRef } from "react"; import { Text, TextInput, View } from "react-native"; import { cn } from "@/lib/utils"; +import { TailwindResolver } from "../TailwindResolver"; + export interface InputProps extends React.ComponentPropsWithoutRef<typeof TextInput> { label?: string; @@ -13,13 +15,20 @@ const Input = forwardRef<React.ElementRef<typeof TextInput>, InputProps>( ({ className, label, labelClasses, inputClasses, ...props }, ref) => ( <View className={cn("flex flex-col gap-1.5", className)}> {label && <Text className={cn("text-base", labelClasses)}>{label}</Text>} - <TextInput - ref={ref} - className={cn( - inputClasses, - "rounded-lg border border-input px-4 py-2.5", + <TailwindResolver + className="text-gray-400" + comp={(styles) => ( + <TextInput + placeholderTextColor={styles?.color?.toString()} + ref={ref} + className={cn( + "bg-background text-foreground", + inputClasses, + "rounded-lg border border-input px-4 py-2.5", + )} + {...props} + /> )} - {...props} /> </View> ), diff --git a/apps/mobile/components/ui/PageTitle.tsx b/apps/mobile/components/ui/PageTitle.tsx index 57b19e7d..1c1543ce 100644 --- a/apps/mobile/components/ui/PageTitle.tsx +++ b/apps/mobile/components/ui/PageTitle.tsx @@ -1,5 +1,7 @@ import { Text } from "react-native"; export default function PageTitle({ title }: { title: string }) { - return <Text className="p-4 text-4xl font-bold">{title}</Text>; + return ( + <Text className="p-4 text-4xl font-bold text-foreground">{title}</Text> + ); } |
