Add theme dropdown in settings with Light, Dark, System plus Zinc, Midnight, Claude, and Ghostty dark variants. Each theme has its own accent color and surface tints. Desaturate terminal ANSI colors across all dark themes. Add surfaceSidebarHover semantic token so hover states go the right direction in both light and dark modes. Self-heal if a stored theme no longer exists.
122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
import { useCallback } from "react";
|
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
export const APP_SETTINGS_KEY = "@paseo:app-settings";
|
|
const LEGACY_SETTINGS_KEY = "@paseo:settings";
|
|
const APP_SETTINGS_QUERY_KEY = ["app-settings"];
|
|
|
|
import { THEME_TO_UNISTYLES, type ThemeName } from "@/styles/theme";
|
|
|
|
export type SendBehavior = "interrupt" | "queue";
|
|
|
|
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
|
|
|
export interface AppSettings {
|
|
theme: ThemeName | "auto";
|
|
manageBuiltInDaemon: boolean;
|
|
sendBehavior: SendBehavior;
|
|
}
|
|
|
|
export const DEFAULT_APP_SETTINGS: AppSettings = {
|
|
theme: "auto",
|
|
manageBuiltInDaemon: true,
|
|
sendBehavior: "interrupt",
|
|
};
|
|
|
|
export interface UseAppSettingsReturn {
|
|
settings: AppSettings;
|
|
isLoading: boolean;
|
|
error: unknown | null;
|
|
updateSettings: (updates: Partial<AppSettings>) => Promise<void>;
|
|
resetSettings: () => Promise<void>;
|
|
}
|
|
|
|
export function useAppSettings(): UseAppSettingsReturn {
|
|
const queryClient = useQueryClient();
|
|
const { data, isPending, error } = useQuery({
|
|
queryKey: APP_SETTINGS_QUERY_KEY,
|
|
queryFn: loadSettingsFromStorage,
|
|
staleTime: Infinity,
|
|
gcTime: Infinity,
|
|
});
|
|
|
|
const updateSettings = useCallback(
|
|
async (updates: Partial<AppSettings>) => {
|
|
try {
|
|
const prev =
|
|
queryClient.getQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY) ?? DEFAULT_APP_SETTINGS;
|
|
const next = { ...prev, ...updates };
|
|
queryClient.setQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY, next);
|
|
await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(next));
|
|
} catch (err) {
|
|
console.error("[AppSettings] Failed to save settings:", err);
|
|
throw err;
|
|
}
|
|
},
|
|
[queryClient],
|
|
);
|
|
|
|
const resetSettings = useCallback(async () => {
|
|
try {
|
|
const next = { ...DEFAULT_APP_SETTINGS };
|
|
queryClient.setQueryData<AppSettings>(APP_SETTINGS_QUERY_KEY, next);
|
|
await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(next));
|
|
} catch (err) {
|
|
console.error("[AppSettings] Failed to reset settings:", err);
|
|
throw err;
|
|
}
|
|
}, [queryClient]);
|
|
|
|
return {
|
|
settings: data ?? DEFAULT_APP_SETTINGS,
|
|
isLoading: isPending,
|
|
error: error ?? null,
|
|
updateSettings,
|
|
resetSettings,
|
|
};
|
|
}
|
|
|
|
export async function loadSettingsFromStorage(): Promise<AppSettings> {
|
|
try {
|
|
const stored = await AsyncStorage.getItem(APP_SETTINGS_KEY);
|
|
if (stored) {
|
|
const parsed = JSON.parse(stored) as Partial<AppSettings>;
|
|
if (parsed.theme && !VALID_THEMES.has(parsed.theme)) {
|
|
parsed.theme = DEFAULT_APP_SETTINGS.theme;
|
|
}
|
|
return { ...DEFAULT_APP_SETTINGS, ...parsed };
|
|
}
|
|
|
|
const legacyStored = await AsyncStorage.getItem(LEGACY_SETTINGS_KEY);
|
|
if (legacyStored) {
|
|
const legacyParsed = JSON.parse(legacyStored) as Record<string, unknown>;
|
|
const next = {
|
|
...DEFAULT_APP_SETTINGS,
|
|
...pickAppSettingsFromLegacy(legacyParsed),
|
|
} satisfies AppSettings;
|
|
await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(next));
|
|
return next;
|
|
}
|
|
|
|
await AsyncStorage.setItem(APP_SETTINGS_KEY, JSON.stringify(DEFAULT_APP_SETTINGS));
|
|
return DEFAULT_APP_SETTINGS;
|
|
} catch (error) {
|
|
console.error("[AppSettings] Failed to load settings:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function pickAppSettingsFromLegacy(legacy: Record<string, unknown>): Partial<AppSettings> {
|
|
const result: Partial<AppSettings> = {};
|
|
if (legacy.theme === "dark" || legacy.theme === "light" || legacy.theme === "auto") {
|
|
result.theme = legacy.theme;
|
|
}
|
|
if (typeof legacy.manageBuiltInDaemon === "boolean") {
|
|
result.manageBuiltInDaemon = legacy.manageBuiltInDaemon;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export const useSettings = useAppSettings;
|