using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; 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; namespace ImageCatalog_2; public partial class AvaloniaMainWindow : Window { private readonly DataModel _model; private readonly PickerPreferenceService _pickerPreferenceService; private bool _isDarkTheme; private bool _startupSettingsRestoreAttempted; public AvaloniaMainWindow() : this(Program.ServiceProvider.GetRequiredService()) { } public AvaloniaMainWindow(DataModel model) { InitializeComponent(); _model = model; _pickerPreferenceService = Program.ServiceProvider.GetRequiredService(); DataContext = _model; Opened += async (_, _) => { SyncThemeStateFromCurrentTheme(); await TryLoadLastSettingsOnStartupAsync(); }; Closing += AvaloniaMainWindow_Closing; // Let DataModel marshal callbacks onto Avalonia UI thread. _model.UiInvoker = action => Dispatcher.UIThread.Invoke(action); _model.ConfirmAiCsvOverwriteAsync = ShowConfirmationDialogAsync; _model.SelectSourceFolderRequested += async (_, _) => { var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.SourceFolder, _model.SourcePath); var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { 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", 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"] } ] }); 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", 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"] }], SuggestedStartLocation = suggestedStartLocation }); if (file is not null) { _model.CsvOutputPath = file.Path.LocalPath; _pickerPreferenceService.RememberPath(PickerPreferenceKeys.CsvOutput, _model.CsvOutputPath); } }; _model.SaveSettingsRequested += async (_, _) => { var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.SettingsFile); var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { Title = "Salva impostazioni", DefaultExtension = "xml", FileTypeChoices = [new FilePickerFileType("Setup") { Patterns = ["*.xml"] }], SuggestedStartLocation = suggestedStartLocation }); if (file is not null) { await _model.SaveSettingsToFileAsync(file.Path.LocalPath); _pickerPreferenceService.RememberPath(PickerPreferenceKeys.SettingsFile, file.Path.LocalPath); _pickerPreferenceService.RememberValue(PickerPreferenceKeys.LastSettingsFile, file.Path.LocalPath); } }; _model.LoadSettingsRequested += async (_, _) => { var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(StorageProvider, PickerPreferenceKeys.SettingsFile); var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { Title = "Carica impostazioni", FileTypeFilter = [new FilePickerFileType("Setup") { Patterns = ["*.xml"] }], SuggestedStartLocation = suggestedStartLocation }); if (files.Count > 0) { await _model.LoadSettingsFromFileAsync(files[0].Path.LocalPath); _pickerPreferenceService.RememberPath(PickerPreferenceKeys.SettingsFile, files[0].Path.LocalPath); _pickerPreferenceService.RememberValue(PickerPreferenceKeys.LastSettingsFile, files[0].Path.LocalPath); } }; _model.SelectColorRequested += (_, _) => { // Color is set by typing hex directly in the TextBox. }; _model.SelectTransparentColorRequested += (_, _) => { // Color is set by typing hex directly in the TextBox. }; _model.ShowMessageRequested += async (_, args) => { await ShowMessageDialogAsync(args.Item1, args.Item2); }; } private bool _isStoppingFaceEncoderForClose; private async Task TryLoadLastSettingsOnStartupAsync() { if (_startupSettingsRestoreAttempted) { return; } _startupSettingsRestoreAttempted = true; var lastSettingsFile = _pickerPreferenceService.GetRememberedValue(PickerPreferenceKeys.LastSettingsFile); if (string.IsNullOrWhiteSpace(lastSettingsFile)) { return; } if (!File.Exists(lastSettingsFile)) { _pickerPreferenceService.ForgetValue(PickerPreferenceKeys.LastSettingsFile); return; } try { await _model.LoadSettingsFromFileAsync(lastSettingsFile); _pickerPreferenceService.RememberPath(PickerPreferenceKeys.SettingsFile, lastSettingsFile); } catch (Exception ex) { await ShowMessageDialogAsync("Impostazioni", $"Impossibile caricare il file impostazioni automatico:\n{ex.GetBaseException().Message}"); } } private async void AvaloniaMainWindow_Closing(object? sender, CancelEventArgs e) { if (_isStoppingFaceEncoderForClose || (!_model.IsFaceEncoderRunning && !_model.IsFaceMatcherRunning)) { return; } e.Cancel = true; _isStoppingFaceEncoderForClose = true; try { 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 { _isStoppingFaceEncoderForClose = false; Close(); } } private void ToggleTheme_Click(object? sender, RoutedEventArgs e) { _isDarkTheme = !_isDarkTheme; if (Avalonia.Application.Current is not null) { Avalonia.Application.Current.RequestedThemeVariant = _isDarkTheme ? ThemeVariant.Dark : ThemeVariant.Light; } UpdateThemeToggleButtonContent(); } private void SyncThemeStateFromCurrentTheme() { _isDarkTheme = ActualThemeVariant == ThemeVariant.Dark; UpdateThemeToggleButtonContent(); } private void UpdateThemeToggleButtonContent() { _ = this.FindControl("ThemeToggleButton"); } private async Task ShowMessageDialogAsync(string title, string message) { var dialog = new Window { Title = title, Width = 480, CanResize = false, WindowStartupLocation = WindowStartupLocation.CenterOwner, SizeToContent = SizeToContent.Height }; dialog.Content = BuildMessageDialogContent(message, () => dialog.Close()); await dialog.ShowDialog(this); } private async Task ShowConfirmationDialogAsync(string title, string message) { var dialog = new Window { Title = title, Width = 520, CanResize = false, WindowStartupLocation = WindowStartupLocation.CenterOwner, SizeToContent = SizeToContent.Height }; dialog.Content = BuildConfirmationDialogContent( message, () => dialog.Close(true), () => dialog.Close(false)); return await dialog.ShowDialog(this); } private static Control BuildMessageDialogContent(string message, Action closeDialog) { var layout = new StackPanel { Margin = new Thickness(16), Spacing = 12 }; layout.Children.Add(new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap, MaxWidth = 420 }); var closeButton = new Button { Content = "OK", MinWidth = 88, HorizontalAlignment = HorizontalAlignment.Right }; closeButton.Click += (_, _) => closeDialog(); layout.Children.Add(closeButton); return layout; } private static Control BuildConfirmationDialogContent(string message, Action confirmDialog, Action cancelDialog) { var layout = new StackPanel { Margin = new Thickness(16), Spacing = 12 }; layout.Children.Add(new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap, MaxWidth = 460 }); var buttons = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Spacing = 8 }; var cancelButton = new Button { Content = "Annulla", MinWidth = 96 }; cancelButton.Click += (_, _) => cancelDialog(); var confirmButton = new Button { Content = "Sovrascrivi", MinWidth = 96 }; confirmButton.Click += (_, _) => confirmDialog(); buttons.Children.Add(cancelButton); buttons.Children.Add(confirmButton); layout.Children.Add(buttons); return layout; } }