From 93dd48595cf3a3efe4974d94479c2963d8c2da85 Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Sat, 1 Aug 2026 05:26:29 +0000
Subject: [PATCH 01/12] Centralize WinUI theme primitives
---
.../Assets/Styles/Styles.Common.axaml | 270 +++++++++++++++++-
.../Infrastructure/SettingsAnchor.cs | 6 +-
.../Views/Controls/InfoBar.axaml.cs | 4 +-
.../Views/Controls/Settings/ButtonCard.cs | 1 +
.../Controls/Settings/CheckboxButtonCard.cs | 1 +
.../Views/Controls/StatusBadge.cs | 128 +++++++++
.../Views/Controls/UserAvatarControl.axaml | 4 +-
.../DialogPages/PackageDetailsWindow.axaml | 2 +-
.../Pages/SettingsPages/General.axaml.cs | 2 +-
.../SettingsPages/ManagersHomepage.axaml.cs | 101 +------
.../SettingsPages/SourceManagerCard.axaml | 2 +-
src/UniGetUI.Avalonia/Views/SidebarView.axaml | 6 +-
12 files changed, 425 insertions(+), 102 deletions(-)
create mode 100644 src/UniGetUI.Avalonia/Views/Controls/StatusBadge.cs
diff --git a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
index a05097453c..4d2227129e 100644
--- a/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
+++ b/src/UniGetUI.Avalonia/Assets/Styles/Styles.Common.axaml
@@ -13,6 +13,14 @@
+
+
+
+
+
+
+
+
@@ -45,6 +53,15 @@
+
+
+
+
+
+
+
+
+
@@ -58,6 +75,10 @@
+
+
+
@@ -114,6 +135,14 @@
+
+
+
+
+
+
+
+
@@ -145,6 +174,15 @@
+
+
+
+
+
+
+
+
+
@@ -158,6 +196,9 @@
+
+
+
@@ -213,11 +254,234 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -376,7 +640,7 @@
diff --git a/src/UniGetUI.Avalonia/Infrastructure/SettingsAnchor.cs b/src/UniGetUI.Avalonia/Infrastructure/SettingsAnchor.cs
index 0192c04ad5..f46d568267 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/SettingsAnchor.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/SettingsAnchor.cs
@@ -59,12 +59,12 @@ private static void Highlight(Control target)
var layer = AdornerLayer.GetAdornerLayer(target);
if (layer is null) return;
- Color accent = target.TryFindResource("SystemAccentColor", target.ActualThemeVariant, out var res)
- && res is Color c ? c : Colors.DodgerBlue;
+ IBrush accent = target.TryFindResource("AccentFillColorDefaultBrush", target.ActualThemeVariant, out var res)
+ && res is IBrush brush ? brush : Brushes.DodgerBlue;
var adorner = new Border
{
- BorderBrush = new SolidColorBrush(accent),
+ BorderBrush = accent,
BorderThickness = new Thickness(2),
CornerRadius = target is Border b ? b.CornerRadius : new CornerRadius(8),
IsHitTestVisible = false,
diff --git a/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs b/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs
index 1058a46f97..215e839041 100644
--- a/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/Controls/InfoBar.axaml.cs
@@ -88,10 +88,10 @@ private void ApplySeverity(InfoBarSeverity severity)
SeverityIcon.ClearValue(ForegroundProperty);
_severityStripBinding = SeverityStrip.Bind(
Border.BackgroundProperty,
- this.GetResourceObservable("SystemAccentColor"));
+ this.GetResourceObservable("AccentFillColorDefaultBrush"));
_severityIconBinding = SeverityIcon.Bind(
ForegroundProperty,
- this.GetResourceObservable("SystemAccentColor"));
+ this.GetResourceObservable("AccentFillColorDefaultBrush"));
}
}
}
diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/ButtonCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/ButtonCard.cs
index bb3f8c8dbb..6f11baf7d9 100644
--- a/src/UniGetUI.Avalonia/Views/Controls/Settings/ButtonCard.cs
+++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/ButtonCard.cs
@@ -31,6 +31,7 @@ public string Text
public ButtonCard()
{
+ _button.Classes.Add("secondary-action");
_button.MinWidth = 200;
_button.Click += (_, _) => Click?.Invoke(this, EventArgs.Empty);
Content = _button;
diff --git a/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs b/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs
index e10f4388ec..1783f8842d 100644
--- a/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs
+++ b/src/UniGetUI.Avalonia/Views/Controls/Settings/CheckboxButtonCard.cs
@@ -72,6 +72,7 @@ public bool ButtonAlwaysOn
public CheckboxButtonCard()
{
Button = new Button { Margin = new Thickness(0, 8, 0, 0) };
+ Button.Classes.Add("secondary-action");
_checkbox = new ToggleSwitch
{
// OnContent/OffContent intentionally left null — state label is a
diff --git a/src/UniGetUI.Avalonia/Views/Controls/StatusBadge.cs b/src/UniGetUI.Avalonia/Views/Controls/StatusBadge.cs
new file mode 100644
index 0000000000..35377cfddd
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Views/Controls/StatusBadge.cs
@@ -0,0 +1,128 @@
+using Avalonia;
+using Avalonia.Automation;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Layout;
+using Avalonia.Media;
+using AvaloniaPath = Avalonia.Controls.Shapes.Path;
+
+namespace UniGetUI.Avalonia.Views.Controls;
+
+public enum StatusBadgeSeverity
+{
+ Info,
+ Success,
+ Warning,
+ Error,
+}
+
+/// A compact, theme-aware status indicator shared across status surfaces.
+public sealed class StatusBadge : Border
+{
+ public static readonly StyledProperty TextProperty =
+ AvaloniaProperty.Register(nameof(Text), "");
+
+ public static readonly StyledProperty SeverityProperty =
+ AvaloniaProperty.Register(nameof(Severity));
+
+ private readonly TextBlock _text;
+ private readonly AvaloniaPath _glyph;
+
+ public string Text
+ {
+ get => GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ public StatusBadgeSeverity Severity
+ {
+ get => GetValue(SeverityProperty);
+ set => SetValue(SeverityProperty, value);
+ }
+
+ public StatusBadge()
+ {
+ CornerRadius = new CornerRadius(4);
+ Padding = new Thickness(6, 3);
+ HorizontalAlignment = HorizontalAlignment.Left;
+ VerticalAlignment = VerticalAlignment.Center;
+
+ var icon = new Ellipse { Width = 12, Height = 12 };
+ icon.Classes.Add("status-badge-icon");
+ _glyph = new AvaloniaPath
+ {
+ Width = 5,
+ Height = 5,
+ StrokeThickness = 1.5,
+ StrokeLineCap = PenLineCap.Round,
+ StrokeJoin = PenLineJoin.Round,
+ Stretch = Stretch.None,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ };
+ _glyph.Classes.Add("status-badge-glyph");
+
+ var iconHost = new Grid
+ {
+ Width = 12,
+ Height = 12,
+ VerticalAlignment = VerticalAlignment.Center,
+ Children = { icon, _glyph },
+ };
+ AutomationProperties.SetAccessibilityView(iconHost, AccessibilityView.Raw);
+
+ _text = new TextBlock
+ {
+ FontSize = 12,
+ FontWeight = FontWeight.SemiBold,
+ VerticalAlignment = VerticalAlignment.Center,
+ };
+ _text.Classes.Add("status-badge-text");
+
+ Child = new StackPanel
+ {
+ Orientation = Orientation.Horizontal,
+ Spacing = 5,
+ VerticalAlignment = VerticalAlignment.Center,
+ Children = { iconHost, _text },
+ };
+ UpdateAppearance();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == TextProperty || change.Property == SeverityProperty)
+ UpdateAppearance();
+ }
+
+ private void UpdateAppearance()
+ {
+ if (_text is null || _glyph is null)
+ return;
+
+ _text.Text = Text;
+ Classes.Remove("status-info");
+ Classes.Remove("status-success");
+ Classes.Remove("status-warning");
+ Classes.Remove("status-error");
+ Classes.Add(Severity switch
+ {
+ StatusBadgeSeverity.Success => "status-success",
+ StatusBadgeSeverity.Warning => "status-warning",
+ StatusBadgeSeverity.Error => "status-error",
+ _ => "status-info",
+ });
+
+ _glyph.RenderTransform = null;
+ _glyph.Data = Severity switch
+ {
+ StatusBadgeSeverity.Success => Geometry.Parse("M0.5,2.6 L2,4.1 L4.5,0.9"),
+ StatusBadgeSeverity.Warning => Geometry.Parse("M2.5,0.4 L2.5,2.1 M2.1,4.5 L2.9,4.5"),
+ StatusBadgeSeverity.Error => Geometry.Parse("M0.75,0.75 L4.25,4.25 M4.25,0.75 L0.75,4.25"),
+ _ => Geometry.Parse("M2.5,2 L2.5,4.4 M2.5,0.6 L2.5,0.7"),
+ };
+ if (Severity == StatusBadgeSeverity.Success)
+ _glyph.RenderTransform = new TranslateTransform(0, 0.5);
+ }
+}
diff --git a/src/UniGetUI.Avalonia/Views/Controls/UserAvatarControl.axaml b/src/UniGetUI.Avalonia/Views/Controls/UserAvatarControl.axaml
index ec2b11859c..f074d1c63a 100644
--- a/src/UniGetUI.Avalonia/Views/Controls/UserAvatarControl.axaml
+++ b/src/UniGetUI.Avalonia/Views/Controls/UserAvatarControl.axaml
@@ -42,7 +42,7 @@
Padding="0"
Background="Transparent"
BorderThickness="0"
- Foreground="{DynamicResource SystemAccentColor}"
+ Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
FontSize="11"
Cursor="Hand"
automation:AutomationProperties.Name="{t:Translate More details}"/>
@@ -77,7 +77,7 @@
Padding="0"
Background="Transparent"
BorderThickness="0"
- Foreground="{DynamicResource SystemAccentColor}"
+ Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
FontSize="11"
Cursor="Hand"
automation:AutomationProperties.Name="{t:Translate More details}"/>
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
index 740c25cdd0..c1efef248d 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml
@@ -196,7 +196,7 @@
Classes="accent action-chevron"
automation:AutomationProperties.Name="{Binding MainActionLabel}">
CoreTools.Launch("https://github.com/Devolutions/UniGetUI/blob/main/TRANSLATION.md");
diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs
index 4719bfe73c..333cafae62 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/ManagersHomepage.axaml.cs
@@ -1,16 +1,14 @@
using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
-using Avalonia.Controls.Shapes;
using Avalonia.Layout;
-using Avalonia.Media;
using UniGetUI.Avalonia.Infrastructure;
using UniGetUI.Avalonia.ViewModels.Pages.SettingsPages;
+using UniGetUI.Avalonia.Views.Controls;
using UniGetUI.Avalonia.Views.Controls.Settings;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine;
using UniGetUI.PackageEngine.Interfaces;
-using AvaloniaPath = Avalonia.Controls.Shapes.Path;
using CoreSettings = UniGetUI.Core.SettingsEngine.Settings;
namespace UniGetUI.Avalonia.Views.Pages.SettingsPages;
@@ -24,8 +22,7 @@ public event EventHandler? RestartRequired { add { } remove { } }
public event EventHandler? NavigationRequested { add { } remove { } }
public event EventHandler? ManagerNavigationRequested;
- private readonly List<(ToggleSwitch Toggle, IPackageManager Manager, Border Badge,
- Ellipse BadgeIcon, AvaloniaPath BadgeGlyph, TextBlock BadgeText)> _rows = [];
+ private readonly List<(ToggleSwitch Toggle, IPackageManager Manager, StatusBadge Badge)> _rows = [];
private bool _isLoadingToggles;
public ManagersHomepage()
@@ -47,53 +44,7 @@ public ManagersHomepage()
var thickness = isFirst ? new Thickness(1) : new Thickness(1, 0, 1, 1);
// ── Status badge (decorative — status surfaced via toggle HelpText) ─
- var badgeText = new TextBlock
- {
- FontSize = 12,
- FontWeight = FontWeight.SemiBold,
- VerticalAlignment = VerticalAlignment.Center,
- };
- AutomationProperties.SetAccessibilityView(badgeText, AccessibilityView.Raw);
-
- var badgeIcon = new Ellipse
- {
- Width = 12,
- Height = 12,
- };
- var badgeGlyph = new AvaloniaPath
- {
- Width = 5,
- Height = 5,
- StrokeThickness = 1.5,
- StrokeLineCap = PenLineCap.Round,
- StrokeJoin = PenLineJoin.Round,
- Stretch = Stretch.None,
- HorizontalAlignment = HorizontalAlignment.Center,
- VerticalAlignment = VerticalAlignment.Center,
- };
- var iconHost = new Grid
- {
- Width = 12,
- Height = 12,
- VerticalAlignment = VerticalAlignment.Center,
- Children = { badgeIcon, badgeGlyph },
- };
- AutomationProperties.SetAccessibilityView(iconHost, AccessibilityView.Raw);
-
- var badgeContent = new StackPanel
- {
- Orientation = Orientation.Horizontal,
- Spacing = 5,
- VerticalAlignment = VerticalAlignment.Center,
- Children = { iconHost, badgeText },
- };
- var badge = new Border
- {
- CornerRadius = new CornerRadius(4),
- Padding = new Thickness(6, 3, 6, 3),
- HorizontalAlignment = HorizontalAlignment.Center,
- Child = badgeContent,
- };
+ var badge = new StatusBadge { HorizontalAlignment = HorizontalAlignment.Center };
AutomationProperties.SetAccessibilityView(badge, AccessibilityView.Raw);
// ── Enable/disable toggle ────────────────────────────────────────
@@ -110,14 +61,14 @@ public ManagersHomepage()
_isLoadingToggles = true;
toggle.IsChecked = manager.IsEnabled();
_isLoadingToggles = false;
- ApplyStatusBadge(manager, toggle, badge, badgeIcon, badgeGlyph, badgeText);
+ ApplyStatusBadge(manager, toggle, badge);
};
toggle.IsCheckedChanged += async (_, _) =>
{
if (_isLoadingToggles) return;
CoreSettings.SetDictionaryItem(CoreSettings.K.DisabledManagers, manager.Name, toggle.IsChecked != true);
await Task.Run(manager.Initialize);
- ApplyStatusBadge(manager, toggle, badge, badgeIcon, badgeGlyph, badgeText);
+ ApplyStatusBadge(manager, toggle, badge);
AccessibilityAnnouncementService.AnnounceToggle(manager.DisplayName, toggle.IsChecked == true);
};
@@ -146,7 +97,7 @@ public ManagersHomepage()
btn.Click += (_, _) => ManagerNavigationRequested?.Invoke(this, capturedManager);
ManagersPanel.Children.Add(btn);
- _rows.Add((toggle, manager, badge, badgeIcon, badgeGlyph, badgeText));
+ _rows.Add((toggle, manager, badge));
}
}
@@ -154,61 +105,39 @@ public ManagersHomepage()
public void RefreshToggles()
{
_isLoadingToggles = true;
- foreach (var (toggle, manager, badge, badgeIcon, badgeGlyph, badgeText) in _rows)
+ foreach (var (toggle, manager, badge) in _rows)
{
toggle.IsChecked = manager.IsEnabled();
- ApplyStatusBadge(manager, toggle, badge, badgeIcon, badgeGlyph, badgeText);
+ ApplyStatusBadge(manager, toggle, badge);
}
_isLoadingToggles = false;
}
- private void ApplyStatusBadge(
+ private static void ApplyStatusBadge(
IPackageManager manager,
ToggleSwitch toggle,
- Border badge,
- Ellipse icon,
- AvaloniaPath glyph,
- TextBlock text)
+ StatusBadge badge)
{
- string bgKey, fgKey, label;
- glyph.RenderTransform = null;
+ string label;
if (!manager.IsEnabled())
{
- bgKey = "WarningBannerBackground";
- fgKey = "StatusWarningForeground";
+ badge.Severity = StatusBadgeSeverity.Warning;
label = CoreTools.Translate("Disabled");
- glyph.Data = Geometry.Parse("M2.5,0.4 L2.5,2.1 M2.1,4.5 L2.9,4.5");
}
else if (manager.Status.Found)
{
- bgKey = "StatusSuccessBackground";
- fgKey = "StatusSuccessForeground";
+ badge.Severity = StatusBadgeSeverity.Success;
label = CoreTools.Translate("Ready");
- glyph.Data = Geometry.Parse("M0.5,2.6 L2,4.1 L4.5,0.9");
- glyph.RenderTransform = new TranslateTransform(0, 0.5);
}
else
{
- bgKey = "StatusErrorBackground";
- fgKey = "StatusErrorForeground";
+ badge.Severity = StatusBadgeSeverity.Error;
label = CoreTools.Translate("Not found");
- glyph.Data = Geometry.Parse("M0.75,0.75 L4.25,4.25 M4.25,0.75 L0.75,4.25");
}
- IBrush background = LookupBrush(bgKey);
- badge.Background = background;
- icon.Fill = LookupBrush(fgKey);
- glyph.Stroke = background;
- text.Foreground = LookupBrush("TextFillColorPrimaryBrush");
- text.Text = label;
+ badge.Text = label;
// Bake state into Name so VoiceOver always announces it on macOS
AutomationProperties.SetName(toggle, $"{manager.DisplayName}, {label}");
AutomationProperties.SetItemStatus(toggle, label);
}
- private IBrush LookupBrush(string key)
- {
- if (this.TryFindResource(key, ActualThemeVariant, out var res) && res is IBrush brush)
- return brush;
- return Brushes.Transparent;
- }
}
diff --git a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SourceManagerCard.axaml b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SourceManagerCard.axaml
index 4d6b84d79e..2e09283d88 100644
--- a/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SourceManagerCard.axaml
+++ b/src/UniGetUI.Avalonia/Views/Pages/SettingsPages/SourceManagerCard.axaml
@@ -64,7 +64,7 @@
MinHeight="32">
diff --git a/src/UniGetUI.Avalonia/Views/SidebarView.axaml b/src/UniGetUI.Avalonia/Views/SidebarView.axaml
index 7dc13927d9..d73f0ff5fb 100644
--- a/src/UniGetUI.Avalonia/Views/SidebarView.axaml
+++ b/src/UniGetUI.Avalonia/Views/SidebarView.axaml
@@ -66,7 +66,7 @@
IsVisible="{Binding $parent[local:SidebarView].ShowLabels}"/>
-
From 5e365248d4d87698656e516863f281109ea09bb2 Mon Sep 17 00:00:00 2001
From: Meteoni-San <141850520+Meteony@users.noreply.github.com>
Date: Sat, 1 Aug 2026 05:26:48 +0000
Subject: [PATCH 02/12] Unify immersive confirmation dialogs
---
.../Infrastructure/ConfirmationDialog.cs | 57 +++----------
.../UninstallConfirmationDialog.cs | 84 +++----------------
.../DiscardBundleChangesDialog.axaml | 39 ---------
.../DiscardBundleChangesDialog.axaml.cs | 27 +++---
.../ImmersiveConfirmationDialog.axaml | 52 ++++++++++++
.../ImmersiveConfirmationDialog.axaml.cs | 69 +++++++++++++++
.../Views/DialogPages/ImmersiveDialog.cs | 2 +
.../DialogPages/ResetSettingsDialog.axaml | 39 ---------
.../DialogPages/ResetSettingsDialog.axaml.cs | 28 ++++---
.../Views/DialogPages/TelemetryDialog.axaml | 49 -----------
.../DialogPages/TelemetryDialog.axaml.cs | 58 +++++++++----
src/UniGetUI.Avalonia/Views/MainWindow.axaml | 16 ++--
.../Views/MainWindow.axaml.cs | 1 +
13 files changed, 231 insertions(+), 290 deletions(-)
delete mode 100644 src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml
create mode 100644 src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml
create mode 100644 src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml.cs
delete mode 100644 src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml
delete mode 100644 src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml
diff --git a/src/UniGetUI.Avalonia/Infrastructure/ConfirmationDialog.cs b/src/UniGetUI.Avalonia/Infrastructure/ConfirmationDialog.cs
index fee7e88bf3..ef72d861d8 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/ConfirmationDialog.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/ConfirmationDialog.cs
@@ -1,8 +1,7 @@
-using Avalonia;
using Avalonia.Controls;
-using Avalonia.Layout;
using Avalonia.Media;
using UniGetUI.Avalonia.Views;
+using UniGetUI.Avalonia.Views.DialogPages;
using UniGetUI.Core.Tools;
namespace UniGetUI.Avalonia.Infrastructure;
@@ -13,52 +12,22 @@ internal static class ConfirmationDialog
public static async Task ShowAsync(string message)
{
if (MainWindow.Instance is not { } owner)
- return true;
+ return false;
- bool confirmed = false;
- var dialog = new Window
+ var messageBlock = new TextBlock
{
- Width = 460,
- SizeToContent = SizeToContent.Height,
- MinHeight = 170,
- CanResize = false,
- ShowInTaskbar = false,
- WindowStartupLocation = WindowStartupLocation.CenterOwner,
- Title = CoreTools.Translate("Are you sure?"),
- };
-
- var noButton = new Button { Content = CoreTools.Translate("No"), MinWidth = 100 };
- noButton.Click += (_, _) => dialog.Close();
-
- var yesButton = new Button { Content = CoreTools.Translate("Yes"), MinWidth = 100 };
- yesButton.Classes.Add("accent");
- yesButton.Click += (_, _) => { confirmed = true; dialog.Close(); };
-
- dialog.Content = new StackPanel
- {
- Margin = new Thickness(20),
- Spacing = 16,
- Children =
- {
- new TextBlock
- {
- Text = CoreTools.Translate("Are you sure?"),
- FontSize = 18,
- FontWeight = FontWeight.SemiBold,
- TextWrapping = TextWrapping.Wrap,
- },
- new TextBlock { Text = message, Opacity = 0.82, TextWrapping = TextWrapping.Wrap },
- new StackPanel
- {
- Orientation = Orientation.Horizontal,
- Spacing = 8,
- HorizontalAlignment = HorizontalAlignment.Right,
- Children = { noButton, yesButton },
- },
- },
+ Text = message,
+ FontSize = 14,
+ LineHeight = 20,
+ TextWrapping = TextWrapping.Wrap,
};
+ var dialog = new ImmersiveConfirmationDialog(
+ CoreTools.Translate("Are you sure?"),
+ messageBlock,
+ CoreTools.Translate("Yes"),
+ CoreTools.Translate("No"));
await dialog.ShowDialog(owner);
- return confirmed;
+ return dialog.Result is true;
}
}
diff --git a/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs b/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs
index 47d89a17ea..2afa9d7a77 100644
--- a/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs
+++ b/src/UniGetUI.Avalonia/Infrastructure/UninstallConfirmationDialog.cs
@@ -1,9 +1,8 @@
-using Avalonia;
using Avalonia.Controls;
-using Avalonia.Layout;
using Avalonia.Media;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Interfaces;
+using UniGetUI.Avalonia.Views.DialogPages;
namespace UniGetUI.Avalonia.Infrastructure;
@@ -21,22 +20,6 @@ public static async Task ConfirmAsync(Window owner, IReadOnlyList ConfirmAsync(Window owner, IReadOnlyList 1)
{
@@ -87,45 +53,19 @@ public static async Task ConfirmAsync(Window owner, IReadOnlyList dialog.Close();
-
- var yesButton = new Button
- {
- Content = CoreTools.Translate("Yes"),
- MinWidth = 100,
- };
- yesButton.Classes.Add("accent");
- yesButton.Click += (_, _) =>
- {
- isConfirmed = true;
- dialog.Close();
- };
-
- var footerPanel = new StackPanel
+ var dialog = new ImmersiveConfirmationDialog(
+ CoreTools.Translate("Are you sure?"),
+ body,
+ CoreTools.Translate("Yes"),
+ CoreTools.Translate("No"))
{
- Orientation = Orientation.Horizontal,
- Spacing = 8,
- HorizontalAlignment = HorizontalAlignment.Right,
- Children =
- {
- noButton,
- yesButton,
- },
+ MaxWidth = packages.Count == 1 ? 520 : 560,
+ MaxHeight = packages.Count == 1 ? 220 : 380,
};
- Grid.SetRow(footerPanel, 2);
- root.Children.Add(footerPanel);
-
- dialog.Content = root;
await dialog.ShowDialog(owner);
- return isConfirmed;
+ return dialog.Result is true;
}
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml
deleted file mode 100644
index 8648171cf3..0000000000
--- a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs
index 1f1e1cc868..988939da9d 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/DiscardBundleChangesDialog.axaml.cs
@@ -1,22 +1,25 @@
using Avalonia.Controls;
-using Avalonia.Threading;
+using UniGetUI.Core.Tools;
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class DiscardBundleChangesDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
+public sealed class DiscardBundleChangesDialog : ImmersiveConfirmationDialog
{
- public bool Confirmed { get; private set; }
+ public bool Confirmed => Result is true;
public DiscardBundleChangesDialog()
{
- InitializeComponent();
- CancelButton.Click += (_, _) => Close();
- DiscardButton.Click += (_, _) => { Confirmed = true; Close(); };
- }
-
- protected override void OnOpened(EventArgs e)
- {
- base.OnOpened(e);
- Dispatcher.UIThread.Post(() => DiscardButton.Focus(), DispatcherPriority.Background);
+ Configure(
+ CoreTools.Translate("Unsaved changes"),
+ new TextBlock
+ {
+ Text = CoreTools.Translate("You have unsaved changes in the current bundle. Do you want to discard them?"),
+ TextWrapping = global::Avalonia.Media.TextWrapping.Wrap,
+ Opacity = 0.85,
+ },
+ CoreTools.Translate("Discard changes"),
+ CoreTools.Translate("Cancel"));
+ MaxWidth = 460;
+ MinHeight = 160;
}
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml
new file mode 100644
index 0000000000..326d7ce93d
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml.cs
new file mode 100644
index 0000000000..5e3cd0f0dc
--- /dev/null
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveConfirmationDialog.axaml.cs
@@ -0,0 +1,69 @@
+using Avalonia.Automation;
+using Avalonia.Controls;
+using Avalonia.Threading;
+
+namespace UniGetUI.Avalonia.Views.DialogPages;
+
+///
+/// Shared WinUI-style immersive confirmation surface. Dialog-specific classes supply only
+/// their body and labels; title spacing, footer hierarchy, actions, focus, and result handling
+/// remain consistent across the application.
+///
+public partial class ImmersiveConfirmationDialog : ImmersiveDialog
+{
+ public bool? Result { get; private set; }
+ public bool RequireChoice { get; set; }
+ public bool FocusPrimaryButton { get; set; } = true;
+
+ public ImmersiveConfirmationDialog()
+ {
+ InitializeComponent();
+ PrimaryButton.Click += (_, _) => Complete(true);
+ SecondaryButton.Click += (_, _) => Complete(false);
+ Closing += (_, e) =>
+ {
+ if (RequireChoice && Result is null)
+ e.Cancel = true;
+ };
+ }
+
+ public ImmersiveConfirmationDialog(
+ string title,
+ object body,
+ string primaryText,
+ string secondaryText) : this()
+ {
+ Configure(title, body, primaryText, secondaryText);
+ }
+
+ protected void Configure(
+ string title,
+ object body,
+ string primaryText,
+ string secondaryText)
+ {
+ Title = title;
+ BodyPresenter.Content = body;
+ PrimaryButton.Content = primaryText;
+ SecondaryButton.Content = secondaryText;
+ AutomationProperties.SetName(PrimaryButton, primaryText);
+ AutomationProperties.SetName(SecondaryButton, secondaryText);
+ }
+
+ protected override void OnOpened(EventArgs e)
+ {
+ // A dialog instance may be shown again. Never let a previous choice leak into a
+ // later presentation (especially when RequireChoice governs close behavior).
+ Result = null;
+ base.OnOpened(e);
+ Dispatcher.UIThread.Post(
+ () => (FocusPrimaryButton ? PrimaryButton : SecondaryButton).Focus(),
+ DispatcherPriority.Background);
+ }
+
+ private void Complete(bool result)
+ {
+ Result = result;
+ Close();
+ }
+}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs
index c18adf3df1..b9e00c6166 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ImmersiveDialog.cs
@@ -20,6 +20,8 @@ public class ImmersiveDialog : ContentControl
internal event EventHandler? CloseRequested;
public event EventHandler? Closing;
+ public Thickness TitleMargin { get; set; } = new(20, 0, 0, 0);
+
public string? Title
{
get => GetValue(TitleProperty);
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml
deleted file mode 100644
index 16b5baae40..0000000000
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs
index 1d38565837..dafc25ce91 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/ResetSettingsDialog.axaml.cs
@@ -1,22 +1,26 @@
using Avalonia.Controls;
-using Avalonia.Threading;
+using UniGetUI.Core.Tools;
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class ResetSettingsDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
+public sealed class ResetSettingsDialog : ImmersiveConfirmationDialog
{
- public bool Confirmed { get; private set; }
+ public bool Confirmed => Result is true;
public ResetSettingsDialog()
{
- InitializeComponent();
- CancelButton.Click += (_, _) => Close();
- ResetButton.Click += (_, _) => { Confirmed = true; Close(); };
- }
-
- protected override void OnOpened(EventArgs e)
- {
- base.OnOpened(e);
- Dispatcher.UIThread.Post(() => CancelButton.Focus(), DispatcherPriority.Background);
+ Configure(
+ CoreTools.Translate("Reset UniGetUI"),
+ new TextBlock
+ {
+ Text = CoreTools.Translate("Do you really want to reset UniGetUI to its default settings? This action cannot be undone."),
+ TextWrapping = global::Avalonia.Media.TextWrapping.Wrap,
+ Opacity = 0.85,
+ },
+ CoreTools.Translate("Reset UniGetUI"),
+ CoreTools.Translate("Cancel"));
+ MaxWidth = 460;
+ MinHeight = 160;
+ FocusPrimaryButton = false;
}
}
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml
deleted file mode 100644
index 9a85f9369b..0000000000
--- a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs
index c30a2f656e..f186a71fa8 100644
--- a/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs
+++ b/src/UniGetUI.Avalonia/Views/DialogPages/TelemetryDialog.axaml.cs
@@ -1,35 +1,57 @@
+using Avalonia.Automation;
+using Avalonia.Automation.Peers;
using Avalonia.Controls;
-using Avalonia.Threading;
+using Avalonia.Input;
using UniGetUI.Core.Tools;
namespace UniGetUI.Avalonia.Views.DialogPages;
-public partial class TelemetryDialog : UniGetUI.Avalonia.Views.DialogPages.ImmersiveDialog
+public sealed class TelemetryDialog : ImmersiveConfirmationDialog
{
- public bool? Result { get; private set; }
-
public TelemetryDialog()
{
- InitializeComponent();
-
- Body2.Text = CoreTools.Translate("No personal information is collected nor sent, and the collected data is anonimized, so it can't be back-tracked to you.");
-
- DetailsLink.Bind(TextBlock.ForegroundProperty,
- DetailsLink.GetResourceObservable("SystemControlHighlightAccentBrush"));
- DetailsLink.PointerPressed += (_, e) =>
+ var detailsLink = new TextBlock
+ {
+ Text = CoreTools.Translate("More details about the shared data and how it will be processed"),
+ TextDecorations = global::Avalonia.Media.TextDecorations.Underline,
+ Cursor = new Cursor(StandardCursorType.Hand),
+ Opacity = 0.9,
+ };
+ AutomationProperties.SetName(detailsLink, detailsLink.Text);
+ AutomationProperties.SetControlTypeOverride(detailsLink, AutomationControlType.Hyperlink);
+ detailsLink.Bind(TextBlock.ForegroundProperty,
+ detailsLink.GetResourceObservable("AccentTextFillColorPrimaryBrush"));
+ detailsLink.PointerPressed += (_, e) =>
{
if (e.GetCurrentPoint(null).Properties.IsLeftButtonPressed)
CoreTools.Launch("https://devolutions.net/legal/");
};
- Closing += (_, e) => { if (Result is null) e.Cancel = true; };
- DeclineButton.Click += (_, _) => { Result = false; Close(); };
- AcceptButton.Click += (_, _) => { Result = true; Close(); };
+ var body = new StackPanel { Spacing = 8 };
+ body.Children.Add(BodyText(CoreTools.Translate(
+ "UniGetUI collects anonymous usage data with the sole purpose of understanding and improving the user experience.")));
+ body.Children.Add(BodyText(CoreTools.Translate(
+ "No personal information is collected nor sent, and the collected data is anonimized, so it can't be back-tracked to you.")));
+ body.Children.Add(detailsLink);
+ body.Children.Add(BodyText(CoreTools.Translate(
+ "Do you accept that UniGetUI collects and sends anonymous usage statistics, with the sole purpose of understanding and improving the user experience?")));
+
+ Configure(
+ CoreTools.Translate("Share anonymous usage data"),
+ body,
+ CoreTools.Translate("Accept"),
+ CoreTools.Translate("Decline"));
+ MaxWidth = 548;
+ MinWidth = 420;
+ MinHeight = 250;
+ IsCloseButtonVisible = false;
+ RequireChoice = true;
}
- protected override void OnOpened(EventArgs e)
+ private static TextBlock BodyText(string text) => new()
{
- base.OnOpened(e);
- Dispatcher.UIThread.Post(() => AcceptButton.Focus(), DispatcherPriority.Background);
- }
+ Text = text,
+ TextWrapping = global::Avalonia.Media.TextWrapping.Wrap,
+ Opacity = 0.85,
+ };
}
diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
index 231075a32c..7689e43e36 100644
--- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml
+++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml
@@ -40,7 +40,7 @@
+
+
+
+
+ 0.5
@@ -187,6 +189,8 @@
+
+ 0
@@ -275,8 +279,8 @@