diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IDialogServiceBase.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IDialogServiceBase.cs new file mode 100644 index 0000000..01f0991 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IDialogServiceBase.cs @@ -0,0 +1,11 @@ +using FileTime.App.Core.ViewModels; +using FileTime.Core.Interactions; +using ObservableComputations; + +namespace FileTime.App.Core.Services; + +public interface IDialogServiceBase : IUserCommunicationService +{ + ScalarComputing ReadInput { get; } + ScalarComputing LastMessageBox { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IModalService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IModalService.cs index 32859fd..c91b08e 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IModalService.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IModalService.cs @@ -1,11 +1,11 @@ -using DynamicData; +using System.Collections.ObjectModel; using FileTime.App.Core.ViewModels; namespace FileTime.App.Core.Services; public interface IModalService { - IObservable> OpenModals { get; } + ReadOnlyObservableCollection OpenModals { get; } void OpenModal(IModalViewModel modalToOpen); void CloseModal(IModalViewModel modalToClose); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/MessageBoxViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/MessageBoxViewModel.cs similarity index 77% rename from src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/MessageBoxViewModel.cs rename to src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/MessageBoxViewModel.cs index 29ef78e..0a68d4d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/MessageBoxViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/MessageBoxViewModel.cs @@ -1,11 +1,8 @@ -using FileTime.App.Core.ViewModels; using FileTime.Core.Interactions; -using MvvmGen; -namespace FileTime.GuiApp.App.ViewModels; +namespace FileTime.App.Core.ViewModels; -[ViewModel] -public partial class MessageBoxViewModel : IModalViewModel +public class MessageBoxViewModel : IModalViewModel { private readonly Action _handler; public string Text { get; } @@ -19,7 +16,7 @@ public partial class MessageBoxViewModel : IModalViewModel Action handler, bool showCancel = true, string? okText = null, - string? cancelText = null) : this() + string? cancelText = null) { _handler = handler; Text = text; @@ -28,9 +25,7 @@ public partial class MessageBoxViewModel : IModalViewModel CancelText = cancelText ?? "No"; } - [Command] public void Ok() => _handler.Invoke(this, MessageBoxResult.Ok); - [Command] public void Cancel() => _handler.Invoke(this, MessageBoxResult.Cancel); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/ReadInputsViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ReadInputsViewModel.cs similarity index 92% rename from src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/ReadInputsViewModel.cs rename to src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ReadInputsViewModel.cs index 4a302b7..b6f0520 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/ViewModels/ReadInputsViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ReadInputsViewModel.cs @@ -1,8 +1,7 @@ using System.Collections.ObjectModel; -using FileTime.App.Core.ViewModels; using FileTime.Core.Interactions; -namespace FileTime.GuiApp.App.ViewModels; +namespace FileTime.App.Core.ViewModels; public class ReadInputsViewModel : IModalViewModel { diff --git a/src/AppCommon/FileTime.App.Core/Services/DefaultModeKeyInputHandler.cs b/src/AppCommon/FileTime.App.Core/Services/DefaultModeKeyInputHandler.cs index bfceeec..f28984c 100644 --- a/src/AppCommon/FileTime.App.Core/Services/DefaultModeKeyInputHandler.cs +++ b/src/AppCommon/FileTime.App.Core/Services/DefaultModeKeyInputHandler.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using DeclarativeProperty; using FileTime.App.Core.Configuration; using FileTime.App.Core.Extensions; @@ -23,7 +24,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly IIdentifiableUserCommandService _identifiableUserCommandService; private readonly IPossibleCommandsService _possibleCommandsService; - private readonly BindedCollection _openModals; + private readonly ReadOnlyObservableCollection _openModals; public DefaultModeKeyInputHandler( IAppState appState, @@ -46,7 +47,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler .Map(t => t?.CurrentLocation) .Switch(); - _openModals = modalService.OpenModals.ToBindedCollection(); + _openModals = modalService.OpenModals; _keysToSkip.Add(new[] {new KeyConfig(Keys.Up)}); _keysToSkip.Add(new[] {new KeyConfig(Keys.Down)}); @@ -75,9 +76,9 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler { var doGeneralReset = _appState.PreviousKeys.Count > 1; - if ((_openModals.Collection?.Count ?? 0) > 0) + if (_openModals.Count > 0) { - _modalService.CloseModal(_openModals.Collection!.Last()); + _modalService.CloseModal(_openModals.Last()); } else if (_currentLocation.Value?.GetExtension() is { } escHandler) { diff --git a/src/AppCommon/FileTime.App.Core/Services/DialogServiceBase.cs b/src/AppCommon/FileTime.App.Core/Services/DialogServiceBase.cs new file mode 100644 index 0000000..cad7c13 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/Services/DialogServiceBase.cs @@ -0,0 +1,130 @@ +using DynamicData; +using FileTime.App.Core.ViewModels; +using FileTime.Core.Interactions; +using ObservableComputations; + +namespace FileTime.App.Core.Services; + +public abstract class DialogServiceBase : IDialogServiceBase +{ + private readonly IModalService _modalService; + private OcConsumer _readInputConsumer = new(); + private OcConsumer _lastMessageBoxConsumer = new(); + public ScalarComputing ReadInput { get; } + public ScalarComputing LastMessageBox { get; } + + protected DialogServiceBase(IModalService modalService) + { + _modalService = modalService; + ReadInput = modalService + .OpenModals + .OfTypeComputing() + .FirstComputing() + .For(_readInputConsumer); + + LastMessageBox = + modalService + .OpenModals + .OfTypeComputing() + .LastComputing() + .For(_lastMessageBoxConsumer); + } + + private void ReadInputs( + IEnumerable inputs, + Action inputHandler, + Action? cancelHandler = null, + IEnumerable? previews = null) + { + var modalViewModel = new ReadInputsViewModel + { + Inputs = inputs.ToList(), + SuccessHandler = HandleReadInputsSuccess, + CancelHandler = HandleReadInputsCancel + }; + + if (previews is not null) + { + modalViewModel.Previews.AddRange(previews); + } + + _modalService.OpenModal(modalViewModel); + + void HandleReadInputsSuccess(ReadInputsViewModel readInputsViewModel) + { + _modalService.CloseModal(readInputsViewModel); + inputHandler(); + } + + void HandleReadInputsCancel(ReadInputsViewModel readInputsViewModel) + { + _modalService.CloseModal(readInputsViewModel); + cancelHandler?.Invoke(); + } + } + + public Task ReadInputs(IEnumerable fields, IEnumerable? previews = null) + { + var taskCompletionSource = new TaskCompletionSource(); + ReadInputs( + fields, + () => taskCompletionSource.SetResult(true), + () => taskCompletionSource.SetResult(false), + previews + ); + + return taskCompletionSource.Task; + } + + + public Task ReadInputs(params IInputElement[] fields) + { + var taskCompletionSource = new TaskCompletionSource(); + ReadInputs( + fields, + () => taskCompletionSource.SetResult(true), + () => taskCompletionSource.SetResult(false) + ); + + return taskCompletionSource.Task; + } + + public Task ReadInputs(IInputElement field, IEnumerable? previews = null) + { + var taskCompletionSource = new TaskCompletionSource(); + ReadInputs( + new[] {field}, + () => taskCompletionSource.SetResult(true), + () => taskCompletionSource.SetResult(false), + previews + ); + + return taskCompletionSource.Task; + } + + public abstract void ShowToastMessage(string text); + + public Task ShowMessageBox( + string text, + bool showCancel = true, + string? okText = null, + string? cancelText = null) + { + var taskCompletionSource = new TaskCompletionSource(); + _modalService.OpenModal( + new MessageBoxViewModel( + text, + (vm, result) => + { + _modalService.CloseModal(vm); + taskCompletionSource.SetResult(result); + }, + showCancel, + okText, + cancelText + ) + ); + + return taskCompletionSource.Task; + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/ModalService.cs b/src/AppCommon/FileTime.App.Core/Services/ModalService.cs index 2300fdb..17d3203 100644 --- a/src/AppCommon/FileTime.App.Core/Services/ModalService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/ModalService.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using DynamicData; using FileTime.App.Core.ViewModels; using Microsoft.Extensions.DependencyInjection; @@ -7,14 +8,14 @@ namespace FileTime.App.Core.Services; public class ModalService : IModalService { private readonly IServiceProvider _serviceProvider; - private readonly SourceList _openModals = new(); - public IObservable> OpenModals { get; } + private readonly ObservableCollection _openModals = new(); + public ReadOnlyObservableCollection OpenModals { get; } public event EventHandler? AllModalClosed; public ModalService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - OpenModals = _openModals.Connect().StartWithEmpty(); + OpenModals = new ReadOnlyObservableCollection(_openModals); } public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen); diff --git a/src/AppCommon/FileTime.App.Core/Services/RapidTravelModeKeyInputHandler.cs b/src/AppCommon/FileTime.App.Core/Services/RapidTravelModeKeyInputHandler.cs index a05e62d..85562e7 100644 --- a/src/AppCommon/FileTime.App.Core/Services/RapidTravelModeKeyInputHandler.cs +++ b/src/AppCommon/FileTime.App.Core/Services/RapidTravelModeKeyInputHandler.cs @@ -21,7 +21,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly ILogger _logger; private readonly IIdentifiableUserCommandService _identifiableUserCommandService; - private readonly BindedCollection _openModals; private ITabViewModel? _selectedTab; public RapidTravelModeKeyInputHandler( @@ -41,8 +40,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler _appState.SelectedTab.Subscribe(t => _selectedTab = t); - _openModals = modalService.OpenModals.ToBindedCollection(); - _appState.RapidTravelTextDebounced.Subscribe((v, _) => { if (_selectedTab?.Tab is not { } tab) return Task.CompletedTask; @@ -62,9 +59,9 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler if (args.Key == Keys.Escape) { args.Handled = true; - if ((_openModals.Collection?.Count ?? 0) > 0) + if (_modalService.OpenModals.Count > 0) { - _modalService.CloseModal(_openModals.Collection!.Last()); + _modalService.CloseModal(_modalService.OpenModals.Last()); } else { diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs index fed0df6..f159f15 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs @@ -1,5 +1,7 @@ using FileTime.App.CommandPalette.ViewModels; using FileTime.App.Core.ViewModels; +using FileTime.ConsoleUI.App.Services; +using FileTime.Core.Interactions; namespace FileTime.ConsoleUI.App; @@ -10,4 +12,6 @@ public interface IRootViewModel string UserName { get; } string MachineName { get; } ICommandPaletteViewModel CommandPalette { get; } + IDialogService DialogService { get; } + event Action? FocusReadInputElement; } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Services/IDialogService.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Services/IDialogService.cs new file mode 100644 index 0000000..6c31283 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/Services/IDialogService.cs @@ -0,0 +1,8 @@ +using FileTime.App.Core.Services; + +namespace FileTime.ConsoleUI.App.Services; + +public interface IDialogService : IDialogServiceBase +{ + +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs index 86880c7..9220598 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs @@ -6,11 +6,18 @@ using FileTime.ConsoleUI.App.KeyInputHandling; using GeneralInputKey; using TerminalUI; using TerminalUI.ConsoleDrivers; +using TerminalUI.Traits; namespace FileTime.ConsoleUI.App; public class App : IApplication { + private static readonly List KeysToFurtherProcess = new() + { + Keys.Enter, + Keys.Escape + }; + private readonly ILifecycleService _lifecycleService; private readonly IConsoleAppState _consoleAppState; @@ -86,11 +93,13 @@ public class App : IApplication SpecialKeysStatus = specialKeysStatus }; - if (focusManager.Focused is { } focused) + var focused = focusManager.Focused; + if (focused is { }) { focused.HandleKeyInput(keyEventArgs); } - else + + if (focused is null || (!keyEventArgs.Handled && KeysToFurtherProcess.Contains(keyEventArgs.Key))) { _keyInputHandlerService.HandleKeyInput(keyEventArgs, specialKeysStatus); } diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleUserCommunicationService.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleUserCommunicationService.cs deleted file mode 100644 index b738711..0000000 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/ConsoleUserCommunicationService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using FileTime.Core.Interactions; - -namespace FileTime.ConsoleUI.App; - -public class ConsoleUserCommunicationService : IUserCommunicationService -{ - public Task ReadInputs(params IInputElement[] fields) => throw new NotImplementedException(); - - public Task ReadInputs(IInputElement field, IEnumerable? previews = null) => throw new NotImplementedException(); - - public Task ReadInputs(IEnumerable fields, IEnumerable? previews = null) => throw new NotImplementedException(); - - public void ShowToastMessage(string text) => throw new NotImplementedException(); - - public Task ShowMessageBox(string text, bool showCancel = true, string? okText = null, string? cancelText = null) => throw new NotImplementedException(); -} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/CommandPalette.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/CommandPalette.cs index daf502f..41ac5ae 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/CommandPalette.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/CommandPalette.cs @@ -65,7 +65,7 @@ public class CommandPalette { new Border { - Margin = new Thickness(0, 0, 0, 2), + Margin = new Thickness(0, 0, 0, 1), Content = inputTextBox }, new ListView @@ -103,15 +103,13 @@ public class CommandPalette item.Bind( item.Parent, d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedBackgroundColor : null, - t => t.Background, - v => v + t => t.Background ); item.Bind( item.Parent, d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedForegroundColor : null, - t => t.Foreground, - v => v + t => t.Foreground ); return root; diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/KeyInputHandling/KeyInputHandlerService.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/KeyInputHandling/KeyInputHandlerService.cs index 7ca1330..192b6fc 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/KeyInputHandling/KeyInputHandlerService.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/KeyInputHandling/KeyInputHandlerService.cs @@ -28,11 +28,11 @@ public class KeyInputHandlerService : IKeyInputHandlerService if (_appState.ViewMode.Value == ViewMode.Default) { - Task.Run(async () => await _defaultModeKeyInputHandler.HandleInputKey(keyEvent)).Wait(); + Task.Run(async () => await _defaultModeKeyInputHandler.HandleInputKey(keyEvent)); } else { - Task.Run(async () => await _rapidTravelModeKeyInputHandler.HandleInputKey(keyEvent)).Wait(); + Task.Run(async () => await _rapidTravelModeKeyInputHandler.HandleInputKey(keyEvent)); } } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs index c632ec7..8858c6d 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs @@ -1,13 +1,18 @@ -using FileTime.App.Core.Models.Enums; +using System.Collections.Specialized; +using System.ComponentModel; +using FileTime.App.Core.Models.Enums; using FileTime.App.Core.ViewModels; using FileTime.ConsoleUI.App.Controls; using FileTime.ConsoleUI.App.Styling; using FileTime.Core.Enums; +using FileTime.Core.Interactions; +using GeneralInputKey; using TerminalUI; using TerminalUI.Color; using TerminalUI.Controls; using TerminalUI.Extensions; using TerminalUI.Models; +using TerminalUI.Traits; using TerminalUI.ViewExtensions; namespace FileTime.ConsoleUI.App; @@ -18,9 +23,12 @@ public class MainWindow private readonly IApplicationContext _applicationContext; private readonly ITheme _theme; private readonly CommandPalette _commandPalette; - private readonly Lazy _root; + private ItemsControl _readInputs = null!; + private IInputElement? _inputElementToFocus; + private Action? _readInputChildHandlerUnsubscriber; + public MainWindow( IRootViewModel rootViewModel, IApplicationContext applicationContext, @@ -32,6 +40,40 @@ public class MainWindow _theme = theme; _commandPalette = commandPalette; _root = new Lazy(Initialize); + + rootViewModel.FocusReadInputElement += element => + { + _inputElementToFocus = element; + UpdateReadInputsFocus(); + }; + } + + private void UpdateReadInputsFocus() + { + foreach (var readInputsChild in _readInputs.Children) + { + if (readInputsChild.DataContext == _inputElementToFocus) + { + if (FindFocusable(readInputsChild) is { } focusable) + { + focusable.Focus(); + _inputElementToFocus = null; + break; + } + } + } + + IFocusable? FindFocusable(IView view) + { + if (view is IFocusable focusable) return focusable; + foreach (var viewVisualChild in view.VisualChildren) + { + if (FindFocusable(viewVisualChild) is { } focusableChild) + return focusableChild; + } + + return null; + } } public IEnumerable RootViews() => new[] @@ -46,10 +88,33 @@ public class MainWindow Name = "root", DataContext = _rootViewModel, ApplicationContext = _applicationContext, + Foreground = _theme.DefaultForegroundColor, ChildInitializer = { MainContent(), - _commandPalette.View() + _commandPalette.View(), + Dialogs(), + } + }; + + ((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) => + { + if (e.PropertyName == nameof(ItemsControl.Children)) + { + _readInputChildHandlerUnsubscriber?.Invoke(); + UpdateReadInputsFocus(); + if (_readInputs.Children is INotifyCollectionChanged notifyCollectionChanged) + { + notifyCollectionChanged.CollectionChanged += NotifyCollectionChangedEventHandler; + _readInputChildHandlerUnsubscriber = () => { notifyCollectionChanged.CollectionChanged -= NotifyCollectionChangedEventHandler; }; + } + + void NotifyCollectionChangedEventHandler( + object? sender, + NotifyCollectionChangedEventArgs e) + { + UpdateReadInputsFocus(); + } } }; return root; @@ -188,7 +253,6 @@ public class MainWindow ItemTemplate = item => { var textBlock = item.CreateChild>(); - textBlock.Foreground = _theme.DefaultForegroundColor; textBlock.Bind( textBlock, @@ -356,4 +420,137 @@ public class MainWindow (ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemBackgroundColor, _ => throw new NotImplementedException() }; + + private IView Dialogs() + { + var root = new Border() + { + Margin = 5, + BorderThickness = 1, + Content = new Grid() + { + ChildInitializer = + { + ReadInputs() + } + } + }; + + root.Bind( + root, + d => d.DialogService.ReadInput.Value != null, + v => v.IsVisible); + return root; + } + + private ItemsControl ReadInputs() + { + var readInputs = new ItemsControl + { + ItemTemplate = () => + { + var root = new Grid + { + ColumnDefinitionsObject = "* *", + ChildInitializer = + { + new TextBlock() + .Setup(t => t.Bind( + t, + c => c.Label, + tb => tb.Text + )), + new Grid() + { + Extensions = + { + new GridPositionExtension(1, 0) + }, + ChildInitializer = + { + new Border + { + Content = + new TextBox() + .Setup(t => t.Bind( + t, + d => ((TextInputElement) d).Value, + tb => tb.Text, + v => v ?? string.Empty, + fallbackValue: string.Empty + )) + .WithTextHandler((tb, t) => + { + if (tb.DataContext is TextInputElement textInputElement) + textInputElement.Value = t; + }) + } + .Setup(t => t.Bind( + t, + d => d.Type == InputType.Text, + tb => tb.IsVisible + )), + new Border + { + Content = + new TextBox + { + PasswordChar = '*' + } + .Setup(t => t.Bind( + t, + d => ((PasswordInputElement) d).Value, + tb => tb.Text, + v => v ?? string.Empty, + fallbackValue: string.Empty + )) + .WithTextHandler((tb, t) => + { + if (tb.DataContext is PasswordInputElement textInputElement) + textInputElement.Value = t; + }) + } + .Setup(t => t.Bind( + t, + d => d.Type == InputType.Password, + tb => tb.IsVisible + )) + //TODO: OptionInputElement + } + } + } + }; + + return root; + } + } + .Setup(t => t.Bind( + t, + d => d.DialogService.ReadInput.Value.Inputs, + c => c.ItemsSource, + v => v + )); + + readInputs.WithKeyHandler((_, e) => + { + if (e.Key == Keys.Enter) + { + if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel) + readInputsViewModel.Process(); + + e.Handled = true; + } + else if (e.Key == Keys.Escape) + { + if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel) + readInputsViewModel.Cancel(); + + e.Handled = true; + } + }); + + _readInputs = readInputs; + + return readInputs; + } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs index 7afe124..72873ca 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs @@ -1,5 +1,7 @@ using FileTime.App.CommandPalette.ViewModels; using FileTime.App.Core.ViewModels; +using FileTime.ConsoleUI.App.Services; +using FileTime.Core.Interactions; namespace FileTime.ConsoleUI.App; @@ -10,14 +12,29 @@ public class RootViewModel : IRootViewModel public IPossibleCommandsViewModel PossibleCommands { get; } public IConsoleAppState AppState { get; } public ICommandPaletteViewModel CommandPalette { get; } + public IDialogService DialogService { get; } + public event Action? FocusReadInputElement; public RootViewModel( IConsoleAppState appState, IPossibleCommandsViewModel possibleCommands, - ICommandPaletteViewModel commandPalette) + ICommandPaletteViewModel commandPalette, + IDialogService dialogService) { AppState = appState; PossibleCommands = possibleCommands; CommandPalette = commandPalette; + DialogService = dialogService; + + DialogService.ReadInput.PropertyChanged += (o, e) => + { + if (e.PropertyName == nameof(DialogService.ReadInput.Value)) + { + if (DialogService.ReadInput.Value is {Inputs.Count: > 0} readInputs) + { + FocusReadInputElement?.Invoke(readInputs.Inputs[0]); + } + } + }; } } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Services/DialogService.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Services/DialogService.cs new file mode 100644 index 0000000..82ed560 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Services/DialogService.cs @@ -0,0 +1,15 @@ +using FileTime.App.Core.Services; + +namespace FileTime.ConsoleUI.App.Services; + +public class DialogService : DialogServiceBase, IDialogService +{ + public DialogService(IModalService modalService) : base(modalService) + { + } + + public override void ShowToastMessage(string text) + { + // TODO: Implement + } +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs index 27bf589..df9a1df 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs @@ -19,13 +19,14 @@ public static class Startup services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(sp => sp.GetRequiredService()); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton, ConsoleAppKeyService>(); services.TryAddSingleton(); services.AddSingleton(); services.TryAddSingleton(new ApplicationConfiguration(true)); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(sp => sp.GetRequiredService()); services.Configure(configuration); return services; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/FileTime.GuiApp.App.Abstractions.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/FileTime.GuiApp.App.Abstractions.csproj index ced9812..f9e8a9c 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/FileTime.GuiApp.App.Abstractions.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/FileTime.GuiApp.App.Abstractions.csproj @@ -21,6 +21,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/Services/IDialogService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/Services/IDialogService.cs index 73602ff..e3eb1bb 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/Services/IDialogService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App.Abstractions/Services/IDialogService.cs @@ -1,10 +1,7 @@ -using FileTime.Core.Interactions; -using FileTime.GuiApp.App.ViewModels; +using FileTime.App.Core.Services; namespace FileTime.GuiApp.App.Services; -public interface IDialogService : IUserCommunicationService +public interface IDialogService : IDialogServiceBase { - IObservable ReadInput { get; } - IObservable LastMessageBox { get; } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Services/DialogService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Services/DialogService.cs index ad8323d..79a7c9d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Services/DialogService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Services/DialogService.cs @@ -1,145 +1,24 @@ -using System.Reactive.Linq; using Avalonia.Threading; -using DynamicData; using FileTime.App.Core.Services; -using FileTime.Core.Interactions; using FileTime.GuiApp.App.ViewModels; namespace FileTime.GuiApp.App.Services; -public class DialogService : IDialogService +public class DialogService : DialogServiceBase, IDialogService { - private readonly IModalService _modalService; private readonly IGuiAppState _guiAppState; - public IObservable ReadInput { get; } - public IObservable LastMessageBox { get; } - - public DialogService(IModalService modalService, IGuiAppState guiAppState) + public DialogService(IModalService modalService, IGuiAppState guiAppState) : base(modalService) { - _modalService = modalService; _guiAppState = guiAppState; - ReadInput = modalService - .OpenModals - .ToCollection() - .Select(modals => - (ReadInputsViewModel?) modals.FirstOrDefault(m => m is ReadInputsViewModel) - ) - .Publish(null) - .RefCount(); - - LastMessageBox = - modalService - .OpenModals - .Filter(m => m is MessageBoxViewModel) - .Transform(m => (MessageBoxViewModel) m) - .ToCollection() - .Select(m => m.LastOrDefault()); } - private void ReadInputs( - IEnumerable inputs, - Action inputHandler, - Action? cancelHandler = null, - IEnumerable? previews = null) - { - var modalViewModel = new ReadInputsViewModel - { - Inputs = inputs.ToList(), - SuccessHandler = HandleReadInputsSuccess, - CancelHandler = HandleReadInputsCancel - }; - - if (previews is not null) - { - modalViewModel.Previews.AddRange(previews); - } - - _modalService.OpenModal(modalViewModel); - - void HandleReadInputsSuccess(ReadInputsViewModel readInputsViewModel) - { - _modalService.CloseModal(readInputsViewModel); - inputHandler(); - } - - void HandleReadInputsCancel(ReadInputsViewModel readInputsViewModel) - { - _modalService.CloseModal(readInputsViewModel); - cancelHandler?.Invoke(); - } - } - - public Task ReadInputs(IEnumerable fields, IEnumerable? previews = null) - { - var taskCompletionSource = new TaskCompletionSource(); - ReadInputs( - fields, - () => taskCompletionSource.SetResult(true), - () => taskCompletionSource.SetResult(false), - previews - ); - - return taskCompletionSource.Task; - } - - - public Task ReadInputs(params IInputElement[] fields) - { - var taskCompletionSource = new TaskCompletionSource(); - ReadInputs( - fields, - () => taskCompletionSource.SetResult(true), - () => taskCompletionSource.SetResult(false) - ); - - return taskCompletionSource.Task; - } - - public Task ReadInputs(IInputElement field, IEnumerable? previews = null) - { - var taskCompletionSource = new TaskCompletionSource(); - ReadInputs( - new[] {field}, - () => taskCompletionSource.SetResult(true), - () => taskCompletionSource.SetResult(false), - previews - ); - - return taskCompletionSource.Task; - } - - public void ShowToastMessage(string text) - { - Task.Run(async () => - { - await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Add(text)); - await Task.Delay(5000); - await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Remove(text)); - }); - } - - public Task ShowMessageBox( - string text, - bool showCancel = true, - string? okText = null, - string? cancelText = null) - { - var taskCompletionSource = new TaskCompletionSource(); - _modalService.OpenModal( - new MessageBoxViewModel( - text, - (vm, result) => - { - _modalService.CloseModal(vm); - taskCompletionSource.SetResult(result); - }, - showCancel, - okText, - cancelText - ) - ); - - return taskCompletionSource.Task; - } + public override void ShowToastMessage(string text) + => + Task.Run(async () => + { + await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Add(text)); + await Task.Delay(5000); + await Dispatcher.UIThread.InvokeAsync(() => _guiAppState.PopupTexts.Remove(text)); + }); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml index 180a64e..5210bbe 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml @@ -22,8 +22,6 @@ xmlns="https://github.com/avaloniaui" xmlns:appCoreModels="using:FileTime.App.Core.Models" xmlns:appInteractions="using:FileTime.App.Core.Interactions" - xmlns:config="using:FileTime.GuiApp.App.Configuration" - xmlns:configuration="clr-namespace:FileTime.App.Core.Configuration;assembly=FileTime.App.Core.Abstraction" xmlns:corevm="using:FileTime.App.Core.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" @@ -787,7 +785,7 @@ @@ -839,7 +837,7 @@ - + @@ -913,7 +911,7 @@ @@ -935,7 +933,7 @@ @@ -952,12 +950,12 @@ Margin="0,10,0,0" Orientation="Horizontal">