diff --git a/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs b/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs new file mode 100644 index 000000000000..0fb7531dddb3 --- /dev/null +++ b/src/Files.App/Data/Contexts/HomePage/HomePageContext.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Files.App.UserControls.Widgets; +using Files.App.ViewModels.Widgets; +using Microsoft.UI.Xaml.Controls; +using System.Collections.Immutable; + +namespace Files.App.Data.Contexts +{ + internal class HomePageContext : ObservableObject, IHomePageContext + { + private static readonly IImmutableList emptyTaggedItems = Enumerable.Empty().ToImmutableList(); + + public bool IsAnyItemRightClicked => rightClickedItem is not null; + + private WidgetCardItem? rightClickedItem = null; + public WidgetCardItem? RightClickedItem => rightClickedItem; + + private CommandBarFlyout? itemContextFlyoutMenu = null; + public CommandBarFlyout? ItemContextFlyoutMenu => itemContextFlyoutMenu; + + private IReadOnlyList selectedTaggedItems = emptyTaggedItems; + public IReadOnlyList SelectedTaggedItems + { + get => selectedTaggedItems; + set => selectedTaggedItems = value ?? emptyTaggedItems; + } + + public HomePageContext() + { + HomePageWidget.RightClickedItemChanged += HomePageWidget_RightClickedItemChanged; + FileTagsWidget.SelectedTaggedItemsChanged += FileTagsWidget_SelectedTaggedItemsChanged; + } + + private void FileTagsWidget_SelectedTaggedItemsChanged(object? sender, IEnumerable e) + { + SetProperty(ref selectedTaggedItems, e.ToList()); + } + + private void HomePageWidget_RightClickedItemChanged(object? sender, WidgetsRightClickedItemChangedEventArgs e) + { + if (SetProperty(ref rightClickedItem, e.Item, nameof(RightClickedItem))) + OnPropertyChanged(nameof(IsAnyItemRightClicked)); + + SetProperty(ref itemContextFlyoutMenu, e.Flyout, nameof(ItemContextFlyoutMenu)); + } + } +} diff --git a/src/Files.App/Data/Contexts/HomePage/IHomePageContext.cs b/src/Files.App/Data/Contexts/HomePage/IHomePageContext.cs new file mode 100644 index 000000000000..a08a26185482 --- /dev/null +++ b/src/Files.App/Data/Contexts/HomePage/IHomePageContext.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Files.App.UserControls.Widgets; +using Files.App.ViewModels.Widgets; +using Microsoft.UI.Xaml.Controls; + +namespace Files.App.Data.Contexts +{ + internal interface IHomePageContext + { + /// + /// The last right clicked item + /// + WidgetCardItem? RightClickedItem { get; } + + /// + /// The last opened widget's context menu instance + /// + CommandBarFlyout? ItemContextFlyoutMenu { get; } + + /// + /// An list containing all the selected tagged items + /// + IReadOnlyList SelectedTaggedItems { get; } + + /// + /// Tells whether any item has been right clicked + /// + bool IsAnyItemRightClicked { get; } + } +} diff --git a/src/Files.App/Data/EventArguments/WidgetsRightClickedItemChangedEventArgs.cs b/src/Files.App/Data/EventArguments/WidgetsRightClickedItemChangedEventArgs.cs new file mode 100644 index 000000000000..e820d3ae2bac --- /dev/null +++ b/src/Files.App/Data/EventArguments/WidgetsRightClickedItemChangedEventArgs.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2023 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Files.App.UserControls.Widgets; +using Microsoft.UI.Xaml.Controls; + +namespace Files.App.Data.EventArguments +{ + public class WidgetsRightClickedItemChangedEventArgs + { + public WidgetCardItem? Item { get; set; } + + public CommandBarFlyout? Flyout { get; set; } + + public WidgetsRightClickedItemChangedEventArgs(WidgetCardItem? item = null, CommandBarFlyout? flyout = null) + { + Item = item; + Flyout = flyout; + } + } +} diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index e5b5832216e3..52d05abd8b8a 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -122,6 +122,7 @@ public static IHost ConfigureHost() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs index c6ade116d51b..c0bdc8d31f53 100644 --- a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs @@ -61,6 +61,7 @@ public async Task LoadCardThumbnailAsync() public sealed partial class DrivesWidget : HomePageWidget, IWidgetItem, INotifyPropertyChanged { public IUserSettingsService userSettingsService { get; } = Ioc.Default.GetRequiredService(); + private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); private DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); @@ -294,13 +295,17 @@ private void FormatDrive(DriveCardItem? item) private void OpenProperties(DriveCardItem item) { + if (!HomePageContext.IsAnyItemRightClicked) + return; + EventHandler flyoutClosed = null!; - flyoutClosed = async (s, e) => + flyoutClosed = (s, e) => { - ItemContextMenuFlyout.Closed -= flyoutClosed; + HomePageContext.ItemContextFlyoutMenu!.Closed -= flyoutClosed; FilePropertiesHelpers.OpenPropertiesWindow(item.Item, associatedInstance); }; - ItemContextMenuFlyout.Closed += flyoutClosed; + + HomePageContext.ItemContextFlyoutMenu!.Closed += flyoutClosed; } private async void Button_Click(object sender, RoutedEventArgs e) diff --git a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs index f656ae085c55..a17c55ebedd5 100644 --- a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs @@ -11,13 +11,12 @@ using System.Windows.Input; using Windows.Storage; -// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 - namespace Files.App.UserControls.Widgets { public sealed partial class FileTagsWidget : HomePageWidget, IWidgetItem { private readonly IUserSettingsService userSettingsService; + private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); public FileTagsWidgetViewModel ViewModel { @@ -32,6 +31,7 @@ public FileTagsWidgetViewModel ViewModel public delegate void FileTagsOpenLocationInvokedEventHandler(object sender, PathNavigationEventArgs e); public delegate void FileTagsNewPaneInvokedEventHandler(object sender, QuickAccessCardInvokedEventArgs e); + public static event EventHandler>? SelectedTaggedItemsChanged; public event FileTagsOpenLocationInvokedEventHandler FileTagsOpenLocationInvoked; public event FileTagsNewPaneInvokedEventHandler FileTagsNewPaneInvoked; @@ -69,10 +69,14 @@ public FileTagsWidget() private void OpenProperties(WidgetCardItem? item) { + if (!HomePageContext.IsAnyItemRightClicked) + return; + EventHandler flyoutClosed = null!; flyoutClosed = (s, e) => { - ItemContextMenuFlyout.Closed -= flyoutClosed; + HomePageContext.ItemContextFlyoutMenu!.Closed -= flyoutClosed; + ListedItem listedItem = new(null!) { ItemPath = (item.Item as FileTagsItemViewModel)?.Path ?? string.Empty, @@ -82,7 +86,8 @@ private void OpenProperties(WidgetCardItem? item) }; FilePropertiesHelpers.OpenPropertiesWindow(listedItem, AppInstance); }; - ItemContextMenuFlyout.Closed += flyoutClosed; + + HomePageContext.ItemContextFlyoutMenu!.Closed += flyoutClosed; } private void OpenInNewPane(WidgetCardItem? item) @@ -101,50 +106,45 @@ private async void FileTagItem_ItemClick(object sender, ItemClickEventArgs e) private void AdaptiveGridView_RightTapped(object sender, RightTappedRoutedEventArgs e) { + // Ensure values are not null if (e.OriginalSource is not FrameworkElement element || element.DataContext is not FileTagsItemViewModel item) - { return; - } - LoadContextMenu( - element, - e, - GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), item.IsFolder), - rightClickedItem: item); - } + // Create a new Flyout + var itemContextMenuFlyout = new CommandBarFlyout() + { + Placement = FlyoutPlacementMode.Full + }; - private void LoadContextMenu( - FrameworkElement element, - RightTappedRoutedEventArgs e, - List menuItems, - FileTagsItemViewModel? rightClickedItem = null) - { - var itemContextMenuFlyout = new CommandBarFlyout { Placement = FlyoutPlacementMode.Full }; + // Hook events itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; + itemContextMenuFlyout.Opened += (sender, e) => OnRightClickedItemChanged(null, null); + + FlyoutItemPath = item.Path; + + // Notify of the change on right clicked item + OnRightClickedItemChanged(item, itemContextMenuFlyout); + // Get items for the flyout + var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), item.IsFolder); var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - if (!UserSettingsService.GeneralSettingsService.MoveShellExtensionsToSubMenu) - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); // Set menu min width if the overflow menu setting is disabled + // Set max width of the flyout + secondaryElements + .OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); - secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); - ItemContextMenuFlyout = itemContextMenuFlyout; - if (rightClickedItem is not null) - { - FlyouItemPath = rightClickedItem.Path; - ItemContextMenuFlyout.Opened += ItemContextMenuFlyout_Opened; - } - itemContextMenuFlyout.ShowAt(element, new FlyoutShowOptions { Position = e.GetPosition(element) }); + // Add menu items to the secondary flyout + secondaryElements.ForEach(itemContextMenuFlyout.SecondaryCommands.Add); - e.Handled = true; - } + // Show the flyout + itemContextMenuFlyout.ShowAt(element, new() { Position = e.GetPosition(element) }); - private async void ItemContextMenuFlyout_Opened(object? sender, object e) - { - ItemContextMenuFlyout.Opened -= ItemContextMenuFlyout_Opened; - await ShellContextmenuHelper.LoadShellMenuItemsAsync(FlyouItemPath, ItemContextMenuFlyout, showOpenWithMenu: true, showSendToMenu: true); + // Load shell menu items + _ = ShellContextmenuHelper.LoadShellMenuItemsAsync(FlyoutItemPath, itemContextMenuFlyout); + + e.Handled = true; } public override List GetItemMenuItems(WidgetCardItem item, bool isPinned, bool isFolder = false) diff --git a/src/Files.App/UserControls/Widgets/HomePageWidget.cs b/src/Files.App/UserControls/Widgets/HomePageWidget.cs index cee8a410444a..551333c4ee93 100644 --- a/src/Files.App/UserControls/Widgets/HomePageWidget.cs +++ b/src/Files.App/UserControls/Widgets/HomePageWidget.cs @@ -13,51 +13,81 @@ namespace Files.App.UserControls.Widgets { public abstract class HomePageWidget : UserControl { + // Dependency injections + public IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public IStorageService StorageService { get; } = Ioc.Default.GetRequiredService(); - public ICommand RemoveRecentItemCommand; - public ICommand ClearAllItemsCommand; - public ICommand OpenFileLocationCommand; - public ICommand OpenInNewTabCommand; - public ICommand OpenInNewWindowCommand; - public ICommand OpenPropertiesCommand; - public ICommand PinToFavoritesCommand; - public ICommand UnpinFromFavoritesCommand; + // Fields + + protected string? FlyoutItemPath; + + // Commands + + public ICommand? RemoveRecentItemCommand { get; protected set; } + public ICommand? ClearAllItemsCommand { get; protected set; } + public ICommand? OpenFileLocationCommand { get; protected set; } + public ICommand? OpenInNewTabCommand { get; protected set; } + public ICommand? OpenInNewWindowCommand { get; protected set; } + public ICommand? OpenPropertiesCommand { get; protected set; } + public ICommand? PinToFavoritesCommand { get; protected set; } + public ICommand? UnpinFromFavoritesCommand { get; protected set; } + + // Events - protected CommandBarFlyout ItemContextMenuFlyout; - protected string FlyouItemPath; + public static event EventHandler? RightClickedItemChanged; + + // Abstract methods public abstract List GetItemMenuItems(WidgetCardItem item, bool isPinned, bool isFolder = false); + // Event methods + public void Button_RightTapped(object sender, RightTappedRoutedEventArgs e) { - var itemContextMenuFlyout = new CommandBarFlyout { Placement = FlyoutPlacementMode.Full }; - itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; - if (sender is not Button widgetCardItem || widgetCardItem.DataContext is not WidgetCardItem item) + // Ensure values are not null + if (sender is not Button widgetCardItem || + widgetCardItem.DataContext is not WidgetCardItem item) return; + // Create a new Flyout + var itemContextMenuFlyout = new CommandBarFlyout() + { + Placement = FlyoutPlacementMode.Full + }; + + // Hook events + itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; + itemContextMenuFlyout.Opened += (sender, e) => OnRightClickedItemChanged(null, null); + + FlyoutItemPath = item.Path; + + // Notify of the change on right clicked item + OnRightClickedItemChanged(item, itemContextMenuFlyout); + + // Get items for the flyout var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path)); var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); + // Set max width of the flyout + secondaryElements + .OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); + + // Add menu items to the secondary flyout + secondaryElements.ForEach(itemContextMenuFlyout.SecondaryCommands.Add); + + // Show the flyout + itemContextMenuFlyout.ShowAt(widgetCardItem, new() { Position = e.GetPosition(widgetCardItem) }); - secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); - ItemContextMenuFlyout = itemContextMenuFlyout; - FlyouItemPath = item.Path; - ItemContextMenuFlyout.Opened += ItemContextMenuFlyout_Opened; - itemContextMenuFlyout.ShowAt(widgetCardItem, new FlyoutShowOptions { Position = e.GetPosition(widgetCardItem) }); + // Load shell menu items + _ = ShellContextmenuHelper.LoadShellMenuItemsAsync(FlyoutItemPath, itemContextMenuFlyout); e.Handled = true; } - private async void ItemContextMenuFlyout_Opened(object? sender, object e) - { - ItemContextMenuFlyout.Opened -= ItemContextMenuFlyout_Opened; - await ShellContextmenuHelper.LoadShellMenuItemsAsync(FlyouItemPath, ItemContextMenuFlyout); - } + // Command methods public async Task OpenInNewTabAsync(WidgetCardItem item) { @@ -71,13 +101,17 @@ public async Task OpenInNewWindowAsync(WidgetCardItem item) public virtual async Task PinToFavoritesAsync(WidgetCardItem item) { - _ = QuickAccessService.PinToSidebarAsync(item.Path); + await QuickAccessService.PinToSidebarAsync(item.Path); } public virtual async Task UnpinFromFavoritesAsync(WidgetCardItem item) { - _ = QuickAccessService.UnpinFromSidebarAsync(item.Path); + await QuickAccessService.UnpinFromSidebarAsync(item.Path); } + protected void OnRightClickedItemChanged(WidgetCardItem? item, CommandBarFlyout? flyout) + { + RightClickedItemChanged?.Invoke(this, new WidgetsRightClickedItemChangedEventArgs(item, flyout)); + } } } diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index c82f667410ff..8994d6f0a7af 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -92,6 +92,7 @@ public async Task LoadCardThumbnailAsync() public sealed partial class QuickAccessWidget : HomePageWidget, IWidgetItem, INotifyPropertyChanged { public IUserSettingsService userSettingsService { get; } = Ioc.Default.GetRequiredService(); + private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); public static ObservableCollection ItemsAdded = new(); @@ -364,13 +365,18 @@ private async void Button_PointerPressed(object sender, PointerRoutedEventArgs e private void OpenProperties(FolderCardItem item) { + if (!HomePageContext.IsAnyItemRightClicked) + return; + EventHandler flyoutClosed = null!; + flyoutClosed = (s, e) => { - ItemContextMenuFlyout.Closed -= flyoutClosed; + HomePageContext.ItemContextFlyoutMenu!.Closed -= flyoutClosed; CardPropertiesInvoked?.Invoke(this, new QuickAccessCardEventArgs { Item = item.Item }); }; - ItemContextMenuFlyout.Closed += flyoutClosed; + + HomePageContext.ItemContextFlyoutMenu!.Closed += flyoutClosed; } public override async Task PinToFavoritesAsync(WidgetCardItem item) diff --git a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs index 4511c6f67fa5..5d04bd6e5c97 100644 --- a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs @@ -17,6 +17,8 @@ namespace Files.App.UserControls.Widgets { public sealed partial class RecentFilesWidget : HomePageWidget, IWidgetItem, INotifyPropertyChanged { + private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); + public delegate void RecentFilesOpenLocationInvokedEventHandler(object sender, PathNavigationEventArgs e); public event RecentFilesOpenLocationInvokedEventHandler RecentFilesOpenLocationInvoked; @@ -109,27 +111,43 @@ public RecentFilesWidget() private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e) { - ItemContextMenuFlyout = new CommandBarFlyout { Placement = FlyoutPlacementMode.Full }; - ItemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; - if (e.OriginalSource is not FrameworkElement element || element.DataContext is not RecentItem item) + // Ensure values are not null + if (e.OriginalSource is not FrameworkElement element || + element.DataContext is not RecentItem item) return; - var menuItems = GetItemMenuItems(item, false); + // Create a new Flyout + var itemContextMenuFlyout = new CommandBarFlyout() + { + Placement = FlyoutPlacementMode.Full + }; + + // Hook events + itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; + itemContextMenuFlyout.Opened += (sender, e) => OnRightClickedItemChanged(null, null); + + FlyoutItemPath = item.Path; + + // Notify of the change on right clicked item + OnRightClickedItemChanged(item, itemContextMenuFlyout); + + // Get items for the flyout + var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path)); var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); + // Set max width of the flyout + secondaryElements + .OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); - secondaryElements.ForEach(i => ItemContextMenuFlyout.SecondaryCommands.Add(i)); - FlyouItemPath = item.Path; - ItemContextMenuFlyout.Opened += ItemContextMenuFlyout_Opened; - ItemContextMenuFlyout.ShowAt(element, new FlyoutShowOptions { Position = e.GetPosition(element) }); - } + // Add menu items to the secondary flyout + secondaryElements.ForEach(itemContextMenuFlyout.SecondaryCommands.Add); - private async void ItemContextMenuFlyout_Opened(object? sender, object e) - { - ItemContextMenuFlyout.Opened -= ItemContextMenuFlyout_Opened; - await ShellContextmenuHelper.LoadShellMenuItemsAsync(FlyouItemPath, ItemContextMenuFlyout, showOpenWithMenu: true, showSendToMenu: true); + // Show the flyout + itemContextMenuFlyout.ShowAt(element, new() { Position = e.GetPosition(element) }); + + // Load shell menu items + _ = ShellContextmenuHelper.LoadShellMenuItemsAsync(FlyoutItemPath, itemContextMenuFlyout); } public override List GetItemMenuItems(WidgetCardItem item, bool isPinned, bool isFolder = false) @@ -227,11 +245,11 @@ private void OpenProperties(RecentItem item) EventHandler flyoutClosed = null!; flyoutClosed = async (s, e) => { - ItemContextMenuFlyout.Closed -= flyoutClosed; + HomePageContext.ItemContextFlyoutMenu!.Closed -= flyoutClosed; var listedItem = await UniversalStorageEnumerator.AddFileAsync(await BaseStorageFile.GetFileFromPathAsync(item.Path), null, default); FilePropertiesHelpers.OpenPropertiesWindow(listedItem, associatedInstance); }; - ItemContextMenuFlyout.Closed += flyoutClosed; + HomePageContext.ItemContextFlyoutMenu!.Closed += flyoutClosed; } private async Task UpdateRecentsListAsync(NotifyCollectionChangedEventArgs e)