using System; using System.Linq; using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Controls; using FluentAvalonia.UI.Controls; using Ninject; using Ninject.Parameters; using ReactiveUI; namespace Artemis.UI.Shared.Services.Builders; /// /// Represents a builder that can be used to create Fluent UI dialogs. /// public class ContentDialogBuilder { private readonly ContentDialog _contentDialog; private readonly IKernel _kernel; private readonly Window _parent; private ContentDialogViewModelBase? _viewModel; internal ContentDialogBuilder(IKernel kernel, Window parent) { _kernel = kernel; _parent = parent; _contentDialog = new ContentDialog { CloseButtonText = "Close" }; } /// /// Changes the title of the dialog. /// /// The new title. /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithTitle(string? title) { _contentDialog.Title = title; return this; } /// /// Changes the content of the dialog. /// /// The new content. /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithContent(string? content) { _contentDialog.Content = content; return this; } /// /// Changes the default button of the dialog that is pressed on enter. /// /// The default button. /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton) { _contentDialog.DefaultButton = (FluentAvalonia.UI.Controls.ContentDialogButton) defaultButton; return this; } /// /// Changes the primary button of the dialog. /// /// An action to configure the button. /// The builder that can be used to further build the dialog. public ContentDialogBuilder HavingPrimaryButton(Action configure) { ContentDialogButtonBuilder builder = new(); configure(builder); _contentDialog.IsPrimaryButtonEnabled = true; _contentDialog.PrimaryButtonText = builder.Text; _contentDialog.PrimaryButtonCommand = builder.Command; _contentDialog.PrimaryButtonCommandParameter = builder.CommandParameter; // I feel like this isn't my responsibility... if (builder.Command != null) { _contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter); builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter); } return this; } /// /// Changes the secondary button of the dialog. /// /// An action to configure the button. /// The builder that can be used to further build the dialog. public ContentDialogBuilder HavingSecondaryButton(Action configure) { ContentDialogButtonBuilder builder = new(); configure(builder); _contentDialog.IsSecondaryButtonEnabled = true; _contentDialog.SecondaryButtonText = builder.Text; _contentDialog.SecondaryButtonCommand = builder.Command; _contentDialog.SecondaryButtonCommandParameter = builder.CommandParameter; // I feel like this isn't my responsibility... if (builder.Command != null) { _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter); builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter); } return this; } /// /// Changes the text of the close button of the dialog. /// /// The new text. /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithCloseButtonText(string? text) { _contentDialog.CloseButtonText = text; return this; } /// /// Changes the view model of the content dialog, hosting it inside the dialog. /// /// The type of the view model to host. /// The resulting view model. /// Optional parameters to pass to the constructor of the view model, case and order sensitive. /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithViewModel(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase { IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast().ToArray(); viewModel = _kernel.Get(paramsArray); viewModel.ContentDialog = _contentDialog; _contentDialog.Content = viewModel; _viewModel = viewModel; return this; } /// /// Changes the dialog to take the full height of the window it's being hosted in. /// /// The builder that can be used to further build the dialog. public ContentDialogBuilder WithFullSize() { _contentDialog.FullSizeDesired = true; return this; } /// /// Asynchronously shows the content dialog. /// /// A task containing the result of the content dialog. /// Thrown when the parent window does not contain a panel at its root. public async Task ShowAsync() { if (_parent.Content is not Panel panel) throw new ArtemisSharedUIException($"The parent window {_parent.GetType().FullName} should contain a panel at its root"); try { panel.Children.Add(_contentDialog); ContentDialogResult result = await _contentDialog.ShowAsync(); // Take the dialog away from the VM in case it's going to try to hide it again or whatever... if (_viewModel != null) _viewModel.ContentDialog = null; return result; } finally { panel.Children.Remove(_contentDialog); } } } /// /// Represents a content dialog button. /// public enum ContentDialogButton { /// /// No button. /// None, /// /// The primary button. /// Primary, /// /// The secondary button. /// Secondary, /// /// The close button. /// Close } /// /// Represents a builder that can be used to create buttons inside content dialogs. /// public class ContentDialogButtonBuilder { internal ContentDialogButtonBuilder() { } internal string? Text { get; set; } internal ICommand? Command { get; set; } internal Action? Action { get; set; } internal object? CommandParameter { get; set; } /// /// Changes text message of the button. /// /// The new text. /// The notification builder that can be used to further build the button. public ContentDialogButtonBuilder WithText(string? text) { Text = text; return this; } /// /// Changes action that is called when the button is clicked. /// /// The action to call when the button is clicked. /// The builder that can be used to further build the button. public ContentDialogButtonBuilder WithAction(Action action) { Action = action; Command = ReactiveCommand.Create(() => Action()); return this; } /// /// Changes command that is called when the button is clicked. /// /// The command to call when the button is clicked. /// The builder that can be used to further build the button. public ContentDialogButtonBuilder WithCommand(ICommand? command) { Command = command; return this; } /// /// Changes parameter of the command that is called when the button is clicked. /// /// The parameter of the command to call when the button is clicked. /// The builder that can be used to further build the button. public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter) { CommandParameter = commandParameter; return this; } }