Skip to content

Commit

Permalink
feature(mobile): Add settings page for configuring the theme
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedBassem committed Sep 14, 2024
1 parent b9c7857 commit 66fcf02
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 11 deletions.
8 changes: 7 additions & 1 deletion apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ 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 useAppSettings from "@/lib/settings";
import { cn } from "@/lib/utils";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import { useColorScheme } from "nativewind";

export default function RootLayout() {
const router = useRouter();
const { hasShareIntent } = useShareIntent();
const { colorScheme } = useColorScheme();
const { colorScheme, setColorScheme } = useColorScheme();
const { settings } = useAppSettings();

useEffect(() => {
if (hasShareIntent) {
Expand All @@ -27,6 +29,10 @@ export default function RootLayout() {
}
}, [hasShareIntent]);

useEffect(() => {
setColorScheme(settings.theme);
}, [settings.theme]);

return (
<ShareIntentProvider>
<Providers>
Expand Down
33 changes: 27 additions & 6 deletions apps/mobile/app/dashboard/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { useEffect } from "react";
import { Text, View } from "react-native";
import { Pressable, Text, View } from "react-native";
import { Slider } from "react-native-awesome-slider";
import { useSharedValue } from "react-native-reanimated";
import { Link } from "expo-router";
import { Button } from "@/components/ui/Button";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Divider } from "@/components/ui/Divider";
import PageTitle from "@/components/ui/PageTitle";
import { useSession } from "@/lib/session";
import useAppSettings from "@/lib/settings";
import { api } from "@/lib/trpc";
import { ChevronRight } from "lucide-react-native";

export default function Dashboard() {
const { logout } = useSession();
Expand Down Expand Up @@ -36,18 +38,35 @@ export default function Dashboard() {
<CustomSafeAreaView>
<PageTitle title="Settings" />
<View className="flex h-full w-full items-center gap-3 px-4 py-2">
<View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent">
<View className="flex w-full gap-3 rounded-lg bg-white px-4 py-2 dark:bg-accent">
<Text className="text-lg text-accent-foreground">
{isSettingsLoading ? "Loading ..." : settings.address}
</Text>
</View>
<View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent">
<Divider orientation="horizontal" />
<Text className="text-lg text-accent-foreground">
{isLoading ? "Loading ..." : data?.email}
</Text>
</View>
<Button className="w-full" label="Log Out" onPress={logout} />
<Divider orientation="horizontal" />
<Text className="w-full p-1 text-2xl font-bold text-foreground">
App Settings
</Text>
<View className="flex w-full flex-row items-center justify-between gap-8 rounded-lg bg-white px-4 py-2 dark:bg-accent">
<Link asChild href="/dashboard/settings/theme" className="flex-1">
<Pressable className="flex flex-row justify-between">
<Text className="text-lg text-accent-foreground">Theme</Text>
<View className="flex flex-row items-center gap-2">
<Text className="text-lg text-muted-foreground">
{
{ light: "Light", dark: "Dark", system: "System" }[
settings.theme
]
}
</Text>
<ChevronRight color="rgb(0, 122, 255)" />
</View>
</Pressable>
</Link>
</View>
<Text className="w-full p-1 text-2xl font-bold text-foreground">
Upload Settings
</Text>
Expand All @@ -70,6 +89,8 @@ export default function Dashboard() {
/>
</View>
</View>
<Divider orientation="horizontal" />
<Button className="w-full" label="Log Out" onPress={logout} />
</View>
</CustomSafeAreaView>
);
Expand Down
11 changes: 11 additions & 0 deletions apps/mobile/app/dashboard/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export default function Dashboard() {
<StyledStack
contentClassName="bg-gray-100 dark:bg-background"
headerClassName="bg-gray-100 dark:bg-background text-foreground"
screenOptions={{
headerTransparent: true,
}}
>
<Stack.Screen
name="(tabs)"
Expand All @@ -54,6 +57,14 @@ export default function Dashboard() {
headerTransparent: true,
}}
/>
<Stack.Screen
name="settings/theme"
options={{
title: "Theme",
headerTitle: "Theme",
headerBackTitle: "Back",
}}
/>
</StyledStack>
);
}
47 changes: 47 additions & 0 deletions apps/mobile/app/dashboard/settings/theme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Pressable, Text, View } from "react-native";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import { Divider } from "@/components/ui/Divider";
import useAppSettings from "@/lib/settings";
import { Check } from "lucide-react-native";

export default function ThemePage() {
const { settings, setSettings } = useAppSettings();

const options = (["light", "dark", "system"] as const)
.map((theme) => {
const isChecked = settings.theme === theme;
return [
<Pressable
onPress={() => setSettings({ ...settings, theme })}
className="flex flex-row justify-between"
key={theme}
>
<Text className="text-lg text-accent-foreground">
{
{ light: "Light Mode", dark: "Dark Mode", system: "System" }[
theme
]
}
</Text>
{isChecked && <Check />}
</Pressable>,
<Divider
key={theme + "-divider"}
orientation="horizontal"
className="my-3 h-0.5 w-full"
/>,
];
})
.flat();
options.pop();

return (
<CustomSafeAreaView>
<View className="flex h-full w-full items-center px-4 py-2">
<View className="w-full rounded-lg bg-white px-4 py-2 dark:bg-accent">
{options}
</View>
</View>
</CustomSafeAreaView>
);
}
18 changes: 14 additions & 4 deletions apps/mobile/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const zSettingsSchema = z.object({
apiKeyId: z.string().optional(),
address: z.string(),
imageQuality: z.number().optional().default(0.2),
theme: z.enum(["light", "dark", "system"]).optional().default("system"),
});

export type Settings = z.infer<typeof zSettingsSchema>;
Expand All @@ -22,7 +23,7 @@ interface AppSettingsState {
const useSettings = create<AppSettingsState>((set, get) => ({
settings: {
isLoading: true,
settings: { address: "", imageQuality: 0.2 },
settings: { address: "", imageQuality: 0.2, theme: "system" },
},
setSettings: async (settings) => {
await SecureStore.setItemAsync(SETTING_NAME, JSON.stringify(settings));
Expand All @@ -39,9 +40,18 @@ const useSettings = create<AppSettingsState>((set, get) => ({
}));
return;
}
// TODO Wipe the state if invalid
const parsed = zSettingsSchema.parse(JSON.parse(strVal));
set((_state) => ({ settings: { isLoading: false, settings: parsed } }));
const parsed = zSettingsSchema.safeParse(JSON.parse(strVal));
if (!parsed.success) {
// Wipe the state if invalid
set((state) => ({
settings: { isLoading: false, settings: state.settings.settings },
}));
return;
}

set((_state) => ({
settings: { isLoading: false, settings: parsed.data },
}));
},
}));

Expand Down

0 comments on commit 66fcf02

Please sign in to comment.