Skip to content

Commit 27cbf73

Browse files
authored
RichCommand: Base and commands ShowFileExtensions/ShowHiddenItems (#11327)
1 parent f4ff356 commit 27cbf73

File tree

12 files changed

+303
-19
lines changed

12 files changed

+303
-19
lines changed

src/Files.App/Actions/IAction.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Threading.Tasks;
2+
3+
namespace Files.App.Actions
4+
{
5+
public interface IAction
6+
{
7+
string Label { get; }
8+
9+
bool IsExecutable => true;
10+
11+
Task ExecuteAsync();
12+
}
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Files.App.Actions
2+
{
3+
public interface IToggleAction : IAction
4+
{
5+
bool IsOn { get; }
6+
}
7+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using CommunityToolkit.Mvvm.DependencyInjection;
3+
using Files.App.Extensions;
4+
using Files.Backend.Services.Settings;
5+
using System.ComponentModel;
6+
using System.Threading.Tasks;
7+
8+
namespace Files.App.Actions
9+
{
10+
internal class ShowFileExtensionsAction : ObservableObject, IToggleAction
11+
{
12+
private readonly IFoldersSettingsService settings = Ioc.Default.GetRequiredService<IFoldersSettingsService>();
13+
14+
public string Label => "ShowFileExtensions".GetLocalizedResource();
15+
16+
public bool IsOn => settings.ShowFileExtensions;
17+
18+
public ShowFileExtensionsAction() => settings.PropertyChanged += Settings_PropertyChanged;
19+
20+
public Task ExecuteAsync()
21+
{
22+
settings.ShowFileExtensions = !settings.ShowFileExtensions;
23+
return Task.CompletedTask;
24+
}
25+
26+
private void Settings_PropertyChanged(object? _, PropertyChangedEventArgs e)
27+
{
28+
if (e.PropertyName is nameof(IFoldersSettingsService.ShowFileExtensions))
29+
OnPropertyChanged(nameof(IsOn));
30+
}
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using CommunityToolkit.Mvvm.DependencyInjection;
3+
using Files.App.Extensions;
4+
using Files.Backend.Services.Settings;
5+
using System.ComponentModel;
6+
using System.Threading.Tasks;
7+
8+
namespace Files.App.Actions
9+
{
10+
internal class ShowHiddenItemsAction : ObservableObject, IToggleAction
11+
{
12+
private readonly IFoldersSettingsService settings = Ioc.Default.GetRequiredService<IFoldersSettingsService>();
13+
14+
public string Label => "ShowHiddenItems".GetLocalizedResource();
15+
16+
public bool IsOn => settings.ShowHiddenItems;
17+
18+
public ShowHiddenItemsAction() => settings.PropertyChanged += Settings_PropertyChanged;
19+
20+
public Task ExecuteAsync()
21+
{
22+
settings.ShowHiddenItems = !settings.ShowHiddenItems;
23+
return Task.CompletedTask;
24+
}
25+
26+
private void Settings_PropertyChanged(object? _, PropertyChangedEventArgs e)
27+
{
28+
if (e.PropertyName is nameof(IFoldersSettingsService.ShowHiddenItems))
29+
OnPropertyChanged(nameof(IsOn));
30+
}
31+
}
32+
}

src/Files.App/App.xaml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CommunityToolkit.WinUI;
33
using CommunityToolkit.WinUI.Helpers;
44
using CommunityToolkit.WinUI.Notifications;
5+
using Files.App.Commands;
56
using Files.App.DataModels;
67
using Files.App.Extensions;
78
using Files.App.Filesystem;
@@ -78,7 +79,7 @@ public partial class App : Application
7879

7980
public static string AppVersion = $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}.{Package.Current.Id.Version.Build}.{Package.Current.Id.Version.Revision}";
8081
public static string LogoPath;
81-
82+
8283
public IServiceProvider Services { get; private set; }
8384

8485
/// <summary>
@@ -129,6 +130,7 @@ private IServiceProvider ConfigureServices()
129130
.AddSingleton<ILocalizationService, LocalizationService>()
130131
.AddSingleton<ICloudDetector, CloudDetector>()
131132
.AddSingleton<IFileTagsService, FileTagsService>()
133+
.AddSingleton<ICommandManager, CommandManager>()
132134
#if UWP
133135
.AddSingleton<IStorageService, WindowsStorageService>()
134136
#else
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Files.App.Commands
2+
{
3+
public enum CommandCodes
4+
{
5+
None,
6+
7+
// show
8+
ShowHiddenItems,
9+
ShowFileExtensions,
10+
}
11+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Microsoft.UI.Xaml.Input;
2+
using System.ComponentModel;
3+
using System.Threading.Tasks;
4+
using System.Windows.Input;
5+
6+
namespace Files.App.Commands
7+
{
8+
public interface IRichCommand : ICommand, INotifyPropertyChanging, INotifyPropertyChanged
9+
{
10+
CommandCodes Code { get; }
11+
12+
string Label { get; }
13+
14+
bool IsToggle { get; }
15+
bool IsOn { get; set; }
16+
bool IsExecutable { get; }
17+
18+
Task ExecuteAsync();
19+
20+
void ExecuteTapped(object sender, TappedRoutedEventArgs e);
21+
}
22+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
using CommunityToolkit.Mvvm.Input;
3+
using Files.App.Actions;
4+
using Microsoft.UI.Xaml.Input;
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.ComponentModel;
9+
using System.Diagnostics;
10+
using System.Threading.Tasks;
11+
using System.Windows.Input;
12+
13+
namespace Files.App.Commands
14+
{
15+
internal class CommandManager : ICommandManager
16+
{
17+
private readonly IDictionary<CommandCodes, IRichCommand> commands = new Dictionary<CommandCodes, IRichCommand>
18+
{
19+
[CommandCodes.None] = new NoneCommand(),
20+
};
21+
22+
public IRichCommand this[CommandCodes code] => GetCommand(code);
23+
24+
public IRichCommand None => GetCommand(CommandCodes.None);
25+
public IRichCommand ShowHiddenItems => GetCommand(CommandCodes.ShowHiddenItems);
26+
public IRichCommand ShowFileExtensions => GetCommand(CommandCodes.ShowFileExtensions);
27+
28+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
29+
public IEnumerator<IRichCommand> GetEnumerator() => commands.Values.GetEnumerator();
30+
31+
private IRichCommand GetCommand(CommandCodes code)
32+
{
33+
if (commands.TryGetValue(code, out IRichCommand? command))
34+
return command;
35+
36+
var action = GetAction(code);
37+
var newCommand = new ActionCommand(code, action);
38+
commands.Add(code, newCommand);
39+
40+
return newCommand;
41+
}
42+
43+
private static IAction GetAction(CommandCodes code) => code switch
44+
{
45+
CommandCodes.ShowHiddenItems => new ShowHiddenItemsAction(),
46+
CommandCodes.ShowFileExtensions => new ShowFileExtensionsAction(),
47+
_ => throw new ArgumentOutOfRangeException(nameof(code)),
48+
};
49+
50+
[DebuggerDisplay("Command None")]
51+
private class NoneCommand : IRichCommand
52+
{
53+
public event EventHandler? CanExecuteChanged { add {} remove {} }
54+
public event PropertyChangingEventHandler? PropertyChanging { add { } remove { } }
55+
public event PropertyChangedEventHandler? PropertyChanged { add { } remove { } }
56+
57+
public CommandCodes Code => CommandCodes.None;
58+
59+
public string Label => string.Empty;
60+
61+
public bool IsToggle => false;
62+
public bool IsOn { get => false; set {} }
63+
public bool IsExecutable => false;
64+
65+
public bool CanExecute(object? _) => false;
66+
public void Execute(object? _) {}
67+
public Task ExecuteAsync() => Task.CompletedTask;
68+
public void ExecuteTapped(object _, TappedRoutedEventArgs e) {}
69+
}
70+
71+
[DebuggerDisplay("Command {Code}")]
72+
private class ActionCommand : ObservableObject, IRichCommand
73+
{
74+
public event EventHandler? CanExecuteChanged;
75+
76+
private readonly IAction action;
77+
private readonly ICommand command;
78+
79+
public CommandCodes Code { get; }
80+
81+
public string Label => action.Label;
82+
83+
public bool IsToggle => action is IToggleAction;
84+
85+
public bool IsOn
86+
{
87+
get => action is IToggleAction toggleAction && toggleAction.IsOn;
88+
set
89+
{
90+
if (action is IToggleAction toggleAction && toggleAction.IsOn != value)
91+
command.Execute(null);
92+
}
93+
}
94+
95+
public bool IsExecutable => action.IsExecutable;
96+
97+
public ActionCommand(CommandCodes code, IAction action)
98+
{
99+
Code = code;
100+
this.action = action;
101+
102+
command = new AsyncRelayCommand(ExecuteAsync, () => action.IsExecutable);
103+
104+
if (action is INotifyPropertyChanging notifyPropertyChanging)
105+
notifyPropertyChanging.PropertyChanging += Action_PropertyChanging;
106+
if (action is INotifyPropertyChanged notifyPropertyChanged)
107+
notifyPropertyChanged.PropertyChanged += Action_PropertyChanged;
108+
}
109+
110+
public bool CanExecute(object? parameter) => command.CanExecute(parameter);
111+
public void Execute(object? parameter) => command.Execute(parameter);
112+
113+
public Task ExecuteAsync()
114+
{
115+
if (IsExecutable)
116+
return action.ExecuteAsync();
117+
return Task.CompletedTask;
118+
}
119+
120+
public async void ExecuteTapped(object _, TappedRoutedEventArgs e) => await action.ExecuteAsync();
121+
122+
private void Action_PropertyChanging(object? _, PropertyChangingEventArgs e)
123+
{
124+
switch (e.PropertyName)
125+
{
126+
case nameof(IAction.Label):
127+
OnPropertyChanging(nameof(Label));
128+
break;
129+
case nameof(IToggleAction.IsOn) when IsToggle:
130+
OnPropertyChanging(nameof(IsOn));
131+
break;
132+
case nameof(IAction.IsExecutable):
133+
OnPropertyChanging(nameof(IsExecutable));
134+
break;
135+
}
136+
}
137+
private void Action_PropertyChanged(object? sender, PropertyChangedEventArgs e)
138+
{
139+
switch (e.PropertyName)
140+
{
141+
case nameof(IAction.Label):
142+
OnPropertyChanged(nameof(Label));
143+
break;
144+
case nameof(IToggleAction.IsOn) when IsToggle:
145+
OnPropertyChanged(nameof(IsOn));
146+
break;
147+
case nameof(IAction.IsExecutable):
148+
OnPropertyChanged(nameof(IsExecutable));
149+
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
150+
break;
151+
}
152+
}
153+
}
154+
}
155+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Collections.Generic;
2+
3+
namespace Files.App.Commands
4+
{
5+
public interface ICommandManager : IEnumerable<IRichCommand>
6+
{
7+
IRichCommand this[CommandCodes code] { get; }
8+
9+
IRichCommand None { get; }
10+
11+
IRichCommand ShowHiddenItems { get; }
12+
IRichCommand ShowFileExtensions { get; }
13+
}
14+
}

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,12 +1293,6 @@
12931293
<data name="NavToolbarTiles.ToolTipService.ToolTip" xml:space="preserve">
12941294
<value>Tiles (Ctrl+Shift+2)</value>
12951295
</data>
1296-
<data name="NavToolbarShowFileExtensionsHeader.Text" xml:space="preserve">
1297-
<value>Show file extensions</value>
1298-
</data>
1299-
<data name="NavToolbarShowHiddenItems.AutomationProperties.Name" xml:space="preserve">
1300-
<value>Show hidden items</value>
1301-
</data>
13021296
<data name="DateDeleted" xml:space="preserve">
13031297
<value>Date deleted</value>
13041298
</data>
@@ -2310,12 +2304,6 @@
23102304
<data name="Tiles" xml:space="preserve">
23112305
<value>Tiles</value>
23122306
</data>
2313-
<data name="NavToolbarShowFileExtensions.AutomationProperties.Name" xml:space="preserve">
2314-
<value>Show file extensions</value>
2315-
</data>
2316-
<data name="NavToolbarShowHiddenItemsHeader.Text" xml:space="preserve">
2317-
<value>Show hidden items</value>
2318-
</data>
23192307
<data name="SettingsOpenFoldersNewTabToggleSwitch.AutomationProperties.Name" xml:space="preserve">
23202308
<value>Open folders in new tab</value>
23212309
</data>
@@ -2958,4 +2946,10 @@
29582946
<data name="ContextMenuOptions" xml:space="preserve">
29592947
<value>Context menu options</value>
29602948
</data>
2949+
<data name="ShowFileExtensions" xml:space="preserve">
2950+
<value>Show file extensions</value>
2951+
</data>
2952+
<data name="ShowHiddenItems" xml:space="preserve">
2953+
<value>Show hidden items</value>
2954+
</data>
29612955
</root>

0 commit comments

Comments
 (0)