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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<string | null>(null)
|
||||
const [lastCheckedAt, setLastCheckedAt] = useState<Date | null>(null)
|
||||
const [downloadProgress, setDownloadProgress] = useState<{
|
||||
downloaded: number
|
||||
total: number | null
|
||||
phase: "downloading" | "installing"
|
||||
} | null>(null)
|
||||
|
||||
const [appLanguage, setAppLanguage] = useState<LanguageSelectValue>(
|
||||
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() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{updateStatusMessage && (
|
||||
{updateStatusMessage && !downloadProgress && (
|
||||
<p className="text-muted-foreground">{updateStatusMessage}</p>
|
||||
)}
|
||||
|
||||
{downloadProgress && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between text-muted-foreground">
|
||||
<span>
|
||||
{downloadProgress.phase === "downloading"
|
||||
? t("downloading")
|
||||
: t("updating")}
|
||||
</span>
|
||||
<span>
|
||||
{formatBytes(downloadProgress.downloaded)}
|
||||
{downloadProgress.total
|
||||
? ` / ${formatBytes(downloadProgress.total)}`
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-muted overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary transition-all duration-300"
|
||||
style={{
|
||||
width:
|
||||
downloadProgress.total && downloadProgress.total > 0
|
||||
? `${Math.min(100, (downloadProgress.downloaded / downloadProgress.total) * 100)}%`
|
||||
: "30%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{availableUpdate && (
|
||||
<div className="space-y-2 pt-2 border-t border-border/70">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"checking": "جارٍ التحقق...",
|
||||
"checkUpdate": "التحقق من التحديثات",
|
||||
"updating": "جارٍ التثبيت...",
|
||||
"downloading": "جارٍ التنزيل...",
|
||||
"upgradeTo": "الترقية إلى v{version}",
|
||||
"foundUpdate": "تم العثور على إصدار جديد v{version}",
|
||||
"alreadyLatest": "أنت على أحدث إصدار",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"checking": "確認中...",
|
||||
"checkUpdate": "更新を確認",
|
||||
"updating": "インストール中...",
|
||||
"downloading": "ダウンロード中...",
|
||||
"upgradeTo": "v{version} にアップグレード",
|
||||
"foundUpdate": "新しいバージョン v{version} が見つかりました",
|
||||
"alreadyLatest": "すでに最新バージョンです",
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"checking": "확인 중...",
|
||||
"checkUpdate": "업데이트 확인",
|
||||
"updating": "설치 중...",
|
||||
"downloading": "다운로드 중...",
|
||||
"upgradeTo": "v{version}로 업그레이드",
|
||||
"foundUpdate": "새 버전 v{version}을 찾았습니다",
|
||||
"alreadyLatest": "이미 최신 버전입니다",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"checking": "检查中...",
|
||||
"checkUpdate": "检查更新",
|
||||
"updating": "升级中...",
|
||||
"downloading": "下载中...",
|
||||
"upgradeTo": "升级到 v{version}",
|
||||
"foundUpdate": "发现新版本 v{version}",
|
||||
"alreadyLatest": "当前已经是最新版本",
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
"checking": "檢查中...",
|
||||
"checkUpdate": "檢查更新",
|
||||
"updating": "升級中...",
|
||||
"downloading": "下載中...",
|
||||
"upgradeTo": "升級到 v{version}",
|
||||
"foundUpdate": "發現新版本 v{version}",
|
||||
"alreadyLatest": "目前已是最新版本",
|
||||
|
||||
@@ -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<AppUpdateCheckResult> {
|
||||
}
|
||||
|
||||
export async function installAppUpdate(
|
||||
update: NonNullable<Update>
|
||||
update: NonNullable<Update>,
|
||||
onEvent?: (progress: DownloadEvent) => void
|
||||
): Promise<void> {
|
||||
await update.downloadAndInstall()
|
||||
await update.downloadAndInstall(onEvent)
|
||||
}
|
||||
|
||||
export async function relaunchApp(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user