diff --git a/CHANGELOG.md b/CHANGELOG.md index b81501b3..ed4000d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,20 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - **Traceroute live output** — ConsoleView with hop-by-hop results and explanations (gateway detection, ISP backbone, filtered nodes, destination reached). +### Fixed +- **DashboardView** — replaced 30+ hardcoded hex colors with StaticResource tokens + (Surface1, Surface2, Border1, TextPrimary, TextSecondary, Info). +- **AppBlockerView** — full structural modernization (Display header, Card wrappers, + button styles, DataGrid accessibility, Background, margins). +- **DnsHostsView** — DataGrid grid-lines removed, accessibility name added, text token. +- **ObservableCollection → BulkObservableCollection** — AppAlerts, AppBlocker, + ShortcutCleaner now use single-notification ReplaceWith() instead of N Add() events. +- **Missing toast notifications** — added on Drivers, Services, ShortcutCleaner, + DeepCleanup (3 operations), NetworkRepair. +- **UninstallerView** — hardcoded `#6366F1` replaced with `{StaticResource Accent}`. + +## [1.10.1] - 2026-05-26 + ### Fixed - **UI uniformity audit** — replaced all remaining CheckBoxes with purple ToggleSwitch on: Performance (5 toggles), Logs (5 severity filters), Ping targets, Process Manager, @@ -38,6 +52,13 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - **Startup Manager hide system** — toggle to filter out Windows/Microsoft system entries. - **Filter chip styles** — reusable green/amber/red radio pill components. +## [1.9.1] - 2026-05-26 + +### Fixed +- **Startup Manager columns** — reduced fixed widths to prevent last column overflow. +- **Startup Manager icons** — use extracted executable path for more accurate icon resolution. +- **Windows Update live output** — increased MinHeight/MaxHeight for better visibility. + ## [1.9.0] - 2026-05-26 ### Added diff --git a/SysManager/SysManager/ViewModels/AppAlertsViewModel.cs b/SysManager/SysManager/ViewModels/AppAlertsViewModel.cs index 93aa4e09..35c8b4cf 100644 --- a/SysManager/SysManager/ViewModels/AppAlertsViewModel.cs +++ b/SysManager/SysManager/ViewModels/AppAlertsViewModel.cs @@ -2,12 +2,12 @@ // Author: laurentiu021 · https://github.com/laurentiu021/SystemManager // License: MIT -using System.Collections.ObjectModel; using System.Windows; using System.Windows.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Serilog; +using SysManager.Helpers; using SysManager.Models; using SysManager.Services; @@ -22,7 +22,7 @@ public sealed partial class AppAlertsViewModel : ViewModelBase private readonly AppAlertService _service; private readonly Dispatcher _dispatcher; - public ObservableCollection Alerts { get; } = new(); + public BulkObservableCollection Alerts { get; } = new(); [ObservableProperty] private bool _isMonitoring; [ObservableProperty] private string _monitorStatus = "Click Start to begin monitoring for new installations."; @@ -90,13 +90,13 @@ private void RefreshInstalledApps() try { var apps = AppAlertService.GetRegistryApps(); - Alerts.Clear(); - foreach (var app in apps.OrderBy(a => a.Name)) + var sorted = apps.OrderBy(a => a.Name).ToList(); + foreach (var app in sorted) { app.DetectedAt = DateTime.Now; app.IsAcknowledged = true; - Alerts.Add(app); } + Alerts.ReplaceWith(sorted); AlertCount = Alerts.Count; UnacknowledgedCount = 0; MonitorStatus = $"Loaded {AlertCount} currently installed applications."; diff --git a/SysManager/SysManager/ViewModels/AppBlockerViewModel.cs b/SysManager/SysManager/ViewModels/AppBlockerViewModel.cs index 95d5ee18..3bd78864 100644 --- a/SysManager/SysManager/ViewModels/AppBlockerViewModel.cs +++ b/SysManager/SysManager/ViewModels/AppBlockerViewModel.cs @@ -2,7 +2,6 @@ // Author: laurentiu021 · https://github.com/laurentiu021/SystemManager // License: MIT -using System.Collections.ObjectModel; using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -19,7 +18,7 @@ namespace SysManager.ViewModels; /// public sealed partial class AppBlockerViewModel : ViewModelBase { - public ObservableCollection BlockedApps { get; } = new(); + public BulkObservableCollection BlockedApps { get; } = new(); [ObservableProperty] private string _newExeName = ""; [ObservableProperty] private string _blockStatus = "Enter an executable name and click Block to prevent it from running."; @@ -42,10 +41,8 @@ private void RelaunchAsAdmin() [RelayCommand] private void RefreshList() { - BlockedApps.Clear(); var apps = AppBlockerService.GetBlockedApps(); - foreach (var a in apps) - BlockedApps.Add(a); + BlockedApps.ReplaceWith(apps); BlockedCount = BlockedApps.Count; BlockStatus = BlockedCount == 0 ? "No applications are currently blocked." diff --git a/SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs b/SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs index eee0f49e..03f5d08f 100644 --- a/SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs +++ b/SysManager/SysManager/ViewModels/DeepCleanupViewModel.cs @@ -187,6 +187,7 @@ private async Task ScanCoreAsync() var total = cats.Sum(c => c.TotalSizeBytes); ScanSummary = $"Found {FormatHelper.FormatSize(total)} across {cats.Count} categories. Untick anything you want to keep."; ScanStatusLine = "Scan complete."; + ToastService.Instance.Show("Deep cleanup scan complete", $"{FormatHelper.FormatSize(total)} across {cats.Count} categories"); Log.Information("Deep cleanup scan completed: {Size} across {Count} categories", FormatHelper.FormatSize(total), cats.Count); OnPropertyChanged(nameof(TotalSelectedBytes)); @@ -227,6 +228,7 @@ private async Task CleanAsync() var result = await _cleanup.CleanAsync(Categories, progress, _cleanCts.Token); CleanSummary = result.Summary; CleanStatusLine = "Clean complete."; + ToastService.Instance.Show("Deep cleanup complete", result.Summary); Log.Information("Deep cleanup completed"); await ScanCoreAsync(); } @@ -293,6 +295,7 @@ private async Task ScanLargeFilesAsync() ct: _largeCts.Token); LargeFiles.ReplaceWith(list); LargeScanStatus = $"Found {list.Count} files ≥ {MinSizeMB} MB in {SelectedLocation.Label.Trim()}."; + ToastService.Instance.Show("Large file scan complete", $"{list.Count} files found ≥ {MinSizeMB} MB"); Log.Information("Large file scan completed: {Count} files ≥ {MinSize} MB", list.Count, MinSizeMB); } diff --git a/SysManager/SysManager/ViewModels/DriversViewModel.cs b/SysManager/SysManager/ViewModels/DriversViewModel.cs index 7bc1d2b0..eeeac420 100644 --- a/SysManager/SysManager/ViewModels/DriversViewModel.cs +++ b/SysManager/SysManager/ViewModels/DriversViewModel.cs @@ -2,7 +2,6 @@ // Author: laurentiu021 · https://github.com/laurentiu021/SystemManager // License: MIT -using System.Collections.ObjectModel; using System.Globalization; using System.Text.Json; using CommunityToolkit.Mvvm.ComponentModel; @@ -69,6 +68,7 @@ await _runner.RunScriptViaPwshAsync(@" Summary = $"{_allDrivers.Count} drivers found" + (HideSystemDrivers ? $" ({DriverCount} shown, system drivers hidden)." : "."); StatusMessage = "Done"; + ToastService.Instance.Show("Driver scan complete", $"{_allDrivers.Count} drivers found"); } catch (OperationCanceledException) { StatusMessage = "Cancelled."; } catch (InvalidOperationException ex) { StatusMessage = ex.Message; } diff --git a/SysManager/SysManager/ViewModels/NetworkRepairViewModel.cs b/SysManager/SysManager/ViewModels/NetworkRepairViewModel.cs index 43ba47b8..3f646406 100644 --- a/SysManager/SysManager/ViewModels/NetworkRepairViewModel.cs +++ b/SysManager/SysManager/ViewModels/NetworkRepairViewModel.cs @@ -92,7 +92,10 @@ private async Task RunRepairAsync( ? $"✓ {r.ToolName} completed successfully." : $"✗ {r.ToolName} failed: {r.Output}"; if (r.Success) + { + ToastService.Instance.Show("Network repair complete", $"{r.ToolName} completed successfully"); Log.Information("Network repair completed: {Tool}", r.ToolName); + } else Log.Warning("Network repair failed: {Tool}", r.ToolName); if (r.NeedsReboot && r.Success) diff --git a/SysManager/SysManager/ViewModels/ServicesViewModel.cs b/SysManager/SysManager/ViewModels/ServicesViewModel.cs index 6b89e7e6..ae15ac0b 100644 --- a/SysManager/SysManager/ViewModels/ServicesViewModel.cs +++ b/SysManager/SysManager/ViewModels/ServicesViewModel.cs @@ -2,7 +2,6 @@ // Author: laurentiu021 · https://github.com/laurentiu021/SystemManager // License: MIT -using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; @@ -89,6 +88,7 @@ private void ApplyFilterCore() RunningCount = _allServices.Count(s => s.Status == "Running"); ApplyFilter(); StatusMessage = $"Loaded {TotalCount} services ({RunningCount} running)."; + ToastService.Instance.Show("Services refreshed", $"{TotalCount} services ({RunningCount} running)"); } [RelayCommand] diff --git a/SysManager/SysManager/ViewModels/ShortcutCleanerViewModel.cs b/SysManager/SysManager/ViewModels/ShortcutCleanerViewModel.cs index 9aa8ccd2..d829593c 100644 --- a/SysManager/SysManager/ViewModels/ShortcutCleanerViewModel.cs +++ b/SysManager/SysManager/ViewModels/ShortcutCleanerViewModel.cs @@ -2,11 +2,11 @@ // Author: laurentiu021 · https://github.com/laurentiu021/SystemManager // License: MIT -using System.Collections.ObjectModel; using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Serilog; +using SysManager.Helpers; using SysManager.Models; using SysManager.Services; @@ -20,7 +20,7 @@ public sealed partial class ShortcutCleanerViewModel : ViewModelBase private readonly ShortcutCleanerService _service; private CancellationTokenSource? _cts; - public ObservableCollection BrokenShortcuts { get; } = new(); + public BulkObservableCollection BrokenShortcuts { get; } = new(); [ObservableProperty] private string _scanStatus = "Click Scan to find broken shortcuts."; [ObservableProperty] private string _currentLocation = ""; @@ -53,7 +53,7 @@ private async Task ScanAsync() // PropertyChanged lambda leaks across rescans. foreach (var old in BrokenShortcuts) old.PropertyChanged -= OnShortcutPropertyChanged; - BrokenShortcuts.Clear(); + BrokenShortcuts.ReplaceWith(Array.Empty()); BrokenCount = 0; SelectedCount = 0; ScanStatus = "Scanning..."; @@ -66,10 +66,8 @@ private async Task ScanAsync() var results = await _service.ScanAsync(progress, _cts.Token); foreach (var s in results) - { s.PropertyChanged += OnShortcutPropertyChanged; - BrokenShortcuts.Add(s); - } + BrokenShortcuts.ReplaceWith(results); BrokenCount = BrokenShortcuts.Count; SelectedCount = BrokenShortcuts.Count(x => x.IsSelected); @@ -78,6 +76,7 @@ private async Task ScanAsync() : $"Found {BrokenCount} broken shortcut{(BrokenCount == 1 ? "" : "s")}."; CurrentLocation = ""; Log.Information("Shortcut scan completed: {Count} broken shortcuts found", BrokenCount); + ToastService.Instance.Show("Shortcut scan complete", $"{BrokenCount} broken shortcut{(BrokenCount == 1 ? "" : "s")} found"); } catch (OperationCanceledException) { ScanStatus = "Scan cancelled."; } catch (System.IO.IOException ex) { ScanStatus = $"Scan failed: {ex.Message}"; } @@ -116,6 +115,7 @@ private void DeleteSelected() BrokenCount = BrokenShortcuts.Count; SelectedCount = BrokenShortcuts.Count(x => x.IsSelected); ScanStatus = $"Deleted {deleted} shortcut{(deleted == 1 ? "" : "s")}. {BrokenCount} remaining."; + ToastService.Instance.Show("Shortcuts deleted", $"{deleted} shortcut{(deleted == 1 ? "" : "s")} removed"); Log.Information("Deleted {Count} broken shortcuts (recycle bin: {RecycleBin})", deleted, MoveToRecycleBin); } diff --git a/SysManager/SysManager/Views/AppBlockerView.xaml b/SysManager/SysManager/Views/AppBlockerView.xaml index 75b096f9..5e7ec98b 100644 --- a/SysManager/SysManager/Views/AppBlockerView.xaml +++ b/SysManager/SysManager/Views/AppBlockerView.xaml @@ -6,9 +6,10 @@ d:DataContext="{d:DesignInstance vm:AppBlockerViewModel}" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d"> + mc:Ignorable="d" + Background="{StaticResource Surface0}"> - + @@ -20,10 +21,8 @@ - - + + @@ -61,17 +60,17 @@ -