From d4dc061374428b6fe84b7af175ed8578de05ca72 Mon Sep 17 00:00:00 2001 From: JusterZhu Date: Wed, 20 May 2026 22:50:26 +0800 Subject: [PATCH] feat: add Simulate Update UI (model, view, viewmodel, nav entry) - Add SimulateConfigModel with all configuration fields - Add SimulateViewModel with folder/file pickers and placeholder logic - Add SimulateView.axaml with three sections (target, config, output) - Add Simulate nav item to MainWindowViewModel - Add i18n strings for zh-CN and en-US --- src/Models/SimulateConfigModel.cs | 36 ++++++++++++ src/Services/LocalizationService.cs | 42 ++++++++++++++ src/ViewModels/MainWindowViewModel.cs | 4 +- src/ViewModels/SimulateViewModel.cs | 76 ++++++++++++++++++++++++ src/Views/SimulateView.axaml | 84 +++++++++++++++++++++++++++ src/Views/SimulateView.axaml.cs | 8 +++ 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 src/Models/SimulateConfigModel.cs create mode 100644 src/ViewModels/SimulateViewModel.cs create mode 100644 src/Views/SimulateView.axaml create mode 100644 src/Views/SimulateView.axaml.cs diff --git a/src/Models/SimulateConfigModel.cs b/src/Models/SimulateConfigModel.cs new file mode 100644 index 0000000..5e4c804 --- /dev/null +++ b/src/Models/SimulateConfigModel.cs @@ -0,0 +1,36 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace GeneralUpdate.Tools.Models; + +/// +/// Configuration for the simulate-update module. +/// +public partial class SimulateConfigModel : ObservableObject +{ + /// User-provided old-version app directory to test against. + [ObservableProperty] private string _appDirectory = string.Empty; + + /// Path to a patch .zip generated by the Patch module. + [ObservableProperty] private string _patchFilePath = string.Empty; + + /// The current version of the app being tested. + [ObservableProperty] private string _currentVersion = "1.0.0.0"; + + /// The target version the patch upgrades to. + [ObservableProperty] private string _targetVersion = "2.0.0.0"; + + /// Platform selector (1=Windows, 2=Linux). + [ObservableProperty] private int _platform = 1; + + /// AppType sent to the server (1=ClientApp, 2=UpgradeApp). + [ObservableProperty] private int _appType = 1; + + /// Application secret key for the update API. + [ObservableProperty] private string _appSecretKey = "dfeb5833-975e-4afb-88f1-6278ee9aeff6"; + + /// Product identifier. + [ObservableProperty] private string _productId = "2d974e2a-31e6-4887-9bb1-b4689e98c77a"; + + /// Directory where client.cs / upgrade.cs and server are generated. + [ObservableProperty] private string _outputDirectory = string.Empty; +} diff --git a/src/Services/LocalizationService.cs b/src/Services/LocalizationService.cs index 416a402..3717eb7 100644 --- a/src/Services/LocalizationService.cs +++ b/src/Services/LocalizationService.cs @@ -115,6 +115,27 @@ public string this[string key] ["Theme.Light"] = "浅色", ["Theme.Dark"] = "深色", ["Theme.Toggle"] = "切换主题", + ["Nav.Simulate"] = "模拟更新", + ["Sim.Title"] = "模拟更新", + ["Sim.TestTarget"] = "测试目标", + ["Sim.OldAppDir"] = "旧版本应用目录", + ["Sim.PatchFile"] = "补丁包文件", + ["Sim.Select"] = "选择", + ["Sim.UpdateConfig"] = "更新配置", + ["Sim.CurrentVer"] = "当前版本", + ["Sim.TargetVer"] = "目标版本", + ["Sim.Platform"] = "平台", + ["Sim.AppType"] = "应用类型", + ["Sim.AppSecret"] = "应用密钥", + ["Sim.ProductId"] = "产品ID", + ["Sim.Output"] = "输出", + ["Sim.OutputDir"] = "模拟目录", + ["Sim.Start"] = "开始模拟", + ["Sim.SelectAppDir"] = "选择旧版本应用目录", + ["Sim.SelectPatch"] = "选择补丁包", + ["Sim.SelectOutput"] = "选择模拟输出目录", + ["Sim.ValidateDirs"] = "请填写所有必填项", + ["Sim.DotnetCheck"] = "需要 .NET 10.0 SDK,请先安装", }, ["en-US"] = new() { @@ -187,6 +208,27 @@ public string this[string key] ["Theme.Light"] = "Light", ["Theme.Dark"] = "Dark", ["Theme.Toggle"] = "Toggle Theme", + ["Nav.Simulate"] = "Simulate", + ["Sim.Title"] = "Simulate Update", + ["Sim.TestTarget"] = "Test Target", + ["Sim.OldAppDir"] = "Old App Directory", + ["Sim.PatchFile"] = "Patch Package", + ["Sim.Select"] = "Select", + ["Sim.UpdateConfig"] = "Update Config", + ["Sim.CurrentVer"] = "Current Version", + ["Sim.TargetVer"] = "Target Version", + ["Sim.Platform"] = "Platform", + ["Sim.AppType"] = "App Type", + ["Sim.AppSecret"] = "App Secret", + ["Sim.ProductId"] = "Product ID", + ["Sim.Output"] = "Output", + ["Sim.OutputDir"] = "Simulate Directory", + ["Sim.Start"] = "Start Simulation", + ["Sim.SelectAppDir"] = "Select old version directory", + ["Sim.SelectPatch"] = "Select patch package", + ["Sim.SelectOutput"] = "Select simulation output directory", + ["Sim.ValidateDirs"] = "Please fill in all required fields", + ["Sim.DotnetCheck"] = ".NET 10.0 SDK is required. Please install it first.", } }; diff --git a/src/ViewModels/MainWindowViewModel.cs b/src/ViewModels/MainWindowViewModel.cs index df0e4cb..d0786ba 100644 --- a/src/ViewModels/MainWindowViewModel.cs +++ b/src/ViewModels/MainWindowViewModel.cs @@ -35,6 +35,7 @@ private void SyncNavItems() NavItems.Add(new("Patch", _loc["Nav.Patch"], typeof(PatchViewModel), true)); NavItems.Add(new("Extension", _loc["Nav.Extension"], typeof(ExtensionViewModel), false)); NavItems.Add(new("OSS", _loc["Nav.OSS"], typeof(OSSViewModel), false)); + NavItems.Add(new("Simulate", _loc["Nav.Simulate"], typeof(SimulateViewModel), false)); } [RelayCommand] @@ -46,7 +47,8 @@ private void Navigate(NavItem item) { "Patch" => new PatchViewModel(), "Extension" => new ExtensionViewModel(), - _ => new OSSViewModel() + "OSS" => new OSSViewModel(), + _ => new SimulateViewModel() }; } diff --git a/src/ViewModels/SimulateViewModel.cs b/src/ViewModels/SimulateViewModel.cs new file mode 100644 index 0000000..211be8d --- /dev/null +++ b/src/ViewModels/SimulateViewModel.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using GeneralUpdate.Tools.Models; +using GeneralUpdate.Tools.Services; + +namespace GeneralUpdate.Tools.ViewModels; + +public partial class SimulateViewModel : ViewModelBase +{ + private readonly LocalizationService _loc = LocalizationService.Instance; + + public SimulateConfigModel Config { get; } = new(); + + [ObservableProperty] private bool _isRunning; + [ObservableProperty] private string _status; + [ObservableProperty] private ObservableCollection _log = new(); + + public ObservableCollection Platforms { get; } = new() + { + new(1, "Windows"), + new(2, "Linux") + }; + + public ObservableCollection AppTypes { get; } = new() + { + new(1, "ClientApp"), + new(2, "UpgradeApp") + }; + + public SimulateViewModel() + { + _status = _loc["Patch.Ready"]; + } + + async Task PickFolder(string title) + { + var tl = Avalonia.Controls.TopLevel.GetTopLevel( + (Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.MainWindow); + if (tl == null) return null; + var r = await tl.StorageProvider.OpenFolderPickerAsync( + new Avalonia.Platform.Storage.FolderPickerOpenOptions { Title = title, AllowMultiple = false }); + return r.Count > 0 ? r[0].Path.LocalPath : null; + } + + async Task PickFile(string title) + { + var tl = Avalonia.Controls.TopLevel.GetTopLevel( + (Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.MainWindow); + if (tl == null) return null; + var r = await tl.StorageProvider.OpenFilePickerAsync( + new Avalonia.Platform.Storage.FilePickerOpenOptions { Title = title, AllowMultiple = false }); + return r.Count > 0 ? r[0].Path.LocalPath : null; + } + + [RelayCommand] async Task SelectAppDir() { var p = await PickFolder("选择旧版本应用目录"); if (p != null) Config.AppDirectory = p; } + [RelayCommand] async Task SelectPatch() { var p = await PickFile("选择补丁包"); if (p != null) Config.PatchFilePath = p; } + [RelayCommand] async Task SelectOutputDir() { var p = await PickFolder("选择模拟输出目录"); if (p != null) Config.OutputDirectory = p; } + + [RelayCommand] + async Task StartSimulation() + { + // Validation will be implemented in issue #4 + if (string.IsNullOrWhiteSpace(Config.AppDirectory)) { Status = "请选择旧版本应用目录"; return; } + if (string.IsNullOrWhiteSpace(Config.PatchFilePath)) { Status = "请选择补丁包文件"; return; } + if (string.IsNullOrWhiteSpace(Config.OutputDirectory)) { Status = "请选择模拟输出目录"; return; } + Status = "模拟功能将在后续 issue 中实现"; + } + + void L(string msg) => Log.Add($"[{DateTime.Now:HH:mm:ss}] {msg}"); +} + +public record PlatformItem(int Value, string DisplayName) { public override string ToString() => DisplayName; } +public record AppTypeItem(int Value, string DisplayName) { public override string ToString() => DisplayName; } diff --git a/src/Views/SimulateView.axaml b/src/Views/SimulateView.axaml new file mode 100644 index 0000000..aa2dc5d --- /dev/null +++ b/src/Views/SimulateView.axaml @@ -0,0 +1,84 @@ + + + + + + + + + + + + +