cambooth/src/CamBooth/CamBooth.App/MainWindow.xaml.cs

377 lines
13 KiB
C#

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;
/// <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 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;
}
/// <summary>Briefly flashes the screen white to simulate a camera shutter.</summary>
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
}