Skip to content
Merged
6 changes: 5 additions & 1 deletion src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -850,5 +850,9 @@
"Log in failed: ": "Log in failed: ",
"Log out failed: ": "Log out failed: ",
"Package backup settings": "Package backup settings",
"Show the release notes after UniGetUI is updated": "Show the release notes after UniGetUI is updated"
"Show the release notes after UniGetUI is updated": "Show the release notes after UniGetUI is updated",
"Operations completed": "Operations completed",
"Operations finished with errors": "Operations finished with errors",
"{0} of {1} succeeded, {2} failed": "{0} of {1} succeeded, {2} failed",
"Show an in-app summary toast when a batch of operations finishes": "Show an in-app summary toast when a batch of operations finishes"
}
3 changes: 3 additions & 0 deletions src/SharedAssets/Assets/Symbols/success_round.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@
<SolidColorBrush x:Key="SettingWarningTextForeground" Color="#9D5D00"/>
<!-- Operation log output text -->
<SolidColorBrush x:Key="LogOutputVerboseForeground" Color="#5C5C5C"/>
<!-- Toast: failure-badge red and drop shadow (light) -->
<SolidColorBrush x:Key="ToastBadgeBrush" Color="#C42B1C"/>
<BoxShadows x:Key="ToastShadow">0 4 16 0 #33000000</BoxShadows>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<!-- Accent fills (lighter accents on dark bg) -->
Expand Down Expand Up @@ -164,6 +167,9 @@
<SolidColorBrush x:Key="SettingWarningTextForeground" Color="#FCE100"/>
<!-- Operation log output text -->
<SolidColorBrush x:Key="LogOutputVerboseForeground" Color="#9D9D9D"/>
<!-- Toast: failure-badge red and drop shadow (dark) -->
<SolidColorBrush x:Key="ToastBadgeBrush" Color="#FF5449"/>
<BoxShadows x:Key="ToastShadow">0 4 18 0 #80000000</BoxShadows>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
Expand Down
45 changes: 6 additions & 39 deletions src/UniGetUI.Avalonia/Infrastructure/AvaloniaOperationRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,19 +196,8 @@ private static void ShowOperationProgressNotification(AbstractOperation op)
$"{title}. {message}",
AutomationLiveSetting.Polite);

if (OperatingSystem.IsWindows() && WindowsAppNotificationBridge.ShowProgress(op))
return;

if (OperatingSystem.IsMacOS() && MacOsNotificationBridge.ShowProgress(op))
return;

if (TryGetMainWindow() is not { } mainWindow)
return;

mainWindow.ShowRuntimeNotification(
title,
message,
UniGetUI.Avalonia.Views.MainWindow.RuntimeNotificationLevel.Progress);
if (OperatingSystem.IsWindows()) WindowsAppNotificationBridge.ShowProgress(op);
else if (OperatingSystem.IsMacOS()) MacOsNotificationBridge.ShowProgress(op);
}

private static void ShowOperationSuccessNotification(AbstractOperation op)
Expand All @@ -230,11 +219,8 @@ private static void ShowOperationSuccessNotification(AbstractOperation op)

WindowsAppNotificationBridge.RemoveProgress(op);

if (OperatingSystem.IsWindows() && WindowsAppNotificationBridge.ShowSuccess(op))
return;

if (OperatingSystem.IsMacOS() && MacOsNotificationBridge.ShowSuccess(op))
return;
if (OperatingSystem.IsWindows()) WindowsAppNotificationBridge.ShowSuccess(op);
else if (OperatingSystem.IsMacOS()) MacOsNotificationBridge.ShowSuccess(op);
}

private static void ShowOperationFailureNotification(AbstractOperation op)
Expand All @@ -256,27 +242,8 @@ private static void ShowOperationFailureNotification(AbstractOperation op)

WindowsAppNotificationBridge.RemoveProgress(op);

if (OperatingSystem.IsWindows() && WindowsAppNotificationBridge.ShowError(op))
return;

if (OperatingSystem.IsMacOS() && MacOsNotificationBridge.ShowError(op))
return;

if (TryGetMainWindow() is not { } mainWindow)
return;

mainWindow.ShowRuntimeNotification(
title,
message,
UniGetUI.Avalonia.Views.MainWindow.RuntimeNotificationLevel.Error);
}

private static UniGetUI.Avalonia.Views.MainWindow? TryGetMainWindow()
{
return Application.Current?.ApplicationLifetime
is IClassicDesktopStyleApplicationLifetime { MainWindow: UniGetUI.Avalonia.Views.MainWindow mw }
? mw
: null;
if (OperatingSystem.IsWindows()) WindowsAppNotificationBridge.ShowError(op);
else if (OperatingSystem.IsMacOS()) MacOsNotificationBridge.ShowError(op);
}

private static void AppendOperationHistory(AbstractOperation op)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
/// single-instance handler foregrounds the window. The inline action buttons carried
/// by the old WinRT toasts are intentionally dropped; activation is surfaced via
/// <see cref="NotificationActivated"/> only when the app is already running and the toast
/// is clicked, so the main-window view model keeps its handler. On non-Windows, or if
/// the toast COM surface is unavailable, every call falls back to the in-app Avalonia
/// banner via <see cref="MainWindow.ShowRuntimeNotification"/>.
/// is clicked, so the main-window view model keeps its handler. When the toast COM surface
/// is unavailable, feature notifications fall back to the in-app Avalonia banner via
/// <see cref="MainWindow.ShowRuntimeNotification"/>; per-operation results do not (they
/// already appear in the operations panel — see the <c>allowInAppFallback</c> flag on Show).
/// </summary>
internal static class WindowsAppNotificationBridge
{
Expand Down Expand Up @@ -64,7 +65,7 @@
? operation.Metadata.Status
: CoreTools.Translate("Please wait...");

return Show(title, message, MainWindow.RuntimeNotificationLevel.Progress, launchAction: NotificationArguments.Show);
return Show(title, message, MainWindow.RuntimeNotificationLevel.Progress, launchAction: NotificationArguments.Show, allowInAppFallback: false);
}

public static bool ShowSuccess(AbstractOperation operation)
Expand All @@ -77,7 +78,7 @@
? operation.Metadata.SuccessMessage
: CoreTools.Translate("Success!");

return Show(title, message, MainWindow.RuntimeNotificationLevel.Success, launchAction: NotificationArguments.Show);
return Show(title, message, MainWindow.RuntimeNotificationLevel.Success, launchAction: NotificationArguments.Show, allowInAppFallback: false);
}

public static bool ShowError(AbstractOperation operation)
Expand All @@ -90,7 +91,7 @@
? operation.Metadata.FailureMessage
: CoreTools.Translate("An error occurred while processing this package");

return Show(title, message, MainWindow.RuntimeNotificationLevel.Error, launchAction: NotificationArguments.Show);
return Show(title, message, MainWindow.RuntimeNotificationLevel.Error, launchAction: NotificationArguments.Show, allowInAppFallback: false);
}

public static void RemoveProgress(AbstractOperation operation)
Expand Down Expand Up @@ -258,12 +259,15 @@
/// argument so the single-instance handler can route the activation when the toast
/// is clicked. Returns <c>true</c> once the notification has been surfaced (toast or
/// banner), so callers skip their own fallback banner and avoid double-showing.
/// <paramref name="allowInAppFallback"/> is <c>false</c> for per-operation results:
/// those already live in the operations panel, so we don't duplicate them as a toast.
/// </summary>
private static bool Show(
string title,
string message,
MainWindow.RuntimeNotificationLevel level,
string launchAction)
string launchAction,
bool allowInAppFallback = true)
{
#if WINDOWS
if (OperatingSystem.IsWindows() && Win32ToastNotifier.IsAvailable())
Expand All @@ -281,7 +285,7 @@
}
#endif

return ShowInAppBanner(title, message, level);
return allowInAppFallback && ShowInAppBanner(title, message, level);
}

private static bool ShowInAppBanner(
Expand All @@ -297,6 +301,6 @@
return true;
}

private static string BuildLaunchArgument(string action)

Check warning on line 304 in src/UniGetUI.Avalonia/Infrastructure/WindowsAppNotificationBridge.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Private member 'WindowsAppNotificationBridge.BuildLaunchArgument' is unused (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0051)
=> $"unigetui://{action}";
}
176 changes: 173 additions & 3 deletions src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Avalonia;
using Avalonia.Automation;
Expand Down Expand Up @@ -63,12 +64,24 @@
public AvaloniaList<OperationViewModel> Operations => AvaloniaOperationRegistry.OperationViewModels;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowFailedOperationBadge))]
private bool _operationsPanelVisible;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowFailedOperationBadge))]
private bool _operationsPanelExpanded = true;

private readonly List<AbstractOperation> _operationBatch = new();
private bool _batchSummaryShown;

public bool HasFailedOperations => Operations.Any(o => o.Operation.Status is OperationStatus.Failed);

public OperationViewModel? FirstFailedOperation =>
Operations.FirstOrDefault(o => o.Operation.Status is OperationStatus.Failed);

// Red badge on the chevron when the panel is collapsed and an operation failed
// (failures no longer pop a toast; expanded, the failure is already visible).
public bool ShowFailedOperationBadge => OperationsPanelVisible && !OperationsPanelExpanded && HasFailedOperations;

public string OperationsHeaderText
{
Expand All @@ -91,6 +104,7 @@
foreach (var old in _operationBatch)
old.StatusChanged -= OnOperationStatusChanged;
_operationBatch.Clear();
_batchSummaryShown = false;
}

if (!_operationBatch.Contains(op))
Expand All @@ -101,22 +115,57 @@
}

private void OnOperationStatusChanged(object? sender, OperationStatus e)
=> Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(OperationsHeaderText)));
=> Dispatcher.UIThread.Post(() =>
{
OnPropertyChanged(nameof(OperationsHeaderText));
OnPropertyChanged(nameof(HasFailedOperations));
OnPropertyChanged(nameof(ShowFailedOperationBadge));
MaybeShowBatchSummaryToast();
});

// Opt-in: one toast summarizing the batch once every operation in it has finished.
private void MaybeShowBatchSummaryToast()
{
if (_batchSummaryShown || _operationBatch.Count == 0) return;
if (!Settings.Get(Settings.K.ShowOperationSummaryNotifications)) return;
if (_operationBatch.Any(o => o.Status is OperationStatus.InQueue or OperationStatus.Running)) return;

_batchSummaryShown = true;

int total = _operationBatch.Count;
int failed = _operationBatch.Count(o => o.Status is OperationStatus.Failed);
int succeeded = _operationBatch.Count(o => o.Status is OperationStatus.Succeeded);

var toast = new InfoBarViewModel
{
Title = failed > 0
? CoreTools.Translate("Operations finished with errors")
: CoreTools.Translate("Operations completed"),
Message = failed > 0
? CoreTools.Translate("{0} of {1} succeeded, {2} failed", succeeded, total, failed)
: CoreTools.Translate("{0} of {1} operations completed", succeeded, total),
Severity = failed > 0 ? InfoBarSeverity.Error : InfoBarSeverity.Success,
IsClosable = true,
IsOpen = true,
};
toast.OnClosed = () => DismissToast(toast);
ShowToast(toast);
}

[RelayCommand]
private void ToggleOperationsPanel() => OperationsPanelExpanded = !OperationsPanelExpanded;

[RelayCommand]
private void RetryFailedOperations() => AvaloniaOperationRegistry.RetryFailed();

Check warning on line 159 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 159 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 159 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 159 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 159 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void ClearSuccessfulOperations() => AvaloniaOperationRegistry.ClearSuccessful();

Check warning on line 162 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 162 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 162 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 162 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 162 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void ClearFinishedOperations() => AvaloniaOperationRegistry.ClearFinished();

Check warning on line 165 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 165 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 165 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 165 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 165 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void CancelAllOperations() => AvaloniaOperationRegistry.CancelAll();

Check warning on line 168 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 168 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 168 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 168 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 168 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

// ─── Sidebar ─────────────────────────────────────────────────────────────
public SidebarViewModel Sidebar { get; } = new();
Expand Down Expand Up @@ -169,12 +218,126 @@
? $"UniGetUI {CoreTools.Translate("version {0}", CoreData.VersionName)}"
: "UniGetUI";

// ─── Banners ─────────────────────────────────────────────────────────────
// ─── Notifications (bottom-right toasts) ───────────────────────────────────
// Persistent banners kept as singletons; RegisterBannerToast mirrors their IsOpen into Toasts.
public InfoBarViewModel UpdatesBanner { get; } = new() { Severity = InfoBarSeverity.Success };
public InfoBarViewModel ErrorBanner { get; } = new() { Severity = InfoBarSeverity.Error };
public InfoBarViewModel WinGetWarningBanner { get; } = new() { Severity = InfoBarSeverity.Warning };
public InfoBarViewModel TelemetryWarner { get; } = new() { Severity = InfoBarSeverity.Informational };

// Oldest first (rendered bottom-up so the newest sits nearest the corner).
public ObservableCollection<InfoBarViewModel> Toasts { get; } = new();
private readonly Dictionary<InfoBarViewModel, DispatcherTimer> _toastTimers = new();
private const int MaxVisibleToasts = 5;

// A toast with an action button stays until acted on; everything else auto-dismisses.
private static bool IsSticky(InfoBarViewModel t) => !string.IsNullOrEmpty(t.ActionButtonText);
private static TimeSpan ToastDuration(InfoBarViewModel t)
=> t.Severity is InfoBarSeverity.Error or InfoBarSeverity.Warning
? TimeSpan.FromSeconds(8)
: TimeSpan.FromSeconds(5);

public void ShowToast(InfoBarViewModel toast)
{
if (!Dispatcher.UIThread.CheckAccess())
{
Dispatcher.UIThread.Post(() => ShowToast(toast));
return;
}
if (!Toasts.Contains(toast))
{
Toasts.Add(toast);
AnnounceToast(toast);
}
TrimToastStack(keep: toast);
ArmToastTimer(toast);
}

// Keep the stack from overflowing the screen: drop the oldest auto-dismissing toasts,
// but never a sticky (action-button) one or the toast just shown.
private void TrimToastStack(InfoBarViewModel keep)
{
while (Toasts.Count > MaxVisibleToasts)
{
var oldest = Toasts.FirstOrDefault(t => t != keep && !IsSticky(t));
if (oldest is null) break;
DismissToast(oldest);
}
}

// Surface the toast to screen readers via the live region (assertive for errors so it
// interrupts, polite otherwise) — the toast's own automation name isn't announced on its own.
private static void AnnounceToast(InfoBarViewModel toast)
{
string message = string.IsNullOrEmpty(toast.Message)
? toast.Title
: $"{toast.Title}. {toast.Message}";
AccessibilityAnnouncementService.Announce(
message,
toast.Severity is InfoBarSeverity.Error
? AutomationLiveSetting.Assertive
: AutomationLiveSetting.Polite);
}

public void DismissToast(InfoBarViewModel toast)
{
if (!Dispatcher.UIThread.CheckAccess())
{
Dispatcher.UIThread.Post(() => DismissToast(toast));
return;
}
DisposeToastTimer(toast);
Toasts.Remove(toast);
}

// Pause the auto-dismiss countdown while the pointer hovers a toast; restart it on leave.
public void PauseToast(InfoBarViewModel toast)
{
if (_toastTimers.TryGetValue(toast, out var timer)) timer.Stop();
}

public void ResumeToast(InfoBarViewModel toast)
{
if (_toastTimers.TryGetValue(toast, out var timer)) { timer.Stop(); timer.Start(); }
}

private void ArmToastTimer(InfoBarViewModel toast)
{
DisposeToastTimer(toast);
if (IsSticky(toast))
return;

var timer = new DispatcherTimer { Interval = ToastDuration(toast) };
timer.Tick += (_, _) => toast.IsOpen = false;
_toastTimers[toast] = timer;
timer.Start();
}

private void DisposeToastTimer(InfoBarViewModel toast)
{
if (_toastTimers.Remove(toast, out var timer))
timer.Stop();
}

// Mirrors a persistent banner's IsOpen into Toasts so code that just flips IsOpen keeps working.
private void RegisterBannerToast(InfoBarViewModel banner)
{
banner.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(InfoBarViewModel.IsOpen))
{
if (banner.IsOpen) ShowToast(banner);
else DismissToast(banner);
}
// Content can change in place while the banner stays open (the updater cycles
// statuses on the same banner, finally adding the "Update now" action); re-arm so
// stickiness/countdown track the new content instead of a stale timer dismissing it.
else if (banner.IsOpen && Toasts.Contains(banner))
{
ArmToastTimer(banner);
}
};
}

// ─── Constructor ─────────────────────────────────────────────────────────
[RelayCommand]
private void ToggleSidebar() => Sidebar.IsPaneOpen = !Sidebar.IsPaneOpen;
Expand All @@ -183,6 +346,11 @@
{
AccessibilityAnnouncementService.AnnouncementRequested += OnAnnouncementRequested;

// Wire before the blocks below flip the banners' IsOpen flags.
RegisterBannerToast(UpdatesBanner);
RegisterBannerToast(WinGetWarningBanner);
RegisterBannerToast(TelemetryWarner);

DiscoverPage = new DiscoverSoftwarePage();
UpdatesPage = new SoftwareUpdatesPage();
InstalledPage = new InstalledPackagesPage();
Expand Down Expand Up @@ -264,6 +432,8 @@
AddToBatch(vm.Operation);
OperationsPanelVisible = Operations.Count > 0;
OnPropertyChanged(nameof(OperationsHeaderText));
OnPropertyChanged(nameof(HasFailedOperations));
OnPropertyChanged(nameof(ShowFailedOperationBadge));
};

if (OperatingSystem.IsWindows() && CoreTools.IsAdministrator() && !Settings.Get(Settings.K.AlreadyWarnedAboutAdmin))
Expand Down
Loading
Loading