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() } }