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 e07a12af15..4621842fcb 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) @@ -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,6 +383,22 @@ private static async Task ExportSelectionToBundleAsync(PackagesPageViewModel vm) await PackageBundlesLoader.Instance.AddPackagesAsync(packages); } + // 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, + 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;