458 lines
12 KiB
C#
458 lines
12 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|