v0.9.0 — DL UX: cancel/restart confirmations, auto-revalidate license, fix progress refresh
UX - Red "Annuler" button next to "↻ Reprendre" on rows with a partial download. Confirmation MessageBox before discarding the .partial + state.json. - "Recommencer depuis zéro" entry in the row's "..." context menu, visible only when a partial exists. - Cancel button visibility tied to a new ShowRestartFromZero property (HasResumableDownload && State == AvailableIdle): hides during active DL to avoid colliding with the inline cancel. - ResumableBytes refreshed from state.json after cancel/error so the "Reprendre (X%)" label reflects the actual percentage reached, not the stale value from launcher startup. - MainWindow.Closing prompts for confirmation if a download is in progress (HasActiveDownload), so an accidental ✕ doesn't waste a 14 GB transfer. - "🔧 Préparation du téléchargement v…" status set immediately on click and kept visible during HEAD/SetLength so the user knows something's happening before the first byte. ProgressDetail wired with NotifyPropertyChangedFor (FooterText) so download speed shows during DL and not just at install transition. Auto-revalidation - CheckForUpdatesAsync now runs RefreshLicenseFromServerAsync first: every startup auto-check + every manual "Vérifier les MAJ" click pulls the latest license state from /api/license/validate. Date changes / revocations done in the backoffice propagate to clients without waiting for the 100-day cache to expire. Silent fallback to cached state if offline. Version bumped to 0.9.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using PSLauncher.Core.Localization;
|
||||
using PSLauncher.Models;
|
||||
|
||||
namespace PSLauncher.App.ViewModels;
|
||||
@@ -29,6 +30,7 @@ public sealed partial class VersionRowViewModel : ObservableObject
|
||||
[NotifyPropertyChangedFor(nameof(IsBusy))]
|
||||
[NotifyPropertyChangedFor(nameof(IsInstalled))]
|
||||
[NotifyPropertyChangedFor(nameof(IsRemoteOnly))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowRestartFromZero))]
|
||||
[NotifyCanExecuteChangedFor(nameof(LaunchCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(InstallCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(UninstallCommand))]
|
||||
@@ -40,6 +42,8 @@ public sealed partial class VersionRowViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(InstallButtonLabel))]
|
||||
[NotifyPropertyChangedFor(nameof(HasResumableDownload))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowRestartFromZero))]
|
||||
[NotifyCanExecuteChangedFor(nameof(RestartFromZeroCommand))]
|
||||
private long _resumableBytes;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -51,15 +55,23 @@ public sealed partial class VersionRowViewModel : ObservableObject
|
||||
public bool HasResumableDownload => ResumableBytes > 0;
|
||||
public bool LicenseAllowsInstall => LicenseAllowsDownload;
|
||||
|
||||
/// <summary>
|
||||
/// Visibilité du bouton rouge « Annuler » à côté de « ↻ Reprendre ».
|
||||
/// Doit être vrai uniquement quand un partial existe ET qu'aucun DL n'est en
|
||||
/// cours sur cette row (sinon le bouton « Annuler » se mélange visuellement
|
||||
/// avec la barre de progression du DL actif, qui a son propre cancel).
|
||||
/// </summary>
|
||||
public bool ShowRestartFromZero => HasResumableDownload && State == VersionRowState.AvailableIdle;
|
||||
|
||||
public string InstallButtonLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!LicenseAllowsDownload) return "🔒 License insuffisante";
|
||||
if (!HasResumableDownload) return "⬇ Installer";
|
||||
if (Remote is null || Remote.Download.SizeBytes <= 0) return "↻ Reprendre";
|
||||
var pct = (double)ResumableBytes / Remote.Download.SizeBytes * 100.0;
|
||||
return $"↻ Reprendre ({pct:0}%)";
|
||||
if (!LicenseAllowsDownload) return Strings.ActionLicenseInsufficient;
|
||||
if (!HasResumableDownload) return Strings.ActionInstall;
|
||||
if (Remote is null || Remote.Download.SizeBytes <= 0) return Strings.ResumeButtonNoPercent;
|
||||
var pct = (int)Math.Round((double)ResumableBytes / Remote.Download.SizeBytes * 100.0);
|
||||
return Strings.ResumeButton(pct);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,16 +81,16 @@ public sealed partial class VersionRowViewModel : ObservableObject
|
||||
|
||||
public string StateLabel => State switch
|
||||
{
|
||||
VersionRowState.InstalledIdle => "● Installée",
|
||||
VersionRowState.AvailableIdle => "○ Disponible",
|
||||
VersionRowState.Downloading => "⬇ Téléchargement…",
|
||||
VersionRowState.Installing => "📦 Installation…",
|
||||
VersionRowState.Uninstalling => "🗑 Suppression…",
|
||||
VersionRowState.InstalledIdle => Strings.StatusInstalled,
|
||||
VersionRowState.AvailableIdle => Strings.StatusAvailable,
|
||||
VersionRowState.Downloading => Strings.StatusDownloading,
|
||||
VersionRowState.Installing => Strings.StatusInstalling,
|
||||
VersionRowState.Uninstalling => Strings.StatusUninstalling,
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
public string PrimaryDate => ReleasedAt > DateTime.MinValue
|
||||
? ReleasedAt.ToLocalTime().ToString("dd/MM/yyyy")
|
||||
? Strings.FormatDate(ReleasedAt)
|
||||
: "—";
|
||||
|
||||
public string SizeDisplay => FormatSize(SizeBytes);
|
||||
@@ -134,6 +146,7 @@ public sealed partial class VersionRowViewModel : ObservableObject
|
||||
public Action<VersionRowViewModel>? UninstallHandler { get; set; }
|
||||
public Action<VersionRowViewModel>? ShowReleaseNotesHandler { get; set; }
|
||||
public Action<VersionRowViewModel>? OpenFolderHandler { get; set; }
|
||||
public Action<VersionRowViewModel>? RestartFromZeroHandler { get; set; }
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanLaunch))]
|
||||
private void Launch() => LaunchHandler?.Invoke(this);
|
||||
@@ -153,13 +166,10 @@ public sealed partial class VersionRowViewModel : ObservableObject
|
||||
[RelayCommand]
|
||||
private void OpenFolder() => OpenFolderHandler?.Invoke(this);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRestartFromZero))]
|
||||
private void RestartFromZero() => RestartFromZeroHandler?.Invoke(this);
|
||||
private bool CanRestartFromZero() => HasResumableDownload;
|
||||
|
||||
private static string FormatSize(long bytes)
|
||||
{
|
||||
if (bytes <= 0) return "—";
|
||||
string[] units = { "o", "Ko", "Mo", "Go", "To" };
|
||||
double v = bytes;
|
||||
int u = 0;
|
||||
while (v >= 1024 && u < units.Length - 1) { v /= 1024; u++; }
|
||||
return $"{v:0.#} {units[u]}";
|
||||
}
|
||||
=> bytes <= 0 ? "—" : Strings.FormatSize(bytes);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user