Files
AUTO-MAS-test/Go_Updater/gui/manager.go
AoXuan 228e66315c feat(Go_Updater): 添加全新 Go 语言实现的自动更新器
- 新增多个源文件和目录,包括 app.rc、assets、build 脚本等
- 实现了与 MirrorChyan API 交互的客户端逻辑
- 添加了版本检查、更新检测和下载 URL 生成等功能
- 嵌入了配置模板和资源文件系统
- 提供了完整的构建和发布流程
2025-07-20 16:30:14 +08:00

522 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gui
import (
"fmt"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2"
)
// UpdateStatus represents the current status of the update process
type UpdateStatus int
const (
StatusChecking UpdateStatus = iota
StatusUpdateAvailable
StatusDownloading
StatusInstalling
StatusCompleted
StatusError
)
// Config represents the configuration structure for the GUI
type Config struct {
ResourceID string
CurrentVersion string
CDK string
UserAgent string
BackupURL string
}
// GUIManager interface defines the methods for GUI management
type GUIManager interface {
ShowMainWindow()
UpdateStatus(status UpdateStatus, message string)
ShowProgress(percentage float64)
ShowError(errorMsg string)
ShowConfigDialog() (*Config, error)
Close()
}
// Manager implements the GUIManager interface
type Manager struct {
app fyne.App
window fyne.Window
statusLabel *widget.Label
progressBar *widget.ProgressBar
actionButton *widget.Button
versionLabel *widget.Label
releaseNotes *widget.RichText
currentStatus UpdateStatus
onCheckUpdate func()
onCancel func()
}
// NewManager creates a new GUI manager instance
func NewManager() *Manager {
a := app.New()
a.SetIcon(theme.ComputerIcon())
w := a.NewWindow("轻量级更新器")
w.Resize(fyne.NewSize(500, 400))
w.SetFixedSize(false)
w.CenterOnScreen()
return &Manager{
app: a,
window: w,
}
}
// SetCallbacks sets the callback functions for user actions
func (m *Manager) SetCallbacks(onCheckUpdate, onCancel func()) {
m.onCheckUpdate = onCheckUpdate
m.onCancel = onCancel
}
// ShowMainWindow displays the main application window
func (m *Manager) ShowMainWindow() {
// Create UI components
m.createUIComponents()
// Create main layout
content := m.createMainLayout()
m.window.SetContent(content)
m.window.ShowAndRun()
}
// createUIComponents initializes all UI components
func (m *Manager) createUIComponents() {
// Status label
m.statusLabel = widget.NewLabel("准备检查更新...")
m.statusLabel.Alignment = fyne.TextAlignCenter
// Progress bar
m.progressBar = widget.NewProgressBar()
m.progressBar.Hide()
// Version label
m.versionLabel = widget.NewLabel("当前版本: 未知")
m.versionLabel.TextStyle = fyne.TextStyle{Italic: true}
// Release notes
m.releaseNotes = widget.NewRichText()
m.releaseNotes.Hide()
// Action button
m.actionButton = widget.NewButton("检查更新", func() {
if m.onCheckUpdate != nil {
m.onCheckUpdate()
}
})
m.actionButton.Importance = widget.HighImportance
}
// createMainLayout creates the main window layout
func (m *Manager) createMainLayout() *container.VBox {
// Header section
header := container.NewVBox(
widget.NewCard("", "", container.NewVBox(
widget.NewLabelWithStyle("轻量级更新器", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
m.versionLabel,
)),
)
// Status section
statusSection := container.NewVBox(
m.statusLabel,
m.progressBar,
)
// Release notes section
releaseNotesCard := widget.NewCard("更新日志", "", container.NewScroll(m.releaseNotes))
releaseNotesCard.Hide()
// Button section
buttonSection := container.NewHBox(
widget.NewButton("配置", func() {
m.showConfigDialog()
}),
widget.NewSpacer(),
m.actionButton,
)
// Main layout
return container.NewVBox(
header,
widget.NewSeparator(),
statusSection,
releaseNotesCard,
widget.NewSeparator(),
buttonSection,
)
}
// UpdateStatus updates the current status and UI accordingly
func (m *Manager) UpdateStatus(status UpdateStatus, message string) {
m.currentStatus = status
m.statusLabel.SetText(message)
switch status {
case StatusChecking:
m.actionButton.SetText("检查中...")
m.actionButton.Disable()
m.progressBar.Hide()
case StatusUpdateAvailable:
m.actionButton.SetText("开始更新")
m.actionButton.Enable()
m.progressBar.Hide()
case StatusDownloading:
m.actionButton.SetText("下载中...")
m.actionButton.Disable()
m.progressBar.Show()
case StatusInstalling:
m.actionButton.SetText("安装中...")
m.actionButton.Disable()
m.progressBar.Show()
case StatusCompleted:
m.actionButton.SetText("完成")
m.actionButton.Enable()
m.progressBar.Hide()
case StatusError:
m.actionButton.SetText("重试")
m.actionButton.Enable()
m.progressBar.Hide()
}
}
// ShowProgress updates the progress bar
func (m *Manager) ShowProgress(percentage float64) {
if percentage < 0 {
percentage = 0
}
if percentage > 100 {
percentage = 100
}
m.progressBar.SetValue(percentage / 100.0)
m.progressBar.Show()
}
// ShowError displays an error dialog
func (m *Manager) ShowError(errorMsg string) {
dialog.ShowError(fmt.Errorf(errorMsg), m.window)
}
// ShowConfigDialog displays the configuration dialog
func (m *Manager) ShowConfigDialog() (*Config, error) {
return m.showConfigDialog()
}
// showConfigDialog creates and shows the configuration dialog
func (m *Manager) showConfigDialog() (*Config, error) {
// Create form entries
resourceIDEntry := widget.NewEntry()
resourceIDEntry.SetPlaceHolder("例如: M9A")
versionEntry := widget.NewEntry()
versionEntry.SetPlaceHolder("例如: v1.0.0")
cdkEntry := widget.NewPasswordEntry()
cdkEntry.SetPlaceHolder("输入您的CDK可选")
userAgentEntry := widget.NewEntry()
userAgentEntry.SetText("LightweightUpdater/1.0")
backupURLEntry := widget.NewEntry()
backupURLEntry.SetPlaceHolder("备用下载地址(可选)")
// Create form
form := &widget.Form{
Items: []*widget.FormItem{
{Text: "资源ID:", Widget: resourceIDEntry},
{Text: "当前版本:", Widget: versionEntry},
{Text: "CDK:", Widget: cdkEntry},
{Text: "用户代理:", Widget: userAgentEntry},
{Text: "备用下载地址:", Widget: backupURLEntry},
},
}
// Create result channel
resultChan := make(chan *Config, 1)
errorChan := make(chan error, 1)
// Create dialog
configDialog := dialog.NewCustomConfirm(
"配置设置",
"保存",
"取消",
form,
func(confirmed bool) {
if confirmed {
config := &Config{
ResourceID: resourceIDEntry.Text,
CurrentVersion: versionEntry.Text,
CDK: cdkEntry.Text,
UserAgent: userAgentEntry.Text,
BackupURL: backupURLEntry.Text,
}
// Basic validation
if config.ResourceID == "" {
errorChan <- fmt.Errorf("资源ID不能为空")
return
}
if config.CurrentVersion == "" {
errorChan <- fmt.Errorf("当前版本不能为空")
return
}
resultChan <- config
} else {
errorChan <- fmt.Errorf("用户取消了配置")
}
},
m.window,
)
// Add help text
helpText := widget.NewRichTextFromMarkdown(`
**配置说明:**
- **资源ID**: Mirror酱服务中的资源标识符
- **当前版本**: 当前软件的版本号
- **CDK**: Mirror酱服务的访问密钥可选提供更好的下载体验
- **用户代理**: HTTP请求的用户代理字符串
- **备用下载地址**: 当Mirror酱服务不可用时的备用下载地址
如需获取CDK请访问 [Mirror酱官网](https://mirrorchyan.com)
`)
// Create container with help text
dialogContent := container.NewVBox(
form,
widget.NewSeparator(),
helpText,
)
configDialog.SetContent(dialogContent)
configDialog.Resize(fyne.NewSize(600, 500))
configDialog.Show()
// Wait for result
select {
case config := <-resultChan:
return config, nil
case err := <-errorChan:
return nil, err
}
}
// SetVersionInfo updates the version display
func (m *Manager) SetVersionInfo(version string) {
m.versionLabel.SetText(fmt.Sprintf("当前版本: %s", version))
}
// ShowReleaseNotes displays the release notes
func (m *Manager) ShowReleaseNotes(notes string) {
if notes != "" {
m.releaseNotes.ParseMarkdown(notes)
// Find the release notes card and show it
if parent := m.window.Content().(*container.VBox); parent != nil {
for _, obj := range parent.Objects {
if card, ok := obj.(*widget.Card); ok && card.Title == "更新日志" {
card.Show()
break
}
}
}
}
}
// UpdateStatusWithDetails updates status with detailed information
func (m *Manager) UpdateStatusWithDetails(status UpdateStatus, message string, details map[string]string) {
m.UpdateStatus(status, message)
// Update version info if provided
if version, ok := details["version"]; ok {
m.SetVersionInfo(version)
}
// Show release notes if provided
if notes, ok := details["release_notes"]; ok {
m.ShowReleaseNotes(notes)
}
// Update progress if provided
if progress, ok := details["progress"]; ok {
if p, err := fmt.Sscanf(progress, "%f", new(float64)); err == nil && p == 1 {
var progressValue float64
fmt.Sscanf(progress, "%f", &progressValue)
m.ShowProgress(progressValue)
}
}
}
// ShowProgressWithSpeed shows progress with download speed information
func (m *Manager) ShowProgressWithSpeed(percentage float64, speed int64, eta string) {
m.ShowProgress(percentage)
// Update status with speed and ETA information
speedText := m.formatSpeed(speed)
statusText := fmt.Sprintf("下载中... %.1f%% (%s)", percentage, speedText)
if eta != "" {
statusText += fmt.Sprintf(" - 剩余时间: %s", eta)
}
m.statusLabel.SetText(statusText)
}
// formatSpeed formats the download speed for display
func (m *Manager) formatSpeed(bytesPerSecond int64) string {
if bytesPerSecond < 1024 {
return fmt.Sprintf("%d B/s", bytesPerSecond)
} else if bytesPerSecond < 1024*1024 {
return fmt.Sprintf("%.1f KB/s", float64(bytesPerSecond)/1024)
} else {
return fmt.Sprintf("%.1f MB/s", float64(bytesPerSecond)/(1024*1024))
}
}
// ShowConfirmDialog shows a confirmation dialog
func (m *Manager) ShowConfirmDialog(title, message string, callback func(bool)) {
dialog.ShowConfirm(title, message, callback, m.window)
}
// ShowInfoDialog shows an information dialog
func (m *Manager) ShowInfoDialog(title, message string) {
dialog.ShowInformation(title, message, m.window)
}
// ShowUpdateAvailableDialog shows a dialog when update is available
func (m *Manager) ShowUpdateAvailableDialog(currentVersion, newVersion, releaseNotes string, onConfirm func()) {
content := container.NewVBox(
widget.NewLabel(fmt.Sprintf("发现新版本: %s", newVersion)),
widget.NewLabel(fmt.Sprintf("当前版本: %s", currentVersion)),
widget.NewSeparator(),
)
if releaseNotes != "" {
notesWidget := widget.NewRichText()
notesWidget.ParseMarkdown(releaseNotes)
notesScroll := container.NewScroll(notesWidget)
notesScroll.SetMinSize(fyne.NewSize(400, 200))
content.Add(widget.NewLabel("更新内容:"))
content.Add(notesScroll)
}
dialog.ShowCustomConfirm(
"发现新版本",
"立即更新",
"稍后提醒",
content,
func(confirmed bool) {
if confirmed && onConfirm != nil {
onConfirm()
}
},
m.window,
)
}
// SetActionButtonCallback sets the callback for the main action button
func (m *Manager) SetActionButtonCallback(callback func()) {
if m.actionButton != nil {
m.actionButton.OnTapped = callback
}
}
// EnableActionButton enables or disables the action button
func (m *Manager) EnableActionButton(enabled bool) {
if m.actionButton != nil {
if enabled {
m.actionButton.Enable()
} else {
m.actionButton.Disable()
}
}
}
// SetActionButtonText sets the text of the action button
func (m *Manager) SetActionButtonText(text string) {
if m.actionButton != nil {
m.actionButton.SetText(text)
}
}
// ShowErrorWithRetry shows an error with retry option
func (m *Manager) ShowErrorWithRetry(errorMsg string, onRetry func()) {
dialog.ShowCustomConfirm(
"错误",
"重试",
"取消",
widget.NewLabel(errorMsg),
func(retry bool) {
if retry && onRetry != nil {
onRetry()
}
},
m.window,
)
}
// UpdateProgressBar updates the progress bar with custom styling
func (m *Manager) UpdateProgressBar(percentage float64, color string) {
m.ShowProgress(percentage)
// Note: Fyne doesn't support custom colors easily, but we keep the interface for future enhancement
}
// HideProgressBar hides the progress bar
func (m *Manager) HideProgressBar() {
if m.progressBar != nil {
m.progressBar.Hide()
}
}
// ShowProgressBar shows the progress bar
func (m *Manager) ShowProgressBar() {
if m.progressBar != nil {
m.progressBar.Show()
}
}
// SetWindowTitle sets the window title
func (m *Manager) SetWindowTitle(title string) {
if m.window != nil {
m.window.SetTitle(title)
}
}
// GetCurrentStatus returns the current update status
func (m *Manager) GetCurrentStatus() UpdateStatus {
return m.currentStatus
}
// IsWindowVisible returns whether the window is currently visible
func (m *Manager) IsWindowVisible() bool {
return m.window != nil && m.window.Content() != nil
}
// RefreshUI refreshes the user interface
func (m *Manager) RefreshUI() {
if m.window != nil && m.window.Content() != nil {
m.window.Content().Refresh()
}
}
// Close closes the application
func (m *Manager) Close() {
if m.window != nil {
m.window.Close()
}
}