"use client"; import { invoke } from "@tauri-apps/api/core"; import { ThemeProvider } from "next-themes"; import { useEffect, useState } from "react"; interface AppSettings { show_settings_on_startup: boolean; theme: string; } interface SystemTheme { theme: string; } interface CustomThemeProviderProps { children: React.ReactNode; } // Helper function to detect system dark mode preference function getSystemTheme(): string { if (typeof window !== "undefined") { const isDarkMode = window.matchMedia( "(prefers-color-scheme: dark)", ).matches; return isDarkMode ? "dark" : "light"; } return "light"; } // Function to get native system theme (fallback to CSS media query) async function getNativeSystemTheme(): Promise { try { const systemTheme = await invoke("get_system_theme"); if (systemTheme.theme === "dark" || systemTheme.theme === "light") { return systemTheme.theme; } // Fallback to CSS media query if native detection returns "unknown" return getSystemTheme(); } catch (error) { console.warn( "Failed to get native system theme, falling back to CSS media query:", error, ); // Fallback to CSS media query return getSystemTheme(); } } export function CustomThemeProvider({ children }: CustomThemeProviderProps) { const [isLoading, setIsLoading] = useState(true); const [defaultTheme, setDefaultTheme] = useState("system"); const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); useEffect(() => { const loadTheme = async () => { try { const settings = await invoke("get_app_settings"); setDefaultTheme(settings.theme); } catch (error) { console.error("Failed to load theme settings:", error); // For first-time users, detect system preference and apply it const systemTheme = await getNativeSystemTheme(); console.log( "First-time user detected, applying system theme:", systemTheme, ); // Save the detected theme as the default try { await invoke("save_app_settings", { settings: { show_settings_on_startup: true, theme: "system", auto_updates_enabled: true, }, }); } catch (saveError) { console.error("Failed to save initial theme settings:", saveError); } setDefaultTheme("system"); } finally { setIsLoading(false); } }; void loadTheme(); }, []); // Monitor system theme changes when using "system" theme useEffect(() => { if (!mounted || defaultTheme !== "system") { return; } const checkSystemTheme = async () => { try { const currentSystemTheme = await getNativeSystemTheme(); // Force re-evaluation by toggling the theme const html = document.documentElement; const currentClass = html.className; // Apply the system theme class if (currentSystemTheme === "dark") { if (!html.classList.contains("dark")) { html.classList.add("dark"); html.classList.remove("light"); } } else { if ( !html.classList.contains("light") || html.classList.contains("dark") ) { html.classList.add("light"); html.classList.remove("dark"); } } } catch (error) { console.warn("Failed to check system theme:", error); } }; // Check system theme every 2 seconds when using system theme const intervalId = setInterval(() => void checkSystemTheme(), 2000); // Initial check void checkSystemTheme(); return () => { clearInterval(intervalId); }; }, [mounted, defaultTheme]); if (isLoading) { // Use a consistent loading screen that doesn't depend on system theme during SSR // This prevents hydration mismatch by ensuring server and client render the same initially let loadingBgColor = "bg-white"; let spinnerColor = "border-gray-900"; // Only apply system theme detection after component is mounted (client-side only) if (mounted) { // Use CSS media query for loading screen since async call would complicate this const systemTheme = getSystemTheme(); loadingBgColor = systemTheme === "dark" ? "bg-gray-900" : "bg-white"; spinnerColor = systemTheme === "dark" ? "border-white" : "border-gray-900"; } return (
); } return ( {children} ); }