From b367cdbb0c4dc2d83cbe931f4ec132d3e2e0ecb2 Mon Sep 17 00:00:00 2001 From: David Brett Date: Wed, 8 Apr 2026 16:45:16 +0200 Subject: [PATCH 1/2] PluginStoreSorting: Add dropdown for sorting options - Added new row in header - Added dropdown menu to that new row - Added localized options: "Name", "Release Date," "Updated Date" as well as a localized label "Sorting Mode:" No functionality added, only ui changes. --- Flow.Launcher/Languages/en.xaml | 5 + .../SettingsPanePluginStoreViewModel.cs | 38 +++++ .../Views/SettingsPanePluginStore.xaml | 157 ++++++++++-------- 3 files changed, 135 insertions(+), 65 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 02c7becacca..62b32b2c136 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -242,6 +242,11 @@ Recently Updated Plugins Installed + Default + Name + Release Date + Updated Date + Sorting Mode: Refresh Install Uninstall diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index 752d6890338..56269357e72 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -12,6 +12,30 @@ namespace Flow.Launcher.SettingPages.ViewModels; public partial class SettingsPanePluginStoreViewModel : BaseModel { + public class SortModeData : DropdownDataGeneric { } + + public List SortModes { get; } = + DropdownDataGeneric.GetValues("PluginStoreSortMode"); + + public SettingsPanePluginStoreViewModel() + { + UpdateEnumDropdownLocalizations(); + } + + private PluginStoreSortMode _selectedSortMode = PluginStoreSortMode.Default; + public PluginStoreSortMode SelectedSortMode + { + get => _selectedSortMode; + set + { + if (_selectedSortMode != value) + { + _selectedSortMode = value; + OnPropertyChanged(); + } + } + } + private string filterText = string.Empty; public string FilterText { @@ -168,4 +192,18 @@ public bool SatisfiesFilter(PluginStoreItemViewModel plugin) App.API.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() || App.API.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet(); } + + private void UpdateEnumDropdownLocalizations() + { + DropdownDataGeneric.UpdateLabels(SortModes); + } + +} + +public enum PluginStoreSortMode +{ + Default, + Name, + ReleaseDate, + UpdatedDate } diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml index 4d37dc93acc..174f20b17f4 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml @@ -32,7 +32,7 @@ - + - + Margin="5 24 0 8"> - - - - + + + + + + + + Margin="0 0 26 0" + Orientation="Horizontal" + Spacing="8"> + + + From c6da34b487971e44eee3e79fe940f500676e1d34 Mon Sep 17 00:00:00 2001 From: David Brett Date: Wed, 8 Apr 2026 17:54:18 +0200 Subject: [PATCH 2/2] PluginStoreSorting: Add sorting functionality - Add GetSortedPlugins to handle different sorts based on the SelectedSortMode - Show only "None"/"Plugins" & "Installed" categories for sort modes other than default --- .../SettingsPanePluginStoreViewModel.cs | 40 +++++++++++++++---- .../Views/SettingsPanePluginStore.xaml | 2 +- .../Views/SettingsPanePluginStore.xaml.cs | 28 +++++++++++++ .../ViewModel/PluginStoreItemViewModel.cs | 6 ++- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index 56269357e72..a85c06a1eb1 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -32,6 +32,7 @@ public PluginStoreSortMode SelectedSortMode { _selectedSortMode = value; OnPropertyChanged(); + OnPropertyChanged(nameof(ExternalPlugins)); } } } @@ -106,13 +107,8 @@ public bool ShowExecutable } } - public IList ExternalPlugins => App.API.GetPluginManifest() - .Select(p => new PluginStoreItemViewModel(p)) - .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed) - .ToList(); + public IList ExternalPlugins => GetSortedPlugins( + App.API.GetPluginManifest().Select(p => new PluginStoreItemViewModel(p))); [RelayCommand] private async Task RefreshExternalPluginsAsync() @@ -198,6 +194,36 @@ private void UpdateEnumDropdownLocalizations() DropdownDataGeneric.UpdateLabels(SortModes); } + private IList GetSortedPlugins(IEnumerable plugins) + { + return SelectedSortMode switch + { + PluginStoreSortMode.Name => plugins + .OrderBy(p => p.LabelInstalled) + .ThenBy(p => p.Name) + .ToList(), + + PluginStoreSortMode.ReleaseDate => plugins + .OrderBy(p => p.LabelInstalled) + .ThenByDescending(p => p.DateAdded.HasValue) + .ThenByDescending(p => p.DateAdded) + .ToList(), + + PluginStoreSortMode.UpdatedDate => plugins + .OrderBy(p => p.LabelInstalled) + .ThenByDescending(p => p.UpdatedDate.HasValue) + .ThenByDescending(p => p.UpdatedDate) + .ToList(), + + _ => plugins + .OrderByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.NewRelease) + .ThenByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.RecentlyUpdated) + .ThenByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.None) + .ThenByDescending(p => p.DefaultCategory == PluginStoreItemViewModel.Installed) + .ToList(), + }; + } + } public enum PluginStoreSortMode diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml index 174f20b17f4..d729cc72008 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml @@ -22,7 +22,7 @@ Filter="PluginStoreCollectionView_OnFilter" Source="{Binding ExternalPlugins}"> - + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs index c0a77957ac8..686f22ef333 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs @@ -29,12 +29,20 @@ protected override void OnNavigatedTo(NavigationEventArgs e) { InitializeComponent(); } + UpdateCategoryGrouping(); _viewModel.PropertyChanged += ViewModel_PropertyChanged; base.OnNavigatedTo(e); } private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { + // If SelectedSortMode changed, then we need to update the categories + if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.SelectedSortMode)) + { + UpdateCategoryGrouping(); + } + + // Check if changed property requires PluginStoreCollectionView refresh switch (e.PropertyName) { case nameof(SettingsPanePluginStoreViewModel.FilterText): @@ -42,6 +50,7 @@ private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e case nameof(SettingsPanePluginStoreViewModel.ShowPython): case nameof(SettingsPanePluginStoreViewModel.ShowNodeJs): case nameof(SettingsPanePluginStoreViewModel.ShowExecutable): + case nameof(SettingsPanePluginStoreViewModel.SelectedSortMode): ((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh(); break; } @@ -75,4 +84,23 @@ private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e e.Accepted = _viewModel.SatisfiesFilter(plugin); } + + private void UpdateCategoryGrouping() + { + var collectionView = (CollectionViewSource)FindResource("PluginStoreCollectionView"); + var groupDescriptions = collectionView.GroupDescriptions; + + groupDescriptions.Clear(); + + // For default sorting mode we use the default categories + if (_viewModel.SelectedSortMode == PluginStoreSortMode.Default) + { + groupDescriptions.Add(new PropertyGroupDescription(nameof(PluginStoreItemViewModel.DefaultCategory))); + } + + // Otherwise we only split by installed or not + else{ + groupDescriptions.Add(new PropertyGroupDescription(nameof(PluginStoreItemViewModel.InstallCategory))); + } + } } diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs index f5523212e13..177534b612e 100644 --- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs @@ -28,6 +28,8 @@ public PluginStoreItemViewModel(UserPlugin plugin) public string UrlDownload => _newPlugin.UrlDownload; public string UrlSourceCode => _newPlugin.UrlSourceCode; public string IcoPath => _newPlugin.IcoPath; + public DateTime? DateAdded => _newPlugin.DateAdded; + public DateTime? UpdatedDate => _newPlugin.LatestReleaseDate; public bool LabelInstalled => _oldPluginPair != null; public bool LabelUpdate => LabelInstalled && new Version(_newPlugin.Version) > new Version(_oldPluginPair.Metadata.Version); @@ -37,7 +39,9 @@ public PluginStoreItemViewModel(UserPlugin plugin) internal const string NewRelease = "NewRelease"; internal const string Installed = "Installed"; - public string Category + public string InstallCategory => LabelInstalled ? Installed : None; + + public string DefaultCategory { get {