Compare commits

...

4 Commits

Author SHA1 Message Date
lincube
c1f148f7d6 changed.更新界面微调 2026-05-26 17:52:44 +08:00
lincube
a75ed0ced1 fix.使用正确的图标 2026-05-26 17:40:35 +08:00
lincube
2dc40c53e2 feat.更新界面中文补充 2026-05-26 17:21:21 +08:00
lincube
a99ed9fef2 changed.将更新系统全面纳入到统一的设置接口 2026-05-26 14:25:52 +08:00
7 changed files with 600 additions and 293 deletions

View File

@@ -0,0 +1,355 @@
using CommunityToolkit.Mvvm.Input;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Services.Update;
using LanMountainDesktop.Shared.Contracts.Update;
using LanMountainDesktop.ViewModels;
using Xunit;
using UpdateDownloadResult = LanMountainDesktop.Services.Update.DownloadResult;
using SettingsUpdateState = LanMountainDesktop.Services.Settings.UpdateSettingsState;
namespace LanMountainDesktop.Tests;
public sealed class UpdateSettingsInterfaceTests
{
[Fact]
public async Task UpdateSettingsViewModel_RoutesActionsThroughUpdateSettingsService()
{
var update = new FakeUpdateSettingsService();
var viewModel = new UpdateSettingsViewModel(new FakeSettingsFacade(update));
Assert.Equal(0, update.SaveCalls);
update.CheckReport = new UpdateCheckReport(
true,
"1.2.3",
"1.0.0",
UpdatePayloadKind.DeltaPlonds,
"dist-1",
UpdateSettingsValues.ChannelStable,
DateTimeOffset.Parse("2026-05-06T00:00:00Z"),
42,
null,
null);
await ((IAsyncRelayCommand)viewModel.CheckCommand).ExecuteAsync(null);
Assert.Equal(1, update.CheckCalls);
Assert.Equal("1.2.3", viewModel.LatestVersionText);
Assert.True(viewModel.IsDeltaUpdate);
update.SetPhase(UpdatePhase.Checked);
await ((IAsyncRelayCommand)viewModel.DownloadCommand).ExecuteAsync(null);
Assert.Equal(1, update.DownloadCalls);
update.SetPhase(UpdatePhase.Downloaded);
await ((IAsyncRelayCommand)viewModel.InstallCommand).ExecuteAsync(null);
Assert.Equal(1, update.InstallCalls);
update.SetPhase(UpdatePhase.Downloading);
await ((IAsyncRelayCommand)viewModel.PauseCommand).ExecuteAsync(null);
Assert.Equal(1, update.PauseCalls);
update.SetPhase(UpdatePhase.PausedDownloading);
await ((IAsyncRelayCommand)viewModel.ResumeCommand).ExecuteAsync(null);
Assert.Equal(1, update.ResumeCalls);
update.SetPhase(UpdatePhase.Downloading);
await ((IAsyncRelayCommand)viewModel.CancelCommand).ExecuteAsync(null);
Assert.Equal(1, update.CancelCalls);
}
[Fact]
public void UpdateSettingsViewModel_SavesPreferencesThroughUpdateSettingsService()
{
var update = new FakeUpdateSettingsService();
var viewModel = new UpdateSettingsViewModel(new FakeSettingsFacade(update));
viewModel.SelectedUpdateChannelValue = UpdateSettingsValues.ChannelPreview;
viewModel.SelectedUpdateSourceValue = UpdateSettingsValues.DownloadSourceGitHub;
viewModel.SelectedUpdateModeValue = UpdateSettingsValues.ModeManual;
viewModel.DownloadThreadsSliderValue = 12;
viewModel.ForceReinstall = true;
Assert.True(update.SaveCalls >= 5);
Assert.Equal(UpdateSettingsValues.ChannelPreview, update.State.UpdateChannel);
Assert.Equal(UpdateSettingsValues.DownloadSourceGitHub, update.State.UpdateDownloadSource);
Assert.Equal(UpdateSettingsValues.ModeManual, update.State.UpdateMode);
Assert.Equal(12, update.State.UpdateDownloadThreads);
Assert.True(update.State.ForceUpdateReinstall);
}
[Fact]
public void UpdateSettingsViewModel_RestoresPersistedPendingAndLastCheckedState()
{
var update = new FakeUpdateSettingsService
{
State = DefaultUpdateState() with
{
PendingUpdateVersion = "2.0.0",
PendingUpdatePublishedAtUtcMs = DateTimeOffset.Parse("2026-05-06T00:00:00Z").ToUnixTimeMilliseconds(),
LastUpdateCheckUtcMs = DateTimeOffset.Parse("2026-05-07T00:00:00Z").ToUnixTimeMilliseconds()
}
};
var viewModel = new UpdateSettingsViewModel(new FakeSettingsFacade(update));
Assert.True(viewModel.IsUpdateAvailable);
Assert.Equal("2.0.0", viewModel.LatestVersionText);
Assert.NotEmpty(viewModel.PublishedAtText);
Assert.Contains("Last checked", viewModel.LastCheckedText, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task SettingsUpdateManifestProvider_UsesSelectedUpdateSource()
{
var update = new FakeUpdateSettingsService
{
State = DefaultUpdateState() with { UpdateDownloadSource = UpdateSettingsValues.DownloadSourceGitHub }
};
var plonds = new FakeManifestProvider("plonds");
var github = new FakeManifestProvider("github");
var provider = new SettingsUpdateManifestProvider(new FakeSettingsFacade(update), plonds, github);
var manifest = await provider.GetLatestAsync(
UpdateSettingsValues.ChannelStable,
"windows-x64",
new Version(1, 0, 0),
CancellationToken.None);
Assert.Equal("github", manifest?.DistributionId);
Assert.Equal(0, plonds.GetLatestCalls);
Assert.Equal(1, github.GetLatestCalls);
update.State = update.State with { UpdateDownloadSource = UpdateSettingsValues.DownloadSourcePlonds };
manifest = await provider.GetLatestAsync(
UpdateSettingsValues.ChannelStable,
"windows-x64",
new Version(1, 0, 0),
CancellationToken.None);
Assert.Equal("plonds", manifest?.DistributionId);
Assert.Equal(1, plonds.GetLatestCalls);
}
[Fact]
public void FromFullInstaller_IncludesPreferredInstallerInMirrors()
{
var release = new GitHubReleaseInfo(
"v1.2.3",
"Release",
false,
false,
DateTimeOffset.Parse("2026-05-06T00:00:00Z"),
[new GitHubReleaseAsset("LanMountainDesktop-setup-x64.exe", "https://example.test/setup.exe", 123, "abc")]);
var manifest = UpdateManifestMapper.FromFullInstaller(release, UpdateSettingsValues.ChannelStable, "windows-x64");
Assert.NotNull(manifest.InstallerMirrors);
var mirror = Assert.Single(manifest.InstallerMirrors!);
Assert.Equal("https://example.test/setup.exe", mirror.Url);
}
[Fact]
public void ApplyDownloadSource_UsesGhProxyForGithubProxySource()
{
var url = "https://github.com/owner/repo/releases/download/v1/app.exe";
Assert.Equal(url, UpdateDownloadEngine.ApplyDownloadSource(url, UpdateSettingsValues.DownloadSourceGitHub));
Assert.Equal(
$"{UpdateSettingsValues.DefaultGhProxyBaseUrl}{url}",
UpdateDownloadEngine.ApplyDownloadSource(url, UpdateSettingsValues.DownloadSourceGhProxy));
}
private static SettingsUpdateState DefaultUpdateState() => new(
IncludePrereleaseUpdates: false,
UpdateChannel: UpdateSettingsValues.ChannelStable,
UpdateMode: UpdateSettingsValues.ModeSilentDownload,
UpdateDownloadSource: UpdateSettingsValues.DownloadSourcePlonds,
UpdateDownloadThreads: UpdateSettingsValues.DefaultDownloadThreads,
ForceUpdateReinstall: false,
UseGhProxyMirror: false,
PendingUpdateInstallerPath: null,
PendingUpdateVersion: null,
PendingUpdatePublishedAtUtcMs: null,
LastUpdateCheckUtcMs: null,
PendingUpdateSha256: null);
private sealed class FakeUpdateSettingsService : IUpdateSettingsService
{
public SettingsUpdateState State { get; set; } = DefaultUpdateState();
public UpdatePhase CurrentPhase { get; private set; } = UpdatePhase.Idle;
public UpdateCheckReport CheckReport { get; set; } = new(false, null, null, null, null, null, null, null, null, null);
public UpdateDownloadResult DownloadResult { get; set; } = new(true, "downloaded", null, true);
public InstallResult InstallResult { get; set; } = new(true, null, false);
public int SaveCalls { get; private set; }
public int CheckCalls { get; private set; }
public int DownloadCalls { get; private set; }
public int InstallCalls { get; private set; }
public int PauseCalls { get; private set; }
public int ResumeCalls { get; private set; }
public int CancelCalls { get; private set; }
public event Action<UpdatePhase>? PhaseChanged;
public event Action<UpdateProgressReport>? ProgressChanged;
public void SetPhase(UpdatePhase phase)
{
CurrentPhase = phase;
PhaseChanged?.Invoke(phase);
}
public SettingsUpdateState Get() => State;
public void Save(SettingsUpdateState state)
{
SaveCalls++;
State = state;
}
public Task<UpdateCheckReport> CheckAsync(CancellationToken cancellationToken = default)
{
CheckCalls++;
SetPhase(UpdatePhase.Checked);
return Task.FromResult(CheckReport);
}
public Task<UpdateDownloadResult> DownloadAsync(CancellationToken cancellationToken = default)
{
DownloadCalls++;
SetPhase(UpdatePhase.Downloaded);
return Task.FromResult(DownloadResult);
}
public Task<InstallResult> InstallAsync(CancellationToken cancellationToken = default)
{
InstallCalls++;
SetPhase(UpdatePhase.Installed);
return Task.FromResult(InstallResult);
}
public Task RollbackAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
public Task PauseAsync()
{
PauseCalls++;
SetPhase(UpdatePhase.PausedDownloading);
return Task.CompletedTask;
}
public Task<UpdateDownloadResult> ResumeAsync(CancellationToken cancellationToken = default)
{
ResumeCalls++;
SetPhase(UpdatePhase.Downloaded);
return Task.FromResult(DownloadResult);
}
public Task CancelAsync()
{
CancelCalls++;
SetPhase(UpdatePhase.Idle);
return Task.CompletedTask;
}
public Task AutoCheckIfEnabledAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
public bool TryApplyOnExit() => false;
public Task<UpdateCheckResult> CheckForUpdatesAsync(Version currentVersion, bool includePrerelease, CancellationToken cancellationToken = default)
=> Task.FromResult(new UpdateCheckResult(true, false, currentVersion.ToString(), string.Empty, null, null, null));
public Task<UpdateCheckResult> ForceCheckForUpdatesAsync(Version currentVersion, bool includePrerelease, CancellationToken cancellationToken = default)
=> CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
public Task<PlondsUpdatePayload?> GetPlondsUpdatePayloadAsync(Version currentVersion, bool includePrerelease, bool isForce = false, CancellationToken cancellationToken = default)
=> Task.FromResult<PlondsUpdatePayload?>(null);
public Task<LanMountainDesktop.Services.UpdateDownloadResult> DownloadAssetAsync(
GitHubReleaseAsset asset,
string destinationFilePath,
string downloadSource,
int maxParallelSegments,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default)
=> Task.FromResult(new LanMountainDesktop.Services.UpdateDownloadResult(false, null, "not used", false));
public Task<LanMountainDesktop.Services.UpdateDownloadResult> RedownloadAssetAsync(
GitHubReleaseAsset asset,
string destinationFilePath,
string downloadSource,
int maxParallelSegments,
IProgress<double>? progress = null,
CancellationToken cancellationToken = default)
=> Task.FromResult(new LanMountainDesktop.Services.UpdateDownloadResult(false, null, "not used", false));
}
private sealed class FakeManifestProvider(string providerName) : IUpdateManifestProvider
{
public string ProviderName { get; } = providerName;
public int GetLatestCalls { get; private set; }
public Task<UpdateManifest?> GetLatestAsync(string channel, string platform, Version currentVersion, CancellationToken ct)
{
GetLatestCalls++;
return Task.FromResult<UpdateManifest?>(CreateManifest(ProviderName, channel, platform));
}
public Task<UpdateManifest?> GetByVersionAsync(string version, string channel, string platform, CancellationToken ct)
=> Task.FromResult<UpdateManifest?>(CreateManifest(ProviderName, channel, platform));
public Task<IReadOnlyList<UpdateManifest>> GetIncrementalChainAsync(string channel, string platform, Version fromVersion, Version toVersion, CancellationToken ct)
=> Task.FromResult<IReadOnlyList<UpdateManifest>>([CreateManifest(ProviderName, channel, platform)]);
private static UpdateManifest CreateManifest(string id, string channel, string platform) => new(
id,
"1.0.0",
"1.1.0",
platform,
channel,
DateTimeOffset.Parse("2026-05-06T00:00:00Z"),
UpdatePayloadKind.DeltaPlonds,
"https://example.test/filemap.json",
"https://example.test/filemap.json.sig",
null,
[],
null,
new Dictionary<string, string>());
}
private sealed class FakeSettingsFacade(IUpdateSettingsService update) : ISettingsFacadeService
{
public ISettingsService Settings => throw new NotSupportedException();
public ISettingsCatalog Catalog => throw new NotSupportedException();
public IGridSettingsService Grid => throw new NotSupportedException();
public IWallpaperSettingsService Wallpaper => throw new NotSupportedException();
public IWallpaperMediaService WallpaperMedia => throw new NotSupportedException();
public IThemeAppearanceService Theme => throw new NotSupportedException();
public IStatusBarSettingsService StatusBar => throw new NotSupportedException();
public ITextCapsuleSettingsService TextCapsule => throw new NotSupportedException();
public IWeatherSettingsService Weather => throw new NotSupportedException();
public IRegionSettingsService Region { get; } = new FakeRegionSettingsService();
public IPrivacySettingsService Privacy => throw new NotSupportedException();
public IUpdateSettingsService Update { get; } = update;
public ILauncherCatalogService LauncherCatalog => throw new NotSupportedException();
public ILauncherPolicyService LauncherPolicy => throw new NotSupportedException();
public IPluginManagementSettingsService PluginManagement => throw new NotSupportedException();
public IPluginCatalogSettingsService PluginCatalog => throw new NotSupportedException();
public IApplicationInfoService ApplicationInfo { get; } = new FakeApplicationInfoService();
}
private sealed class FakeRegionSettingsService : IRegionSettingsService
{
public RegionSettingsState Get() => new("en-US", null);
public void Save(RegionSettingsState state) { }
public TimeZoneService GetTimeZoneService() => throw new NotSupportedException();
}
private sealed class FakeApplicationInfoService : IApplicationInfoService
{
public string GetAppVersionText() => "1.0.0";
public string GetAppCodenameText() => "Test";
public AppRenderBackendInfo GetRenderBackendInfo() => throw new NotSupportedException();
}
}

View File

@@ -1210,7 +1210,7 @@ public partial class App : Application
try try
{ {
HostUpdateOrchestratorProvider.GetOrCreate().TryApplyOnExit(); _settingsFacade.Update.TryApplyOnExit();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -1441,5 +1441,29 @@
"settings.general.back_to_windows_fluent_icon_desc": "搜索并选择左侧图标位使用的内置 Fluent 图标。", "settings.general.back_to_windows_fluent_icon_desc": "搜索并选择左侧图标位使用的内置 Fluent 图标。",
"settings.general.back_to_windows_icon_text_header": "文字图标", "settings.general.back_to_windows_icon_text_header": "文字图标",
"settings.general.back_to_windows_icon_text_desc": "输入最多四个字符,作为左侧图标显示。", "settings.general.back_to_windows_icon_text_desc": "输入最多四个字符,作为左侧图标显示。",
"settings.general.back_to_windows_fluent_icon_search_placeholder": "搜索图标" "settings.general.back_to_windows_fluent_icon_search_placeholder": "搜索图标",
"settings.update.channel_description": "选择“正式版”以保证稳定性,选择“预览版”体验早期功能。",
"settings.update.check_card_title": "检查更新",
"settings.update.download_threads_description": "设置应用更新下载的并行线程数,可随时暂停并在支持的情况下恢复下载。",
"settings.update.force_reinstall_description": "下载所选版本的完整包,将此次运行标记为重新安装,而不是增量更新。",
"settings.update.force_reinstall_label": "强制重新安装",
"settings.update.latest_version_none": "已是最新",
"settings.update.mode_description": "“手动更新”不自动下载与安装。“静默下载”在后台下载,由你确认安装。“静默安装”在后台下载并于下次退出时应用。",
"settings.update.mode_silent_download": "静默下载",
"settings.update.mode_silent_install": "静默安装",
"settings.update.resume_support_description": "下载操作会保留部分文件与包元数据,以便在服务器支持时通过暂停和继续功能恢复之前的进度。",
"settings.update.resume_support_label": "断点续传支持",
"settings.update.source_description": "选择更新工作流所使用的清单与安装包来源。",
"settings.update.source_gh_proxy": "gh-proxy 镜像",
"settings.update.status_download_failed": "下载失败。",
"settings.update.status_install_failed": "安装失败。",
"settings.update.status_installed": "安装完成。",
"settings.update.status_paused": "更新已暂停。",
"settings.update.status_resuming": "正在恢复下载...",
"settings.update.status_rolled_back": "已回滚更新。",
"settings.update.status_section_header": "更新状态",
"settings.update.transfer_controls_description": "暂停正在运行的下载,从保存的状态恢复,或取消并清除待处理的更新文件。",
"settings.update.transfer_controls_title": "传输控制",
"settings.update.type_reinstall": "重新安装",
"settings.update.update_type_label": "更新类型"
} }

View File

@@ -6,7 +6,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using LanMountainDesktop.Services; using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings; using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Services.Update;
using LanMountainDesktop.Shared.Contracts.Update; using LanMountainDesktop.Shared.Contracts.Update;
using UpdateSettingsValues = LanMountainDesktop.Services.UpdateSettingsValues; using UpdateSettingsValues = LanMountainDesktop.Services.UpdateSettingsValues;
@@ -14,27 +13,28 @@ namespace LanMountainDesktop.ViewModels;
public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
{ {
private readonly UpdateOrchestrator _orchestrator;
private readonly ISettingsFacadeService _settingsFacade; private readonly ISettingsFacadeService _settingsFacade;
private readonly IUpdateSettingsService _updateSettingsService;
private readonly LocalizationService _localizationService; private readonly LocalizationService _localizationService;
private readonly string _languageCode; private readonly string _languageCode;
private bool _suppressPreferenceSave;
private bool _disposed; private bool _disposed;
public UpdateSettingsViewModel(UpdateOrchestrator orchestrator, ISettingsFacadeService settingsFacade) public UpdateSettingsViewModel(ISettingsFacadeService settingsFacade)
{ {
_orchestrator = orchestrator ?? throw new ArgumentNullException(nameof(orchestrator));
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade)); _settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
_updateSettingsService = _settingsFacade.Update;
_localizationService = new LocalizationService(); _localizationService = new LocalizationService();
_languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode); _languageCode = _localizationService.NormalizeLanguageCode(_settingsFacade.Region.Get().LanguageCode);
CurrentPhase = _orchestrator.CurrentPhase; CurrentPhase = _updateSettingsService.CurrentPhase;
CurrentVersionText = _settingsFacade.ApplicationInfo.GetAppVersionText(); CurrentVersionText = _settingsFacade.ApplicationInfo.GetAppVersionText();
RefreshLocalizedText(); RefreshLocalizedText();
LoadPreferenceState(); LoadPreferenceState();
StatusMessage = GetPhaseStatusText(CurrentPhase); StatusMessage = GetPhaseStatusText(CurrentPhase);
_orchestrator.PhaseChanged += OnOrchestratorPhaseChanged; _updateSettingsService.PhaseChanged += OnUpdatePhaseChanged;
_orchestrator.ProgressChanged += OnOrchestratorProgressChanged; _updateSettingsService.ProgressChanged += OnUpdateProgressChanged;
} }
[ObservableProperty] private UpdatePhase _currentPhase = UpdatePhase.Idle; [ObservableProperty] private UpdatePhase _currentPhase = UpdatePhase.Idle;
@@ -208,7 +208,7 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private async Task CheckAsync() private async Task CheckAsync()
{ {
StatusMessage = GetCheckingStatusText(); StatusMessage = GetCheckingStatusText();
var report = await _orchestrator.CheckAsync(CancellationToken.None); var report = await _updateSettingsService.CheckAsync(CancellationToken.None);
LastCheckedText = string.Format( LastCheckedText = string.Format(
CultureInfo.CurrentCulture, CultureInfo.CurrentCulture,
L("settings.update.last_checked_format", "Last checked: {0}"), L("settings.update.last_checked_format", "Last checked: {0}"),
@@ -244,7 +244,7 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private async Task DownloadAsync() private async Task DownloadAsync()
{ {
StatusMessage = GetDownloadingStatusText(); StatusMessage = GetDownloadingStatusText();
var result = await _orchestrator.DownloadAsync(CancellationToken.None); var result = await _updateSettingsService.DownloadAsync(CancellationToken.None);
if (result.Success) if (result.Success)
{ {
StatusMessage = GetDownloadCompleteStatusText(); StatusMessage = GetDownloadCompleteStatusText();
@@ -263,7 +263,7 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private async Task InstallAsync() private async Task InstallAsync()
{ {
StatusMessage = GetInstallingStatusText(); StatusMessage = GetInstallingStatusText();
var result = await _orchestrator.InstallAsync(CancellationToken.None); var result = await _updateSettingsService.InstallAsync(CancellationToken.None);
if (result.Success) if (result.Success)
{ {
StatusMessage = GetInstallSuccessStatusText(); StatusMessage = GetInstallSuccessStatusText();
@@ -278,14 +278,14 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private async Task RollbackAsync() private async Task RollbackAsync()
{ {
StatusMessage = GetRollingBackStatusText(); StatusMessage = GetRollingBackStatusText();
await _orchestrator.RollbackAsync(CancellationToken.None); await _updateSettingsService.RollbackAsync(CancellationToken.None);
StatusMessage = GetRollbackCompleteStatusText(); StatusMessage = GetRollbackCompleteStatusText();
} }
[RelayCommand(CanExecute = nameof(CanPause))] [RelayCommand(CanExecute = nameof(CanPause))]
private async Task PauseAsync() private async Task PauseAsync()
{ {
await _orchestrator.PauseAsync(); await _updateSettingsService.PauseAsync();
StatusMessage = GetPausedStatusText(); StatusMessage = GetPausedStatusText();
} }
@@ -293,7 +293,7 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private async Task ResumeAsync() private async Task ResumeAsync()
{ {
StatusMessage = GetResumingStatusText(); StatusMessage = GetResumingStatusText();
var result = await _orchestrator.ResumeAsync(CancellationToken.None); var result = await _updateSettingsService.ResumeAsync(CancellationToken.None);
if (result.Success) if (result.Success)
{ {
StatusMessage = GetResumeCompleteStatusText(); StatusMessage = GetResumeCompleteStatusText();
@@ -307,18 +307,18 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
[RelayCommand(CanExecute = nameof(CanCancel))] [RelayCommand(CanExecute = nameof(CanCancel))]
private async Task CancelAsync() private async Task CancelAsync()
{ {
await _orchestrator.CancelAsync(); await _updateSettingsService.CancelAsync();
StatusMessage = GetCancelStatusText(); StatusMessage = GetCancelStatusText();
ProgressDetail = string.Empty; ProgressDetail = string.Empty;
ProgressFraction = 0; ProgressFraction = 0;
} }
private void OnOrchestratorPhaseChanged(UpdatePhase phase) private void OnUpdatePhaseChanged(UpdatePhase phase)
{ {
CurrentPhase = phase; CurrentPhase = phase;
} }
private void OnOrchestratorProgressChanged(UpdateProgressReport report) private void OnUpdateProgressChanged(UpdateProgressReport report)
{ {
ProgressFraction = report.ProgressFraction; ProgressFraction = report.ProgressFraction;
@@ -348,16 +348,56 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private void LoadPreferenceState() private void LoadPreferenceState()
{ {
var state = _settingsFacade.Update.Get(); var state = _updateSettingsService.Get();
SelectedUpdateChannelValue = state.UpdateChannel; _suppressPreferenceSave = true;
SelectedUpdateSourceValue = state.UpdateDownloadSource; try
SelectedUpdateModeValue = state.UpdateMode; {
DownloadThreadsSliderValue = UpdateSettingsValues.NormalizeDownloadThreads(state.UpdateDownloadThreads); SelectedUpdateChannelValue = state.UpdateChannel;
ForceReinstall = state.ForceUpdateReinstall; SelectedUpdateSourceValue = state.UpdateDownloadSource;
SelectedUpdateModeValue = state.UpdateMode;
DownloadThreadsSliderValue = UpdateSettingsValues.NormalizeDownloadThreads(state.UpdateDownloadThreads);
ForceReinstall = state.ForceUpdateReinstall;
ApplyPersistedUpdateState(state);
}
finally
{
_suppressPreferenceSave = false;
}
SyncComboBoxSelections(); SyncComboBoxSelections();
} }
private void ApplyPersistedUpdateState(LanMountainDesktop.Services.Settings.UpdateSettingsState state)
{
if (!string.IsNullOrWhiteSpace(state.PendingUpdateVersion))
{
IsUpdateAvailable = true;
LatestVersionText = state.PendingUpdateVersion;
PublishedAtText = state.PendingUpdatePublishedAtUtcMs is > 0
? DateTimeOffset
.FromUnixTimeMilliseconds(state.PendingUpdatePublishedAtUtcMs.Value)
.ToLocalTime()
.ToString("g", CultureInfo.CurrentCulture)
: string.Empty;
UpdateTypeText = ForceReinstall
? L("settings.update.type_reinstall", "Reinstall")
: UpdateTypeText;
}
if (state.LastUpdateCheckUtcMs is > 0)
{
LastCheckedText = string.Format(
CultureInfo.CurrentCulture,
L("settings.update.last_checked_format", "Last checked: {0}"),
DateTimeOffset
.FromUnixTimeMilliseconds(state.LastUpdateCheckUtcMs.Value)
.ToLocalTime()
.ToString("g", CultureInfo.CurrentCulture));
}
OnPropertyChanged(nameof(LatestVersionDisplayText));
}
private void SyncComboBoxSelections() private void SyncComboBoxSelections()
{ {
SelectedChannel = ChannelOptions.FirstOrDefault(o => o.Value == SelectedUpdateChannelValue) SelectedChannel = ChannelOptions.FirstOrDefault(o => o.Value == SelectedUpdateChannelValue)
@@ -463,8 +503,13 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
private void SavePreferenceState() private void SavePreferenceState()
{ {
var current = _settingsFacade.Update.Get(); if (_suppressPreferenceSave)
_settingsFacade.Update.Save(current with {
return;
}
var current = _updateSettingsService.Get();
_updateSettingsService.Save(current with
{ {
UpdateChannel = SelectedUpdateChannelValue, UpdateChannel = SelectedUpdateChannelValue,
UpdateDownloadSource = SelectedUpdateSourceValue, UpdateDownloadSource = SelectedUpdateSourceValue,
@@ -599,7 +644,7 @@ public sealed partial class UpdateSettingsViewModel : ViewModelBase, IDisposable
} }
_disposed = true; _disposed = true;
_orchestrator.PhaseChanged -= OnOrchestratorPhaseChanged; _updateSettingsService.PhaseChanged -= OnUpdatePhaseChanged;
_orchestrator.ProgressChanged -= OnOrchestratorProgressChanged; _updateSettingsService.ProgressChanged -= OnUpdateProgressChanged;
} }
} }

View File

@@ -511,9 +511,7 @@ public partial class MainWindow : Window
{ {
try try
{ {
await HostUpdateOrchestratorProvider await _updateSettingsService.AutoCheckIfEnabledAsync(default);
.GetOrCreate()
.AutoCheckIfEnabledAsync(default);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -8,288 +8,176 @@
x:DataType="vm:UpdateSettingsViewModel"> x:DataType="vm:UpdateSettingsViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container settings-page-animated"> <StackPanel Classes="settings-page-container settings-page-animated">
<StackPanel Spacing="6"> <StackPanel Spacing="6" Margin="0,0,0,16">
<TextBlock Classes="settings-section-title" <TextBlock Classes="settings-section-title" Text="{Binding PageTitle}" />
Text="{Binding PageTitle}" /> <TextBlock Classes="settings-section-description" Text="{Binding PageDescription}" />
<TextBlock Classes="settings-section-description"
Text="{Binding PageDescription}" />
</StackPanel> </StackPanel>
<controls:IconText Icon="ArrowSync" <StackPanel Spacing="16" Margin="0,0,0,24">
Text="{Binding StatusSectionHeader}" <Grid ColumnDefinitions="Auto,*,Auto">
Margin="0,0,0,4" /> <ui:FAFontIcon Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" FontSize="40" VerticalAlignment="Center" Margin="0,0,20,0" Foreground="{DynamicResource AccentFillColorDefaultBrush}" />
<ui:FASettingsExpander Header="{Binding CheckCardTitle}" <StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="4">
Description="{Binding StatusMessage}" <TextBlock Text="{Binding StatusMessage}" FontSize="24" FontWeight="Medium" TextWrapping="Wrap" />
IsClickEnabled="{Binding CanCheck}" <TextBlock Text="{Binding LastCheckedText}" Classes="settings-item-description" />
Command="{Binding CheckCommand}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<Button Classes="settings-accent-button"
Content="{Binding CheckButtonText}"
Command="{Binding CheckCommand}"
IsEnabled="{Binding CanCheck}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<ui:FASettingsExpander Header="{Binding ProgressTitle}"
Description="{Binding ProgressDescription}"
IsVisible="{Binding IsProgressSectionVisible}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0BB2;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<StackPanel Orientation="Horizontal"
Spacing="8"
VerticalAlignment="Center">
<TextBlock Classes="settings-item-label"
Text="{Binding PhaseText}"
VerticalAlignment="Center" />
<TextBlock Classes="settings-item-description"
Text="{Binding ProgressFraction, StringFormat='{}{0:P0}'}"
VerticalAlignment="Center" />
</StackPanel>
</ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem>
<StackPanel Spacing="12">
<ProgressBar Minimum="0"
Maximum="1"
Value="{Binding ProgressFraction}"
IsVisible="{Binding IsProgressVisible}" />
<StackPanel Orientation="Horizontal"
Spacing="10"
VerticalAlignment="Center"
IsVisible="{Binding IsBusy}">
<ui:FAProgressRing Width="20"
Height="20"
IsIndeterminate="True" />
<TextBlock Classes="settings-item-description"
Text="{Binding ProgressDetail}"
TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<TextBlock Classes="settings-item-description" <Button Grid.Column="2" Classes="settings-accent-button" Content="{Binding CheckButtonText}" Command="{Binding CheckCommand}" IsVisible="{Binding CanCheck}" VerticalAlignment="Center" Margin="16,0,0,0" />
Text="{Binding ProgressDetail}" </Grid>
TextWrapping="Wrap"
IsVisible="{Binding !IsBusy}" />
<ui:FAInfoBar Title="{Binding PausedBadgeText}" <StackPanel IsVisible="{Binding IsProgressSectionVisible}" Spacing="12">
Message="{Binding PausedHintText}" <Grid ColumnDefinitions="*,Auto" IsVisible="{Binding IsProgressVisible}">
IsOpen="True" <ProgressBar Grid.Column="0" Minimum="0" Maximum="1" Value="{Binding ProgressFraction}" VerticalAlignment="Center" Margin="0,0,12,0" />
IsClosable="False" <TextBlock Grid.Column="1" Text="{Binding ProgressFraction, StringFormat='{}{0:P0}'}" VerticalAlignment="Center" Classes="settings-item-label" />
IsVisible="{Binding IsPaused}"> </Grid>
<StackPanel Orientation="Horizontal" Spacing="10" VerticalAlignment="Center" IsVisible="{Binding IsBusy}">
<ui:FAProgressRing Width="20" Height="20" IsIndeterminate="True" />
<TextBlock Classes="settings-item-description" Text="{Binding ProgressDetail}" TextWrapping="Wrap" />
</StackPanel>
<TextBlock Classes="settings-item-description" Text="{Binding ProgressDetail}" TextWrapping="Wrap" IsVisible="{Binding !IsBusy}" />
<ui:FAInfoBar Title="{Binding PausedBadgeText}" Message="{Binding PausedHintText}" IsOpen="True" IsClosable="False" IsVisible="{Binding IsPaused}">
<ui:FAInfoBar.IconSource> <ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF28D;" <ui:FAFontIconSource Glyph="&#xF28D;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource> </ui:FAInfoBar.IconSource>
</ui:FAInfoBar> </ui:FAInfoBar>
<ui:FAInfoBar Title="{Binding ResumeSupportLabel}" <ui:FAInfoBar Title="{Binding ResumeSupportLabel}" Message="{Binding ResumeSupportDescription}" IsOpen="True" IsClosable="False" Severity="Informational">
Message="{Binding ResumeSupportDescription}"
IsOpen="True"
IsClosable="False"
Severity="Informational">
<ui:FAInfoBar.IconSource> <ui:FAInfoBar.IconSource>
<ui:FAFontIconSource Glyph="&#xF0647;" <ui:FAFontIconSource Glyph="&#xF0647;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FAInfoBar.IconSource> </ui:FAInfoBar.IconSource>
</ui:FAInfoBar> </ui:FAInfoBar>
<StackPanel Orientation="Horizontal" <WrapPanel Orientation="Horizontal" ItemWidth="NaN">
Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8" Margin="0,8,0,0">
<Button Classes="settings-accent-button" <Button Classes="settings-accent-button" Content="{Binding DownloadButtonText}" Command="{Binding DownloadCommand}" IsVisible="{Binding CanDownload}" />
Content="{Binding DownloadButtonText}" <Button Classes="settings-accent-button" Content="{Binding InstallButtonText}" Command="{Binding InstallCommand}" IsVisible="{Binding CanInstall}" />
Command="{Binding DownloadCommand}" <Button Content="{Binding PauseButtonText}" Command="{Binding PauseCommand}" IsVisible="{Binding CanPause}" />
IsVisible="{Binding CanDownload}" /> <Button Classes="settings-accent-button" Content="{Binding ResumeButtonText}" Command="{Binding ResumeCommand}" IsVisible="{Binding CanResume}" />
<Button Classes="settings-accent-button" <Button Content="{Binding RollbackButtonText}" Command="{Binding RollbackCommand}" IsVisible="{Binding CanRollback}" />
Content="{Binding InstallButtonText}" <Button Content="{Binding CancelButtonText}" Command="{Binding CancelCommand}" IsVisible="{Binding CanCancel}" />
Command="{Binding InstallCommand}" </StackPanel>
IsVisible="{Binding CanInstall}" /> </WrapPanel>
<Button Content="{Binding PauseButtonText}"
Command="{Binding PauseCommand}"
IsVisible="{Binding CanPause}" />
<Button Classes="settings-accent-button"
Content="{Binding ResumeButtonText}"
Command="{Binding ResumeCommand}"
IsVisible="{Binding CanResume}" />
<Button Content="{Binding RollbackButtonText}"
Command="{Binding RollbackCommand}"
IsVisible="{Binding CanRollback}" />
<Button Content="{Binding CancelButtonText}"
Command="{Binding CancelCommand}"
IsVisible="{Binding CanCancel}" />
</StackPanel>
</StackPanel> </StackPanel>
</ui:FASettingsExpanderItem> </StackPanel>
</ui:FASettingsExpander>
<Separator Classes="settings-separator" /> <TabControl Margin="0,0,0,16">
<TabItem Header="{Binding ReleaseFactsTitle}">
<StackPanel Spacing="2" Margin="0,16,0,0">
<TextBlock Classes="settings-section-description" Text="{Binding ReleaseFactsDescription}" Margin="0,0,0,12" />
<controls:IconText Icon="Info" <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding CurrentVersionLabel}">
Text="{Binding ReleaseFactsTitle}" <ui:FASettingsExpander.IconSource>
Margin="0,0,0,4" /> <ui:FAFontIconSource Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<TextBlock Classes="settings-item-description" Text="{Binding CurrentVersionText}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<ui:FASettingsExpander Header="{Binding CurrentVersionLabel}"> <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding LatestVersionLabel}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0288;" <ui:FAFontIconSource Glyph="&#xF0288;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> </ui:FASettingsExpander.IconSource>
</ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.Footer>
<ui:FASettingsExpander.Footer> <TextBlock Classes="settings-item-description" Text="{Binding LatestVersionDisplayText}" />
<TextBlock Classes="settings-item-description" </ui:FASettingsExpander.Footer>
Text="{Binding CurrentVersionText}" /> </ui:FASettingsExpander>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<ui:FASettingsExpander Header="{Binding LatestVersionLabel}"> <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding PublishedAtLabel}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0B4E;" <ui:FAFontIconSource Glyph="&#xF0168;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> </ui:FASettingsExpander.IconSource>
</ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.Footer>
<ui:FASettingsExpander.Footer> <TextBlock Classes="settings-item-description" Text="{Binding PublishedAtText}" />
<TextBlock Classes="settings-item-description" </ui:FASettingsExpander.Footer>
Text="{Binding LatestVersionDisplayText}" /> </ui:FASettingsExpander>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<ui:FASettingsExpander Header="{Binding PublishedAtLabel}"> <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding UpdateTypeLabel}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0168;" <ui:FAFontIconSource Glyph="&#xF0504;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> </ui:FASettingsExpander.IconSource>
</ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.Footer>
<ui:FASettingsExpander.Footer> <TextBlock Classes="settings-item-description" Text="{Binding UpdateTypeText}" />
<TextBlock Classes="settings-item-description" </ui:FASettingsExpander.Footer>
Text="{Binding PublishedAtText}" /> </ui:FASettingsExpander>
</ui:FASettingsExpander.Footer> </StackPanel>
</ui:FASettingsExpander> </TabItem>
<ui:FASettingsExpander Header="{Binding LastCheckedLabel}"> <TabItem Header="{Binding PreferencesTitle}">
<ui:FASettingsExpander.IconSource> <StackPanel Spacing="2" Margin="0,16,0,0">
<ui:FAFontIconSource Glyph="&#xF0B4E;" <TextBlock Classes="settings-section-description" Text="{Binding PreferencesDescription}" Margin="0,0,0,12" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<TextBlock Classes="settings-item-description"
Text="{Binding LastCheckedText}" />
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<ui:FASettingsExpander Header="{Binding UpdateTypeLabel}"> <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding ChannelLabel}" Description="{Binding ChannelDescription}">
<ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0504;" <ui:FAFontIconSource Glyph="&#xF0908;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> </ui:FASettingsExpander.IconSource>
</ui:FASettingsExpander.IconSource> <ui:FASettingsExpander.Footer>
<ui:FASettingsExpander.Footer> <ComboBox Width="220" ItemsSource="{Binding ChannelOptions}" SelectedItem="{Binding SelectedChannel}">
<TextBlock Classes="settings-item-description" <ComboBox.ItemTemplate>
Text="{Binding UpdateTypeText}" /> <DataTemplate x:DataType="vm:SelectionOption">
</ui:FASettingsExpander.Footer> <TextBlock Text="{Binding Label}" />
</ui:FASettingsExpander> </DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<Separator Classes="settings-separator" /> <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding SourceLabel}" Description="{Binding SourceDescription}">
<ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0168;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ComboBox Width="220" ItemsSource="{Binding SourceOptions}" SelectedItem="{Binding SelectedSource}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<controls:IconText Icon="Settings" <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding ModeLabel}" Description="{Binding ModeDescription}">
Text="{Binding PreferencesTitle}" <ui:FASettingsExpander.IconSource>
Margin="0,0,0,4" /> <ui:FAFontIconSource Glyph="&#xF08E8;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpander.IconSource>
<ui:FASettingsExpander.Footer>
<ComboBox Width="220" ItemsSource="{Binding ModeOptions}" SelectedItem="{Binding SelectedMode}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" TextWrapping="Wrap" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpander.Footer>
</ui:FASettingsExpander>
<ui:FASettingsExpander Header="{Binding PreferencesTitle}" <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding ForceReinstallLabel}" Description="{Binding ForceReinstallDescription}">
Description="{Binding PreferencesDescription}" <ui:FASettingsExpander.IconSource>
IsExpanded="True"> <ui:FAFontIconSource Glyph="&#xF0504;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
<ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.IconSource>
<ui:FAFontIconSource Glyph="&#xF0504;" <ui:FASettingsExpander.Footer>
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ToggleSwitch IsChecked="{Binding ForceReinstall}" />
</ui:FASettingsExpander.IconSource> </ui:FASettingsExpander.Footer>
<ui:FASettingsExpanderItem Content="{Binding ChannelLabel}" </ui:FASettingsExpander>
Description="{Binding ChannelDescription}">
<ui:FASettingsExpanderItem.IconSource> <ui:FASettingsExpander Classes="settings-expander-card" Header="{Binding DownloadThreadsLabel}" Description="{Binding DownloadThreadsDescription}">
<ui:FAFontIconSource Glyph="&#xF0908;" <ui:FASettingsExpander.IconSource>
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" /> <ui:FAFontIconSource Glyph="&#xF0168;" FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource> </ui:FASettingsExpander.IconSource>
<ui:FASettingsExpanderItem.Footer> <ui:FASettingsExpander.Footer>
<ComboBox Width="220" <StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
ItemsSource="{Binding ChannelOptions}" <Slider Width="140" Minimum="1" Maximum="128" Value="{Binding DownloadThreadsSliderValue}" TickFrequency="1" IsSnapToTickEnabled="True" />
SelectedItem="{Binding SelectedChannel}"> <TextBlock Classes="settings-item-label" Text="{Binding DownloadThreadsSliderValue, StringFormat='{}{0:F0}'}" VerticalAlignment="Center" />
<ComboBox.ItemTemplate> </StackPanel>
<DataTemplate x:DataType="vm:SelectionOption"> </ui:FASettingsExpander.Footer>
<TextBlock Text="{Binding Label}" /> </ui:FASettingsExpander>
</DataTemplate> </StackPanel>
</ComboBox.ItemTemplate> </TabItem>
</ComboBox> </TabControl>
</ui:FASettingsExpanderItem.Footer>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem Content="{Binding SourceLabel}"
Description="{Binding SourceDescription}">
<ui:FASettingsExpanderItem.IconSource>
<ui:FAFontIconSource Glyph="&#xF0B4E;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource>
<ui:FASettingsExpanderItem.Footer>
<ComboBox Width="220"
ItemsSource="{Binding SourceOptions}"
SelectedItem="{Binding SelectedSource}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpanderItem.Footer>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem Content="{Binding ModeLabel}"
Description="{Binding ModeDescription}">
<ui:FASettingsExpanderItem.IconSource>
<ui:FAFontIconSource Glyph="&#xF08E8;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource>
<ui:FASettingsExpanderItem.Footer>
<ComboBox Width="220"
ItemsSource="{Binding ModeOptions}"
SelectedItem="{Binding SelectedMode}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}"
TextWrapping="Wrap" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:FASettingsExpanderItem.Footer>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem Content="{Binding ForceReinstallLabel}"
Description="{Binding ForceReinstallDescription}">
<ui:FASettingsExpanderItem.IconSource>
<ui:FAFontIconSource Glyph="&#xF0504;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource>
<ui:FASettingsExpanderItem.Footer>
<ToggleSwitch IsChecked="{Binding ForceReinstall}" />
</ui:FASettingsExpanderItem.Footer>
</ui:FASettingsExpanderItem>
<ui:FASettingsExpanderItem Content="{Binding DownloadThreadsLabel}"
Description="{Binding DownloadThreadsDescription}">
<ui:FASettingsExpanderItem.IconSource>
<ui:FAFontIconSource Glyph="&#xF0168;"
FontFamily="avares://fluenticons.resources.avalonia/Assets#Seagull Fluent Icons" />
</ui:FASettingsExpanderItem.IconSource>
<ui:FASettingsExpanderItem.Footer>
<StackPanel Orientation="Horizontal"
Spacing="8"
VerticalAlignment="Center">
<Slider Width="140"
Minimum="1"
Maximum="128"
Value="{Binding DownloadThreadsSliderValue}"
TickFrequency="1"
IsSnapToTickEnabled="True" />
<TextBlock Classes="settings-item-label"
Text="{Binding DownloadThreadsSliderValue, StringFormat='{}{0:F0}'}"
VerticalAlignment="Center" />
</StackPanel>
</ui:FASettingsExpanderItem.Footer>
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View File

@@ -1,6 +1,5 @@
using LanMountainDesktop.PluginSdk; using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings; using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Services.Update;
using LanMountainDesktop.ViewModels; using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views.SettingsPages; namespace LanMountainDesktop.Views.SettingsPages;
@@ -16,9 +15,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
public partial class UpdateSettingsPage : SettingsPageBase public partial class UpdateSettingsPage : SettingsPageBase
{ {
public UpdateSettingsPage() public UpdateSettingsPage()
: this(new UpdateSettingsViewModel( : this(new UpdateSettingsViewModel(HostSettingsFacadeProvider.GetOrCreate()))
HostUpdateOrchestratorProvider.GetOrCreate(),
HostSettingsFacadeProvider.GetOrCreate()))
{ {
} }