diff --git a/src/components/app-update-toast.tsx b/src/components/app-update-toast.tsx index 58e4cbf..e578a52 100644 --- a/src/components/app-update-toast.tsx +++ b/src/components/app-update-toast.tsx @@ -1,69 +1,24 @@ "use client"; -import { FaDownload, FaExternalLinkAlt, FaTimes } from "react-icons/fa"; -import { LuCheckCheck, LuCog, LuRefreshCw } from "react-icons/lu"; -import { Badge } from "@/components/ui/badge"; +import { FaExternalLinkAlt, FaTimes } from "react-icons/fa"; +import { LuCheckCheck } from "react-icons/lu"; import { Button } from "@/components/ui/button"; -import type { AppUpdateInfo, AppUpdateProgress } from "@/types"; +import type { AppUpdateInfo } from "@/types"; import { RippleButton } from "./ui/ripple"; interface AppUpdateToastProps { updateInfo: AppUpdateInfo; - onUpdate: (updateInfo: AppUpdateInfo) => Promise; onRestart: () => Promise; onDismiss: () => void; - isUpdating?: boolean; - updateProgress?: AppUpdateProgress | null; updateReady?: boolean; } -function getStageIcon(stage?: string, isUpdating?: boolean) { - if (!isUpdating) { - return ; - } - - switch (stage) { - case "downloading": - return ; - case "extracting": - return ; - case "installing": - return ; - case "completed": - return ; - default: - return ; - } -} - -function getStageDisplayName(stage?: string) { - switch (stage) { - case "downloading": - return "Downloading"; - case "extracting": - return "Extracting"; - case "installing": - return "Installing"; - case "completed": - return "Completed"; - default: - return "Updating"; - } -} - export function AppUpdateToast({ updateInfo, - onUpdate, onRestart, onDismiss, - isUpdating = false, - updateProgress, updateReady = false, }: AppUpdateToastProps) { - const handleUpdateClick = async () => { - await onUpdate(updateInfo); - }; - const handleRestartClick = async () => { await onRestart(); }; @@ -77,115 +32,37 @@ export function AppUpdateToast({ } }; - const showDownloadProgress = - isUpdating && - updateProgress?.stage === "downloading" && - updateProgress.percentage !== undefined; - - const showOtherStageProgress = - isUpdating && - updateProgress && - (updateProgress.stage === "extracting" || - updateProgress.stage === "installing" || - updateProgress.stage === "completed"); - return (
- {updateReady ? ( - - ) : ( - getStageIcon(updateProgress?.stage, isUpdating) - )} +
-
- - {updateReady - ? "The update is ready, restart app" - : isUpdating - ? `${getStageDisplayName(updateProgress?.stage)} Donut Browser Update` - : "Donut Browser Update Available"} - - {!updateReady && ( - - {updateInfo.is_nightly ? "Nightly" : "Stable"} - - )} + + {updateReady + ? "Update ready, restart to apply" + : "Manual download required"} + +
+ {updateInfo.current_version} → {updateInfo.new_version}
- {!updateReady && ( -
- {isUpdating ? ( - updateProgress?.message || "Updating..." - ) : ( - <> - Update from {updateInfo.current_version} to{" "} - - {updateInfo.new_version} - - {updateInfo.manual_update_required && ( - - Manual download required on Linux - - )} - - )} -
- )}
- {!isUpdating && !updateReady && ( - - )} +
- {!updateReady && showDownloadProgress && updateProgress && ( -
-
-

- {updateProgress.percentage?.toFixed(1)}% - {updateProgress.speed && ` • ${updateProgress.speed} MB/s`} - {updateProgress.eta && ` • ${updateProgress.eta} remaining`} -

-
-
-
-
-
- )} - - {!updateReady && showOtherStageProgress && ( -
-
-
-
-
- )} - - {updateReady ? ( -
+
+ {updateReady ? ( void handleRestartClick()} size="sm" @@ -194,40 +71,27 @@ export function AppUpdateToast({ Restart Now -
- ) : ( - !isUpdating && ( -
- {updateInfo.manual_update_required ? ( - - - View Release - - ) : ( - void handleUpdateClick()} - size="sm" - className="flex gap-2 items-center text-xs" - > - - Download Update - - )} + ) : ( + updateInfo.manual_update_required && ( - Later + + View Release -
- ) - )} + ) + )} + + Later + +
); diff --git a/src/hooks/use-app-update-notifications.tsx b/src/hooks/use-app-update-notifications.tsx index 1b70c4a..cb7378b 100644 --- a/src/hooks/use-app-update-notifications.tsx +++ b/src/hooks/use-app-update-notifications.tsx @@ -2,7 +2,7 @@ import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { AppUpdateToast } from "@/components/app-update-toast"; import { showToast } from "@/lib/toast-utils"; @@ -16,6 +16,7 @@ export function useAppUpdateNotifications() { const [updateReady, setUpdateReady] = useState(false); const [isClient, setIsClient] = useState(false); const [dismissedVersion, setDismissedVersion] = useState(null); + const autoDownloadedVersion = useRef(null); // Ensure we're on the client side to prevent hydration mismatches useEffect(() => { @@ -52,6 +53,7 @@ export function useAppUpdateNotifications() { console.log("Manual check result:", update); // Always show manual check results, even if previously dismissed + autoDownloadedVersion.current = null; setUpdateInfo(update); } catch (error) { console.error("Failed to manually check for app updates:", error); @@ -112,7 +114,7 @@ export function useAppUpdateNotifications() { toast.dismiss("app-update"); }, [isClient, updateInfo]); - // Listen for app update availability + // Listen for app update events useEffect(() => { if (!isClient) return; @@ -127,16 +129,7 @@ export function useAppUpdateNotifications() { const unlistenProgress = listen( "app-update-progress", (event) => { - console.log("App update progress:", event.payload); setUpdateProgress(event.payload); - - // If update is completed, mark as no longer updating after a delay - if (event.payload.stage === "completed") { - setTimeout(() => { - setIsUpdating(false); - setUpdateProgress(null); - }, 5000); // Show completion message for 5 seconds instead of 2 - } }, ); @@ -160,41 +153,59 @@ export function useAppUpdateNotifications() { }; }, [isClient]); - // Show toast when update is available + // Auto-download update in background when found useEffect(() => { - if (!isClient || !updateInfo) return; + if ( + !isClient || + !updateInfo || + updateInfo.manual_update_required || + isUpdating || + updateReady || + autoDownloadedVersion.current === updateInfo.new_version + ) + return; + + autoDownloadedVersion.current = updateInfo.new_version; + console.log("Auto-downloading app update:", updateInfo.new_version); + void handleAppUpdate(updateInfo); + }, [isClient, updateInfo, isUpdating, updateReady, handleAppUpdate]); + + // Show toast only when update is ready to install or requires manual action + useEffect(() => { + if (!isClient) return; + + const showManualToast = updateInfo?.manual_update_required && !isUpdating; + if (!updateReady && !showManualToast) { + return; + } + if (!updateInfo) return; toast.custom( () => ( ), { id: "app-update", - duration: Number.POSITIVE_INFINITY, // Persistent until user action + duration: Number.POSITIVE_INFINITY, position: "top-left", style: { - zIndex: 99999, // Ensure app updates appear above dialogs - pointerEvents: "auto", // Ensure app updates remain interactive - marginTop: "16px", // slightly lower on macOS-like top controls + zIndex: 99999, + pointerEvents: "auto", + marginTop: "16px", }, }, ); }, [ updateInfo, - handleAppUpdate, handleRestart, dismissAppUpdate, - isUpdating, - updateProgress, updateReady, + isUpdating, isClient, ]);