From cfbc69a7d9dc476cbfb4d56e68e7f507d93bf40f Mon Sep 17 00:00:00 2001 From: Aryan Choudhary Date: Wed, 29 Apr 2026 12:49:30 +0530 Subject: [PATCH 1/7] Fix navigation bar accessibility for screen readers Added AutomationProperties.SetName to the CustomNavViewItem control to ensure screen readers correctly announce the localized names for navigation options like 'Software Updates' and 'Installed Packages'. --- src/UniGetUI/Controls/CustomNavViewItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UniGetUI/Controls/CustomNavViewItem.cs b/src/UniGetUI/Controls/CustomNavViewItem.cs index 42d2bc1f29..bca2630848 100644 --- a/src/UniGetUI/Controls/CustomNavViewItem.cs +++ b/src/UniGetUI/Controls/CustomNavViewItem.cs @@ -1,4 +1,5 @@ using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Automation; using Microsoft.UI.Xaml.Controls; using UniGetUI.Core.Tools; using UniGetUI.Interface; @@ -47,6 +48,7 @@ public string Text string text = CoreTools.Translate(value); _textBlock.Text = text; ToolTipService.SetToolTip(this, text); + AutomationProperties.SetName(this, text); } } From 0e8e55735920564a950d70e259ac80dca7e430bd Mon Sep 17 00:00:00 2001 From: Aryan Choudhary Date: Thu, 30 Apr 2026 21:33:13 +0530 Subject: [PATCH 2/7] Fix comprehensive screen reader accessibility issues This commit resolves several UX issues to significantly improve the keyboard and screen reader navigation flow: - Added AutomationProperties.Name to the loading screen grid, operation list resizers, and the ExpandCollapseOpList button. - Disabled IsTabStop on the custom TitleBar so it doesn't get focused unnecessarily. - Fixed accessibility for view mode and filter options by assigning them grouping roles. - Added descriptive labels to the ToggleFiltersButton and MainToolbarButtonDropdown ('More actions'). - Bound AutomationProperties.Name to the dynamic text of MainToolbarButton so it correctly reads 'Install selected packages', etc. - Added AutomationProperties.Name to the BackButton on the package preferences dialog. - Added AutomationProperties.Name to the search mode radio buttons. - Removed 'ghost' tab stops following the Help button by explicitly setting IsTabStop='False' and OverflowButtonVisibility='Collapsed' on the CommandBar and surrounding layout containers. - Fixed CheckboxHeader by setting IsTabStop='False' and AccessibilityView='Raw'. - Improved package list item accessibility by building a custom AutomationPeer for PackageItemContainer that implements IToggleProvider and acts natively as a CheckBox role, allowing direct selection and status announcement without an extra tab stop. - Added AutomationProperties.Name and ToolTip labels ('User profile') to the dynamic GitHub login/cloud backup avatar UserControl in the main title bar. --- src/UniGetUI/Controls/PackageItemContainer.cs | 77 ++++++++++++++++++- src/UniGetUI/Controls/PackageWrapper.cs | 3 + src/UniGetUI/MainWindow.xaml | 3 +- src/UniGetUI/Pages/MainView.xaml | 2 + .../GeneralPages/Updates.xaml.cs | 2 +- .../Pages/SettingsPages/SettingsBasePage.xaml | 1 + .../SoftwarePages/AbstractPackagesPage.xaml | 31 +++++++- src/UniGetUI/Services/UserAvatar.cs | 12 ++- 8 files changed, 122 insertions(+), 9 deletions(-) diff --git a/src/UniGetUI/Controls/PackageItemContainer.cs b/src/UniGetUI/Controls/PackageItemContainer.cs index 988d9a59b8..485cc662fc 100644 --- a/src/UniGetUI/Controls/PackageItemContainer.cs +++ b/src/UniGetUI/Controls/PackageItemContainer.cs @@ -1,3 +1,6 @@ +using Microsoft.UI.Xaml.Automation; +using Microsoft.UI.Xaml.Automation.Peers; +using Microsoft.UI.Xaml.Automation.Provider; using Microsoft.UI.Xaml.Controls; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.PackageClasses; @@ -7,6 +10,78 @@ namespace UniGetUI.Interface.Widgets public partial class PackageItemContainer : ItemContainer { public IPackage? Package { get; set; } - public PackageWrapper Wrapper { get; set; } = null!; + + private PackageWrapper _wrapper = null!; + public PackageWrapper Wrapper + { + get => _wrapper; + set + { + if (_wrapper != null) + { + _wrapper.PropertyChanged -= Wrapper_PropertyChanged; + } + _wrapper = value; + if (_wrapper != null) + { + _wrapper.PropertyChanged += Wrapper_PropertyChanged; + } + } + } + + private void Wrapper_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(PackageWrapper.IsChecked)) + { + var peer = FrameworkElementAutomationPeer.FromElement(this) as PackageItemContainerAutomationPeer; + if (peer != null) + { + ToggleState oldState = !Wrapper.IsChecked ? ToggleState.On : ToggleState.Off; + ToggleState newState = Wrapper.IsChecked ? ToggleState.On : ToggleState.Off; + peer.RaiseToggleStatePropertyChanged(oldState, newState); + } + } + } + + protected override AutomationPeer OnCreateAutomationPeer() + { + return new PackageItemContainerAutomationPeer(this); + } + } + + public partial class PackageItemContainerAutomationPeer : FrameworkElementAutomationPeer, IToggleProvider + { + private readonly PackageItemContainer _owner; + + public PackageItemContainerAutomationPeer(PackageItemContainer owner) : base(owner) + { + _owner = owner; + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.CheckBox; + } + + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.Toggle) + { + return this; + } + return base.GetPatternCore(patternInterface); + } + + public ToggleState ToggleState => (_owner.Wrapper != null && _owner.Wrapper.IsChecked) ? ToggleState.On : ToggleState.Off; + + public void Toggle() + { + _owner.Wrapper?.IsChecked = !_owner.Wrapper.IsChecked; + } + + public void RaiseToggleStatePropertyChanged(ToggleState oldValue, ToggleState newValue) + { + RaisePropertyChangedEvent(TogglePatternIdentifiers.ToggleStateProperty, oldValue, newValue); + } } } diff --git a/src/UniGetUI/Controls/PackageWrapper.cs b/src/UniGetUI/Controls/PackageWrapper.cs index aefd9c77e6..659bb579b1 100644 --- a/src/UniGetUI/Controls/PackageWrapper.cs +++ b/src/UniGetUI/Controls/PackageWrapper.cs @@ -31,10 +31,13 @@ public bool IsChecked { Package.IsChecked = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CheckedStatus))); _page.UpdatePackageCount(); } } + public string CheckedStatus => IsChecked ? CoreTools.Translate("Checked") : CoreTools.Translate("Unchecked"); + public bool IconWasLoaded; public bool AlternateIdIconVisible; public bool ShowCustomPackageIcon; diff --git a/src/UniGetUI/MainWindow.xaml b/src/UniGetUI/MainWindow.xaml index 27052a27a6..87af24f38a 100644 --- a/src/UniGetUI/MainWindow.xaml +++ b/src/UniGetUI/MainWindow.xaml @@ -36,6 +36,7 @@ PaneToggleRequested="TitleBar_PaneToggleRequested" SizeChanged="TitleBar_SizeChanged" Visibility="Collapsed" + IsTabStop="False" > @@ -285,6 +287,8 @@ HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{x:Bind IsChecked, Mode=TwoWay}" + IsTabStop="False" + AutomationProperties.AccessibilityView="Raw" />