diff --git a/src/UniGetUI.Avalonia/Models/PackageCollections.cs b/src/UniGetUI.Avalonia/Models/PackageCollections.cs index b2220f5d61..f7449739bc 100644 --- a/src/UniGetUI.Avalonia/Models/PackageCollections.cs +++ b/src/UniGetUI.Avalonia/Models/PackageCollections.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.ObjectModel; using System.ComponentModel; using System.Net.Http; @@ -489,19 +490,34 @@ public void ClearSelection() public void SetSortDirection(bool ascending) => _ascending = ascending; /// Returns in the current sort order. - public IEnumerable ApplyToList(IEnumerable items) => - _ascending - ? items.OrderBy(GetSortKey, StringComparer.OrdinalIgnoreCase) - : items.OrderByDescending(GetSortKey, StringComparer.OrdinalIgnoreCase); + public IEnumerable ApplyToList(IEnumerable items) + { + var comparer = Comparer.Create((a, b) => Compare(a, b, CurrentSorter)); + return _ascending ? items.OrderBy(w => w, comparer) : items.OrderByDescending(w => w, comparer); + } + + // Shared by the "Order by" menu (ApplyToList) and the DataGrid's native column sorting + // (GetColumnComparer) so both order identically. Versions compare semantically via + // CoreTools.Version; a string key can't be used there because that struct has no ToString + // override, so its key would be a constant type name that never sorts. + private static int Compare(PackageWrapper a, PackageWrapper b, Sorter sorter) => sorter switch + { + Sorter.Version => a.Package.NormalizedVersion.CompareTo(b.Package.NormalizedVersion), + Sorter.NewVersion => a.Package.NormalizedNewVersion.CompareTo(b.Package.NormalizedNewVersion), + _ => string.Compare(GetSortKey(a, sorter), GetSortKey(b, sorter), StringComparison.OrdinalIgnoreCase), + }; - private string GetSortKey(PackageWrapper w) => CurrentSorter switch + private static string GetSortKey(PackageWrapper w, Sorter sorter) => sorter switch { Sorter.Checked => w.IsChecked ? "0" : "1", Sorter.Name => w.Package.Name, Sorter.Id => w.Package.Id, - Sorter.Version => w.Package.NormalizedVersion.ToString() ?? string.Empty, - Sorter.NewVersion => w.Package.NormalizedNewVersion.ToString() ?? string.Empty, Sorter.Source => w.Package.Source.AsString_DisplayName, _ => w.Package.Name, }; + + // Comparer for the DataGrid's native column sorting. Reflection-free (unlike SortMemberPath), + // so it keeps working under full-trim NativeAOT release builds — see issue #5103. + public static IComparer GetColumnComparer(Sorter sorter) => + Comparer.Create((a, b) => Compare(a, b, sorter)); } diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs index 538d0e7737..a55aced453 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs @@ -84,6 +84,26 @@ or nameof(PackagesPageViewModel.SortAscending)) // Double-click a list row → show details PackageList.DoubleTapped += (_, _) => _ = ShowDetailsForPackage(SelectedItem); + // Native DataGrid column sorting. The default SortMemberPath path resolves the property by + // reflection, which full-trim NativeAOT release builds strip away — so CanUserSort reports + // false and header clicks are silently dropped (issue #5103). A strongly-typed + // CustomSortComparer plus an explicit CanUserSort keeps Avalonia's built-in sort AOT-safe. + foreach (var col in PackageList.Columns) + { + ObservablePackageCollection.Sorter? sorter = (col.Tag as string) switch + { + "Name" => ObservablePackageCollection.Sorter.Name, + "Id" => ObservablePackageCollection.Sorter.Id, + "Version" => ObservablePackageCollection.Sorter.Version, + "NewVersion" => ObservablePackageCollection.Sorter.NewVersion, + "Source" => ObservablePackageCollection.Sorter.Source, + _ => null, + }; + if (sorter is null) continue; + col.CanUserSort = true; + col.CustomSortComparer = ObservablePackageCollection.GetColumnComparer(sorter.Value); + } + // Keyboard shortcuts on the package list. Handled on the tunnel route (and even when // already handled) because the DataGrid swallows Enter on the bubble route otherwise. PackageList.AddHandler(KeyDownEvent, PackageList_KeyDown, RoutingStrategies.Tunnel, handledEventsToo: true);