From 4dad849d53276bb94d0749fea117efb2e7e839fb Mon Sep 17 00:00:00 2001 From: GabrielDuf Date: Fri, 16 Oct 2026 14:59:48 -0400 Subject: [PATCH] Add a Release notes item to the updates context menu --- .../SoftwarePages/SoftwareUpdatesPage.cs | 53 ++++++++++++++++++- .../ManagerCapabilities.cs | 1 + .../Chocolatey.cs | 1 + .../DotNet.cs | 1 + .../PowerShell.cs | 1 + .../PowerShell7.cs | 1 + .../WinGet.cs | 1 + 7 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs index 464c26a61d..191a8e41c4 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs @@ -28,6 +28,8 @@ public class SoftwareUpdatesPage : AbstractPackagesPage private MenuItem? _menuSkipHash; private MenuItem? _menuDownloadInstaller; private MenuItem? _menuOpenInstallLocation; + private MenuItem? _menuReleaseNotes; + private IPackage? _releaseNotesPackage; public SoftwareUpdatesPage() : base(new PackagesPageData { @@ -249,6 +251,13 @@ protected override void GenerateToolBar(PackagesPageViewModel vm) }; menuDetails.Click += (_, _) => _ = ShowDetailsForPackage(SelectedItem); + _menuReleaseNotes = new MenuItem + { + Header = CoreTools.Translate("Release notes"), + Icon = LoadMenuIcon("megaphone"), + }; + _menuReleaseNotes.Click += (_, _) => _ = ShowReleaseNotesForPackage(SelectedItem); + var menu = new ContextMenu(); menu.Items.Add(menuUpdate); menu.Items.Add(new Separator()); @@ -268,6 +277,7 @@ protected override void GenerateToolBar(PackagesPageViewModel vm) menu.Items.Add(menuSkipVersion); menu.Items.Add(menuPause); menu.Items.Add(new Separator()); + menu.Items.Add(_menuReleaseNotes); menu.Items.Add(menuDetails); return menu; @@ -276,7 +286,8 @@ protected override void GenerateToolBar(PackagesPageViewModel vm) protected override void WhenShowingContextMenu(IPackage package) { if (_menuAsAdmin is null || _menuInteractive is null || _menuSkipHash is null - || _menuDownloadInstaller is null || _menuOpenInstallLocation is null) + || _menuDownloadInstaller is null || _menuOpenInstallLocation is null + || _menuReleaseNotes is null) { Logger.Warn("Context menu items are null on SoftwareUpdatesPage"); return; @@ -289,8 +300,35 @@ protected override void WhenShowingContextMenu(IPackage package) _menuDownloadInstaller.IsEnabled = caps.CanDownloadInstaller; _menuOpenInstallLocation.IsEnabled = package.Manager.DetailsHelper.GetInstallLocation(package) is not null; + + _releaseNotesPackage = package; + if (!caps.SupportsReleaseNotes) + _menuReleaseNotes.IsEnabled = false; + else if (package.Details.IsPopulated) + _menuReleaseNotes.IsEnabled = HasReleaseNotes(package.Details); + else + { + _menuReleaseNotes.IsEnabled = false; + _ = UpdateReleaseNotesAvailabilityAsync(package); + } + } + + private async Task UpdateReleaseNotesAvailabilityAsync(IPackage package) + { + await package.Details.Load(); + if (_menuReleaseNotes is not null && ReferenceEquals(_releaseNotesPackage, package)) + _menuReleaseNotes.IsEnabled = HasReleaseNotes(package.Details); } + private static bool HasReleaseNotes(IPackageDetails details) => + GetReleaseNotesUrl(details) is not null || !string.IsNullOrWhiteSpace(details.ReleaseNotes); + + private static Uri? GetReleaseNotesUrl(IPackageDetails details) => + details.ReleaseNotesUrl is { IsAbsoluteUri: true } url + && (url.Scheme == Uri.UriSchemeHttps || url.Scheme == Uri.UriSchemeHttp) + ? url + : null; + // ─── Abstract action overrides ──────────────────────────────────────────── protected override void PerformMainPackageAction(IPackage? package) { @@ -335,6 +373,19 @@ private static void OpenInstallLocation(IPackage? package) Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); } + private async Task ShowReleaseNotesForPackage(IPackage? package) + { + if (package is null) return; + + if (!package.Details.IsPopulated) + await package.Details.Load(); + + if (GetReleaseNotesUrl(package.Details) is { } url) + CoreTools.Launch(url.AbsoluteUri); + else if (!string.IsNullOrWhiteSpace(package.Details.ReleaseNotes)) + await ShowDetailsForPackage(package); + } + // ─── Operation launchers ────────────────────────────────────────────────── private static async Task LaunchUpdate( IEnumerable packages, diff --git a/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs b/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs index ba1a2069ba..9f2e32d9b5 100644 --- a/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs +++ b/src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs @@ -33,6 +33,7 @@ public struct ManagerCapabilities public bool CanDownloadInstaller = false; public bool CanUninstallPreviousVersionsAfterUpdate = false; public bool CanListDependencies = false; + public bool SupportsReleaseNotes = false; public bool SupportsCustomVersions = false; public bool SupportsCustomArchitectures = false; public string[] SupportedCustomArchitectures = []; diff --git a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs index a49be26508..1697d73b9e 100644 --- a/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs +++ b/src/UniGetUI.PackageEngine.Managers.Chocolatey/Chocolatey.cs @@ -94,6 +94,7 @@ public Chocolatey() { CanRunAsAdmin = true, CanDownloadInstaller = true, + SupportsReleaseNotes = true, CanSkipIntegrityChecks = true, CanRunInteractively = true, SupportsCustomVersions = true, diff --git a/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs b/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs index 3b119fb2d3..78ec4e1b02 100644 --- a/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs +++ b/src/UniGetUI.PackageEngine.Managers.Dotnet/DotNet.cs @@ -27,6 +27,7 @@ public DotNet() { CanRunAsAdmin = true, CanDownloadInstaller = true, + SupportsReleaseNotes = true, SupportsCustomScopes = true, SupportsCustomArchitectures = true, SupportedCustomArchitectures = diff --git a/src/UniGetUI.PackageEngine.Managers.PowerShell/PowerShell.cs b/src/UniGetUI.PackageEngine.Managers.PowerShell/PowerShell.cs index e647ad1ee0..876b0d8889 100644 --- a/src/UniGetUI.PackageEngine.Managers.PowerShell/PowerShell.cs +++ b/src/UniGetUI.PackageEngine.Managers.PowerShell/PowerShell.cs @@ -26,6 +26,7 @@ public PowerShell() CanSkipIntegrityChecks = true, SupportsCustomVersions = true, CanDownloadInstaller = true, + SupportsReleaseNotes = true, SupportsCustomScopes = true, // Update-Module/Uninstall-Module (PowerShellGet) take no -Scope; only Install-Module does SupportsCustomScopesOnUpdate = false, diff --git a/src/UniGetUI.PackageEngine.Managers.PowerShell7/PowerShell7.cs b/src/UniGetUI.PackageEngine.Managers.PowerShell7/PowerShell7.cs index 8c9a6e6917..340250bfe3 100644 --- a/src/UniGetUI.PackageEngine.Managers.PowerShell7/PowerShell7.cs +++ b/src/UniGetUI.PackageEngine.Managers.PowerShell7/PowerShell7.cs @@ -32,6 +32,7 @@ public PowerShell7() SupportsCustomSources = true, SupportsPreRelease = true, CanDownloadInstaller = true, + SupportsReleaseNotes = true, CanListDependencies = true, SupportsCustomPackageIcons = true, CanUninstallPreviousVersionsAfterUpdate = true, diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs index c6a5a5e882..2aa875a1bb 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs @@ -114,6 +114,7 @@ public WinGet() CanRunInteractively = true, SupportsCustomVersions = true, CanDownloadInstaller = true, + SupportsReleaseNotes = true, CanListDependencies = true, SupportsCustomArchitectures = true, SupportedCustomArchitectures =