Files
AUTO-MAS-test/Go_Updater/gui/manager.go
AoXuan 6b646378b6 refactor(updater): 重构 Go 版本更新器
- 更新项目名称为 AUTO_MAA_Go_Updater
- 重构代码结构,优化函数命名和逻辑
- 移除 CDK 相关的冗余代码
- 调整版本号为 git commit hash
- 更新构建配置和脚本
- 优化 API 客户端实现
2025-07-22 21:51:58 +08:00

514 lines
12 KiB
Go

package gui
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
// UpdateStatus 表示更新过程的当前状态
type UpdateStatus int
const (
StatusChecking UpdateStatus = iota
StatusUpdateAvailable
StatusDownloading
StatusInstalling
StatusCompleted
StatusError
)
// Config 表示 GUI 的配置结构
type Config struct {
ResourceID string
CurrentVersion string
UserAgent string
BackupURL string
}
// GUIManager 定义 GUI 管理的接口方法
type GUIManager interface {
ShowMainWindow()
UpdateStatus(status UpdateStatus, message string)
ShowProgress(percentage float64)
ShowError(errorMsg string)
ShowConfigDialog() (*Config, error)
Close()
}
// Manager 实现 GUIManager 接口
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("AUTO_MAA_Go_Updater")
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() *fyne.Container {
// Header section
header := container.NewVBox(
widget.NewCard("", "", container.NewVBox(
widget.NewLabelWithStyle("AUTO_MAA_Go_Updater", 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")
userAgentEntry := widget.NewEntry()
userAgentEntry.SetText("AUTO_MAA_Go_Updater/1.0")
backupURLEntry := widget.NewEntry()
backupURLEntry.SetPlaceHolder("备用下载地址(可选)")
// Create form
form := &widget.Form{
Items: []*widget.FormItem{
{Text: "资源ID:", Widget: resourceIDEntry},
{Text: "当前版本:", Widget: versionEntry},
{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,
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酱服务中的资源标识符
- **当前版本**: 当前软件的版本号
- **用户代理**: HTTP请求的用户代理字符串
- **备用下载地址**: 当Mirror酱服务不可用时的备用下载地址
`)
// 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()
}
}