using System.ComponentModel; using System.Diagnostics; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; 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.LycheeUpload; using CamBooth.App.Features.PictureGallery; using Wpf.Ui.Controls; 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 LycheeUploadService _lycheeUploadService; private bool _isDebugConsoleVisible = true; private bool _isPicturePanelVisible = false; private LiveViewPage? _liveViewPage; private bool _isPhotoProcessRunning; private bool _isCameraStarted; private bool _isShutdownSliderOpen; private const string ShutdownGlyphClosed = "\uE7E8"; private const string ShutdownGlyphOpen = "\uE711"; private const double ShutdownSliderOffset = 160; private readonly DispatcherTimer _focusStatusAnimationTimer = new() { Interval = TimeSpan.FromMilliseconds(250) }; private readonly DispatcherTimer _galleryPromptTimer = new() { Interval = TimeSpan.FromSeconds(5) }; private int _focusStatusDots; public MainWindow( Logger logger, AppSettingsService appSettings, PictureGalleryService pictureGalleryService, CameraService cameraService, LycheeUploadService lycheeUploadService) { this._logger = logger; this._appSettings = appSettings; this._pictureGalleryService = pictureGalleryService; this._cameraService = cameraService; this._lycheeUploadService = lycheeUploadService; InitializeComponent(); this.SetVisibilityDebugConsole(_appSettings.IsDebugConsoleVisible); this.SetVisibilityPicturePanel(this._isPicturePanelVisible); _ = this._pictureGalleryService.LoadThumbnailsToCache(); this.Closing += OnClosing; TimerControlRectangleAnimation.OnTimerEllapsed += TimerControlRectangleAnimation_OnTimerEllapsed; this._focusStatusAnimationTimer.Tick += (_, _) => { this._focusStatusDots = (this._focusStatusDots + 1) % 4; this.CaptureStatusText.Text = $"Scharfstellen{new string('.', this._focusStatusDots)}"; }; this._galleryPromptTimer.Tick += (_, _) => this.HideGalleryPrompt(); // Subscribe to new photo count changes this._pictureGalleryService.NewPhotoCountChanged += PictureGalleryService_NewPhotoCountChanged; this.DebugCloseButton.Visibility = Visibility.Collapsed; this.HideDebugButton.Visibility = this._appSettings.IsDebugConsoleVisible ? Visibility.Visible : Visibility.Collapsed; // Initialize Lychee upload if auto-upload is enabled if (appSettings.LycheeAutoUploadEnabled) { logger.Info("Lychee Auto-Upload ist aktiviert. Authentifiziere..."); _ = Task.Run(async () => { var authSuccess = await _lycheeUploadService.AuthenticateAsync(); if (authSuccess) { logger.Info("Lychee-Authentifizierung erfolgreich!"); } else { logger.Warning("Lychee-Authentifizierung fehlgeschlagen. Auto-Upload wird nicht funktionieren."); } }); } logger.Info($"config file loaded: '{appSettings.ConfigFileName}'"); logger.Info("MainWindow initialized"); } private void TimerControlRectangleAnimation_OnTimerEllapsed() { var photoTakenSuccessfully = false; try { this._cameraService.TakePhoto(); photoTakenSuccessfully = true; } catch (Exception exception) { //TODO: mit content dialog ersetzen System.Windows.MessageBox.Show("Sorry, da ging was schief! Bitte nochmal probieren."); this._logger.Info(exception.Message); } finally { this.StopFocusStatusAnimation(); this.CaptureStatusText.Visibility = Visibility.Collapsed; SwitchButtonAndTimerPanel(); this._isPhotoProcessRunning = false; if (photoTakenSuccessfully) { this.ShowGalleryPrompt(); } } } private void SetVisibilityPicturePanel(bool visibility) { if (visibility) { this.HideGalleryPrompt(); this.PicturePanel.Navigate(new PictureGalleryPage(this._appSettings, this._logger, this._pictureGalleryService)); // Reset new photo count when opening gallery this._pictureGalleryService.ResetNewPhotoCount(); } else { this.PicturePanel.ClearValue(MainWindow.ContentProperty); } this._isPicturePanelVisible = !visibility; } private void ClosePicturePanel() { if (this.PicturePanel.Content is PictureGalleryPage pictureGalleryPage) { pictureGalleryPage.CloseOpenDialog(); } if (this.PicturePanel.Content is null) { return; } this.SetVisibilityPicturePanel(false); } private void OnClosing(object? sender, CancelEventArgs e) { this._liveViewPage?.Dispose(); } private void StartExperience(object sender, RoutedEventArgs e) { this.StartLiveViewIfNeeded(); this.WelcomeOverlay.Visibility = Visibility.Collapsed; this.ButtonPanel.Visibility = Visibility.Visible; this.PictureGalleryDock.Visibility = Visibility.Visible; this.ShutdownDock.Visibility = Visibility.Visible; } private void StartLiveViewIfNeeded() { if (this._isCameraStarted) { return; } try { this._liveViewPage = new LiveViewPage(this._logger, this._appSettings, this._cameraService); this.MainFrame.Navigate(this._liveViewPage); this._isCameraStarted = true; } catch (Exception ex) { this._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); } } private void SetVisibilityDebugConsole(object sender, RoutedEventArgs e) { this.SetVisibilityDebugConsole(this._isDebugConsoleVisible); } private void SetVisibilityDebugConsole(bool visibility) { if (!_appSettings.IsDebugConsoleVisible) { return; } if (visibility) { this.DebugFrame.Navigate(new DebugConsolePage(this._logger)); } else { this.DebugFrame.ClearValue(MainWindow.ContentProperty); } this._isDebugConsoleVisible = !visibility; } private async void StartTakePhotoProcess(object sender, RoutedEventArgs e) { this.HideGalleryPrompt(); this.ClosePicturePanel(); if (this._isPhotoProcessRunning) { return; } this._isPhotoProcessRunning = true; try { SwitchButtonAndTimerPanel(); TimerControlRectangleAnimation.StartTimer(this._appSettings.PhotoCountdownSeconds); this.StartFocusStatusAnimation(); this.CaptureStatusText.Visibility = Visibility.Visible; await Task.Delay(TimeSpan.FromSeconds(this._appSettings.FocusDelaySeconds)); if (!this._isPhotoProcessRunning) { return; } await this._cameraService.PrepareFocusAsync(focusTimeoutMs: this._appSettings.FocusTimeoutMs); this.StopFocusStatusAnimation(); this.CaptureStatusText.Visibility = Visibility.Collapsed; } catch (Exception exception) { this._isPhotoProcessRunning = false; this.StopFocusStatusAnimation(); this.CaptureStatusText.Visibility = Visibility.Collapsed; if (this.TimerPanel.Visibility == Visibility.Visible) { SwitchButtonAndTimerPanel(); } this._logger.Error(exception.Message); } } private void SwitchButtonAndTimerPanel() { this.ButtonPanel.Visibility = this.ButtonPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; this.PictureGalleryDock.Visibility = this.PictureGalleryDock.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; this.TimerPanel.Visibility = this.TimerPanel.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden; } private void SetVisibilityPicturePanel(object sender, RoutedEventArgs e) { this.SetVisibilityPicturePanel(this._isPicturePanelVisible); } private void CloseApp(object sender, RoutedEventArgs e) { this.Close(); } private async void ShutdownWindows(object sender, RoutedEventArgs e) { // Show confirmation dialog var confirmDialog = new ContentDialog(this.DialogPresenter); confirmDialog.Title = "Sicherheitsabfrage"; confirmDialog.Content = "Möchtest du die Fotobox wirklich ausschalten?"; confirmDialog.PrimaryButtonText = "Ja, ausschalten"; confirmDialog.CloseButtonText = "Abbrechen"; confirmDialog.DefaultButton = ContentDialogButton.Close; confirmDialog.PrimaryButtonAppearance = ControlAppearance.Danger; confirmDialog.CloseButtonAppearance = ControlAppearance.Secondary; confirmDialog.Background = new SolidColorBrush(Colors.White); confirmDialog.Foreground = new SolidColorBrush(Colors.Black); var result = await confirmDialog.ShowAsync(); // Only proceed with shutdown if user confirmed if (result != ContentDialogResult.Primary) { return; } if (this._appSettings.IsShutdownEnabled) { try { Process.Start(new ProcessStartInfo { FileName = "shutdown", Arguments = "/s /t 0", CreateNoWindow = true, UseShellExecute = false }); } catch (Exception exception) { this._logger.Error(exception.Message); System.Windows.MessageBox.Show("Windows konnte nicht heruntergefahren werden. Bitte erneut versuchen."); } } this.Close(); } private void ToggleShutdownSlider(object sender, RoutedEventArgs e) { this._isShutdownSliderOpen = !this._isShutdownSliderOpen; this.ShutdownToggleButton.Content = this._isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed; this.AnimateShutdownSlider(this._isShutdownSliderOpen); } private void AnimateShutdownSlider(bool open) { var animation = new DoubleAnimation { To = open ? 0 : ShutdownSliderOffset, Duration = TimeSpan.FromMilliseconds(250), EasingFunction = new QuadraticEase() }; this.ShutdownSliderTransform.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, animation); } private void StartFocusStatusAnimation() { this._focusStatusDots = 0; this.CaptureStatusText.Text = "Scharfstellen"; this._focusStatusAnimationTimer.Start(); } private void StopFocusStatusAnimation() { this._focusStatusAnimationTimer.Stop(); this._focusStatusDots = 0; } private void ShowGalleryPrompt() { this.GalleryPrompt.Visibility = Visibility.Visible; this._galleryPromptTimer.Stop(); this._galleryPromptTimer.Start(); } private void HideGalleryPrompt() { this._galleryPromptTimer.Stop(); this.GalleryPrompt.Visibility = Visibility.Collapsed; } private async void OpenGalleryFromPrompt(object sender, RoutedEventArgs e) { this.HideGalleryPrompt(); this.SetVisibilityPicturePanel(true); // Wait a bit for the page to load before showing the dialog await Task.Delay(300); // Show the latest photo in a dialog await this.ShowLatestPhotoDialogAsync(); } private async Task ShowLatestPhotoDialogAsync() { try { // Get the latest photo path await this._pictureGalleryService.LoadThumbnailsToCache(1); var latestPhotos = this._pictureGalleryService.ThumbnailsOrderedByNewestDescending; if (latestPhotos.Count == 0) { this._logger.Error("No photos found in gallery"); return; } // Get the file path from the BitmapImage source var latestPhoto = latestPhotos[0]; if (latestPhoto.UriSource == null) { this._logger.Error("Latest photo UriSource is null"); return; } string photoPath = Uri.UnescapeDataString(latestPhoto.UriSource.AbsolutePath); this._logger.Info($"Opening photo dialog for: {photoPath}"); // Get the current gallery page if (this.PicturePanel.Content is PictureGalleryPage galleryPage) { // Show the photo dialog await galleryPage.ShowPhotoDialogAsync(photoPath); } else { this._logger.Error("PicturePanel content is not a PictureGalleryPage"); } } catch (Exception ex) { this._logger.Error($"Error showing photo dialog: {ex.Message}"); } } private void PictureGalleryService_NewPhotoCountChanged(object? sender, int newPhotoCount) { if (newPhotoCount > 0) { this.NewPhotosBadge.Visibility = Visibility.Visible; this.NewPhotoCountText.Text = newPhotoCount.ToString(); } else { this.NewPhotosBadge.Visibility = Visibility.Collapsed; } } }