aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/lib
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-22 15:10:24 +0000
committerMohamedBassem <me@mbassem.com>2024-03-22 15:14:24 +0000
commit2cd2f92e9e0c82eaa5f21fe0c30e20ebea7aba24 (patch)
treeacf86f033b976a40079c3efe8ec1fb727ae2a452 /apps/mobile/lib
parent95cc9e6ff29cd39dc80aa09c80a6d1c9489b5d6a (diff)
downloadkarakeep-2cd2f92e9e0c82eaa5f21fe0c30e20ebea7aba24.tar.zst
fix(mobile): Fix setting propagatin
Diffstat (limited to 'apps/mobile/lib')
-rw-r--r--apps/mobile/lib/providers.tsx33
-rw-r--r--apps/mobile/lib/session.ts15
-rw-r--r--apps/mobile/lib/settings.ts51
-rw-r--r--apps/mobile/lib/storage-state.ts51
4 files changed, 55 insertions, 95 deletions
diff --git a/apps/mobile/lib/providers.tsx b/apps/mobile/lib/providers.tsx
index 38eaa99e..688ecd5d 100644
--- a/apps/mobile/lib/providers.tsx
+++ b/apps/mobile/lib/providers.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from "react";
+import { useEffect, useMemo } from "react";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { ToastProvider } from "@/components/ui/Toast";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -6,16 +6,15 @@ import { httpBatchLink } from "@trpc/client";
import superjson from "superjson";
import type { Settings } from "./settings";
-import useAppSettings, { getAppSettings } from "./settings";
+import useAppSettings from "./settings";
import { api } from "./trpc";
-function getTRPCClient(address: string) {
+function getTRPCClient(settings: Settings) {
return api.createClient({
links: [
httpBatchLink({
- url: `${address}/api/trpc`,
- async headers() {
- const settings = await getAppSettings();
+ url: `${settings.address}/api/trpc`,
+ headers() {
return {
Authorization: settings?.apiKey
? `Bearer ${settings.apiKey}`
@@ -35,22 +34,12 @@ function TrpcProvider({
settings: Settings;
children: React.ReactNode;
}) {
- const [queryClient] = useState(() => new QueryClient());
+ const queryClient = useMemo(() => new QueryClient(), [settings]);
- const [trpcClient, setTrpcClient] = useState<
- ReturnType<typeof getTRPCClient>
- >(getTRPCClient(settings.address));
-
- useEffect(() => {
- setTrpcClient(getTRPCClient(settings.address));
- }, [settings.address]);
+ const trpcClient = useMemo(() => getTRPCClient(settings), [settings]);
return (
- <api.Provider
- key={settings.address}
- client={trpcClient}
- queryClient={queryClient}
- >
+ <api.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<ToastProvider>{children}</ToastProvider>
</QueryClientProvider>
@@ -59,7 +48,11 @@ function TrpcProvider({
}
export function Providers({ children }: { children: React.ReactNode }) {
- const { settings, isLoading } = useAppSettings();
+ const { settings, isLoading, load } = useAppSettings();
+
+ useEffect(() => {
+ load();
+ }, []);
if (isLoading) {
// Don't render anything if the settings still hasn't been loaded
diff --git a/apps/mobile/lib/session.ts b/apps/mobile/lib/session.ts
index 071748b9..bafb3a09 100644
--- a/apps/mobile/lib/session.ts
+++ b/apps/mobile/lib/session.ts
@@ -1,20 +1,21 @@
-import { useCallback, useMemo } from "react";
+import { useCallback } from "react";
import useAppSettings from "./settings";
export function useSession() {
- const { settings, isLoading, setSettings } = useAppSettings();
- const isLoggedIn = useMemo(() => {
- return isLoading ? undefined : !!settings.apiKey;
- }, [isLoading, settings]);
+ const { settings, setSettings } = useAppSettings();
const logout = useCallback(() => {
setSettings({ ...settings, apiKey: undefined });
}, [settings, setSettings]);
return {
- isLoggedIn,
- isLoading,
logout,
};
}
+
+export function useIsLoggedIn() {
+ const { settings, isLoading } = useAppSettings();
+
+ return isLoading ? undefined : !!settings.apiKey;
+}
diff --git a/apps/mobile/lib/settings.ts b/apps/mobile/lib/settings.ts
index 13de067e..3ec53231 100644
--- a/apps/mobile/lib/settings.ts
+++ b/apps/mobile/lib/settings.ts
@@ -1,6 +1,5 @@
import * as SecureStore from "expo-secure-store";
-
-import { useStorageState } from "./storage-state";
+import { create } from "zustand";
const SETTING_NAME = "settings";
@@ -9,22 +8,40 @@ export interface Settings {
address: string;
}
-export default function useAppSettings() {
- const [settingsState, setSettings] = useStorageState<Settings>(SETTING_NAME);
- const [isLoading] = settingsState;
- let [, settings] = settingsState;
+interface AppSettingsState {
+ settings: { isLoading: boolean; settings: Settings };
+ setSettings: (settings: Settings) => Promise<void>;
+ load: () => Promise<void>;
+}
- settings ||= {
- address: "https://demo.hoarder.app",
- };
+const useSettings = create<AppSettingsState>((set, get) => ({
+ settings: {
+ isLoading: true,
+ settings: { address: "" },
+ },
+ setSettings: async (settings) => {
+ await SecureStore.setItemAsync(SETTING_NAME, JSON.stringify(settings));
+ set((_state) => ({ settings: { isLoading: false, settings } }));
+ },
+ load: async () => {
+ if (!get().settings.isLoading) {
+ return;
+ }
+ const strVal = await SecureStore.getItemAsync(SETTING_NAME);
+ if (!strVal) {
+ set((state) => ({
+ settings: { isLoading: false, settings: state.settings.settings },
+ }));
+ return;
+ }
+ // TODO Wipe the state if invalid
+ const parsed = JSON.parse(strVal) as Settings;
+ set((_state) => ({ settings: { isLoading: false, settings: parsed } }));
+ },
+}));
- return { settings, setSettings, isLoading };
-}
+export default function useAppSettings() {
+ const { settings, setSettings, load } = useSettings();
-export async function getAppSettings() {
- const val = await SecureStore.getItemAsync(SETTING_NAME);
- if (!val) {
- return null;
- }
- return JSON.parse(val) as Settings;
+ return { ...settings, setSettings, load };
}
diff --git a/apps/mobile/lib/storage-state.ts b/apps/mobile/lib/storage-state.ts
deleted file mode 100644
index f45ddfe5..00000000
--- a/apps/mobile/lib/storage-state.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import * as React from "react";
-import * as SecureStore from "expo-secure-store";
-
-type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void];
-
-function useAsyncState<T>(
- initialValue: [boolean, T | null] = [true, null],
-): UseStateHook<T> {
- return React.useReducer(
- (
- _state: [boolean, T | null],
- action: T | null = null,
- ): [boolean, T | null] => [false, action],
- initialValue,
- ) as UseStateHook<T>;
-}
-
-export async function setStorageItemAsync(key: string, value: string | null) {
- if (value == null) {
- await SecureStore.deleteItemAsync(key);
- } else {
- await SecureStore.setItemAsync(key, value);
- }
-}
-
-export function useStorageState<T>(key: string): UseStateHook<T> {
- // Public
- const [state, setState] = useAsyncState<T>();
-
- // Get
- React.useEffect(() => {
- SecureStore.getItemAsync(key).then((value) => {
- if (!value) {
- setState(null);
- return null;
- }
- setState(JSON.parse(value) as T);
- });
- }, [key]);
-
- // Set
- const setValue = React.useCallback(
- (value: T | null) => {
- setState(value);
- setStorageItemAsync(key, JSON.stringify(value));
- },
- [key],
- );
-
- return [state, setValue];
-}