using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.Logging; using CamBooth.App.Features.Camera; using CamBooth.App.Features.DebugConsole; using CamBooth.App.Features.LiveView; using CamBooth.App.Features.PhotoPrismUpload; using CamBooth.App.Features.PictureGallery; namespace CamBooth.App; /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { private readonly Logger _logger; private readonly AppSettingsService _appSettings; private readonly PictureGalleryService _pictureGalleryService; private readonly CameraService _cameraService; private readonly PhotoPrismUploadService _photoPrismUploadService; private readonly MainWindowViewModel _viewModel; private LiveViewPage? _liveViewPage; private bool _isCameraStarted; private bool _isPicturePanelVisible; private bool _isDebugConsoleVisible; private bool _isShutdownSliderOpen; private const string ShutdownGlyphClosed = "\uE7E8"; private const string ShutdownGlyphOpen = "\uE711"; private const double ShutdownSliderOffset = 160; public MainWindow( Logger logger, AppSettingsService appSettings, PictureGalleryService pictureGalleryService, CameraService cameraService, PhotoPrismUploadService photoPrismUploadService, MainWindowViewModel viewModel) { _logger = logger; _appSettings = appSettings; _pictureGalleryService = pictureGalleryService; _cameraService = cameraService; _photoPrismUploadService = photoPrismUploadService; _viewModel = viewModel; InitializeComponent(); // Wire ViewModel events to UI _viewModel.LoadingProgressChanged += (status, count) => { LoadingStatusText.Text = status; LoadingCountText.Text = count; }; _viewModel.InitializationCompleted += () => { LoadingOverlay.Visibility = Visibility.Collapsed; WelcomeOverlay.Visibility = Visibility.Visible; }; _viewModel.GalleryPromptRequested += () => GalleryPrompt.Visibility = Visibility.Visible; _viewModel.GalleryPromptDismissed += () => GalleryPrompt.Visibility = Visibility.Collapsed; // Wire service events _pictureGalleryService.NewPhotoCountChanged += OnNewPhotoCountChanged; TimerControlRectangleAnimation.OnTimerEllapsed += OnTimerElapsed; // Initial UI state _isDebugConsoleVisible = _appSettings.IsDebugConsoleVisible; SetVisibilityDebugConsole(_isDebugConsoleVisible); SetVisibilityPicturePanel(false); DebugCloseButton.Visibility = Visibility.Collapsed; HideDebugButton.Visibility = _appSettings.IsDebugConsoleVisible ? Visibility.Visible : Visibility.Collapsed; Closing += OnClosing; _ = _viewModel.InitializeAsync(); _logger.Info($"config file loaded: '{appSettings.ConfigFileName}'"); _logger.Info("MainWindow initialized"); } #region Event handlers private void OnTimerElapsed() { SwitchButtonAndTimerPanel(); try { // Focus is guaranteed complete before the timer fires (bounded timeout). _viewModel.TakePhotoAfterTimer(); TriggerShutterFlash(); } catch (Exception ex) { _logger.Error(ex.Message); System.Windows.MessageBox.Show("Sorry, da ging was schief! Bitte nochmal probieren."); } } private void OnNewPhotoCountChanged(object? sender, int count) { NewPhotosBadge.Visibility = count > 0 ? Visibility.Visible : Visibility.Collapsed; if (count > 0) NewPhotoCountText.Text = count.ToString(); } private void OnClosing(object? sender, CancelEventArgs e) { try { _cameraService.CloseSession(); } catch (Exception ex) { _logger.Error($"Fehler beim Schließen der Kamera-Session: {ex.Message}"); } _liveViewPage?.Dispose(); } // XAML-bound handlers private void StartExperience(object sender, RoutedEventArgs e) { StartLiveViewIfNeeded(); WelcomeOverlay.Visibility = Visibility.Collapsed; ButtonPanel.Visibility = Visibility.Visible; ActionButtonsContainer.Visibility = Visibility.Visible; //ShutdownDock.Visibility = Visibility.Visible; } private void StartTakePhotoProcess(object sender, RoutedEventArgs e) { if (_viewModel.IsPhotoProcessRunning) return; ClosePicturePanel(); _viewModel.StartPhotoProcess(); SwitchButtonAndTimerPanel(); TimerControlRectangleAnimation.StartTimer(_appSettings.PhotoCountdownSeconds); // Autofocus runs silently in the background during the countdown. // The effective timeout is capped so focus always completes before the timer fires. _ = _viewModel.BeginFocusAsync( _appSettings.FocusDelaySeconds, _appSettings.FocusTimeoutMs, _appSettings.PhotoCountdownSeconds); } private void SetVisibilityPicturePanel(object sender, MouseButtonEventArgs e) => SetVisibilityPicturePanel(!_isPicturePanelVisible); private void SetVisibilityDebugConsole(object sender, RoutedEventArgs e) { _isDebugConsoleVisible = !_isDebugConsoleVisible; SetVisibilityDebugConsole(_isDebugConsoleVisible); } // private void ToggleShutdownSlider(object sender, RoutedEventArgs e) // { // _isShutdownSliderOpen = !_isShutdownSliderOpen; // ShutdownToggleButton.Content = _isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed; // var animation = new DoubleAnimation // { // To = _isShutdownSliderOpen ? 0 : ShutdownSliderOffset, // Duration = TimeSpan.FromMilliseconds(250), // EasingFunction = new QuadraticEase() // }; // ShutdownSliderTransform.BeginAnimation(TranslateTransform.XProperty, animation); // } // private async void ShutdownWindows(object sender, RoutedEventArgs e) // { // bool confirmed = await ShutdownConfirmOverlay.ShowAsync(); // if (!confirmed) return; // // try // { // _cameraService.CloseSession(); // } // catch // { // } // // var (args, errorMsg) = _appSettings.IsShutdownEnabled // ? ("/s /t 0", "Windows konnte nicht heruntergefahren werden.") // : ("/l", "Abmeldung fehlgeschlagen."); // // try // { // Process.Start(new ProcessStartInfo // { // FileName = "shutdown", // Arguments = args, // CreateNoWindow = true, // UseShellExecute = false // }); // } // catch (Exception ex) // { // _logger.Error(ex.Message); // System.Windows.MessageBox.Show(errorMsg); // } // } private void ShowQRCode(object sender, MouseButtonEventArgs e) { try { var qrCodeImage = _photoPrismUploadService.GenerateAlbumQRCode(); if (qrCodeImage == null) { System.Windows.MessageBox.Show( "QR-Code konnte nicht generiert werden. Bitte überprüfe die PhotoPrism-Konfiguration.", "Fehler", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return; } var content = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center }; content.Children.Add(new System.Windows.Controls.Image { Source = qrCodeImage, Width = 400, Height = 400, Margin = new Thickness(0, 0, 0, 20) }); content.Children.Add(new System.Windows.Controls.TextBlock { Text = "Mit einem QR-Code-Scanner scannen", Foreground = Brushes.White, FontSize = 16, TextAlignment = TextAlignment.Center }); QRCodeOverlay.DialogContent = content; QRCodeOverlay.Show(); } catch (Exception ex) { _logger.Error($"Fehler beim Anzeigen des QR-Codes: {ex.Message}"); System.Windows.MessageBox.Show( $"Fehler beim Anzeigen des QR-Codes: {ex.Message}", "Fehler", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } private async void OpenGalleryFromPrompt(object sender, RoutedEventArgs e) { _viewModel.DismissGalleryPrompt(); SetVisibilityPicturePanel(true); await Task.Delay(300); await ShowLatestPhotoDialogAsync(); } private void CloseApp(object sender, RoutedEventArgs e) => Close(); #endregion #region UI helpers private void StartLiveViewIfNeeded() { if (_isCameraStarted) return; try { _liveViewPage = new LiveViewPage(_logger, _cameraService); MainFrame.Navigate(_liveViewPage); _isCameraStarted = true; } catch (Exception ex) { _logger.Error($"Failed to start live view: {ex.Message}\n{ex.StackTrace}"); System.Windows.MessageBox.Show( $"Failed to initialize camera. Please ensure the camera is properly connected.\n\nError: {ex.Message}", "Camera Connection Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } public void ClosePicturePanel() { if (PicturePanel.Content is PictureGalleryPage page) page.CloseOpenDialog(); if (PicturePanel.Content is null) return; SetVisibilityPicturePanel(false); } private void SetVisibilityPicturePanel(bool visible) { if (visible) { _viewModel.DismissGalleryPrompt(); PicturePanel.Navigate(new PictureGalleryPage(_appSettings, _logger, _pictureGalleryService, _photoPrismUploadService)); _pictureGalleryService.ResetNewPhotoCount(); ButtonPanel.Visibility = Visibility.Hidden; ActionButtonsContainer.Visibility = Visibility.Hidden; //ShutdownDock.Visibility = Visibility.Hidden; } else { PicturePanel.ClearValue(ContentProperty); ButtonPanel.Visibility = Visibility.Visible; ActionButtonsContainer.Visibility = Visibility.Visible; //ShutdownDock.Visibility = Visibility.Visible; } _isPicturePanelVisible = visible; } private void SetVisibilityDebugConsole(bool visible) { if (!_appSettings.IsDebugConsoleVisible) return; if (visible) DebugFrame.Navigate(new DebugConsolePage(_logger)); else DebugFrame.ClearValue(ContentProperty); } private void SwitchButtonAndTimerPanel() { ButtonPanel.Visibility = ButtonPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; ActionButtonsContainer.Visibility = ActionButtonsContainer.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; TimerPanel.Visibility = TimerPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; } /// Briefly flashes the screen white to simulate a camera shutter. private void TriggerShutterFlash() { FlashOverlay.BeginAnimation(OpacityProperty, new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(450)) { EasingFunction = new PowerEase { Power = 2, EasingMode = EasingMode.EaseIn } }); } private async Task ShowLatestPhotoDialogAsync() { try { await _pictureGalleryService.LoadThumbnailsToCache(1); var latestPhotos = _pictureGalleryService.ThumbnailsOrderedByNewestDescending; if (latestPhotos.Count == 0) { _logger.Error("No photos found in gallery"); return; } var latestPhoto = latestPhotos[0]; if (latestPhoto.UriSource == null) { _logger.Error("Latest photo UriSource is null"); return; } var photoPath = Uri.UnescapeDataString(latestPhoto.UriSource.AbsolutePath); if (PicturePanel.Content is PictureGalleryPage galleryPage) await galleryPage.ShowPhotoDialogAsync(photoPath); } catch (Exception ex) { _logger.Error($"Error showing photo dialog: {ex.Message}"); } } #endregion }