UI: featured hero card, vivid status colors, themed Markdown render

Featured version
----------------
The highest installed version (or highest remote if none installed) now
gets a large hero card at the top of the window: 32px Proserve title,
oversized colored status pill, and a much bigger primary action button
(LANCER / INSTALLER 56px padding). All other versions move below into a
"AUTRES VERSIONS" section with a horizontal divider, displayed as the
existing compact rows.

MainViewModel exposes FeaturedVersion + OtherVersions instead of one
flat Versions collection.

Vivid status colors
-------------------
Card backgrounds were too close (dark gray vs slightly bluer dark gray).
New scheme:
- Installed: card stays dark gray, but a 4px green strip on the left and
  a vivid green "● Installée" pill make it unmistakable.
- Available remote-only: distinct dark blue card + vivid blue strip + blue
  "○ Disponible" pill.
- Busy: amber strip + amber pill, amber progress bar.

Brushes added to Theme.xaml: Brush.Status.Installed (#16A34A),
Brush.Status.Available (#3B82F6), Brush.Status.Busy (#F59E0B), each with
a matching very-dark-tint *Bg variant for card backgrounds.

Markdown theming
----------------
Release notes dialogs were unreadable (white background, near-white text).
Markdig.Wpf produces a FlowDocument with default white bg / black fg
regardless of the host control. Added MarkdownTheming.BuildThemedDocument
which renders the Markdown then walks all blocks/inlines to apply the
launcher's dark palette (Brush.Bg.Card transparent, Brush.Text.Primary
foreground, Brush.Accent links). Both UpdateAvailableDialog and
ReleaseNotesViewerDialog use this helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 09:50:51 +02:00
parent 4e9f757ce1
commit 6128f7d220
6 changed files with 479 additions and 202 deletions

View File

@@ -32,7 +32,16 @@ public sealed partial class MainViewModel : ObservableObject
private CancellationTokenSource? _activeDownloadCts;
private VersionRowViewModel? _activeRow;
public ObservableCollection<VersionRowViewModel> Versions { get; } = new();
public ObservableCollection<VersionRowViewModel> OtherVersions { get; } = new();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasFeaturedVersion))]
[NotifyPropertyChangedFor(nameof(HasOtherVersions))]
[NotifyPropertyChangedFor(nameof(EmptyHintVisibility))]
private VersionRowViewModel? _featuredVersion;
public bool HasFeaturedVersion => FeaturedVersion is not null;
public bool HasOtherVersions => OtherVersions.Count > 0;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FooterText))]
@@ -57,7 +66,8 @@ public sealed partial class MainViewModel : ObservableObject
_config.InstallRoot;
public Visibility EmptyHintVisibility =>
Versions.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
FeaturedVersion is null && OtherVersions.Count == 0
? Visibility.Visible : Visibility.Collapsed;
public Visibility FooterVisibility =>
IsBusy || !string.IsNullOrEmpty(StatusMessage) ? Visibility.Visible : Visibility.Collapsed;
@@ -89,7 +99,11 @@ public sealed partial class MainViewModel : ObservableObject
RebuildList();
}
/// <summary>Recompose la liste affichée à partir du scan local + du dernier manifest distant.</summary>
/// <summary>
/// Recompose la liste affichée à partir du scan local + du dernier manifest distant.
/// La version « courante » (FeaturedVersion) est la plus haute installée ; à défaut,
/// la plus haute disponible côté serveur. Toutes les autres vont dans OtherVersions.
/// </summary>
private void RebuildList()
{
var installed = _registry.Scan().ToDictionary(v => v.Version);
@@ -100,7 +114,7 @@ public sealed partial class MainViewModel : ObservableObject
.OrderByDescending(v => SemVer.Parse(v))
.ToList();
Versions.Clear();
var rows = new List<VersionRowViewModel>();
foreach (var ver in allVersions)
{
VersionRowViewModel row;
@@ -113,11 +127,24 @@ public sealed partial class MainViewModel : ObservableObject
row = VersionRowViewModel.ForRemote(remoteByVer[ver]);
}
WireRowHandlers(row);
Versions.Add(row);
rows.Add(row);
}
// Featured : plus haute installée, sinon plus haute distante
var featured = rows.FirstOrDefault(r => r.IsInstalled) ?? rows.FirstOrDefault();
FeaturedVersion = featured;
OtherVersions.Clear();
foreach (var r in rows)
{
if (!ReferenceEquals(r, featured))
OtherVersions.Add(r);
}
OnPropertyChanged(nameof(EmptyHint));
OnPropertyChanged(nameof(EmptyHintVisibility));
OnPropertyChanged(nameof(HasFeaturedVersion));
OnPropertyChanged(nameof(HasOtherVersions));
}
private void WireRowHandlers(VersionRowViewModel row)