From c94e10ff18fa41ef9101845c97b3e2d609b2a6fa Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:15:56 -0400 Subject: [PATCH 1/9] Filter button --- .../Actions/Show/ToggleFilterHeaderAction.cs | 40 +++++++++++++++++++ .../Data/Commands/Manager/CommandCodes.cs | 1 + .../Data/Commands/Manager/CommandManager.cs | 2 + .../Data/Commands/Manager/ICommandManager.cs | 1 + .../Data/Contracts/IGeneralSettingsService.cs | 5 +++ .../Settings/GeneralSettingsService.cs | 6 +++ src/Files.App/Strings/en-US/Resources.resw | 12 ++++++ src/Files.App/UserControls/Toolbar.xaml | 15 +++++++ src/Files.App/ViewModels/ShellViewModel.cs | 28 ++++++++++--- .../Views/Shells/ModernShellPage.xaml | 32 +++++++++++++++ .../Views/Shells/ModernShellPage.xaml.cs | 18 +++++++++ 11 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs diff --git a/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs b/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs new file mode 100644 index 000000000000..b94c89054db3 --- /dev/null +++ b/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs @@ -0,0 +1,40 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +namespace Files.App.Actions +{ + internal sealed partial class ToggleFilterHeaderAction : ObservableObject, IToggleAction + { + private readonly IGeneralSettingsService generalSettingsService = Ioc.Default.GetRequiredService(); + + public string Label + => Strings.ToggleFilterHeader.GetLocalizedResource(); + + public string Description + => Strings.ToggleFilterHeaderDescription.GetLocalizedResource(); + + public RichGlyph Glyph + => new(themedIconStyle: "App.ThemedIcons.Filter"); + + public bool IsOn + => generalSettingsService.ShowFilterHeader; + + public ToggleFilterHeaderAction() + { + generalSettingsService.PropertyChanged += GeneralSettingsService_PropertyChanged; + } + + public Task ExecuteAsync(object? parameter = null) + { + generalSettingsService.ShowFilterHeader = !IsOn; + + return Task.CompletedTask; + } + + private void GeneralSettingsService_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(GeneralSettingsService.ShowFilterHeader)) + OnPropertyChanged(nameof(IsOn)); + } + } +} diff --git a/src/Files.App/Data/Commands/Manager/CommandCodes.cs b/src/Files.App/Data/Commands/Manager/CommandCodes.cs index edaa553cba0e..3d89d1ecc476 100644 --- a/src/Files.App/Data/Commands/Manager/CommandCodes.cs +++ b/src/Files.App/Data/Commands/Manager/CommandCodes.cs @@ -27,6 +27,7 @@ public enum CommandCodes ToggleInfoPane, ToggleToolbar, ToggleShelfPane, + ToggleFilterHeader, // File System CopyItem, diff --git a/src/Files.App/Data/Commands/Manager/CommandManager.cs b/src/Files.App/Data/Commands/Manager/CommandManager.cs index a0aa75e55b31..29f8a9f25c1c 100644 --- a/src/Files.App/Data/Commands/Manager/CommandManager.cs +++ b/src/Files.App/Data/Commands/Manager/CommandManager.cs @@ -58,6 +58,7 @@ public IRichCommand this[HotKey hotKey] public IRichCommand ToggleInfoPane => commands[CommandCodes.ToggleInfoPane]; public IRichCommand ToggleToolbar => commands[CommandCodes.ToggleToolbar]; public IRichCommand ToggleShelfPane => commands[CommandCodes.ToggleShelfPane]; + public IRichCommand ToggleFilterHeader => commands[CommandCodes.ToggleFilterHeader]; public IRichCommand SelectAll => commands[CommandCodes.SelectAll]; public IRichCommand InvertSelection => commands[CommandCodes.InvertSelection]; public IRichCommand ClearSelection => commands[CommandCodes.ClearSelection]; @@ -264,6 +265,7 @@ public IEnumerator GetEnumerator() => [CommandCodes.ToggleInfoPane] = new ToggleInfoPaneAction(), [CommandCodes.ToggleToolbar] = new ToggleToolbarAction(), [CommandCodes.ToggleShelfPane] = new ToggleShelfPaneAction(), + [CommandCodes.ToggleFilterHeader] = new ToggleFilterHeaderAction(), [CommandCodes.SelectAll] = new SelectAllAction(), [CommandCodes.InvertSelection] = new InvertSelectionAction(), [CommandCodes.ClearSelection] = new ClearSelectionAction(), diff --git a/src/Files.App/Data/Commands/Manager/ICommandManager.cs b/src/Files.App/Data/Commands/Manager/ICommandManager.cs index d0ceb7699628..1a891037c5f7 100644 --- a/src/Files.App/Data/Commands/Manager/ICommandManager.cs +++ b/src/Files.App/Data/Commands/Manager/ICommandManager.cs @@ -32,6 +32,7 @@ public interface ICommandManager : IEnumerable IRichCommand ToggleInfoPane { get; } IRichCommand ToggleToolbar { get; } IRichCommand ToggleShelfPane { get; } + IRichCommand ToggleFilterHeader { get; } IRichCommand CopyItem { get; } IRichCommand CopyItemPath { get; } diff --git a/src/Files.App/Data/Contracts/IGeneralSettingsService.cs b/src/Files.App/Data/Contracts/IGeneralSettingsService.cs index b53fb24b756a..a5f193345b12 100644 --- a/src/Files.App/Data/Contracts/IGeneralSettingsService.cs +++ b/src/Files.App/Data/Contracts/IGeneralSettingsService.cs @@ -295,6 +295,11 @@ public interface IGeneralSettingsService : IBaseSettingsService, INotifyProperty /// bool ShowShelfPane { get; set; } + /// + /// Gets or sets a value whether the filter header should be displayed. + /// + bool ShowFilterHeader { get; set; } + /// /// Gets or sets a value indicating whether or not to enable the Omnibar. /// diff --git a/src/Files.App/Services/Settings/GeneralSettingsService.cs b/src/Files.App/Services/Settings/GeneralSettingsService.cs index 0cf219d65a5e..bc25ff1295c9 100644 --- a/src/Files.App/Services/Settings/GeneralSettingsService.cs +++ b/src/Files.App/Services/Settings/GeneralSettingsService.cs @@ -363,6 +363,12 @@ public bool ShowShelfPane set => Set(value); } + public bool ShowFilterHeader + { + get => Get(false); + set => Set(value); + } + public bool EnableOmnibar { get => Get(false); diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 9089acfcd316..d1eb58490662 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -1097,6 +1097,12 @@ Toggle visibility of the toolbar + + Toggle filter header + + + Toggle visibility of the filter header + No preview available @@ -4261,4 +4267,10 @@ See more + + Filtering for + + + Filename + diff --git a/src/Files.App/UserControls/Toolbar.xaml b/src/Files.App/UserControls/Toolbar.xaml index a167218e65e0..45181870f195 100644 --- a/src/Files.App/UserControls/Toolbar.xaml +++ b/src/Files.App/UserControls/Toolbar.xaml @@ -500,6 +500,21 @@ Grid.Column="1" DefaultLabelPosition="Right"> + + + + + SetProperty(ref _FolderBackgroundImageHorizontalAlignment, value); } + public bool ShowFilterHeader => + UserSettingsService.GeneralSettingsService.ShowFilterHeader && + WorkingDirectory != "Home" && + WorkingDirectory != "ReleaseNotes" && + WorkingDirectory != "Settings"; + private GitProperties _EnabledGitProperties; public GitProperties EnabledGitProperties { @@ -214,6 +220,7 @@ public async Task SetWorkingDirectoryAsync(string? value) IsValidGitDirectory = !string.IsNullOrEmpty((await GitHelpers.GetRepositoryHead(GitDirectory))?.Name); OnPropertyChanged(nameof(WorkingDirectory)); + OnPropertyChanged(nameof(ShowFilterHeader)); } public async Task> GetFolderFromPathAsync(string value, CancellationToken cancellationToken = default) @@ -676,6 +683,9 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => await OrderFilesAndFoldersAsync(); await ApplyFilesAndFoldersChangesAsync(); break; + case nameof(UserSettingsService.GeneralSettingsService.ShowFilterHeader): + OnPropertyChanged(nameof(ShowFilterHeader)); + break; } } @@ -715,7 +725,13 @@ public void UpdateEmptyTextType() EmptyTextType = FilesAndFolders.Count == 0 ? (IsSearchResults ? EmptyTextType.NoSearchResultsFound : EmptyTextType.FolderEmpty) : EmptyTextType.None; } - public string? FilesAndFoldersFilter { get; set; } + private string? _filesAndFoldersFilter; + public string? FilesAndFoldersFilter + { + get => _filesAndFoldersFilter; + set => SetProperty(ref _filesAndFoldersFilter, value); + } + // Apply changes immediately after manipulating on filesAndFolders completed public async Task ApplyFilesAndFoldersChangesAsync() @@ -1339,7 +1355,7 @@ public Task UpdateItemsTags(Dictionary newTags) return dispatcherQueue.EnqueueOrInvokeAsync(() => { int count = newTags.Count; - foreach(var item in FilesAndFolders) + foreach (var item in FilesAndFolders) { if (newTags.TryGetValue(item.ItemPath, out var tags)) { @@ -1635,7 +1651,7 @@ private async Task EnumerateItemsFromStandardFolderAsync(string path, Cance !isShellFolder && !isWslDistro; bool isNetdisk = false; - + try { // Special handling for network drives @@ -1643,7 +1659,7 @@ private async Task EnumerateItemsFromStandardFolderAsync(string path, Cance isNetdisk = (new DriveInfo(path).DriveType == System.IO.DriveType.Network); } catch { } - + bool isFtp = FtpHelpers.IsFtpPath(path); bool enumFromStorageFolder = isBoxFolder || isFtp; diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml b/src/Files.App/Views/Shells/ModernShellPage.xaml index 2fb8d79fc7c3..9ae668ecff3a 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml @@ -4,6 +4,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:helpers="using:Files.App.Helpers" xmlns:local="using:Files.App.Views.Shells" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wct="using:CommunityToolkit.WinUI" @@ -49,8 +50,13 @@ + + + + + + + + + + + + diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs index f876feec5dcf..f828fe14abf6 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs @@ -44,6 +44,7 @@ public ModernShellPage() : base(new CurrentInstanceViewModel()) ShellViewModel.PageTypeUpdated += FilesystemViewModel_PageTypeUpdated; ShellViewModel.OnSelectionRequestedEvent += FilesystemViewModel_OnSelectionRequestedEvent; ShellViewModel.GitDirectoryUpdated += FilesystemViewModel_GitDirectoryUpdated; + ShellViewModel.DirectoryInfoUpdated += ShellViewModel_DirectoryInfoUpdated; ToolbarViewModel.PathControlDisplayText = Strings.Home.GetLocalizedResource(); ToolbarViewModel.RefreshWidgetsRequested += ModernShellPage_RefreshWidgetsRequested; @@ -52,6 +53,12 @@ public ModernShellPage() : base(new CurrentInstanceViewModel()) _navigationInteractionTracker.NavigationRequested += OverscrollNavigationRequested; } + private void ShellViewModel_DirectoryInfoUpdated(object sender, EventArgs e) + { + // Regular binding causes issues when refreshing the directory so we set the text manually + FilterTextBox.Text = ShellViewModel.FilesAndFoldersFilter ?? string.Empty; + } + private void ModernShellPage_RefreshWidgetsRequested(object sender, EventArgs e) { if (ItemDisplayFrame?.Content is HomePage currentPage) @@ -166,6 +173,7 @@ private async void ItemDisplayFrame_Navigated(object sender, NavigationEventArgs if (parameters.IsLayoutSwitch) FilesystemViewModel_DirectoryInfoUpdated(sender, EventArgs.Empty); + _navigationInteractionTracker.CanNavigateBackward = CanNavigateBackward; _navigationInteractionTracker.CanNavigateForward = CanNavigateForward; } @@ -347,5 +355,15 @@ public override void NavigateToPath(string? navigationPath, Type? sourcePageType ToolbarViewModel.PathControlDisplayText = ShellViewModel.WorkingDirectory; } + + private async void FilterTextBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason is AutoSuggestionBoxTextChangeReason.UserInput) + { + ShellViewModel.FilesAndFoldersFilter = sender.Text; + await ShellViewModel.ApplyFilesAndFoldersChangesAsync(); + } + + } } } From ef4f16d1b77461edad0c2cd332cb054a6c0b2257 Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:57:09 -0400 Subject: [PATCH 2/9] Update ModernShellPage.xaml --- src/Files.App/Views/Shells/ModernShellPage.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml b/src/Files.App/Views/Shells/ModernShellPage.xaml index 9ae668ecff3a..2f7b9daefd0f 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml @@ -111,7 +111,7 @@ Date: Sun, 13 Jul 2025 16:01:09 -0400 Subject: [PATCH 3/9] Added folder icon to filter header --- src/Files.App/ViewModels/ShellViewModel.cs | 14 ++++++++++++++ src/Files.App/Views/Shells/ModernShellPage.xaml | 8 ++++++-- src/Files.App/Views/Shells/ModernShellPage.xaml.cs | 3 ++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Files.App/ViewModels/ShellViewModel.cs b/src/Files.App/ViewModels/ShellViewModel.cs index 3ab8f75389cf..b3f08c0c3729 100644 --- a/src/Files.App/ViewModels/ShellViewModel.cs +++ b/src/Files.App/ViewModels/ShellViewModel.cs @@ -114,6 +114,13 @@ public HorizontalAlignment FolderBackgroundImageHorizontalAlignment private set => SetProperty(ref _FolderBackgroundImageHorizontalAlignment, value); } + private ImageSource? _FolderThumbnailImageSource; + public ImageSource? FolderThumbnailImageSource + { + get => _FolderThumbnailImageSource; + private set => SetProperty(ref _FolderThumbnailImageSource, value); + } + public bool ShowFilterHeader => UserSettingsService.GeneralSettingsService.ShowFilterHeader && WorkingDirectory != "Home" && @@ -219,6 +226,8 @@ public async Task SetWorkingDirectoryAsync(string? value) GitDirectory = GitHelpers.GetGitRepositoryPath(WorkingDirectory, pathRoot); IsValidGitDirectory = !string.IsNullOrEmpty((await GitHelpers.GetRepositoryHead(GitDirectory))?.Name); + _ = UpdateFolderThumbnailImageSource(); + OnPropertyChanged(nameof(WorkingDirectory)); OnPropertyChanged(nameof(ShowFilterHeader)); } @@ -282,6 +291,11 @@ public EmptyTextType EmptyTextType set => SetProperty(ref emptyTextType, value); } + public async Task UpdateFolderThumbnailImageSource() + { + FolderThumbnailImageSource = await NavigationHelpers.GetIconForPathAsync(WorkingDirectory); + } + public async Task UpdateSortOptionStatusAsync() { OnPropertyChanged(nameof(IsSortedByName)); diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml b/src/Files.App/Views/Shells/ModernShellPage.xaml index 2f7b9daefd0f..4aa97024ef24 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml @@ -111,12 +111,16 @@ + Padding="26,8,12,8" + Visibility="{x:Bind ShellViewModel.ShowFilterHeader, Mode=OneWay}"> + Date: Mon, 14 Jul 2025 16:10:31 -0400 Subject: [PATCH 4/9] Auto focus --- src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs b/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs index b94c89054db3..0121a267dd6a 100644 --- a/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs +++ b/src/Files.App/Actions/Show/ToggleFilterHeaderAction.cs @@ -1,6 +1,10 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using CommunityToolkit.WinUI; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + namespace Files.App.Actions { internal sealed partial class ToggleFilterHeaderAction : ObservableObject, IToggleAction @@ -28,6 +32,12 @@ public Task ExecuteAsync(object? parameter = null) { generalSettingsService.ShowFilterHeader = !IsOn; + if (IsOn) + { + var filterTextBox = (MainWindow.Instance.Content as Frame)?.FindDescendant("FilterTextBox") as AutoSuggestBox; + filterTextBox?.Focus(FocusState.Programmatic); + } + return Task.CompletedTask; } From 2e94b19b536f6cd2a72154abf75412cd5a72425e Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:44:44 -0400 Subject: [PATCH 5/9] Remove focus via esc --- src/Files.App/Views/Shells/ModernShellPage.xaml | 1 + src/Files.App/Views/Shells/ModernShellPage.xaml.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml b/src/Files.App/Views/Shells/ModernShellPage.xaml index 4aa97024ef24..da6d448567b3 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml @@ -130,6 +130,7 @@ Width="180" VerticalAlignment="Center" PlaceholderText="{helpers:ResourceString Name=Filename}" + PreviewKeyDown="FilterTextBox_PreviewKeyDown" TextChanged="FilterTextBox_TextChanged" /> diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs index 413a58bdcdf7..d79f7fab8990 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs @@ -297,7 +297,7 @@ public override void NavigateHome() }, new SuppressNavigationTransitionInfo()); } - + public override void NavigateToReleaseNotes() { ItemDisplayFrame.Navigate( @@ -366,5 +366,13 @@ private async void FilterTextBox_TextChanged(AutoSuggestBox sender, AutoSuggestB } } + + private void FilterTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key is VirtualKey.Escape && + SlimContentPage is BaseGroupableLayoutPage svb && + svb.IsLoaded) + SlimContentPage.ItemManipulationModel.FocusFileList(); + } } } From 3c240bbeeeee72d5f9b94e35d29dfe2c4adf9d6d Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:54:43 -0400 Subject: [PATCH 6/9] Added divider line --- src/Files.App/Views/Shells/ModernShellPage.xaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml b/src/Files.App/Views/Shells/ModernShellPage.xaml index da6d448567b3..dd55215cda6d 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml @@ -112,6 +112,8 @@ x:Name="FilterHeader" Grid.Row="0" Padding="26,8,12,8" + BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}" + BorderThickness="0,0,0,1" Visibility="{x:Bind ShellViewModel.ShowFilterHeader, Mode=OneWay}"> Date: Tue, 15 Jul 2025 00:06:43 -0400 Subject: [PATCH 7/9] Update src/Files.App/Views/Shells/ModernShellPage.xaml.cs Co-authored-by: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Signed-off-by: Yair <39923744+yaira2@users.noreply.github.com> --- src/Files.App/Views/Shells/ModernShellPage.xaml.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs index d79f7fab8990..f475fbe786b5 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs @@ -370,8 +370,7 @@ private async void FilterTextBox_TextChanged(AutoSuggestBox sender, AutoSuggestB private void FilterTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) { if (e.Key is VirtualKey.Escape && - SlimContentPage is BaseGroupableLayoutPage svb && - svb.IsLoaded) + SlimContentPage is BaseGroupableLayoutPage { IsLoaded: true } svb) SlimContentPage.ItemManipulationModel.FocusFileList(); } } From 101a91fdacace5bcf53e6dff30f1cdb08ad6f98f Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:07:01 -0400 Subject: [PATCH 8/9] Update src/Files.App/Views/Shells/ModernShellPage.xaml.cs Co-authored-by: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Signed-off-by: Yair <39923744+yaira2@users.noreply.github.com> --- src/Files.App/Views/Shells/ModernShellPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs index f475fbe786b5..a5c363549e45 100644 --- a/src/Files.App/Views/Shells/ModernShellPage.xaml.cs +++ b/src/Files.App/Views/Shells/ModernShellPage.xaml.cs @@ -56,7 +56,7 @@ public ModernShellPage() : base(new CurrentInstanceViewModel()) private void ShellViewModel_DirectoryInfoUpdated(object sender, EventArgs e) { // Regular binding causes issues when refreshing the directory so we set the text manually - if (FilterTextBox is not null && FilterTextBox.IsLoaded) + if (FilterTextBox?.IsLoaded ?? false) FilterTextBox.Text = ShellViewModel.FilesAndFoldersFilter ?? string.Empty; } From f4dd878059eb5f623b37810e270360e7e8a9df72 Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:03:16 -0400 Subject: [PATCH 9/9] Added search suggestions to Omnibar --- .../Data/Contracts/IGeneralSettingsService.cs | 5 ++ .../Settings/GeneralSettingsService.cs | 6 ++ .../Services/Settings/UserSettingsService.cs | 1 + .../UserControls/NavigationToolbar.xaml | 4 ++ .../UserControls/NavigationToolbar.xaml.cs | 25 ++++++++- .../NavigationToolbarViewModel.cs | 55 +++++++++++++++++++ 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/Files.App/Data/Contracts/IGeneralSettingsService.cs b/src/Files.App/Data/Contracts/IGeneralSettingsService.cs index a5f193345b12..adc5d71eb66e 100644 --- a/src/Files.App/Data/Contracts/IGeneralSettingsService.cs +++ b/src/Files.App/Data/Contracts/IGeneralSettingsService.cs @@ -50,6 +50,11 @@ public interface IGeneralSettingsService : IBaseSettingsService, INotifyProperty /// List PathHistoryList { get; set; } + /// + /// A list containing previous search queries. + /// + List PreviousSearchQueriesList { get; set; } + /// /// Gets or sets a value indicating which date and time format to use. /// diff --git a/src/Files.App/Services/Settings/GeneralSettingsService.cs b/src/Files.App/Services/Settings/GeneralSettingsService.cs index bc25ff1295c9..f1eb5b985944 100644 --- a/src/Files.App/Services/Settings/GeneralSettingsService.cs +++ b/src/Files.App/Services/Settings/GeneralSettingsService.cs @@ -65,6 +65,12 @@ public List PathHistoryList set => Set(value); } + public List PreviousSearchQueriesList + { + get => Get>(null); + set => Set(value); + } + public DateTimeFormats DateTimeFormat { get => Get(DateTimeFormats.Application); diff --git a/src/Files.App/Services/Settings/UserSettingsService.cs b/src/Files.App/Services/Settings/UserSettingsService.cs index d6c82bd8b36c..8af8a85a37db 100644 --- a/src/Files.App/Services/Settings/UserSettingsService.cs +++ b/src/Files.App/Services/Settings/UserSettingsService.cs @@ -74,6 +74,7 @@ public override object ExportSettings() export.Remove(nameof(GeneralSettingsService.LastSessionTabList)); export.Remove(nameof(GeneralSettingsService.LastCrashedTabList)); export.Remove(nameof(GeneralSettingsService.PathHistoryList)); + export.Remove(nameof(GeneralSettingsService.PreviousSearchQueriesList)); return JsonSettingsSerializer.SerializeToJson(export); } diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml b/src/Files.App/UserControls/NavigationToolbar.xaml index a9fa7e44ff6a..b44a3064260f 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml +++ b/src/Files.App/UserControls/NavigationToolbar.xaml @@ -30,6 +30,10 @@ + diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml.cs b/src/Files.App/UserControls/NavigationToolbar.xaml.cs index 1a382b1029d1..291e4ee96223 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml.cs +++ b/src/Files.App/UserControls/NavigationToolbar.xaml.cs @@ -316,7 +316,22 @@ await DialogDisplayHelper.ShowDialogAsync(Strings.InvalidCommand.GetLocalizedRes // Search mode else if (mode == OmnibarSearchMode) { - ContentPageContext.ShellPage?.SubmitSearch(args.Text); + var shellPage = ContentPageContext.ShellPage; + + if (args.Item is SuggestionModel item && !string.IsNullOrWhiteSpace(item.ItemPath) && shellPage is not null) + await NavigationHelpers.OpenPath(item.ItemPath, shellPage); + else + { + var searchQuery = args.Item is SuggestionModel x && !string.IsNullOrWhiteSpace(x.Name) + ? x.Name + : args.Text; + + shellPage?.SubmitSearch(searchQuery); // use the resolved shellPage for consistency + ViewModel.SaveSearchQueryToList(searchQuery); + } + + (MainPageViewModel.SelectedTabItem?.TabItemContent as Control)?.Focus(FocusState.Programmatic); + return; } } @@ -338,6 +353,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(() => } else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode) { + await ViewModel.PopulateOmnibarSuggestionsForSearchMode(); } } @@ -456,7 +472,12 @@ await DispatcherQueue.EnqueueOrInvokeAsync(() => } else if (e.NewMode == OmnibarSearchMode) { + if (!ViewModel.InstanceViewModel.IsPageTypeSearchResults) + ViewModel.OmnibarSearchModeText = string.Empty; + else + ViewModel.OmnibarSearchModeText = ViewModel.InstanceViewModel.CurrentSearchQuery; + await ViewModel.PopulateOmnibarSuggestionsForSearchMode(); } } @@ -486,7 +507,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(() => } else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode) { - + await ViewModel.PopulateOmnibarSuggestionsForSearchMode(); } } } diff --git a/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs b/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs index aec32b8bc425..5a156a255218 100644 --- a/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs @@ -1035,6 +1035,18 @@ private void SavePathToHistory(string path) UserSettingsService.GeneralSettingsService.PathHistoryList = pathHistoryList; } + public void SaveSearchQueryToList(string searchQuery) + { + var previousSearchQueriesList = UserSettingsService.GeneralSettingsService.PreviousSearchQueriesList?.ToList() ?? []; + previousSearchQueriesList.Remove(searchQuery); + previousSearchQueriesList.Insert(0, searchQuery); + + if (previousSearchQueriesList.Count > MaxSuggestionsCount) + UserSettingsService.GeneralSettingsService.PreviousSearchQueriesList = previousSearchQueriesList.RemoveFrom(MaxSuggestionsCount + 1); + else + UserSettingsService.GeneralSettingsService.PreviousSearchQueriesList = previousSearchQueriesList; + } + private static async Task LaunchApplicationFromPath(string currentInput, string workingDir) { var args = CommandLineParser.SplitArguments(currentInput); @@ -1243,6 +1255,49 @@ public void PopulateOmnibarSuggestionsForCommandPaletteMode() } } + public async Task PopulateOmnibarSuggestionsForSearchMode() + { + if (ContentPageContext.ShellPage is null) + return; + + List newSuggestions = []; + + if (string.IsNullOrWhiteSpace(OmnibarSearchModeText)) + { + var previousSearchQueries = UserSettingsService.GeneralSettingsService.PreviousSearchQueriesList; + if (previousSearchQueries is not null) + newSuggestions.AddRange(previousSearchQueries.Select(query => new SuggestionModel(query, true))); + } + else + { + var search = new FolderSearch + { + Query = OmnibarSearchModeText, + Folder = ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory, + MaxItemCount = 10, + }; + + var results = await search.SearchAsync(); + newSuggestions.AddRange(results.Select(result => new SuggestionModel(result))); + } + + // Remove outdated suggestions + var toRemove = OmnibarSearchModeSuggestionItems + .Where(existing => !newSuggestions.Any(newItem => newItem.ItemPath == existing.ItemPath)) + .ToList(); + + foreach (var item in toRemove) + OmnibarSearchModeSuggestionItems.Remove(item); + + // Add new suggestions + var toAdd = newSuggestions + .Where(newItem => !OmnibarSearchModeSuggestionItems.Any(existing => existing.Name == newItem.Name)); + + foreach (var item in toAdd) + OmnibarSearchModeSuggestionItems.Add(item); + } + + [Obsolete("Remove once Omnibar goes out of experimental.")] public async Task SetAddressBarSuggestionsAsync(AutoSuggestBox sender, IShellPage shellpage) {