feat(configmanager): add IDialogService interface and AvaloniaDialogService

Add dialog service abstraction for platform-specific dialogs:
- IDialogService interface with folder picker, message, confirmation,
  diff preview, and validation results methods
- AvaloniaDialogService implementation using MsBox.Avalonia
- Basic implementations for ShowDiffPreviewAsync and ShowValidationResultsAsync
  (full dialogs to be implemented in Tasks 22-23)
- Add MessageBox.Avalonia package reference
This commit is contained in:
Joseph Doherty
2026-01-19 19:52:30 -05:00
parent c3684f5150
commit 7dd4c46cb7
3 changed files with 195 additions and 0 deletions
@@ -17,6 +17,7 @@
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.*" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.*" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="DiffPlex" Version="1.7.*" />
<PackageReference Include="MessageBox.Avalonia" Version="3.1.*" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.*" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.*" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.*" />
@@ -0,0 +1,149 @@
using System.Text;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using MsBox.Avalonia;
using MsBox.Avalonia.Enums;
namespace JdeScoping.ConfigManager.Services;
/// <summary>
/// Avalonia implementation of IDialogService using MsBox.Avalonia and platform storage.
/// </summary>
public class AvaloniaDialogService : IDialogService
{
private readonly Func<Window?> _getMainWindow;
/// <summary>
/// Creates a new instance of AvaloniaDialogService.
/// </summary>
/// <param name="getMainWindow">Factory function to get the main window for dialogs.</param>
public AvaloniaDialogService(Func<Window?> getMainWindow)
{
_getMainWindow = getMainWindow ?? throw new ArgumentNullException(nameof(getMainWindow));
}
/// <inheritdoc />
public async Task<string?> ShowFolderPickerAsync(string? title = null)
{
var window = _getMainWindow();
if (window == null)
return null;
var folders = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = title ?? "Select Folder",
AllowMultiple = false
});
return folders.Count > 0 ? folders[0].Path.LocalPath : null;
}
/// <inheritdoc />
public async Task ShowMessageAsync(string title, string message)
{
var box = MessageBoxManager.GetMessageBoxStandard(title, message, ButtonEnum.Ok, Icon.Info);
var window = _getMainWindow();
if (window != null)
{
await box.ShowWindowDialogAsync(window);
}
else
{
await box.ShowAsync();
}
}
/// <inheritdoc />
public async Task<bool> ShowConfirmationAsync(string title, string message)
{
var box = MessageBoxManager.GetMessageBoxStandard(title, message, ButtonEnum.YesNo, Icon.Question);
var window = _getMainWindow();
ButtonResult result;
if (window != null)
{
result = await box.ShowWindowDialogAsync(window);
}
else
{
result = await box.ShowAsync();
}
return result == ButtonResult.Yes;
}
/// <inheritdoc />
public async Task<bool> ShowDiffPreviewAsync(string title, DiffResult diff)
{
// Basic implementation - full diff preview dialog will be implemented in Task 22
if (!diff.HasChanges)
{
await ShowMessageAsync(title, "No changes detected.");
return false;
}
var summary = new StringBuilder();
summary.AppendLine($"Changes detected: {diff.Insertions} insertion(s), {diff.Deletions} deletion(s)");
summary.AppendLine();
summary.AppendLine("Do you want to apply these changes?");
return await ShowConfirmationAsync(title, summary.ToString());
}
/// <inheritdoc />
public async Task ShowValidationResultsAsync(ValidationResult appSettingsResult, ValidationResult pipelinesResult)
{
// Basic implementation - full validation results dialog will be implemented in Task 23
var message = new StringBuilder();
message.AppendLine("=== AppSettings Validation ===");
if (appSettingsResult.IsValid)
{
message.AppendLine("Valid");
}
else
{
foreach (var error in appSettingsResult.Errors)
{
message.AppendLine($"Error: {error}");
}
}
foreach (var warning in appSettingsResult.Warnings)
{
message.AppendLine($"Warning: {warning}");
}
message.AppendLine();
message.AppendLine("=== Pipelines Validation ===");
if (pipelinesResult.IsValid)
{
message.AppendLine("Valid");
}
else
{
foreach (var error in pipelinesResult.Errors)
{
message.AppendLine($"Error: {error}");
}
}
foreach (var warning in pipelinesResult.Warnings)
{
message.AppendLine($"Warning: {warning}");
}
var title = appSettingsResult.IsValid && pipelinesResult.IsValid
? "Validation Passed"
: "Validation Issues Found";
var icon = appSettingsResult.IsValid && pipelinesResult.IsValid ? Icon.Success : Icon.Warning;
var box = MessageBoxManager.GetMessageBoxStandard(title, message.ToString(), ButtonEnum.Ok, icon);
var window = _getMainWindow();
if (window != null)
{
await box.ShowWindowDialogAsync(window);
}
else
{
await box.ShowAsync();
}
}
}
@@ -0,0 +1,45 @@
namespace JdeScoping.ConfigManager.Services;
/// <summary>
/// Abstraction for platform-specific dialog operations.
/// Enables unit testing of view models that need to show dialogs.
/// </summary>
public interface IDialogService
{
/// <summary>
/// Shows a folder picker dialog.
/// </summary>
/// <param name="title">Optional title for the dialog.</param>
/// <returns>The selected folder path, or null if cancelled.</returns>
Task<string?> ShowFolderPickerAsync(string? title = null);
/// <summary>
/// Shows a message dialog.
/// </summary>
/// <param name="title">The dialog title.</param>
/// <param name="message">The message to display.</param>
Task ShowMessageAsync(string title, string message);
/// <summary>
/// Shows a confirmation dialog with Yes/No options.
/// </summary>
/// <param name="title">The dialog title.</param>
/// <param name="message">The confirmation message to display.</param>
/// <returns>True if user clicked Yes, false otherwise.</returns>
Task<bool> ShowConfirmationAsync(string title, string message);
/// <summary>
/// Shows a diff preview dialog allowing the user to review changes.
/// </summary>
/// <param name="title">The dialog title.</param>
/// <param name="diff">The diff result to display.</param>
/// <returns>True if user confirms the changes, false otherwise.</returns>
Task<bool> ShowDiffPreviewAsync(string title, DiffResult diff);
/// <summary>
/// Shows validation results for configuration files.
/// </summary>
/// <param name="appSettingsResult">Validation result for appsettings.json.</param>
/// <param name="pipelinesResult">Validation result for pipelines.json.</param>
Task ShowValidationResultsAsync(ValidationResult appSettingsResult, ValidationResult pipelinesResult);
}