From 1554425b033046708f3a8be58b7e1bfd1dd61fc2 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Sun, 12 Apr 2026 22:01:46 +0800 Subject: [PATCH] feat: show real-time download progress bar for app updates Leverage the Tauri updater plugin's DownloadEvent callback to display a progress bar with downloaded/total bytes during app updates, replacing the previous spinner-only feedback. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../settings/system-network-settings.tsx | 74 ++++++++++++++++++- src/i18n/messages/ar.json | 1 + src/i18n/messages/de.json | 1 + src/i18n/messages/en.json | 1 + src/i18n/messages/es.json | 1 + src/i18n/messages/fr.json | 1 + src/i18n/messages/ja.json | 1 + src/i18n/messages/ko.json | 1 + src/i18n/messages/pt.json | 1 + src/i18n/messages/zh-CN.json | 1 + src/i18n/messages/zh-TW.json | 1 + src/lib/updater.ts | 10 ++- 12 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/components/settings/system-network-settings.tsx b/src/components/settings/system-network-settings.tsx index 77a9dd3..adbd58d 100644 --- a/src/components/settings/system-network-settings.tsx +++ b/src/components/settings/system-network-settings.tsx @@ -39,8 +39,15 @@ import { normalizeAppUpdateError, relaunchApp, } from "@/lib/updater" +import type { DownloadEvent } from "@/lib/updater" import { APP_LOCALES } from "@/lib/i18n" +function formatBytes(bytes: number): string { + if (bytes < 1024) return `${bytes} B` + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` + return `${(bytes / (1024 * 1024)).toFixed(1)} MB` +} + const PROXY_EXAMPLE = "http://127.0.0.1:7890" const APP_LANGUAGE_VALUES = APP_LOCALES @@ -71,6 +78,11 @@ export function SystemNetworkSettings() { const [installingUpdate, setInstallingUpdate] = useState(false) const [updateError, setUpdateError] = useState(null) const [lastCheckedAt, setLastCheckedAt] = useState(null) + const [downloadProgress, setDownloadProgress] = useState<{ + downloaded: number + total: number | null + phase: "downloading" | "installing" + } | null>(null) const [appLanguage, setAppLanguage] = useState( languageSettings.mode === "system" ? "system" : languageSettings.language @@ -273,9 +285,37 @@ export function SystemNetworkSettings() { setInstallingUpdate(true) setUpdateError(null) + setDownloadProgress(null) + + let downloaded = 0 try { - await installAppUpdate(availableUpdate) + await installAppUpdate(availableUpdate, (event: DownloadEvent) => { + switch (event.event) { + case "Started": + setDownloadProgress({ + downloaded: 0, + total: event.data.contentLength ?? null, + phase: "downloading", + }) + break + case "Progress": + downloaded += event.data.chunkLength + setDownloadProgress((prev) => ({ + downloaded, + total: prev?.total ?? null, + phase: "downloading", + })) + break + case "Finished": + setDownloadProgress((prev) => ({ + downloaded: prev?.downloaded ?? downloaded, + total: prev?.total ?? null, + phase: "installing", + })) + break + } + }) toast.success(t("installSuccess")) await relaunchApp() } catch (err) { @@ -285,6 +325,7 @@ export function SystemNetworkSettings() { console.error("[Settings] install app update failed:", err) } finally { setInstallingUpdate(false) + setDownloadProgress(null) } }, [availableUpdate, formatUpdateError, t]) @@ -389,10 +430,39 @@ export function SystemNetworkSettings() {

)} - {updateStatusMessage && ( + {updateStatusMessage && !downloadProgress && (

{updateStatusMessage}

)} + {downloadProgress && ( +
+
+ + {downloadProgress.phase === "downloading" + ? t("downloading") + : t("updating")} + + + {formatBytes(downloadProgress.downloaded)} + {downloadProgress.total + ? ` / ${formatBytes(downloadProgress.total)}` + : ""} + +
+
+
0 + ? `${Math.min(100, (downloadProgress.downloaded / downloadProgress.total) * 100)}%` + : "30%", + }} + /> +
+
+ )} + {availableUpdate && (
diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index f2acd3b..bc4f199 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -172,6 +172,7 @@ "checking": "جارٍ التحقق...", "checkUpdate": "التحقق من التحديثات", "updating": "جارٍ التثبيت...", + "downloading": "جارٍ التنزيل...", "upgradeTo": "الترقية إلى v{version}", "foundUpdate": "تم العثور على إصدار جديد v{version}", "alreadyLatest": "أنت على أحدث إصدار", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 46cd29e..ffc596a 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -172,6 +172,7 @@ "checking": "Wird geprüft...", "checkUpdate": "Nach Updates suchen", "updating": "Wird installiert...", + "downloading": "Herunterladen...", "upgradeTo": "Auf v{version} aktualisieren", "foundUpdate": "Neue Version v{version} gefunden", "alreadyLatest": "Du verwendest bereits die neueste Version", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 1c8d77a..16e5f25 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -172,6 +172,7 @@ "checking": "Checking...", "checkUpdate": "Check for updates", "updating": "Installing...", + "downloading": "Downloading...", "upgradeTo": "Upgrade to v{version}", "foundUpdate": "New version v{version} found", "alreadyLatest": "You're on the latest version", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index ac84aec..0d72bff 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -172,6 +172,7 @@ "checking": "Comprobando...", "checkUpdate": "Buscar actualizaciones", "updating": "Instalando...", + "downloading": "Descargando...", "upgradeTo": "Actualizar a v{version}", "foundUpdate": "Nueva versión v{version} encontrada", "alreadyLatest": "Ya tienes la versión más reciente", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index c080d75..09b0f71 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -172,6 +172,7 @@ "checking": "Vérification...", "checkUpdate": "Rechercher les mises à jour", "updating": "Installation...", + "downloading": "Téléchargement...", "upgradeTo": "Mettre à jour vers v{version}", "foundUpdate": "Nouvelle version v{version} trouvée", "alreadyLatest": "Vous utilisez déjà la dernière version", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index c6a69c1..e4bf717 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -172,6 +172,7 @@ "checking": "確認中...", "checkUpdate": "更新を確認", "updating": "インストール中...", + "downloading": "ダウンロード中...", "upgradeTo": "v{version} にアップグレード", "foundUpdate": "新しいバージョン v{version} が見つかりました", "alreadyLatest": "すでに最新バージョンです", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index d50f148..6021a50 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -172,6 +172,7 @@ "checking": "확인 중...", "checkUpdate": "업데이트 확인", "updating": "설치 중...", + "downloading": "다운로드 중...", "upgradeTo": "v{version}로 업그레이드", "foundUpdate": "새 버전 v{version}을 찾았습니다", "alreadyLatest": "이미 최신 버전입니다", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index f432c1c..035225d 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -172,6 +172,7 @@ "checking": "Verificando...", "checkUpdate": "Verificar atualizações", "updating": "Instalando...", + "downloading": "Baixando...", "upgradeTo": "Atualizar para v{version}", "foundUpdate": "Nova versão v{version} encontrada", "alreadyLatest": "Você já está na versão mais recente", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 5d76048..797a5b8 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -172,6 +172,7 @@ "checking": "检查中...", "checkUpdate": "检查更新", "updating": "升级中...", + "downloading": "下载中...", "upgradeTo": "升级到 v{version}", "foundUpdate": "发现新版本 v{version}", "alreadyLatest": "当前已经是最新版本", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 3c59d03..b6e53cf 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -172,6 +172,7 @@ "checking": "檢查中...", "checkUpdate": "檢查更新", "updating": "升級中...", + "downloading": "下載中...", "upgradeTo": "升級到 v{version}", "foundUpdate": "發現新版本 v{version}", "alreadyLatest": "目前已是最新版本", diff --git a/src/lib/updater.ts b/src/lib/updater.ts index dab70c3..64bfa67 100644 --- a/src/lib/updater.ts +++ b/src/lib/updater.ts @@ -4,6 +4,11 @@ import { getTransport, isDesktop } from "./transport" // eslint-disable-next-line @typescript-eslint/no-explicit-any type Update = any +export type DownloadEvent = + | { event: "Started"; data: { contentLength?: number } } + | { event: "Progress"; data: { chunkLength: number } } + | { event: "Finished" } + export interface AppUpdateCheckResult { currentVersion: string update: Update | null @@ -46,9 +51,10 @@ export async function checkAppUpdate(): Promise { } export async function installAppUpdate( - update: NonNullable + update: NonNullable, + onEvent?: (progress: DownloadEvent) => void ): Promise { - await update.downloadAndInstall() + await update.downloadAndInstall(onEvent) } export async function relaunchApp(): Promise {