From 1d811830de45e99fbe629480f6ccc10618e7b7e7 Mon Sep 17 00:00:00 2001 From: Maxime Landry Date: Fri, 16 Oct 2026 13:13:38 -0400 Subject: [PATCH 1/2] Asked for confirmation before uninstalling packages from the Installed and Updates pages Co-Authored-By: Claude Opus 5.5 --- .../SoftwarePages/InstalledPackagesPage.cs | 32 ++++++++++++++----- .../SoftwarePages/SoftwareUpdatesPage.cs | 7 +++- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs index e07a12af15..96f5dde971 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs @@ -93,15 +93,15 @@ protected override void GenerateToolBar(PackagesPageViewModel vm) var downloadInstallers = new MenuItem { Header = CoreTools.Translate("Download selected installers") }; SetMainButton("delete", CoreTools.Translate("Uninstall selection"), () => - _ = LaunchUninstall(vm.FilteredPackages.GetCheckedPackages())); + _ = ConfirmAndLaunchUninstall(vm.FilteredPackages.GetCheckedPackages())); SetMainButtonDropdown(new MenuFlyout { Items = { uninstallAsAdmin, uninstallInteractive, new Separator(), downloadInstallers }, }); - uninstallAsAdmin.Click += (_, _) => _ = LaunchUninstall(vm.FilteredPackages.GetCheckedPackages(), elevated: true); - uninstallInteractive.Click += (_, _) => _ = LaunchUninstall(vm.FilteredPackages.GetCheckedPackages(), interactive: true); + uninstallAsAdmin.Click += (_, _) => _ = ConfirmAndLaunchUninstall(vm.FilteredPackages.GetCheckedPackages(), elevated: true); + uninstallInteractive.Click += (_, _) => _ = ConfirmAndLaunchUninstall(vm.FilteredPackages.GetCheckedPackages(), interactive: true); downloadInstallers.Click += (_, _) => _ = AvaloniaPackageOperationHelper.DownloadSelectedAsync( vm.FilteredPackages.GetCheckedPackages(), TEL_InstallReferral.ALREADY_INSTALLED); @@ -144,7 +144,7 @@ protected override void GenerateToolBar(PackagesPageViewModel vm) Header = ShortcutHeader(CoreTools.Translate("Uninstall"), MainActionShortcut), Icon = LoadMenuIcon("delete"), }; - menuUninstall.Click += (_, _) => _ = LaunchUninstall([SelectedItem!]); + menuUninstall.Click += (_, _) => _ = ConfirmAndLaunchUninstall([SelectedItem!]); _menuInstallationOptions = new MenuItem { @@ -169,21 +169,21 @@ protected override void GenerateToolBar(PackagesPageViewModel vm) Icon = LoadMenuIcon("uac"), IsVisible = OperatingSystem.IsWindows(), }; - _menuAsAdmin.Click += (_, _) => _ = LaunchUninstall([SelectedItem!], elevated: true); + _menuAsAdmin.Click += (_, _) => _ = ConfirmAndLaunchUninstall([SelectedItem!], elevated: true); _menuInteractive = new MenuItem { Header = CoreTools.Translate("Interactive uninstall"), Icon = LoadMenuIcon("interactive"), }; - _menuInteractive.Click += (_, _) => _ = LaunchUninstall([SelectedItem!], interactive: true); + _menuInteractive.Click += (_, _) => _ = ConfirmAndLaunchUninstall([SelectedItem!], interactive: true); _menuRemoveData = new MenuItem { Header = CoreTools.Translate("Uninstall and remove data"), Icon = LoadMenuIcon("close_round"), }; - _menuRemoveData.Click += (_, _) => _ = LaunchUninstall([SelectedItem!], remove_data: true); + _menuRemoveData.Click += (_, _) => _ = ConfirmAndLaunchUninstall([SelectedItem!], remove_data: true); _menuUpdate = new MenuItem { @@ -310,7 +310,7 @@ protected override void WhenShowingContextMenu(IPackage package) protected override void PerformMainPackageAction(IPackage? package) { if (package is null) return; - _ = LaunchUninstall([package]); + _ = ConfirmAndLaunchUninstall([package]); } protected override async Task ShowDetailsForPackage(IPackage? package) @@ -383,6 +383,22 @@ private static async Task ExportSelectionToBundleAsync(PackagesPageViewModel vm) await PackageBundlesLoader.Instance.AddPackagesAsync(packages); } + // Started straight from the page (toolbar, context menu, Ctrl+Enter, double-click): nothing else stands + // between the click and the uninstall, so confirm first. Details, options and catalog callers confirm or + // ask in their own dialog, and call LaunchUninstall directly. + private static async Task ConfirmAndLaunchUninstall( + IEnumerable packages, + bool? elevated = null, + bool? interactive = null, + bool? remove_data = null) + { + var list = packages.ToList(); + if (list.Count == 0 || MainWindow.Instance is not { } window) return; + if (!await UninstallConfirmationDialog.ConfirmAsync(window, list)) return; + + await LaunchUninstall(list, elevated, interactive, remove_data); + } + internal static async Task LaunchUninstall( IEnumerable packages, bool? elevated = null, diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs index 464c26a61d..71f8c49e2d 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs @@ -356,9 +356,14 @@ private static async Task LaunchUpdate( } } + // Every row on this page is checked by default, so one click here could uninstall every package with an update. private static async Task LaunchUninstallFromUpdates(IEnumerable packages) { - foreach (var pkg in packages) + var list = packages.ToList(); + if (list.Count == 0 || MainWindow.Instance is not { } window) return; + if (!await UninstallConfirmationDialog.ConfirmAsync(window, list)) return; + + foreach (var pkg in list) { var opts = await InstallOptionsFactory.LoadApplicableAsync(pkg); if (PackageOperation.HasPendingOperation(pkg, OperationType.Uninstall)) continue; From 48847c314e0afc3786a9fab6ca7252d7fc8d2138 Mon Sep 17 00:00:00 2001 From: Maxime Landry Date: Fri, 16 Oct 2026 14:06:21 -0400 Subject: [PATCH 2/2] Asked for confirmation before uninstalling from the package details and the uninstall options dialog Co-Authored-By: Claude Opus 5.5 --- .../Views/DialogPages/PackageDetailsWindow.axaml.cs | 13 +++++++++++-- .../Views/SoftwarePages/InstalledPackagesPage.cs | 8 ++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs index 8a4ce87bf8..bc211c1fa1 100644 --- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs @@ -104,7 +104,7 @@ public PackageDetailsWindow( UpdatePips(); }, DispatcherPriority.Loaded); - MainActionButton.Click += (_, _) => OnMainAction(); + MainActionButton.Click += (_, _) => _ = OnMainActionAsync(); ActionVariantsButton.Flyout = BuildActionFlyout(); InstallOptionsSaveButton.Click += (_, _) => _ = SaveInstallOptionsAndRefreshAsync(); ContributeButton.Click += (_, _) => OpenUrl(ContributeUrl); @@ -1093,12 +1093,20 @@ private MenuFlyout BuildActionFlyout() return flyout; } - private void OnMainAction() + private async Task OnMainActionAsync() { + if (!await ConfirmUninstallAsync(_vm.OperationRole)) return; ShouldProceedWithOperation = true; Close(); } + // Uninstalling cannot be undone: confirm on top of this dialog (the main window stacks them), which + // stays open on Cancel. Covers every caller (Installed page, catalog) and the as-admin / interactive / + // remove-data variants. + private async Task ConfirmUninstallAsync(OperationType role) + => role is not OperationType.Uninstall + || (MainWindow.Instance is { } window && await UninstallConfirmationDialog.ConfirmAsync(window, _vm.Package)); + private async Task LaunchAndClose( OperationType role, bool? elevated = null, @@ -1106,6 +1114,7 @@ private async Task LaunchAndClose( bool? no_integrity = null, bool? remove_data = null) { + if (!await ConfirmUninstallAsync(role)) return; Close(); var pkg = _vm.Package; diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs index 96f5dde971..4621842fcb 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs @@ -337,7 +337,7 @@ protected override async Task ShowInstallationOptionsForPackage(IPackage? packag await InstallOptionsFactory.SaveForPackageAsync(opts, package); if (dialog.ShouldProceedWithOperation) - await LaunchUninstall([package]); + await ConfirmAndLaunchUninstall([package]); } // ─── WinGet malfunction banner ──────────────────────────────────────────── @@ -383,9 +383,9 @@ private static async Task ExportSelectionToBundleAsync(PackagesPageViewModel vm) await PackageBundlesLoader.Instance.AddPackagesAsync(packages); } - // Started straight from the page (toolbar, context menu, Ctrl+Enter, double-click): nothing else stands - // between the click and the uninstall, so confirm first. Details, options and catalog callers confirm or - // ask in their own dialog, and call LaunchUninstall directly. + // Uninstalling cannot be undone, so every page entry point confirms first: toolbar, context menu, + // Ctrl+Enter, double-click and the options dialog. The details window confirms in its own window, + // and the catalog before calling LaunchUninstall, so neither asks twice. private static async Task ConfirmAndLaunchUninstall( IEnumerable packages, bool? elevated = null,