From bbe7d73de2fac4930a35fac80be5a94b8e4a06a5 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 23 Apr 2026 15:05:56 +0200 Subject: [PATCH] feat(ui): wire update-check state and commands into shell VM --- .../ViewModels/IslandsShellViewModel.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs index 3c2c7ee..c364fd6 100644 --- a/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/IslandsShellViewModel.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; +using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using ClaudeDo.Ui.Services; @@ -11,6 +14,7 @@ public sealed partial class IslandsShellViewModel : ViewModelBase public TasksIslandViewModel Tasks { get; } public DetailsIslandViewModel Details { get; } public WorkerClient Worker { get; } + public UpdateCheckService UpdateCheck => _updateCheck; public string ConnectionText => Worker.IsConnected ? "Online" @@ -22,6 +26,11 @@ public sealed partial class IslandsShellViewModel : ViewModelBase private readonly UpdateCheckService _updateCheck; private readonly InstallerLocator _installerLocator; + [ObservableProperty] private bool _isUpdateBannerVisible; + [ObservableProperty] private string? _updateBannerLatestVersion; + [ObservableProperty] private string? _inlineUpdateStatus; + private bool _bannerDismissedThisSession; + [ObservableProperty] private double _windowWidth = 1280; @@ -76,5 +85,74 @@ public sealed partial class IslandsShellViewModel : ViewModelBase } }; _ = Lists.LoadAsync(); + _updateCheck.PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(UpdateCheckService.LastCheckStatus)) + { + RefreshBannerFromStatus(); + } + }; + // Fire-and-forget startup check — never block UI. + _ = Task.Run(async () => + { + try { await _updateCheck.CheckNowAsync(CancellationToken.None); } catch { } + }); + } + + private void RefreshBannerFromStatus() + { + switch (_updateCheck.LastCheckStatus) + { + case UpdateCheckStatus.UpdateAvailable: + if (_bannerDismissedThisSession) { IsUpdateBannerVisible = false; break; } + UpdateBannerLatestVersion = _updateCheck.LatestVersion; + IsUpdateBannerVisible = true; + InlineUpdateStatus = null; + break; + case UpdateCheckStatus.UpToDate: + IsUpdateBannerVisible = false; + ShowInlineStatus($"You're up to date (v{_updateCheck.CurrentVersion})"); + break; + case UpdateCheckStatus.CheckFailed: + ShowInlineStatus("Could not check for updates"); + break; + } + } + + private async void ShowInlineStatus(string text) + { + InlineUpdateStatus = text; + await Task.Delay(3000); + if (InlineUpdateStatus == text) InlineUpdateStatus = null; + } + + [RelayCommand] + private async Task CheckForUpdatesAsync() + { + await _updateCheck.CheckNowAsync(CancellationToken.None); + } + + [RelayCommand] + private void DismissBanner() + { + _bannerDismissedThisSession = true; + IsUpdateBannerVisible = false; + } + + [RelayCommand] + private void UpdateNow() + { + var path = _installerLocator.Find(); + if (path is null) return; + + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); + Environment.Exit(0); + } + catch + { + // Intentionally silent — if this fails there's nothing useful to show. + } } }