From dd7930a84b50f331fbd8844d09bf321aaf873b16 Mon Sep 17 00:00:00 2001 From: Nguyen Quy Hy Date: Sun, 13 Nov 2022 19:07:30 -0500 Subject: [PATCH] Allow saving with default folder & naming --- .../FlightRecorder.Client.Logics.csproj | 1 + FlightRecorder.Client.Logics/IDialogLogic.cs | 8 ++- .../ISettingsLogic.cs | 2 + FlightRecorder.Client.Logics/IStorageLogic.cs | 10 +++ FlightRecorder.Client.Logics/SettingsLogic.cs | 12 ++++ FlightRecorder.Client.Logics/StorageLogic.cs | 63 +++++++++++++++++++ .../TestStateMachine.cs | 16 ++++- .../ShortcutKeyLogic.cs | 17 +++-- .../StateMachine.cs | 59 +++++++++++++---- .../StateMachineCore.cs | 31 ++++++--- .../States/ActionContext.cs | 5 ++ .../States/Transition.cs | 20 +++--- FlightRecorder.Client/App.xaml.cs | 1 + FlightRecorder.Client/DialogLogic.cs | 62 ++++++------------ .../FlightRecorder.Client.csproj | 2 +- FlightRecorder.Client/ShortcutKeysWindow.xaml | 23 ++++++- .../ShortcutKeysWindow.xaml.cs | 58 ++++++++++++----- 17 files changed, 288 insertions(+), 102 deletions(-) create mode 100644 FlightRecorder.Client.Logics/IStorageLogic.cs create mode 100644 FlightRecorder.Client.Logics/StorageLogic.cs create mode 100644 FlightRecorder.Client.ViewModels/States/ActionContext.cs diff --git a/FlightRecorder.Client.Logics/FlightRecorder.Client.Logics.csproj b/FlightRecorder.Client.Logics/FlightRecorder.Client.Logics.csproj index 15a5f4c..958b4b4 100644 --- a/FlightRecorder.Client.Logics/FlightRecorder.Client.Logics.csproj +++ b/FlightRecorder.Client.Logics/FlightRecorder.Client.Logics.csproj @@ -8,6 +8,7 @@ + diff --git a/FlightRecorder.Client.Logics/IDialogLogic.cs b/FlightRecorder.Client.Logics/IDialogLogic.cs index 4cf0e9a..24bad65 100644 --- a/FlightRecorder.Client.Logics/IDialogLogic.cs +++ b/FlightRecorder.Client.Logics/IDialogLogic.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.IO; +using System.Threading.Tasks; namespace FlightRecorder.Client.Logics; @@ -6,6 +7,7 @@ public interface IDialogLogic { bool Confirm(string message); void Error(string error); - Task SaveAsync(SavedData data); - Task<(string? fileName, SavedData? data)> LoadAsync(); + Task PickSaveFileAsync(); + Task<(string filePath, Stream fileStream)?> PickOpenFileAsync(); + Task PickSaveFolderAsync(); } diff --git a/FlightRecorder.Client.Logics/ISettingsLogic.cs b/FlightRecorder.Client.Logics/ISettingsLogic.cs index e5ae8b5..56c8a1a 100644 --- a/FlightRecorder.Client.Logics/ISettingsLogic.cs +++ b/FlightRecorder.Client.Logics/ISettingsLogic.cs @@ -6,4 +6,6 @@ public interface ISettingsLogic { Task IsShortcutKeysEnabledAsync(); Task SetShortcutKeysEnabledAsync(bool value); + Task GetDefaultSaveFolderAsync(); + Task SetDefaultSaveFolderAsync(string? folderPath); } diff --git a/FlightRecorder.Client.Logics/IStorageLogic.cs b/FlightRecorder.Client.Logics/IStorageLogic.cs new file mode 100644 index 0000000..8b9fa0e --- /dev/null +++ b/FlightRecorder.Client.Logics/IStorageLogic.cs @@ -0,0 +1,10 @@ +using System.IO; +using System.Threading.Tasks; + +namespace FlightRecorder.Client.Logics; + +public interface IStorageLogic +{ + Task LoadAsync(Stream file); + Task SaveAsync(string filePath, SavedData data); +} diff --git a/FlightRecorder.Client.Logics/SettingsLogic.cs b/FlightRecorder.Client.Logics/SettingsLogic.cs index 240c10b..61cc69c 100644 --- a/FlightRecorder.Client.Logics/SettingsLogic.cs +++ b/FlightRecorder.Client.Logics/SettingsLogic.cs @@ -29,6 +29,17 @@ public Task SetShortcutKeysEnabledAsync(bool value) return SaveAsync(settings => settings.ShortcutKeysEnabled = value); } + public async Task GetDefaultSaveFolderAsync() + { + var settings = await LoadAsync(); + return settings.DefaultSaveFolder; + } + + public Task SetDefaultSaveFolderAsync(string? folderPath) + { + return SaveAsync(settings => settings.DefaultSaveFolder = folderPath); + } + private static SemaphoreSlim sm = new SemaphoreSlim(1); private Settings? settings = null; @@ -87,4 +98,5 @@ private async Task LoadNoLockAsync() public class Settings { public bool ShortcutKeysEnabled { get; set; } = false; + public string? DefaultSaveFolder { get; set; } = null; } \ No newline at end of file diff --git a/FlightRecorder.Client.Logics/StorageLogic.cs b/FlightRecorder.Client.Logics/StorageLogic.cs new file mode 100644 index 0000000..89d2551 --- /dev/null +++ b/FlightRecorder.Client.Logics/StorageLogic.cs @@ -0,0 +1,63 @@ +using ICSharpCode.SharpZipLib.Zip; +using Microsoft.Extensions.Logging; +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; + +namespace FlightRecorder.Client.Logics; + +public class StorageLogic : IStorageLogic +{ + private readonly ILogger logger; + + public StorageLogic(ILogger logger) + { + this.logger = logger; + } + + /// This must not be null or empty + public async Task SaveAsync(string filePath, SavedData data) + { + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + using var outStream = new ZipOutputStream(fileStream); + + outStream.SetLevel(9); + + var entry = new ZipEntry("data.json") + { + DateTime = DateTime.Now + }; + outStream.PutNextEntry(entry); + + await JsonSerializer.SerializeAsync(outStream, data); + outStream.Finish(); + + logger.LogDebug("Saved file into {fileName}", filePath); + } + } + + public async Task LoadAsync(Stream file) + { + using (file) + { + using var zipFile = new ZipFile(file); + + foreach (ZipEntry entry in zipFile) + { + if (entry.IsFile && entry.Name == "data.json") + { + using var stream = zipFile.GetInputStream(entry); + + var result = await JsonSerializer.DeserializeAsync(stream); + + logger.LogDebug("Loaded file data from file stream"); + + return result; + } + } + } + return null; + } +} diff --git a/FlightRecorder.Client.ViewModels.Tests/TestStateMachine.cs b/FlightRecorder.Client.ViewModels.Tests/TestStateMachine.cs index a4ea5f6..f98a4cc 100644 --- a/FlightRecorder.Client.ViewModels.Tests/TestStateMachine.cs +++ b/FlightRecorder.Client.ViewModels.Tests/TestStateMachine.cs @@ -6,6 +6,7 @@ using Moq; using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; namespace FlightRecorder.Client.ViewModels.Tests @@ -14,6 +15,8 @@ namespace FlightRecorder.Client.ViewModels.Tests public class TestStateMachine { private Mock mockThreadLogic = null!; + private Mock mockSettingsLogic = null!; + private Mock mockStorageLogic = null!; private Mock mockRecorderLogic = null!; private Mock mockReplayLogic = null!; private Mock mockConnector = null!; @@ -32,6 +35,8 @@ public void Setup() var factory = serviceProvider.GetService()!; mockThreadLogic = new Mock(); + mockSettingsLogic = new Mock(); + mockStorageLogic = new Mock(); mockRecorderLogic = new Mock(); mockReplayLogic = new Mock(); mockConnector = new Mock(); @@ -47,6 +52,8 @@ public void Setup() stateMachine = new StateMachine( factory.CreateLogger(), viewModel, + mockSettingsLogic.Object, + mockStorageLogic.Object, mockRecorderLogic.Object, mockReplayLogic.Object, mockDialogLogic.Object, @@ -62,14 +69,17 @@ public async Task TestAllEvents() Assert.AreEqual(StateMachine.State.DisconnectedEmpty, viewModel.State); - mockDialogLogic.Setup(logic => logic.LoadAsync()) - .Returns(Task.FromResult<(string?, SavedData?)>(("test", new SavedData( + mockDialogLogic.Setup(logic => logic.PickOpenFileAsync()) + .Returns(Task.FromResult<(string, Stream)?>(("test", new MemoryStream()))); + mockStorageLogic.Setup(logic => logic.LoadAsync(It.IsAny())) + .Returns(Task.FromResult(new SavedData( "TEST_VERSION", 0, 1, null, new List<(long milliseconds, AircraftPositionStruct position)>() - )))); + ))); + await stateMachine.TransitAsync(StateMachine.Event.Load); Assert.AreEqual(StateMachine.State.DisconnectedSaved, viewModel.State); } diff --git a/FlightRecorder.Client.ViewModels/ShortcutKeyLogic.cs b/FlightRecorder.Client.ViewModels/ShortcutKeyLogic.cs index 647b479..5706e20 100644 --- a/FlightRecorder.Client.ViewModels/ShortcutKeyLogic.cs +++ b/FlightRecorder.Client.ViewModels/ShortcutKeyLogic.cs @@ -19,6 +19,7 @@ public class ShortcutKeyLogic private const uint KeyPause = 0xBC; // , private const uint KeyResume = 0xBE; // . private const uint KeyStopReplaying = 'S'; + private const uint KeySave = 'C'; private readonly ILogger logger; private readonly StateMachine stateMachine; @@ -56,6 +57,7 @@ public async Task RegisterAsync(IntPtr handle) RegisterHotKey(handle, 3, MOD_CONTROL | MOD_ALT | MOD_SHIFT, KeyPause); RegisterHotKey(handle, 4, MOD_CONTROL | MOD_ALT | MOD_SHIFT, KeyResume); RegisterHotKey(handle, 5, MOD_CONTROL | MOD_ALT | MOD_SHIFT, KeyStopReplaying); + RegisterHotKey(handle, 5, MOD_CONTROL | MOD_ALT | MOD_SHIFT, KeySave); return true; } } @@ -94,22 +96,25 @@ public bool HandleWindowsEvent(int message, IntPtr wParam, IntPtr lParam) switch (key) { case KeyRecord: - await stateMachine.TransitAsync(StateMachine.Event.Record); + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.Record); break; case KeyStopRecording: - await stateMachine.TransitAsync(StateMachine.Event.Stop); + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.Stop); break; case KeyReplay: - await stateMachine.TransitAsync(StateMachine.Event.Replay); + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.Replay); break; case KeyPause: - await stateMachine.TransitAsync(StateMachine.Event.Pause); + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.Pause); break; case KeyResume: - await stateMachine.TransitAsync(StateMachine.Event.Resume); + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.Resume); break; case KeyStopReplaying: - await stateMachine.TransitAsync(StateMachine.Event.RequestStopping); + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.RequestStopping); + break; + case KeySave: + await stateMachine.TransitFromShortcutAsync(StateMachine.Event.Save); break; } } diff --git a/FlightRecorder.Client.ViewModels/StateMachine.cs b/FlightRecorder.Client.ViewModels/StateMachine.cs index 8b4221d..051faa3 100644 --- a/FlightRecorder.Client.ViewModels/StateMachine.cs +++ b/FlightRecorder.Client.ViewModels/StateMachine.cs @@ -1,6 +1,8 @@ using FlightRecorder.Client.Logics; using FlightRecorder.Client.ViewModels.States; using Microsoft.Extensions.Logging; +using System; +using System.IO; using System.Threading.Tasks; namespace FlightRecorder.Client @@ -11,6 +13,8 @@ public class StateMachine : StateMachineCore public const string SaveErrorMessage = "Flight Recorder cannot write the file to disk.\nPlease make sure the folder is accessible by Flight Recorder, and you are not overwriting a locked file."; private readonly MainViewModel viewModel; + private readonly ISettingsLogic settingsLogic; + private readonly IStorageLogic storageLogic; private readonly IRecorderLogic recorderLogic; private readonly IReplayLogic replayLogic; private readonly string currentVersion; @@ -56,11 +60,21 @@ public enum State End } - public StateMachine(ILogger logger, MainViewModel viewModel, IRecorderLogic recorderLogic, IReplayLogic replayLogic, IDialogLogic dialogLogic, VersionLogic versionLogic) - : base(logger, dialogLogic, viewModel) + public StateMachine( + ILogger logger, + MainViewModel viewModel, + ISettingsLogic settingsLogic, + IStorageLogic storageLogic, + IRecorderLogic recorderLogic, + IReplayLogic replayLogic, + IDialogLogic dialogLogic, + VersionLogic versionLogic + ) : base(logger, dialogLogic, viewModel) { logger.LogDebug("Creating instance of {class}", nameof(StateMachine)); this.viewModel = viewModel; + this.settingsLogic = settingsLogic; + this.storageLogic = storageLogic; this.recorderLogic = recorderLogic; this.replayLogic = replayLogic; this.currentVersion = versionLogic.GetVersion(); @@ -209,25 +223,46 @@ private bool StopRecording() return true; } - private async Task SaveRecordingAsync() + private async Task SaveRecordingAsync(ActionContext actionContext) { var data = replayLogic.ToData(currentVersion); - var fileName = await dialogLogic.SaveAsync(data); - if (!string.IsNullOrEmpty(fileName)) + + async Task GetSavePath() { - replayLogic.FromData(fileName, data); - return true; + if (actionContext.FromShortcut) + { + var folder = await settingsLogic.GetDefaultSaveFolderAsync(); + if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder)) + { + return Path.Combine(folder, $"{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.fltrec"); + } + } + return await dialogLogic.PickSaveFileAsync(); } - return false; + + var filePath = await GetSavePath(); + + if (string.IsNullOrEmpty(filePath)) + { + return false; + } + + await storageLogic.SaveAsync(filePath, data); + replayLogic.FromData(Path.GetFileName(filePath), data); + return true; } private async Task LoadRecordingAsync() { - var (fileName, data) = await dialogLogic.LoadAsync(); - if (data != null) + var selectedFile = await dialogLogic.PickOpenFileAsync(); + if (selectedFile != null) { - replayLogic.FromData(fileName, data); - return true; + var data = await storageLogic.LoadAsync(selectedFile.Value.fileStream); + if (data != null) + { + replayLogic.FromData(Path.GetFileName(selectedFile.Value.filePath), data); + return true; + } } return false; } diff --git a/FlightRecorder.Client.ViewModels/StateMachineCore.cs b/FlightRecorder.Client.ViewModels/StateMachineCore.cs index 4fc1150..21e8fd3 100644 --- a/FlightRecorder.Client.ViewModels/StateMachineCore.cs +++ b/FlightRecorder.Client.ViewModels/StateMachineCore.cs @@ -35,11 +35,10 @@ public StateMachineCore(ILogger logger, IDialogLogic dialogLogic, MainViewModel this.viewModel = viewModel; } - /// - /// - /// + public Task TransitFromShortcutAsync(Event e) => TransitAsync(e, true); + /// True to indicate that user did not cancel any prompt - public async Task TransitAsync(Event e) + public async Task TransitAsync(Event e, bool fromShortcut = false) { logger.LogDebug("Triggering event {event} from state {state}", e, CurrentState); @@ -53,11 +52,15 @@ public async Task TransitAsync(Event e) if (transition.ViaEvents != null) { - return await ExecuteMultipleTransitionsAsync(e, transition.ViaEvents, transition.WaitForEvents, transition.RevertErrorMessage); + return await ExecuteMultipleTransitionsAsync( + e, + transition.ViaEvents, transition.WaitForEvents, transition.RevertErrorMessage, + fromShortcut + ); } else { - return await ExecuteSingleTransitionAsync(e, transition); + return await ExecuteSingleTransitionAsync(e, transition, fromShortcut); } } else @@ -67,10 +70,12 @@ public async Task TransitAsync(Event e) } } - protected async Task ExecuteSingleTransitionAsync(Event originatingEvent, Transition transition) + protected async Task ExecuteSingleTransitionAsync(Event originatingEvent, Transition transition, bool fromShortcut) { var oldState = CurrentState; - var resultingState = await transition.ExecuteAsync(); + var resultingState = await transition.ExecuteAsync(new ActionContext( + fromShortcut + )); var success = true; @@ -104,7 +109,11 @@ protected async Task ExecuteSingleTransitionAsync(Event originatingEvent, /// The events that state machine should triggered instead /// Some events in the viaEvents list should not be triggered by the state machine itself. Instead, the state machine should wait for the event to be triggered externally before continue with the viaEvents list. /// Revert state if there is a an error and show the error message - protected async Task ExecuteMultipleTransitionsAsync(Event originatingEvent, Event[] viaEvents, Event[]? waitForEvents, string? revertErrorMessage) + protected async Task ExecuteMultipleTransitionsAsync( + Event originatingEvent, + Event[] viaEvents, Event[]? waitForEvents, string? revertErrorMessage, + bool fromShortcut + ) { var success = true; @@ -154,7 +163,9 @@ protected async Task ExecuteMultipleTransitionsAsync(Event originatingEven // TODO: maybe recurse here? var oldState = CurrentState; - var resultingState = await stateLogics[oldState][via].ExecuteAsync(); + var resultingState = await stateLogics[oldState][via].ExecuteAsync(new ActionContext( + fromShortcut + )); if (resultingState.HasValue) { CurrentState = resultingState.Value; diff --git a/FlightRecorder.Client.ViewModels/States/ActionContext.cs b/FlightRecorder.Client.ViewModels/States/ActionContext.cs new file mode 100644 index 0000000..9f7b844 --- /dev/null +++ b/FlightRecorder.Client.ViewModels/States/ActionContext.cs @@ -0,0 +1,5 @@ +namespace FlightRecorder.Client.ViewModels.States; + +public record ActionContext( + bool FromShortcut +); diff --git a/FlightRecorder.Client.ViewModels/States/Transition.cs b/FlightRecorder.Client.ViewModels/States/Transition.cs index 3f04ea7..479a02a 100644 --- a/FlightRecorder.Client.ViewModels/States/Transition.cs +++ b/FlightRecorder.Client.ViewModels/States/Transition.cs @@ -23,9 +23,15 @@ public record Transition /// /// Can be called multiple times /// - public Transition Then(Func action) => this with { Actions = Actions.Add(action) }; + public Transition Then(Func action) => Then((context) => action()); - public Transition Then(Func> action) => this with { Actions = Actions.Add(action) }; + /// + /// Can be called multiple times + /// + public Transition Then(Func action) => this with { Actions = Actions.Add(action) }; + + public Transition Then(Func> action) => Then((context) => action()); + public Transition Then(Func> action) => this with { Actions = Actions.Add(action) }; public Transition By(Event e) => this with { ByEvent = e }; @@ -35,7 +41,7 @@ public record Transition public Transition RevertOnError(string errorMessage) => this with { RevertErrorMessage = errorMessage }; - public async Task ExecuteAsync() + public async Task ExecuteAsync(ActionContext actionContext) { if (ViaEvents != null) throw new InvalidOperationException($"This transition from {FromState} by {ByEvent} cannot be executed directly!"); @@ -43,12 +49,12 @@ public record Transition { switch (action) { - case Func syncAction: - if (!syncAction()) + case Func syncAction: + if (!syncAction(actionContext)) return null; break; - case Func> asyncAction: - if (!await asyncAction()) + case Func> asyncAction: + if (!await asyncAction(actionContext)) return null; break; } diff --git a/FlightRecorder.Client/App.xaml.cs b/FlightRecorder.Client/App.xaml.cs index f315605..eb70674 100644 --- a/FlightRecorder.Client/App.xaml.cs +++ b/FlightRecorder.Client/App.xaml.cs @@ -105,6 +105,7 @@ private void ConfigureServices(ServiceCollection services) services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/FlightRecorder.Client/DialogLogic.cs b/FlightRecorder.Client/DialogLogic.cs index cf1ee16..e486971 100644 --- a/FlightRecorder.Client/DialogLogic.cs +++ b/FlightRecorder.Client/DialogLogic.cs @@ -1,12 +1,13 @@ using FlightRecorder.Client.Logics; -using ICSharpCode.SharpZipLib.Zip; using Microsoft.Extensions.Logging; -using Microsoft.Win32; using System; using System.IO; -using System.Text.Json; using System.Threading.Tasks; using System.Windows; +using System.Windows.Forms; +using MessageBox = System.Windows.MessageBox; +using OpenFileDialog = Microsoft.Win32.OpenFileDialog; +using SaveFileDialog = Microsoft.Win32.SaveFileDialog; namespace FlightRecorder.Client { @@ -31,7 +32,8 @@ public void Error(string error) MessageBox.Show(error, "Flight Recorder", MessageBoxButton.OK, MessageBoxImage.Error); } - public async Task SaveAsync(SavedData data) + /// Full path of the selected file or null + public Task PickSaveFileAsync() { var dialog = new SaveFileDialog { @@ -40,32 +42,13 @@ public void Error(string error) }; if (dialog.ShowDialog() == true) { - using (var fileStream = new FileStream(dialog.FileName, FileMode.Create)) - { - using var outStream = new ZipOutputStream(fileStream); - - outStream.SetLevel(9); - - var entry = new ZipEntry("data.json") - { - DateTime = DateTime.Now - }; - outStream.PutNextEntry(entry); - - await JsonSerializer.SerializeAsync(outStream, data); - - outStream.Finish(); - } - - logger.LogDebug("Saved file into {fileName}", dialog.FileName); - - return Path.GetFileName(dialog.FileName); + return Task.FromResult(dialog.FileName); } - return null; + return Task.FromResult(null); } - public async Task<(string? fileName, SavedData? data)> LoadAsync() + public async Task<(string filePath, Stream fileStream)?> PickOpenFileAsync() { var dialog = new OpenFileDialog { @@ -74,24 +57,19 @@ public async Task<(string? fileName, SavedData? data)> LoadAsync() if (dialog.ShowDialog() == true) { - using var file = dialog.OpenFile(); - using var zipFile = new ZipFile(file); - - foreach (ZipEntry entry in zipFile) - { - if (entry.IsFile && entry.Name == "data.json") - { - using var stream = zipFile.GetInputStream(entry); - - var result = await JsonSerializer.DeserializeAsync(stream); - - logger.LogDebug("Loaded file from {fileName}", dialog.FileName); + return (dialog.FileName, dialog.OpenFile()); + } + return null; + } - return (Path.GetFileName(dialog.FileName), result); - } - } + public Task PickSaveFolderAsync() + { + var dialog = new FolderBrowserDialog(); + if (dialog.ShowDialog() == DialogResult.OK) + { + return Task.FromResult(dialog.SelectedPath); } - return (null, null); + return Task.FromResult(null); } } } diff --git a/FlightRecorder.Client/FlightRecorder.Client.csproj b/FlightRecorder.Client/FlightRecorder.Client.csproj index 717bf0d..2b53237 100644 --- a/FlightRecorder.Client/FlightRecorder.Client.csproj +++ b/FlightRecorder.Client/FlightRecorder.Client.csproj @@ -4,6 +4,7 @@ WinExe net6.0-windows true + true 0.21.0 Nguyen Quy Hy Flight Recorder @@ -31,7 +32,6 @@ - diff --git a/FlightRecorder.Client/ShortcutKeysWindow.xaml b/FlightRecorder.Client/ShortcutKeysWindow.xaml index 8c31687..46c5c3f 100644 --- a/FlightRecorder.Client/ShortcutKeysWindow.xaml +++ b/FlightRecorder.Client/ShortcutKeysWindow.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:FlightRecorder.Client" mc:Ignorable="d" Loaded="Window_Loaded" - Title="Shortcut Keys" ResizeMode="NoResize" WindowStartupLocation="CenterOwner" Height="220" Width="350"> + Title="Shortcut Keys" ResizeMode="NoResize" WindowStartupLocation="CenterOwner" Height="260" Width="350"> @@ -27,6 +27,9 @@ + + + @@ -50,7 +53,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/FlightRecorder.Client/ShortcutKeysWindow.xaml.cs b/FlightRecorder.Client/ShortcutKeysWindow.xaml.cs index e2a8226..1790107 100644 --- a/FlightRecorder.Client/ShortcutKeysWindow.xaml.cs +++ b/FlightRecorder.Client/ShortcutKeysWindow.xaml.cs @@ -1,17 +1,6 @@ using FlightRecorder.Client.Logics; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; namespace FlightRecorder.Client { @@ -21,26 +10,61 @@ namespace FlightRecorder.Client public partial class ShortcutKeysWindow : Window { private readonly ISettingsLogic settingsLogic; + private readonly IDialogLogic dialogLogic; - public ShortcutKeysWindow(ISettingsLogic settingsLogic) + public ShortcutKeysWindow(ISettingsLogic settingsLogic, IDialogLogic dialogLogic) { InitializeComponent(); this.settingsLogic = settingsLogic; + this.dialogLogic = dialogLogic; } private async void Window_Loaded(object sender, RoutedEventArgs e) { - IsShortcutKeysEnabled.IsChecked = await settingsLogic.IsShortcutKeysEnabledAsync(); + var isShortcutKeysEnabled = await settingsLogic.IsShortcutKeysEnabledAsync(); + IsShortcutKeysEnabled.IsChecked = + TextDefaultSaveFolder.IsEnabled = + ButtonPickDefaultSaveFolder.IsEnabled = isShortcutKeysEnabled; + + var defaultSaveFolder = await settingsLogic.GetDefaultSaveFolderAsync(); + TextDefaultSaveFolder.Text = defaultSaveFolder ?? ""; + ButtonRemoveDefaultSaveFolder.IsEnabled = isShortcutKeysEnabled && + !string.IsNullOrEmpty(defaultSaveFolder); + } + + private async void CheckBox_Checked(object sender, RoutedEventArgs e) + { + await settingsLogic.SetShortcutKeysEnabledAsync(true); + var defaultSaveFolder = await settingsLogic.GetDefaultSaveFolderAsync(); + TextDefaultSaveFolder.IsEnabled = + ButtonPickDefaultSaveFolder.IsEnabled = true; + ButtonRemoveDefaultSaveFolder.IsEnabled = !string.IsNullOrEmpty(defaultSaveFolder); + } + + private async void CheckBox_Unchecked(object sender, RoutedEventArgs e) + { + await settingsLogic.SetShortcutKeysEnabledAsync(false); + TextDefaultSaveFolder.IsEnabled = + ButtonPickDefaultSaveFolder.IsEnabled = + ButtonRemoveDefaultSaveFolder.IsEnabled = false; } - private void CheckBox_Checked(object sender, RoutedEventArgs e) + private async void ButtonPickDefaultSaveFolder_Click(object sender, RoutedEventArgs e) { - settingsLogic.SetShortcutKeysEnabledAsync(true); + var folder = await dialogLogic.PickSaveFolderAsync(); + if (!string.IsNullOrEmpty(folder)) + { + await settingsLogic.SetDefaultSaveFolderAsync(folder); + ButtonRemoveDefaultSaveFolder.IsEnabled = true; + TextDefaultSaveFolder.Text = folder; + } } - private void CheckBox_Unchecked(object sender, RoutedEventArgs e) + private async void ButtonRemoveDefaultSaveFolder_Click(object sender, RoutedEventArgs e) { - settingsLogic.SetShortcutKeysEnabledAsync(false); + await settingsLogic.SetDefaultSaveFolderAsync(null); + ButtonRemoveDefaultSaveFolder.IsEnabled = false; + TextDefaultSaveFolder.Text = string.Empty; } } }