feat: Enhance Face AI upload functionality and UI
Some checks failed
Build Windows Avalonia / build (push) Failing after 1m48s
Release Windows Avalonia / build (push) Failing after 1m41s
Release Windows Avalonia / release (push) Has been skipped

- Updated MainWindow.axaml to increase height and add new UI elements for SSH upload configuration.
- Implemented commands for opening source and destination paths in file explorer.
- Added FaceUploadPath and SSH configuration properties to DataModel and AiSettingsViewModel.
- Introduced validation for FaceUploadPath format and commands for uploading face encoder output.
- Enhanced PickerPreferenceService to manage SSH credentials and upload preferences.
- Updated settings persistence to include FaceUploadPath and SSH preferences.
- Added tests for FaceUploadPath validation and upload command enabling logic.
This commit is contained in:
Maddo 2026-06-06 11:54:21 +02:00
commit e9142df97c
22 changed files with 1477 additions and 84 deletions

View file

@ -2,6 +2,8 @@ using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics;
using System.IO;
namespace CatalogLite;
@ -21,24 +23,10 @@ public partial class MainWindow : Window
_viewModel = viewModel;
DataContext = _viewModel;
_viewModel.UiInvoker = action => Dispatcher.UIThread.Invoke(action);
_viewModel.LoadConfigurationRequested += OnLoadConfigurationRequested;
_viewModel.SelectSourceFolderRequested += OnSelectSourceFolderRequested;
_viewModel.SelectDestinationFolderRequested += OnSelectDestinationFolderRequested;
_viewModel.ShowMessageRequested += OnShowMessageRequested;
}
private async void OnLoadConfigurationRequested(object? sender, EventArgs e)
{
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Carica configurazione XML",
FileTypeFilter = [new FilePickerFileType("XML") { Patterns = ["*.xml"] }]
});
if (files.Count > 0)
{
await _viewModel.LoadConfigurationFromFileAsync(files[0].Path.LocalPath);
}
_viewModel.LoadEmbeddedConfiguration();
}
private async void OnSelectSourceFolderRequested(object? sender, EventArgs e)
@ -67,6 +55,16 @@ public partial class MainWindow : Window
}
}
private void OpenSourcePath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
OpenInExplorer(_viewModel.SourcePath);
}
private void OpenDestinationPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
OpenInExplorer(_viewModel.DestinationPath);
}
private async void OnShowMessageRequested(object? sender, LiteMessageEventArgs e)
{
await ShowMessageDialogAsync(e.Title, e.Message);
@ -110,4 +108,46 @@ public partial class MainWindow : Window
closeButton.Click += (_, _) => dialog.Close();
await dialog.ShowDialog(this);
}
private static void OpenInExplorer(string? path)
{
if (string.IsNullOrWhiteSpace(path))
{
return;
}
var normalizedPath = path.Trim().Trim('"');
try
{
if (File.Exists(normalizedPath))
{
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
return;
}
if (Directory.Exists(normalizedPath))
{
Process.Start(new ProcessStartInfo
{
FileName = normalizedPath,
UseShellExecute = true
});
return;
}
var containingDirectory = Path.GetDirectoryName(normalizedPath);
if (!string.IsNullOrWhiteSpace(containingDirectory) && Directory.Exists(containingDirectory))
{
Process.Start(new ProcessStartInfo
{
FileName = containingDirectory,
UseShellExecute = true
});
}
}
catch
{
}
}
}