Files
EpochSilicon/pkg/ui/updater.go
2025-07-22 14:10:57 -07:00

194 lines
4.9 KiB
Go

package ui
import (
"fmt"
"strings"
"time"
"epochsilicon/pkg/debug"
"epochsilicon/pkg/utils"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
)
// ShowUpdateDialog displays an enhanced update dialog with download and install options
func ShowUpdateDialog(updateInfo *utils.UpdateInfo, currentVersion string, myWindow fyne.Window) {
latestVersion := strings.TrimPrefix(updateInfo.TagName, "v")
// Find the DMG asset
var dmgAsset *utils.Asset
for _, asset := range updateInfo.Assets {
if strings.HasSuffix(asset.Name, ".dmg") {
dmgAsset = &asset
break
}
}
if dmgAsset == nil {
dialog.ShowError(fmt.Errorf("no DMG file found in the latest release"), myWindow)
return
}
// Create content for the update dialog
titleLabel := widget.NewLabel(fmt.Sprintf("Update Available: v%s", latestVersion))
titleLabel.TextStyle = fyne.TextStyle{Bold: true}
currentVersionLabel := widget.NewLabel(fmt.Sprintf("Current version: v%s", currentVersion))
// Format file size
fileSize := formatFileSize(dmgAsset.Size)
fileSizeLabel := widget.NewLabel(fmt.Sprintf("Download size: %s", fileSize))
// Release notes
notesLabel := widget.NewLabel("Release notes:")
notesLabel.TextStyle = fyne.TextStyle{Bold: true}
notesText := widget.NewRichTextFromMarkdown(updateInfo.Body)
notesScroll := container.NewScroll(notesText)
notesScroll.SetMinSize(fyne.NewSize(480, 120))
// Progress bar (initially hidden)
progressBar := widget.NewProgressBar()
progressBar.Hide()
progressLabel := widget.NewLabel("")
progressLabel.Hide()
// Checkbox for suppressing this version
suppressCheck := widget.NewCheck("Don't show this update again", nil)
content := container.NewVBox(
titleLabel,
currentVersionLabel,
fileSizeLabel,
widget.NewSeparator(),
notesLabel,
notesScroll,
widget.NewSeparator(),
progressBar,
progressLabel,
suppressCheck,
)
// Create custom dialog
d := dialog.NewCustom("New Update Available", "", content, myWindow)
d.Resize(fyne.NewSize(550, 400))
// Download and install function
downloadAndInstall := func() {
// Show progress elements
progressBar.Show()
progressLabel.Show()
progressLabel.SetText("Starting download...")
// Disable dialog closing during download
d.SetButtons([]fyne.CanvasObject{})
go func() {
// Download with progress
downloadPath, err := utils.DownloadUpdate(dmgAsset.BrowserDownloadURL, func(downloaded, total int64) {
// Update progress on UI thread
fyne.NewAnimation(
time.Millisecond*50,
func(float32) {
if total > 0 {
progress := float64(downloaded) / float64(total)
progressBar.SetValue(progress)
progressLabel.SetText(fmt.Sprintf("Downloaded: %s / %s (%.1f%%)",
formatFileSize(downloaded), formatFileSize(total), progress*100))
}
},
).Curve = fyne.AnimationLinear
})
if err != nil {
progressLabel.SetText(fmt.Sprintf("Download failed: %v", err))
debug.Printf("Download failed: %v", err)
// Re-enable close button
d.SetButtons([]fyne.CanvasObject{
widget.NewButton("Close", func() { d.Hide() }),
})
return
}
fyne.DoAndWait(func() {
progressLabel.SetText("Installing update...")
progressBar.SetValue(1.0)
})
// Install update
err = utils.InstallUpdate(downloadPath)
if err != nil {
progressLabel.SetText(fmt.Sprintf("Installation failed: %v", err))
debug.Printf("Installation failed: %v", err)
// Re-enable close button
fyne.DoAndWait(func() {
d.SetButtons([]fyne.CanvasObject{
widget.NewButton("Close", func() { d.Hide() }),
})
})
return
}
// Success - show restart dialog
fyne.DoAndWait(func() {
progressLabel.SetText("Update installed successfully!")
})
restartDialog := dialog.NewConfirm(
"Update Complete",
"The update has been installed successfully and will require a restart. Would you like to close the application now?",
func(restart bool) {
d.Hide()
if restart {
utils.RestartApp()
fyne.CurrentApp().Quit()
}
},
myWindow,
)
restartDialog.Show()
}()
}
// Set dialog buttons
d.SetButtons([]fyne.CanvasObject{
widget.NewButton("Download & Install", downloadAndInstall),
widget.NewButton("Later", func() {
if suppressCheck.Checked {
// Save suppressed version
prefs, _ := utils.LoadPrefs()
prefs.SuppressedUpdateVersion = latestVersion
utils.SavePrefs(prefs)
}
d.Hide()
}),
})
d.Show()
}
// formatFileSize formats a file size in bytes to a human-readable string
func formatFileSize(bytes int64) string {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
switch {
case bytes >= GB:
return fmt.Sprintf("%.1f GB", float64(bytes)/float64(GB))
case bytes >= MB:
return fmt.Sprintf("%.1f MB", float64(bytes)/float64(MB))
case bytes >= KB:
return fmt.Sprintf("%.1f KB", float64(bytes)/float64(KB))
default:
return fmt.Sprintf("%d bytes", bytes)
}
}