feat: Add Face Matcher functionality and related settings

- Implemented FilePathToBitmapConverter for image loading.
- Enhanced DataModel with commands and properties for Face Matcher.
- Created FaceMatcherResultItem model to store results.
- Updated SettingsDto to include Face Matcher paths and tolerance.
- Introduced PickerPreferenceService for managing folder paths.
- Expanded AiSettingsViewModel to manage Face Matcher settings and results.
This commit is contained in:
MaddoScientisto 2026-05-09 20:27:44 +02:00
commit c261557a29
10 changed files with 2041 additions and 192 deletions

View file

@ -5,6 +5,8 @@ using Avalonia.Layout;
using Avalonia.Platform.Storage;
using Avalonia.Styling;
using Avalonia.Threading;
using ImageCatalog_2.Services;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel;
using System.IO;
@ -13,6 +15,7 @@ namespace ImageCatalog_2;
public partial class AvaloniaMainWindow : Window
{
private readonly DataModel _model;
private readonly PickerPreferenceService _pickerPreferenceService;
private bool _isDarkTheme;
public AvaloniaMainWindow(DataModel model)
@ -20,6 +23,7 @@ public partial class AvaloniaMainWindow : Window
InitializeComponent();
_model = model;
_pickerPreferenceService = Program.ServiceProvider.GetRequiredService<PickerPreferenceService>();
DataContext = _model;
Opened += (_, _) => SyncThemeStateFromCurrentTheme();
@ -30,35 +34,43 @@ public partial class AvaloniaMainWindow : Window
_model.SelectSourceFolderRequested += async (_, _) =>
{
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.SourceFolder, _model.SourcePath);
var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Seleziona cartella sorgente"
Title = "Seleziona cartella sorgente",
SuggestedStartLocation = suggestedStartLocation
});
if (folders.Count > 0)
{
_model.SourcePath = folders[0].Path.LocalPath + Path.DirectorySeparatorChar;
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.SourceFolder, _model.SourcePath);
}
};
_model.SelectDestinationFolderRequested += async (_, _) =>
{
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.DestinationFolder, _model.DestinationPath);
var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Seleziona cartella destinazione"
Title = "Seleziona cartella destinazione",
SuggestedStartLocation = suggestedStartLocation
});
if (folders.Count > 0)
{
_model.DestinationPath = folders[0].Path.LocalPath + Path.DirectorySeparatorChar;
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.DestinationFolder, _model.DestinationPath);
}
};
_model.SelectLogoFileRequested += async (_, _) =>
{
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.LogoFile, _model.LogoFile);
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Seleziona logo",
SuggestedStartLocation = suggestedStartLocation,
FileTypeFilter =
[
new FilePickerFileType("Immagini") { Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"] }
@ -68,34 +80,41 @@ public partial class AvaloniaMainWindow : Window
if (files.Count > 0)
{
_model.LogoFile = files[0].Path.LocalPath;
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.LogoFile, _model.LogoFile);
}
};
_model.SelectModelsFolderRequested += async (_, _) =>
{
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.ModelsFolder, _model.ModelsFolderPath);
var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = "Seleziona cartella modelli"
Title = "Seleziona cartella modelli",
SuggestedStartLocation = suggestedStartLocation
});
if (folders.Count > 0)
{
_model.ModelsFolderPath = folders[0].Path.LocalPath + Path.DirectorySeparatorChar;
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.ModelsFolder, _model.ModelsFolderPath);
}
};
_model.SelectCsvOutputRequested += async (_, _) =>
{
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.CsvOutput, _model.CsvOutputPath);
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
Title = "Salva CSV",
DefaultExtension = "csv",
FileTypeChoices = [new FilePickerFileType("CSV") { Patterns = ["*.csv"] }]
FileTypeChoices = [new FilePickerFileType("CSV") { Patterns = ["*.csv"] }],
SuggestedStartLocation = suggestedStartLocation
});
if (file is not null)
{
_model.CsvOutputPath = file.Path.LocalPath;
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.CsvOutput, _model.CsvOutputPath);
}
};
@ -148,7 +167,7 @@ public partial class AvaloniaMainWindow : Window
private async void AvaloniaMainWindow_Closing(object? sender, CancelEventArgs e)
{
if (_isStoppingFaceEncoderForClose || !_model.IsFaceEncoderRunning)
if (_isStoppingFaceEncoderForClose || (!_model.IsFaceEncoderRunning && !_model.IsFaceMatcherRunning))
{
return;
}
@ -158,7 +177,15 @@ public partial class AvaloniaMainWindow : Window
try
{
await _model.StopFaceEncoderAsync("Arresto face encoder in chiusura...", waitForExit: true);
if (_model.IsFaceMatcherRunning)
{
await _model.StopFaceMatcherAsync("Arresto face matcher in chiusura...", waitForExit: true);
}
if (_model.IsFaceEncoderRunning)
{
await _model.StopFaceEncoderAsync("Arresto face encoder in chiusura...", waitForExit: true);
}
}
finally
{