"use client"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { useTheme } from "next-themes"; import { useCallback, useEffect, useState } from "react"; import { BsCamera, BsMic } from "react-icons/bs"; import { LoadingButton } from "@/components/loading-button"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import type { PermissionType } from "@/hooks/use-permissions"; import { usePermissions } from "@/hooks/use-permissions"; import { getBrowserDisplayName } from "@/lib/browser-utils"; import { dismissToast, showErrorToast, showSuccessToast, showUnifiedVersionUpdateToast, } from "@/lib/toast-utils"; interface AppSettings { set_as_default_browser: boolean; theme: string; } interface PermissionInfo { permission_type: PermissionType; isGranted: boolean; description: string; } interface VersionUpdateProgress { current_browser: string; total_browsers: number; completed_browsers: number; new_versions_found: number; browser_new_versions: number; status: string; // "updating", "completed", "error" } interface SettingsDialogProps { isOpen: boolean; onClose: () => void; } export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) { const [settings, setSettings] = useState({ set_as_default_browser: false, theme: "system", }); const [originalSettings, setOriginalSettings] = useState({ set_as_default_browser: false, theme: "system", }); const [isDefaultBrowser, setIsDefaultBrowser] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isSettingDefault, setIsSettingDefault] = useState(false); const [isClearingCache, setIsClearingCache] = useState(false); const [permissions, setPermissions] = useState([]); const [isLoadingPermissions, setIsLoadingPermissions] = useState(false); const [requestingPermission, setRequestingPermission] = useState(null); const [isMacOS, setIsMacOS] = useState(false); const { setTheme } = useTheme(); const { requestPermission, isMicrophoneAccessGranted, isCameraAccessGranted, } = usePermissions(); const getPermissionIcon = useCallback((type: PermissionType) => { switch (type) { case "microphone": return ; case "camera": return ; } }, []); const getPermissionDisplayName = useCallback((type: PermissionType) => { switch (type) { case "microphone": return "Microphone"; case "camera": return "Camera"; } }, []); const getStatusBadge = useCallback((isGranted: boolean) => { if (isGranted) { return ( Granted ); } return Not Granted; }, []); const getPermissionDescription = useCallback((type: PermissionType) => { switch (type) { case "microphone": return "Access to microphone for browser applications"; case "camera": return "Access to camera for browser applications"; } }, []); const loadSettings = useCallback(async () => { setIsLoading(true); try { const appSettings = await invoke("get_app_settings"); setSettings(appSettings); setOriginalSettings(appSettings); } catch (error) { console.error("Failed to load settings:", error); } finally { setIsLoading(false); } }, []); const loadPermissions = useCallback(async () => { setIsLoadingPermissions(true); try { if (!isMacOS) { // On non-macOS platforms, don't show permissions setPermissions([]); return; } const permissionList: PermissionInfo[] = [ { permission_type: "microphone", isGranted: isMicrophoneAccessGranted, description: getPermissionDescription("microphone"), }, { permission_type: "camera", isGranted: isCameraAccessGranted, description: getPermissionDescription("camera"), }, ]; setPermissions(permissionList); } catch (error) { console.error("Failed to load permissions:", error); } finally { setIsLoadingPermissions(false); } }, [ getPermissionDescription, isCameraAccessGranted, isMacOS, isMicrophoneAccessGranted, ]); const checkDefaultBrowserStatus = useCallback(async () => { try { const isDefault = await invoke("is_default_browser"); setIsDefaultBrowser(isDefault); } catch (error) { console.error("Failed to check default browser status:", error); } }, []); const handleSetDefaultBrowser = useCallback(async () => { setIsSettingDefault(true); try { await invoke("set_as_default_browser"); await checkDefaultBrowserStatus(); } catch (error) { console.error("Failed to set as default browser:", error); } finally { setIsSettingDefault(false); } }, [checkDefaultBrowserStatus]); const handleClearCache = useCallback(async () => { setIsClearingCache(true); try { await invoke("clear_all_version_cache_and_refetch"); // Don't show immediate success toast - let the version update progress events handle it } catch (error) { console.error("Failed to clear cache:", error); showErrorToast("Failed to clear cache", { description: error instanceof Error ? error.message : "Unknown error occurred", duration: 4000, }); } finally { setIsClearingCache(false); } }, []); const handleRequestPermission = useCallback( async (permissionType: PermissionType) => { setRequestingPermission(permissionType); try { await requestPermission(permissionType); showSuccessToast( `${getPermissionDisplayName(permissionType)} access requested`, ); } catch (error) { console.error("Failed to request permission:", error); } finally { setRequestingPermission(null); } }, [getPermissionDisplayName, requestPermission], ); const handleSave = useCallback(async () => { setIsSaving(true); try { await invoke("save_app_settings", { settings }); setTheme(settings.theme); setOriginalSettings(settings); onClose(); } catch (error) { console.error("Failed to save settings:", error); } finally { setIsSaving(false); } }, [onClose, setTheme, settings]); const updateSetting = useCallback( (key: keyof AppSettings, value: boolean | string) => { setSettings((prev) => ({ ...prev, [key]: value })); }, [], ); useEffect(() => { if (isOpen) { loadSettings().catch(console.error); checkDefaultBrowserStatus().catch(console.error); // Check if we're on macOS const userAgent = navigator.userAgent; const isMac = userAgent.includes("Mac"); setIsMacOS(isMac); if (isMac) { loadPermissions().catch(console.error); } // Set up interval to check default browser status const intervalId = setInterval(() => { checkDefaultBrowserStatus().catch(console.error); }, 500); // Check every 500ms // Listen for version update progress events let unlistenFn: (() => void) | null = null; const setupVersionUpdateListener = async () => { try { unlistenFn = await listen( "version-update-progress", (event) => { const progress = event.payload; if (progress.status === "updating") { // Show unified progress toast const currentBrowserName = progress.current_browser ? getBrowserDisplayName(progress.current_browser) : undefined; showUnifiedVersionUpdateToast( "Checking for browser updates...", { description: currentBrowserName ? `Fetching ${currentBrowserName} release information...` : "Initializing version check...", progress: { current: progress.completed_browsers, total: progress.total_browsers, found: progress.new_versions_found, current_browser: currentBrowserName, }, }, ); } else if (progress.status === "completed") { dismissToast("unified-version-update"); if (progress.new_versions_found > 0) { showSuccessToast("Browser versions updated successfully", { duration: 5000, description: "Auto-downloads will start shortly for available updates.", }); } else { showSuccessToast("No new browser versions found", { duration: 3000, description: "All browser versions are up to date", }); } } else if (progress.status === "error") { dismissToast("unified-version-update"); showErrorToast("Failed to update browser versions", { duration: 6000, description: "Check your internet connection and try again", }); } }, ); } catch (error) { console.error( "Failed to setup version update progress listener:", error, ); } }; setupVersionUpdateListener(); // Cleanup interval and listener on component unmount or dialog close return () => { clearInterval(intervalId); if (unlistenFn) { try { unlistenFn(); } catch (error) { console.error( "Failed to cleanup version update progress listener:", error, ); } } }; } }, [isOpen, loadPermissions, checkDefaultBrowserStatus, loadSettings]); // Update permissions when the permission states change useEffect(() => { if (isMacOS) { const permissionList: PermissionInfo[] = [ { permission_type: "microphone", isGranted: isMicrophoneAccessGranted, description: getPermissionDescription("microphone"), }, { permission_type: "camera", isGranted: isCameraAccessGranted, description: getPermissionDescription("camera"), }, ]; setPermissions(permissionList); } else { setPermissions([]); } }, [ isMacOS, isMicrophoneAccessGranted, isCameraAccessGranted, getPermissionDescription, ]); // Check if settings have changed (excluding default browser setting) const hasChanges = settings.theme !== originalSettings.theme; return ( Settings
{/* Appearance Section */}

Choose your preferred theme or follow your system settings.

{/* Default Browser Section */}
{isDefaultBrowser ? "Active" : "Inactive"}
{ handleSetDefaultBrowser().catch(console.error); }} disabled={isDefaultBrowser} variant={isDefaultBrowser ? "outline" : "default"} className="w-full" > {isDefaultBrowser ? "Already Default Browser" : "Set as Default Browser"}

When set as default, Donut Browser will handle web links and allow you to choose which profile to use.

{/* Permissions Section - Only show on macOS */} {isMacOS && (
{isLoadingPermissions ? (
Loading permissions...
) : (
{permissions.map((permission) => (
{getPermissionIcon(permission.permission_type)}
{getPermissionDisplayName( permission.permission_type, )}
{permission.description}
{getStatusBadge(permission.isGranted)} {!permission.isGranted && ( { handleRequestPermission( permission.permission_type, ).catch(console.error); }} > Grant )}
))}
)}

These permissions allow browsers launched from Donut Browser to access system resources. Each website will still ask for your permission individually.

)} {/* Advanced Section */}
{ handleClearCache().catch(console.error); }} variant="outline" className="w-full" > Clear All Version Cache

Clear all cached browser version data and refresh all browser versions from their sources. This will force a fresh download of version information for all browsers.

{ handleSave().catch(console.error); }} disabled={isLoading || !hasChanges} > Save Settings
); }