client(ui): migrate to mvvm

This commit is contained in:
2025-12-12 19:32:05 +00:00
parent 16b1a655aa
commit 8c29cf9e8a
6 changed files with 264 additions and 191 deletions

View File

@@ -0,0 +1,342 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ModpackUpdater.Apps.Client.Gui.LangRes;
using ModpackUpdater.Manager;
using Pilz.Extensions.Collections;
namespace ModpackUpdater.Apps.Client.Gui;
public partial class MainViewModel : ObservableObject
{
public class EmptyRecentFilesItem;
public class RecentFilesItem(string path)
{
public string Path => path;
public string Display => path.Length > 50 ? $"...{path[^50..]}" : path;
}
private readonly UpdateCheckOptions updateOptions = new();
private ModpackInfo modpackInfo = new();
private ModpackConfig updateConfig = new();
private ModpackFeatures? features;
private UpdateCheckResult? lastUpdateCheckResult;
[ObservableProperty] private string? minecraftProfileFolder;
[ObservableProperty] private string? modpackConfigUrl;
[ObservableProperty] private string? installKey;
[ObservableProperty] private string statusText = "-";
[ObservableProperty] private IImage? statusImage;
[ObservableProperty] private bool hasInitialized;
[ObservableProperty] private bool isUpdating;
[ObservableProperty] private bool loadingData;
[ObservableProperty] private bool canUpdate;
[ObservableProperty] private bool canUseExtrasKey;
public ObservableCollection<object> RecentMinecraftProfilePathItems { get; } = [];
public ICommand CheckForUpdatesCommand { get; }
public ICommand InstallCommand { get; }
public ICommand RepairCommand { get; }
public MainViewModel()
{
CheckForUpdatesCommand = new AsyncRelayCommand(async () => await CheckStatusAndUpdate(false));
InstallCommand = new AsyncRelayCommand(async () => await ExecuteUpdate(true, false));
RepairCommand = new AsyncRelayCommand(async () => await ExecuteUpdate(true, true));
}
public async Task CheckForUpdates(Window parent)
{
var updates = new AppUpdates("client", parent);
updates.OnDownloadProgramUpdate += (_, _) => SetStatus(GeneralLangRes.DownloadProgramUpdate, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
await updates.UpdateApp();
}
public async Task Initialize()
{
ClearStatus();
LoadProfileToUi();
LoadRecentFilesToUi();
HasInitialized = true;
await CheckStatusAndUpdate(true);
}
partial void OnMinecraftProfileFolderChanged(string? value)
{
if (!LoadingData)
_ = CheckStatusAndUpdate(true);
}
partial void OnModpackConfigUrlChanged(string? value)
{
if (!LoadingData)
_ = CheckStatusAndUpdate(false);
}
partial void OnInstallKeyChanged(string? value)
{
if (!LoadingData)
_ = CheckStatusAndUpdate(false);
}
public void SetStatus(string statusText, IImage? image)
{
StatusText = statusText;
StatusImage = image;
}
public void ClearStatus()
{
StatusText = "-";
StatusImage = null;
}
private bool AllowExtras()
{
return features != null && features.IsEnabled(ModpackFeatures.FeatureAllowExtas, new AllowExtrasFeatureContext(modpackInfo));
}
public void LoadProfileToUi()
{
LoadingData = true;
MinecraftProfileFolder = modpackInfo.LocalPath ?? AppConfig.Instance.RecentMinecraftProfilePaths.FirstOrDefault() ?? MinecraftProfileFolder;
ModpackConfigUrl = modpackInfo.ConfigUrl ?? ModpackConfigUrl;
InstallKey = modpackInfo.ExtrasKey ?? InstallKey;
LoadingData = false;
}
private void LoadOptionsToUi()
{
//foreach (var set in )
//{
// // ...
//}
}
public void LoadRecentFilesToUi()
{
RecentMinecraftProfilePathItems.Clear();
if (AppConfig.Instance.RecentMinecraftProfilePaths.Count == 0)
{
RecentMinecraftProfilePathItems.Add(new EmptyRecentFilesItem());
return;
}
AppConfig.Instance.RecentMinecraftProfilePaths.ForEach(path =>
{
if (Directory.Exists(path))
RecentMinecraftProfilePathItems.Add(new RecentFilesItem(path));
});
}
private void StoreRecentMinecraftProfilePath(string? path)
{
if (string.IsNullOrWhiteSpace(path))
return;
AppConfig.Instance.RecentMinecraftProfilePaths.RemoveAll(n => n == path);
AppConfig.Instance.RecentMinecraftProfilePaths.Insert(0, path);
AppConfig.Instance.RecentMinecraftProfilePaths.Skip(10).ForEach(n => AppConfig.Instance.RecentMinecraftProfilePaths.Remove(n));
}
[RelayCommand]
private void OpenRecentPath(string path)
{
if (!string.IsNullOrWhiteSpace(path))
MinecraftProfileFolder = path;
}
public async Task CheckStatusAndUpdate(bool loadProfileToUi)
{
if (!CheckStatus(loadProfileToUi))
return;
await ExecuteUpdate(false, false);
StoreRecentMinecraftProfilePath(modpackInfo.LocalPath);
LoadRecentFilesToUi();
}
private bool CheckStatus(bool loadProfileToUi)
{
ClearStatus();
try
{
modpackInfo = ModpackInfo.TryLoad(MinecraftProfileFolder?.Trim());
}
catch
{
// Ignore
}
if (loadProfileToUi)
LoadProfileToUi();
try
{
updateConfig = ModpackConfig.LoadFromUrl(ModpackConfigUrl);
}
catch
{
// Ignore
}
features = new(updateConfig);
modpackInfo.ExtrasKey = InstallKey?.Trim();
if (!features.IsInvalid() && !string.IsNullOrWhiteSpace(modpackInfo.ExtrasKey) && !AllowExtras())
{
SetStatus(GeneralLangRes.InstallationKeyNotValid, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign));
return false;
}
CanUseExtrasKey = CanUseExtrasKey = !string.IsNullOrWhiteSpace(updateConfig.UnleashApiUrl);
if (string.IsNullOrWhiteSpace(MinecraftProfileFolder) /*|| modpackInfo.Valid*/)
{
SetStatus(GeneralLangRes.MinecraftProfileFolderSeemsInvalid, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign));
CanUpdate = false;
return false;
}
else if (string.IsNullOrWhiteSpace(ModpackConfigUrl))
{
SetStatus(GeneralLangRes.ConfigIncompleteOrNotLoaded, AppGlobals.Symbols.GetImageSource(AppSymbols.general_warning_sign));
CanUpdate = false;
return false;
}
else if (updateConfig.Maintenance && !updateOptions.IgnoreMaintenance)
{
SetStatus(GeneralLangRes.UpdateServerInMaintenance, AppGlobals.Symbols.GetImageSource(AppSymbols.services));
CanUpdate = false;
return false;
}
LoadOptionsToUi();
CanUpdate = true;
return true;
}
private async Task ExecuteUpdate(bool doInstall, bool repair)
{
ClearStatus();
// Ensure set extras key
modpackInfo.ExtrasKey = InstallKey?.Trim();
var updater = new ModpackInstaller(updateConfig, modpackInfo);
updater.InstallProgessUpdated += Updater_InstallProgressUpdated;
updater.CheckingProgressUpdated += Updater_CheckingProgressUpdated;
void error()
{
SetStatus(GeneralLangRes.ErrorOnUpdateCheckOrUpdating, AppGlobals.Symbols.GetImageSource(AppSymbols.close));
IsUpdating = false;
}
void installing()
{
SetStatus(GeneralLangRes.Installing, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
IsUpdating = true;
}
void updatesAvailable()
{
SetStatus(GeneralLangRes.AnUpdateIsAvailable, AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
IsUpdating = false;
}
void everythingOk()
{
SetStatus(GeneralLangRes.EverythingIsRightAndUpToDate, AppGlobals.Symbols.GetImageSource(AppSymbols.done));
IsUpdating = false;
}
// Check only if not pressed "install", not really needed otherwise.
if (lastUpdateCheckResult is null || !doInstall || repair)
{
SetStatus(GeneralLangRes.CheckingForUpdates, AppGlobals.Symbols.GetImageSource(AppSymbols.update_done));
// Check for extras once again
updateOptions.IncludeExtras = AllowExtras();
// Force re-install on repair
updateOptions.IgnoreInstalledVersion = repair;
try
{
lastUpdateCheckResult = await updater.Check(updateOptions);
}
catch
{
error();
if (Debugger.IsAttached)
throw;
}
}
// Error while update check
if (lastUpdateCheckResult is null || lastUpdateCheckResult.HasError)
{
error();
return;
}
// Load options
// lastUpdateCheckResult.OptionsAvailable...
// lastUpdateCheckResult.OptionsEnabled...
// No updates available
if (!lastUpdateCheckResult.HasUpdates)
{
everythingOk();
return;
}
// Updates available (but don't install)
if (!doInstall)
{
updatesAvailable();
return;
}
// Install updates
installing();
IsUpdating = true;
try
{
// Install
if (await updater.Install(lastUpdateCheckResult) == false)
{
error();
return;
}
// Success
lastUpdateCheckResult = null; // Reset last update check, a new one would be needed now.
everythingOk();
}
catch
{
// Error
error();
if (Debugger.IsAttached)
throw;
}
}
private void Updater_CheckingProgressUpdated(int toCheck, int processed)
{
SetStatus(Math.Round(processed / (double)toCheck * 100d, 1) + "%", AppGlobals.Symbols.GetImageSource(AppSymbols.update_done));
}
private void Updater_InstallProgressUpdated(UpdateCheckResult result, int processedSyncs)
{
var actionCount = result.Actions.Count;
SetStatus(Math.Round(processedSyncs / (double)actionCount * 100d, 1) + "%", AppGlobals.Symbols.GetImageSource(AppSymbols.software_installer));
}
}