cambooth/src/CamBooth/CamBooth.App/MainWindow.xaml.cs
2026-02-28 23:15:59 +01:00

458 lines
12 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
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;
}
}
}