UI: license summary becomes a colored badge in the top bar

The license info was rendered as plain secondary-grey text, easy to
miss. Now it's a rounded pill (CornerRadius=14) with traffic-light
colors driven by the new MainViewModel.LicenseSeverity property:

- Ok      → green pill (Brush.Status.Installed) with ✓ icon
- Warning → amber pill (Brush.Status.Busy)      with  icon, fires when
            entitlement is < 30 days away from expiry
- Error   → red pill (#EF4444 / #2A1414 bg)     with 🔒/⚠/🚫 icon
            depending on whether license is missing, expired, revoked

Two new VM properties accompany LicenseSummary:
- LicenseIcon: emoji glyph for the pill
- LicenseSeverity: enum-like string Ok/Warning/Error driving DataTriggers

Both are kept in sync via NotifyLicenseChanged(), called wherever
_license used to trigger only OnPropertyChanged(LicenseSummary).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 08:50:10 +02:00
parent b33b35b198
commit 973a1e78ba
2 changed files with 122 additions and 9 deletions

View File

@@ -69,13 +69,66 @@ public sealed partial class MainViewModel : ObservableObject
{
get
{
if (_license is null) return "License : non configurée";
if (_license is null) return "Aucune license";
if (_license.Status == "valid")
return $"License : {_license.OwnerName} • exp. {_license.DownloadEntitlementUntil:dd/MM/yyyy}";
if (_license.Status == "expired")
return $"License expirée le {_license.DownloadEntitlementUntil:dd/MM/yyyy}";
return $"License : {_license.Status}";
{
var until = _license.DownloadEntitlementUntil;
if (until is { } u && (u - DateTime.UtcNow).TotalDays < 30)
return $"{_license.OwnerName} • exp. {u:dd/MM/yyyy}";
return $"{_license.OwnerName} • exp. {until:dd/MM/yyyy}";
}
if (_license.Status == "expired")
return $"Expirée le {_license.DownloadEntitlementUntil:dd/MM/yyyy}";
if (_license.Status == "revoked")
return "Révoquée";
return _license.Status;
}
}
public string LicenseIcon
{
get
{
if (_license is null) return "🔒";
return _license.Status switch
{
"valid" when IsLicenseExpiringSoon() => "⏰",
"valid" => "✓",
"expired" => "⚠",
"revoked" => "🚫",
_ => "❓",
};
}
}
/// <summary>
/// Catégorie utilisée par la UI pour piloter la couleur du badge license.
/// </summary>
public string LicenseSeverity
{
get
{
if (_license is null) return "Error";
return _license.Status switch
{
"valid" when IsLicenseExpiringSoon() => "Warning",
"valid" => "Ok",
_ => "Error",
};
}
}
private bool IsLicenseExpiringSoon()
{
if (_license?.DownloadEntitlementUntil is not { } until) return false;
return (until - DateTime.UtcNow).TotalDays < 30;
}
private void NotifyLicenseChanged()
{
OnPropertyChanged(nameof(LicenseSummary));
OnPropertyChanged(nameof(LicenseIcon));
OnPropertyChanged(nameof(LicenseSeverity));
}
public string EmptyHint =>
@@ -257,7 +310,7 @@ public sealed partial class MainViewModel : ObservableObject
{
// Au retour, l'URL serveur ou l'installRoot ont pu changer → refresh
_license = _licenseService.GetCached();
OnPropertyChanged(nameof(LicenseSummary));
NotifyLicenseChanged();
RebuildList();
}
}
@@ -270,7 +323,7 @@ public sealed partial class MainViewModel : ObservableObject
if (dialog.LicenseActivated)
{
_license = _licenseService.GetCached();
OnPropertyChanged(nameof(LicenseSummary));
NotifyLicenseChanged();
RebuildList();
}
await Task.CompletedTask;

View File

@@ -170,9 +170,69 @@
Content="📁 Dossier"
Command="{Binding OpenInstallRootCommand}"
Margin="0,0,12,0" />
<!-- Badge license -->
<Border CornerRadius="14" Padding="10,4"
VerticalAlignment="Center" Margin="0,0,8,0">
<Border.Style>
<Style TargetType="Border">
<!-- Default: error (rouge) -->
<Setter Property="Background" Value="#2A1414" />
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.AvailableBg}" />
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Ok">
<Setter Property="Background" Value="{StaticResource Brush.Status.InstalledBg}" />
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Installed}" />
</DataTrigger>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Warning">
<Setter Property="Background" Value="{StaticResource Brush.Status.BusyBg}" />
<Setter Property="BorderBrush" Value="{StaticResource Brush.Status.Busy}" />
</DataTrigger>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Error">
<Setter Property="Background" Value="#2A1414" />
<Setter Property="BorderBrush" Value="#EF4444" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LicenseIcon}"
FontSize="14" FontWeight="Bold"
VerticalAlignment="Center"
Margin="0,0,8,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#EF4444" />
<Style.Triggers>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Ok">
<Setter Property="Foreground" Value="{StaticResource Brush.Status.Installed}" />
</DataTrigger>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Warning">
<Setter Property="Foreground" Value="{StaticResource Brush.Status.Busy}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding LicenseSummary}"
Foreground="{StaticResource Brush.Text.Secondary}"
VerticalAlignment="Center" Margin="0,0,8,0" />
FontSize="12" FontWeight="SemiBold"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FCA5A5" />
<Style.Triggers>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Ok">
<Setter Property="Foreground" Value="#86EFAC" />
</DataTrigger>
<DataTrigger Binding="{Binding LicenseSeverity}" Value="Warning">
<Setter Property="Foreground" Value="#FCD34D" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Border>
<Button Style="{StaticResource SecondaryButton}"
Content="🔑 Activer / changer"
Command="{Binding ActivateLicenseCommand}"