"Refactor: Replace PhotoPrism QR code display with generic BaseDialogOverlay for unified dialog handling, remove redundant code, and improve user experience"

This commit is contained in:
iTob 2026-03-10 22:06:47 +01:00
parent a3c9f9b719
commit a973b40789
9 changed files with 574 additions and 498 deletions

View File

@ -0,0 +1,58 @@
<UserControl x:Class="CamBooth.App.Core.BaseDialogOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Visibility="Collapsed">
<!-- Semi-transparent backdrop -->
<Grid Background="#CC000000">
<Border Background="#E61A1A1A"
BorderBrush="#66FFFFFF"
BorderThickness="1"
CornerRadius="20"
Padding="48"
MaxWidth="900"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center">
<!-- Optional title -->
<TextBlock x:Name="TitleTextBlock"
Foreground="White"
FontSize="36"
FontWeight="Bold"
TextAlignment="Center"
Margin="0,0,0,28"
Visibility="Collapsed"/>
<!-- Injectable content -->
<ContentPresenter x:Name="InnerContentPresenter"
HorizontalAlignment="Center"/>
<!-- Buttons -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Margin="0,36,0,0">
<Button x:Name="PrimaryActionButton"
Style="{StaticResource PrimaryActionButtonStyle}"
Foreground="#1F1A00"
MinWidth="200"
Height="60"
FontSize="18"
FontWeight="Bold"
Margin="0,0,16,0"
Visibility="Collapsed"
Click="PrimaryButton_Click"/>
<Button x:Name="CloseActionButton"
Style="{StaticResource PrimaryActionButtonStyle}"
Foreground="#1F1A00"
Content="Schließen"
MinWidth="200"
Height="60"
FontSize="18"
FontWeight="Bold"
Click="CloseButton_Click"/>
</StackPanel>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,136 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace CamBooth.App.Core;
[System.Windows.Markup.ContentProperty(nameof(DialogContent))]
public partial class BaseDialogOverlay : UserControl
{
public static readonly DependencyProperty DialogTitleProperty =
DependencyProperty.Register(nameof(DialogTitle), typeof(string), typeof(BaseDialogOverlay),
new PropertyMetadata(null, OnDialogTitleChanged));
public static readonly DependencyProperty DialogContentProperty =
DependencyProperty.Register(nameof(DialogContent), typeof(object), typeof(BaseDialogOverlay),
new PropertyMetadata(null, OnDialogContentChanged));
public static readonly DependencyProperty CloseButtonTextProperty =
DependencyProperty.Register(nameof(CloseButtonText), typeof(string), typeof(BaseDialogOverlay),
new PropertyMetadata("Schließen", OnCloseButtonTextChanged));
public static readonly DependencyProperty PrimaryButtonTextProperty =
DependencyProperty.Register(nameof(PrimaryButtonText), typeof(string), typeof(BaseDialogOverlay),
new PropertyMetadata(null, OnPrimaryButtonTextChanged));
public string? DialogTitle
{
get => (string?)GetValue(DialogTitleProperty);
set => SetValue(DialogTitleProperty, value);
}
public object? DialogContent
{
get => GetValue(DialogContentProperty);
set => SetValue(DialogContentProperty, value);
}
public string CloseButtonText
{
get => (string)GetValue(CloseButtonTextProperty);
set => SetValue(CloseButtonTextProperty, value);
}
public string? PrimaryButtonText
{
get => (string?)GetValue(PrimaryButtonTextProperty);
set => SetValue(PrimaryButtonTextProperty, value);
}
public event EventHandler? Closed;
public event EventHandler? PrimaryButtonClicked;
private TaskCompletionSource<bool>? _tcs;
public BaseDialogOverlay()
{
InitializeComponent();
// Apply defaults after controls are created, since DP callbacks don't fire for default values
CloseActionButton.Content = CloseButtonText;
CloseActionButton.Foreground = new SolidColorBrush(Color.FromRgb(0x1F, 0x1A, 0x00));
PrimaryActionButton.Foreground = new SolidColorBrush(Color.FromRgb(0x1F, 0x1A, 0x00));
}
public void Show()
{
Visibility = Visibility.Visible;
}
public Task<bool> ShowAsync()
{
_tcs = new TaskCompletionSource<bool>();
Visibility = Visibility.Visible;
return _tcs.Task;
}
public void Hide()
{
Visibility = Visibility.Collapsed;
_tcs?.TrySetResult(false);
_tcs = null;
Closed?.Invoke(this, EventArgs.Empty);
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Visibility = Visibility.Collapsed;
_tcs?.TrySetResult(false);
_tcs = null;
Closed?.Invoke(this, EventArgs.Empty);
}
private void PrimaryButton_Click(object sender, RoutedEventArgs e)
{
Visibility = Visibility.Collapsed;
_tcs?.TrySetResult(true);
_tcs = null;
PrimaryButtonClicked?.Invoke(this, EventArgs.Empty);
}
private static void OnDialogContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var overlay = (BaseDialogOverlay)d;
if (overlay.InnerContentPresenter != null)
overlay.InnerContentPresenter.Content = e.NewValue;
}
private static void OnDialogTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var overlay = (BaseDialogOverlay)d;
var title = (string?)e.NewValue;
overlay.TitleTextBlock.Text = title;
overlay.TitleTextBlock.Visibility = string.IsNullOrEmpty(title)
? Visibility.Collapsed
: Visibility.Visible;
}
private static void OnCloseButtonTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((BaseDialogOverlay)d).CloseActionButton.Content = (string)e.NewValue;
}
private static void OnPrimaryButtonTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var overlay = (BaseDialogOverlay)d;
var text = (string?)e.NewValue;
overlay.PrimaryActionButton.Content = text;
overlay.PrimaryActionButton.Visibility = string.IsNullOrEmpty(text)
? Visibility.Collapsed
: Visibility.Visible;
}
}

View File

@ -1,12 +1,10 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.AppSettings;
using CamBooth.App.Core.Logging; using CamBooth.App.Core.Logging;
using CamBooth.App.Features.PhotoPrismUpload; using CamBooth.App.Features.PhotoPrismUpload;
using CamBooth.App.Features.PictureGallery; using CamBooth.App.Features.PictureGallery;
using EOSDigital.API; using EOSDigital.API;
using EOSDigital.SDK; using EOSDigital.SDK;
@ -14,284 +12,287 @@ namespace CamBooth.App.Features.Camera;
public class CameraService : IDisposable public class CameraService : IDisposable
{ {
private readonly AppSettingsService _appSettings; private readonly AppSettingsService _appSettings;
private readonly Logger _logger; private readonly Logger _logger;
private readonly PictureGalleryService _pictureGalleryService; private readonly PictureGalleryService _pictureGalleryService;
private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService; private readonly PhotoPrismUploadQueueService _photoPrismUploadQueueService;
private readonly ICanonAPI _canonApi; private readonly ICanonAPI _canonApi;
private ICamera? _mainCamera; private ICamera? _mainCamera;
private List<ICamera>? _camList; private List<ICamera>? _camList;
private bool _isConnected; private bool _isConnected;
/// <summary>Fires whenever the camera delivers a new live-view frame.</summary> /// <summary>Fires whenever the camera delivers a new live-view frame.</summary>
public event Action<Stream>? LiveViewUpdated; public event Action<Stream>? LiveViewUpdated;
public bool IsConnected => _isConnected && _mainCamera?.SessionOpen == true; public bool IsConnected => _isConnected && _mainCamera?.SessionOpen == true;
public CameraService( public CameraService(
Logger logger, Logger logger,
AppSettingsService appSettings, AppSettingsService appSettings,
PictureGalleryService pictureGalleryService, PictureGalleryService pictureGalleryService,
PhotoPrismUploadQueueService photoPrismUploadQueueService, PhotoPrismUploadQueueService photoPrismUploadQueueService,
ICanonAPI canonApi) ICanonAPI canonApi)
{ {
_logger = logger; _logger = logger;
_appSettings = appSettings; _appSettings = appSettings;
_pictureGalleryService = pictureGalleryService; _pictureGalleryService = pictureGalleryService;
_photoPrismUploadQueueService = photoPrismUploadQueueService; _photoPrismUploadQueueService = photoPrismUploadQueueService;
_canonApi = canonApi; _canonApi = canonApi;
} }
public void Dispose() public void Dispose()
{ {
CloseSession(); CloseSession();
_canonApi.Dispose(); _canonApi.Dispose();
_mainCamera?.Dispose(); _mainCamera?.Dispose();
} }
public void ConnectCamera() public void ConnectCamera()
{ {
ErrorHandler.SevereErrorHappened += ErrorHandler_SevereErrorHappened; ErrorHandler.SevereErrorHappened += ErrorHandler_SevereErrorHappened;
ErrorHandler.NonSevereErrorHappened += ErrorHandler_NonSevereErrorHappened; ErrorHandler.NonSevereErrorHappened += ErrorHandler_NonSevereErrorHappened;
try try
{ {
RefreshCameraList(); RefreshCameraList();
const int maxRetries = 3; const int maxRetries = 3;
const int retryDelayMs = 750; const int retryDelayMs = 750;
for (int attempt = 0; attempt < maxRetries; attempt++) for (int attempt = 0; attempt < maxRetries; attempt++)
{ {
if (_camList?.Any() == true) break; if (_camList?.Any() == true) break;
if (attempt < maxRetries - 1) if (attempt < maxRetries - 1)
{ {
System.Threading.Thread.Sleep(retryDelayMs); System.Threading.Thread.Sleep(retryDelayMs);
RefreshCameraList(); RefreshCameraList();
} }
} }
if (_camList?.Any() != true) if (_camList?.Any() != true)
throw new InvalidOperationException("No cameras found after multiple attempts."); throw new InvalidOperationException("No cameras found after multiple attempts.");
_mainCamera = _camList[0]; _mainCamera = _camList[0];
_logger.Info($"Camera found: {_mainCamera.DeviceName}"); _logger.Info($"Camera found: {_mainCamera.DeviceName}");
OpenSession(); OpenSession();
SetSaveToComputer(); SetSaveToComputer();
StartLiveView(); StartLiveView();
_isConnected = true; _isConnected = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error($"Error connecting camera: {ex.Message}"); _logger.Error($"Error connecting camera: {ex.Message}");
throw; throw;
} }
} }
public void CloseSession() public void CloseSession()
{ {
try try
{ {
if (_mainCamera?.SessionOpen == true) if (_mainCamera?.SessionOpen == true)
{ {
_mainCamera.CloseSession(); _mainCamera.CloseSession();
_logger.Info("Camera session closed"); _logger.Info("Camera session closed");
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error($"Error closing camera session: {ex.Message}"); _logger.Error($"Error closing camera session: {ex.Message}");
} }
_isConnected = false; _isConnected = false;
} }
public void TakePhoto() public void TakePhoto()
{ {
if (_mainCamera == null) throw new InvalidOperationException("Camera not connected."); if (_mainCamera == null) throw new InvalidOperationException("Camera not connected.");
_mainCamera.TakePhoto(); _mainCamera.TakePhoto();
} }
public async Task PrepareFocusAsync(int focusTimeoutMs = 1500) public async Task PrepareFocusAsync(int focusTimeoutMs = 1500)
{ {
if (_mainCamera is not EOSDigital.API.Camera sdkCamera) if (_mainCamera is not EOSDigital.API.Camera sdkCamera)
{ {
await Task.Delay(200); await Task.Delay(200);
return; return;
} }
var focusCompleted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously); var focusCompleted = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter) void FocusStateChanged(EOSDigital.API.Camera sender, StateEventID eventId, int parameter)
{ {
if (eventId == StateEventID.AfResult) if (eventId == StateEventID.AfResult)
focusCompleted.TrySetResult(true); focusCompleted.TrySetResult(true);
} }
sdkCamera.StateChanged += FocusStateChanged; sdkCamera.StateChanged += FocusStateChanged;
try try
{ {
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway)); await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Halfway));
var completed = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs)); var completed = await Task.WhenAny(focusCompleted.Task, Task.Delay(focusTimeoutMs));
if (completed != focusCompleted.Task) if (completed != focusCompleted.Task)
_logger.Info("Autofocus timeout reached, continuing."); _logger.Info("Autofocus timeout reached, continuing.");
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error(ex.Message); _logger.Error(ex.Message);
} }
finally finally
{ {
try try
{ {
await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF)); await Task.Run(() => sdkCamera.SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF));
} }
catch (Exception ex) { _logger.Error(ex.Message); } catch (Exception ex)
{
sdkCamera.StateChanged -= FocusStateChanged; _logger.Error(ex.Message);
} }
}
sdkCamera.StateChanged -= FocusStateChanged;
}
private void RefreshCameraList() => _camList = _canonApi.GetCameraList(); }
private void OpenSession() private void RefreshCameraList() => _camList = _canonApi.GetCameraList();
{
if (_mainCamera == null)
throw new InvalidOperationException("Camera reference is null."); private void OpenSession()
{
if (_mainCamera.SessionOpen) if (_mainCamera == null)
{ throw new InvalidOperationException("Camera reference is null.");
_logger.Info($"Session already open for {_mainCamera.DeviceName}");
return; if (_mainCamera.SessionOpen)
} {
_logger.Info($"Session already open for {_mainCamera.DeviceName}");
_logger.Info($"Opening session for: {_mainCamera.DeviceName}"); return;
}
const int maxRetries = 3;
const int retryDelayMs = 1000; _logger.Info($"Opening session for: {_mainCamera.DeviceName}");
for (int attempt = 0; attempt < maxRetries; attempt++) const int maxRetries = 3;
{ const int retryDelayMs = 1000;
try
{ for (int attempt = 0; attempt < maxRetries; attempt++)
_mainCamera.OpenSession(); {
break; try
} {
catch (Exception ex) when (attempt < maxRetries - 1 && IsSessionNotOpenError(ex)) _mainCamera.OpenSession();
{ break;
_logger.Warning($"OpenSession attempt {attempt + 1}/{maxRetries} failed, retrying..."); }
System.Threading.Thread.Sleep(retryDelayMs); catch (Exception ex) when (attempt < maxRetries - 1 && IsSessionNotOpenError(ex))
RefreshCameraList(); {
if (_camList?.Any() == true) _logger.Warning($"OpenSession attempt {attempt + 1}/{maxRetries} failed, retrying...");
_mainCamera = _camList[0]; System.Threading.Thread.Sleep(retryDelayMs);
} RefreshCameraList();
catch (Exception ex) if (_camList?.Any() == true)
{ _mainCamera = _camList[0];
_logger.Error($"Failed to open session: {ex.Message}"); }
throw; catch (Exception ex)
} {
} _logger.Error($"Failed to open session: {ex.Message}");
throw;
_logger.Info("Session opened successfully"); }
_mainCamera.StateChanged += MainCamera_StateChanged; }
_mainCamera.DownloadReady += MainCamera_DownloadReady;
_mainCamera.LiveViewUpdated += MainCamera_LiveViewUpdated; _logger.Info("Session opened successfully");
} _mainCamera.StateChanged += MainCamera_StateChanged;
_mainCamera.DownloadReady += MainCamera_DownloadReady;
_mainCamera.LiveViewUpdated += MainCamera_LiveViewUpdated;
private void SetSaveToComputer() }
{
_mainCamera!.SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
_mainCamera.SetCapacity(4096, int.MaxValue); private void SetSaveToComputer()
} {
_mainCamera!.SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
_mainCamera.SetCapacity(4096, int.MaxValue);
private void StartLiveView() }
{
try
{ private void StartLiveView()
if (!_mainCamera!.IsLiveViewOn) {
_mainCamera.StartLiveView(); try
else {
_mainCamera.StopLiveView(); if (!_mainCamera!.IsLiveViewOn)
} _mainCamera.StartLiveView();
catch (Exception ex) else
{ _mainCamera.StopLiveView();
_logger.Error(ex.Message); }
} catch (Exception ex)
} {
_logger.Error(ex.Message);
}
private static bool IsSessionNotOpenError(Exception ex) }
{
const string errorName = "SESSION_NOT_OPEN";
return ex.Message.Contains(errorName) || (ex.InnerException?.Message?.Contains(errorName) ?? false); private static bool IsSessionNotOpenError(Exception ex)
} {
const string errorName = "SESSION_NOT_OPEN";
return ex.Message.Contains(errorName) || (ex.InnerException?.Message?.Contains(errorName) ?? false);
#region Camera event handlers }
private void MainCamera_LiveViewUpdated(ICamera sender, Stream img) =>
LiveViewUpdated?.Invoke(img); #region Camera event handlers
private void MainCamera_LiveViewUpdated(ICamera sender, Stream img) =>
private void MainCamera_StateChanged(EOSDigital.API.Camera sender, StateEventID eventID, int parameter) LiveViewUpdated?.Invoke(img);
{
try
{ private void MainCamera_StateChanged(EOSDigital.API.Camera sender, StateEventID eventID, int parameter)
if (eventID == StateEventID.Shutdown && _isConnected) {
Application.Current.Dispatcher.Invoke(CloseSession); try
} {
catch (Exception ex) if (eventID == StateEventID.Shutdown && _isConnected)
{ Application.Current.Dispatcher.Invoke(CloseSession);
_logger.Error(ex.Message); }
} catch (Exception ex)
} {
_logger.Error(ex.Message);
}
private void MainCamera_DownloadReady(ICamera sender, IDownloadInfo info) }
{
_logger.Info("Download ready");
try private void MainCamera_DownloadReady(ICamera sender, IDownloadInfo info)
{ {
info.FileName = $"img_{Guid.NewGuid()}.jpg"; _logger.Info("Download ready");
sender.DownloadFile(info, _appSettings.PictureLocation); try
var savedPath = Path.Combine(_appSettings.PictureLocation!, info.FileName); {
_logger.Info($"Download complete: {savedPath}"); info.FileName = $"img_{Guid.NewGuid()}.jpg";
sender.DownloadFile(info, _appSettings.PictureLocation);
Application.Current.Dispatcher.Invoke(() => var savedPath = Path.Combine(_appSettings.PictureLocation!, info.FileName);
{ _logger.Info($"Download complete: {savedPath}");
_pictureGalleryService.IncrementNewPhotoCount();
_pictureGalleryService.LoadThumbnailsToCache(); Application.Current.Dispatcher.Invoke(() =>
}); {
_pictureGalleryService.IncrementNewPhotoCount();
_photoPrismUploadQueueService.QueueNewPhoto(savedPath); _pictureGalleryService.LoadThumbnailsToCache();
} });
catch (Exception ex)
{ _photoPrismUploadQueueService.QueueNewPhoto(savedPath);
_logger.Error(ex.Message); }
} catch (Exception ex)
} {
_logger.Error(ex.Message);
}
private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex) => }
_logger.Error($"SDK Error: {ex} (0x{(int)ex:X})");
private void ErrorHandler_NonSevereErrorHappened(object sender, ErrorCode ex) =>
private void ErrorHandler_SevereErrorHappened(object sender, Exception ex) => _logger.Error($"SDK Error: {ex} (0x{(int)ex:X})");
_logger.Error(ex.Message);
#endregion private void ErrorHandler_SevereErrorHappened(object sender, Exception ex) =>
_logger.Error(ex.Message);
#endregion
} }

View File

@ -1,43 +0,0 @@
<Window x:Class="CamBooth.App.Features.PhotoPrismUpload.PhotoPrismQRCodeDisplayWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="PhotoPrism Album QR-Code"
Height="600"
Width="600"
WindowStartupLocation="CenterScreen"
Background="#1F1A00">
<Grid Background="#1F1A00">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Orientation="Vertical">
<TextBlock Text="PhotoPrism Album"
FontSize="28"
Foreground="#D4AF37"
TextAlignment="Center"
Margin="0,0,0,20"
FontWeight="Bold"/>
<Image x:Name="QRCodeImage"
Height="400"
Width="400"
Margin="20"/>
<TextBlock Text="Mit einem QR-Code-Scanner scannen"
FontSize="16"
Foreground="White"
TextAlignment="Center"
Margin="0,20,0,0"/>
<Button Click="CloseButton_Click"
Content="Schließen"
Width="200"
Height="50"
FontSize="16"
Margin="0,30,0,0"
Background="#D4AF37"
Foreground="#1F1A00"
FontWeight="Bold"/>
</StackPanel>
</Grid>
</Window>

View File

@ -1,28 +0,0 @@
using System.Windows;
using System.Windows.Media.Imaging;
namespace CamBooth.App.Features.PhotoPrismUpload;
public partial class PhotoPrismQRCodeDisplayWindow : Window
{
public PhotoPrismQRCodeDisplayWindow()
{
InitializeComponent();
}
/// <summary>
/// Setzt den QR-Code für die Anzeige
/// </summary>
public void SetQRCode(BitmapImage qrCodeImage)
{
if (qrCodeImage != null)
{
QRCodeImage.Source = qrCodeImage;
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}

View File

@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:core="clr-namespace:CamBooth.App.Core"
mc:Ignorable="d" mc:Ignorable="d"
Title="PictureGalleryPage" Width="1600" Height="900" Title="PictureGalleryPage" Width="1600" Height="900"
Background="Black"> Background="Black">
@ -85,5 +85,10 @@
</Border> </Border>
<ContentPresenter x:Name="RootContentDialogPresenter" Grid.Row="0" /> <ContentPresenter x:Name="RootContentDialogPresenter" Grid.Row="0" />
<!-- Foto-Dialog Overlay -->
<core:BaseDialogOverlay x:Name="PhotoDialogOverlay"
Grid.RowSpan="2"
Panel.ZIndex="20"/>
</Grid> </Grid>
</Page> </Page>

View File

@ -2,19 +2,12 @@
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using CamBooth.App.Core.AppSettings; using CamBooth.App.Core.AppSettings;
using CamBooth.App.Core.Logging; using CamBooth.App.Core.Logging;
using CamBooth.App.Features.PhotoPrismUpload; using CamBooth.App.Features.PhotoPrismUpload;
using Wpf.Ui.Controls;
using Image = Wpf.Ui.Controls.Image;
using MessageBox = System.Windows.MessageBox;
using TextBlock = Wpf.Ui.Controls.TextBlock;
namespace CamBooth.App.Features.PictureGallery; namespace CamBooth.App.Features.PictureGallery;
public partial class PictureGalleryPage : Page public partial class PictureGalleryPage : Page
@ -27,8 +20,6 @@ public partial class PictureGalleryPage : Page
private readonly PhotoPrismUploadService _photoPrismUploadService; private readonly PhotoPrismUploadService _photoPrismUploadService;
private ContentDialog? _openContentDialog;
private int _currentPage = 1; private int _currentPage = 1;
private int _itemsPerPage = 11; private int _itemsPerPage = 11;
private int _totalPages = 1; private int _totalPages = 1;
@ -117,15 +108,6 @@ public partial class PictureGalleryPage : Page
} }
private void ContentDialog_OnButtonClicked(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
if (args.Button == ContentDialogButton.Primary)
{
MessageBox.Show($"Print the shit baby {sender.Tag}");
}
}
private void LoadPictures(int startIndex, int count) private void LoadPictures(int startIndex, int count)
{ {
this.Dispatcher.Invoke( this.Dispatcher.Invoke(
@ -170,82 +152,25 @@ public partial class PictureGalleryPage : Page
public void CloseOpenDialog() public void CloseOpenDialog()
{ {
void CloseDialog() if (Dispatcher.CheckAccess())
{ PhotoDialogOverlay.Hide();
if (this._openContentDialog is null) else
{ Dispatcher.Invoke(() => PhotoDialogOverlay.Hide());
return;
}
this._openContentDialog.ButtonClicked -= this.ContentDialog_OnButtonClicked;
this._openContentDialog.GetType().GetMethod("Hide", Type.EmptyTypes)?.Invoke(this._openContentDialog, null);
this.RootContentDialogPresenter.Content = null;
this._openContentDialog = null;
}
if (this.Dispatcher.CheckAccess())
{
CloseDialog();
return;
}
this.Dispatcher.Invoke(CloseDialog);
} }
public async Task ShowPhotoDialogAsync(string picturePath) public async Task ShowPhotoDialogAsync(string picturePath)
{ {
await Application.Current.Dispatcher.InvokeAsync( var imageToShow = new System.Windows.Controls.Image
async () =>
{
this.CloseOpenDialog();
ContentDialog contentDialog = new(this.RootContentDialogPresenter);
this._openContentDialog = contentDialog;
Image imageToShow = new()
{ {
MaxHeight = 570, MaxHeight = 570,
Background = new SolidColorBrush(Colors.White), MaxWidth = 800,
VerticalAlignment = VerticalAlignment.Center, Stretch = System.Windows.Media.Stretch.Uniform,
Source = PictureGalleryService.CreateThumbnail(picturePath, 450, 300) HorizontalAlignment = HorizontalAlignment.Center,
Source = PictureGalleryService.CreateThumbnail(picturePath, 900, 600)
}; };
contentDialog.VerticalAlignment = VerticalAlignment.Top; PhotoDialogOverlay.DialogContent = imageToShow;
contentDialog.PrimaryButtonAppearance = ControlAppearance.Primary; await PhotoDialogOverlay.ShowAsync();
contentDialog.CloseButtonAppearance = ControlAppearance.Light;
contentDialog.Background = new SolidColorBrush(Colors.White);
contentDialog.Foreground = new SolidColorBrush(Colors.White);
contentDialog.SetCurrentValue(ContentControl.ContentProperty, imageToShow);
contentDialog.SetCurrentValue(ContentDialog.CloseButtonTextProperty, "Schließen");
contentDialog.SetCurrentValue(ContentDialog.PrimaryButtonTextProperty, "Drucken");
// Apply gold color to Primary button (Drucken)
contentDialog.Loaded += (s, args) =>
{
// Find the Primary button and apply gold styling
if (contentDialog.Template?.FindName("PrimaryButton", contentDialog) is System.Windows.Controls.Button primaryButton)
{
primaryButton.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#D4AF37"));
primaryButton.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1F1A00"));
primaryButton.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F6E7A1"));
primaryButton.BorderThickness = new Thickness(2);
}
};
contentDialog.Tag = picturePath;
contentDialog.ButtonClicked += this.ContentDialog_OnButtonClicked;
try
{
await contentDialog.ShowAsync();
}
finally
{
contentDialog.ButtonClicked -= this.ContentDialog_OnButtonClicked;
if (ReferenceEquals(this._openContentDialog, contentDialog))
{
this._openContentDialog = null;
}
}
});
} }
private void Hyperlink_OnClick(object sender, RoutedEventArgs e) private void Hyperlink_OnClick(object sender, RoutedEventArgs e)

View File

@ -6,6 +6,7 @@
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:local="clr-namespace:CamBooth.App" xmlns:local="clr-namespace:CamBooth.App"
xmlns:liveView="clr-namespace:CamBooth.App.Features.LiveView" xmlns:liveView="clr-namespace:CamBooth.App.Features.LiveView"
xmlns:core="clr-namespace:CamBooth.App.Core"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Title="MainWindow"
Background="Black" Background="Black"
@ -339,50 +340,50 @@
</Border> </Border>
<!-- Shutdown Slider (bottom-left) --> <!-- Shutdown Slider (bottom-left) -->
<Grid Grid.Row="0" <!-- <Grid Grid.Row="0" -->
x:Name="ShutdownDock" <!-- x:Name="ShutdownDock" -->
HorizontalAlignment="Left" <!-- HorizontalAlignment="Left" -->
VerticalAlignment="Bottom" <!-- VerticalAlignment="Bottom" -->
Margin="20" <!-- Margin="20" -->
Panel.ZIndex="3" <!-- Panel.ZIndex="3" -->
Visibility="Hidden"> <!-- Visibility="Hidden"> -->
<Grid.ColumnDefinitions> <!-- <Grid.ColumnDefinitions> -->
<ColumnDefinition Width="Auto" /> <!-- <ColumnDefinition Width="Auto" /> -->
<ColumnDefinition Width="Auto" /> <!-- <ColumnDefinition Width="Auto" /> -->
</Grid.ColumnDefinitions> <!-- </Grid.ColumnDefinitions> -->
<!-- -->
<ui:Button Grid.Column="0" <!-- <ui:Button Grid.Column="0" -->
x:Name="ShutdownToggleButton" <!-- x:Name="ShutdownToggleButton" -->
Content="&#xE7E8;" <!-- Content="&#xE7E8;" -->
FontFamily="Segoe MDL2 Assets" <!-- FontFamily="Segoe MDL2 Assets" -->
FontSize="28" <!-- FontSize="28" -->
Width="64" <!-- Width="64" -->
Height="64" <!-- Height="64" -->
Appearance="Danger" <!-- Appearance="Danger" -->
Click="ToggleShutdownSlider" /> <!-- Click="ToggleShutdownSlider" /> -->
<!-- -->
<Border Grid.Column="1" <!-- <Border Grid.Column="1" -->
Width="160" <!-- Width="160" -->
Height="64" <!-- Height="64" -->
Margin="8 0 0 0" <!-- Margin="8 0 0 0" -->
CornerRadius="10" <!-- CornerRadius="10" -->
Background="#44202020" <!-- Background="#44202020" -->
ClipToBounds="True"> <!-- ClipToBounds="True"> -->
<Grid RenderTransformOrigin="0.5,0.5"> <!-- <Grid RenderTransformOrigin="0.5,0.5"> -->
<Grid.RenderTransform> <!-- <Grid.RenderTransform> -->
<TranslateTransform x:Name="ShutdownSliderTransform" X="160" /> <!-- <TranslateTransform x:Name="ShutdownSliderTransform" X="160" /> -->
</Grid.RenderTransform> <!-- </Grid.RenderTransform> -->
<ui:Button x:Name="ShutdownConfirmButton" <!-- <ui:Button x:Name="ShutdownConfirmButton" -->
Content="&#xE7E8;" <!-- Content="&#xE7E8;" -->
FontFamily="Segoe MDL2 Assets" <!-- FontFamily="Segoe MDL2 Assets" -->
FontSize="24" <!-- FontSize="24" -->
Appearance="Danger" <!-- Appearance="Danger" -->
Width="160" <!-- Width="160" -->
Height="64" <!-- Height="64" -->
Click="ShutdownWindows" /> <!-- Click="ShutdownWindows" /> -->
</Grid> <!-- </Grid> -->
</Border> <!-- </Border> -->
</Grid> <!-- </Grid> -->
<!-- Flash overlay: briefly flashes white on shutter release --> <!-- Flash overlay: briefly flashes white on shutter release -->
<Grid x:Name="FlashOverlay" <Grid x:Name="FlashOverlay"
@ -522,8 +523,26 @@
Background="LightGreen" Background="LightGreen"
Panel.ZIndex="2" /> Panel.ZIndex="2" />
<!-- Dialog Host for ContentDialogs --> <!-- QR-Code Dialog -->
<ContentPresenter x:Name="DialogPresenter" Grid.Row="0" Panel.ZIndex="100" /> <core:BaseDialogOverlay x:Name="QRCodeOverlay"
DialogTitle="Online-Fotoalbum"
Grid.RowSpan="2"
Panel.ZIndex="200"/>
<!-- Shutdown-Bestätigung Dialog -->
<core:BaseDialogOverlay x:Name="ShutdownConfirmOverlay"
DialogTitle="Sicherheitsabfrage"
PrimaryButtonText="Ja, ausschalten"
CloseButtonText="Abbrechen"
Grid.RowSpan="2"
Panel.ZIndex="200">
<TextBlock Text="Möchtest du die Fotobox wirklich ausschalten?"
Foreground="#FFE8E8E8"
FontSize="24"
TextAlignment="Center"
TextWrapping="Wrap"
MaxWidth="600"/>
</core:BaseDialogOverlay>
</Grid> </Grid>
</Window> </Window>

View File

@ -1,6 +1,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
@ -11,7 +12,6 @@ using CamBooth.App.Features.DebugConsole;
using CamBooth.App.Features.LiveView; using CamBooth.App.Features.LiveView;
using CamBooth.App.Features.PhotoPrismUpload; using CamBooth.App.Features.PhotoPrismUpload;
using CamBooth.App.Features.PictureGallery; using CamBooth.App.Features.PictureGallery;
using Wpf.Ui.Controls;
namespace CamBooth.App; namespace CamBooth.App;
@ -134,7 +134,7 @@ public partial class MainWindow : Window
WelcomeOverlay.Visibility = Visibility.Collapsed; WelcomeOverlay.Visibility = Visibility.Collapsed;
ButtonPanel.Visibility = Visibility.Visible; ButtonPanel.Visibility = Visibility.Visible;
ActionButtonsContainer.Visibility = Visibility.Visible; ActionButtonsContainer.Visibility = Visibility.Visible;
ShutdownDock.Visibility = Visibility.Visible; //ShutdownDock.Visibility = Visibility.Visible;
} }
private void StartTakePhotoProcess(object sender, RoutedEventArgs e) private void StartTakePhotoProcess(object sender, RoutedEventArgs e)
@ -163,64 +163,52 @@ public partial class MainWindow : Window
SetVisibilityDebugConsole(_isDebugConsoleVisible); SetVisibilityDebugConsole(_isDebugConsoleVisible);
} }
private void ToggleShutdownSlider(object sender, RoutedEventArgs e) // private void ToggleShutdownSlider(object sender, RoutedEventArgs e)
{ // {
_isShutdownSliderOpen = !_isShutdownSliderOpen; // _isShutdownSliderOpen = !_isShutdownSliderOpen;
ShutdownToggleButton.Content = _isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed; // ShutdownToggleButton.Content = _isShutdownSliderOpen ? ShutdownGlyphOpen : ShutdownGlyphClosed;
var animation = new DoubleAnimation // var animation = new DoubleAnimation
{ // {
To = _isShutdownSliderOpen ? 0 : ShutdownSliderOffset, // To = _isShutdownSliderOpen ? 0 : ShutdownSliderOffset,
Duration = TimeSpan.FromMilliseconds(250), // Duration = TimeSpan.FromMilliseconds(250),
EasingFunction = new QuadraticEase() // EasingFunction = new QuadraticEase()
}; // };
ShutdownSliderTransform.BeginAnimation(TranslateTransform.XProperty, animation); // ShutdownSliderTransform.BeginAnimation(TranslateTransform.XProperty, animation);
} // }
private async void ShutdownWindows(object sender, RoutedEventArgs e) // private async void ShutdownWindows(object sender, RoutedEventArgs e)
{ // {
var confirmDialog = new ContentDialog(DialogPresenter) // bool confirmed = await ShutdownConfirmOverlay.ShowAsync();
{ // if (!confirmed) return;
Title = "Sicherheitsabfrage", //
Content = "Möchtest du die Fotobox wirklich ausschalten?", // try
PrimaryButtonText = "Ja, ausschalten", // {
CloseButtonText = "Abbrechen", // _cameraService.CloseSession();
DefaultButton = ContentDialogButton.Close, // }
PrimaryButtonAppearance = ControlAppearance.Danger, // catch
CloseButtonAppearance = ControlAppearance.Secondary, // {
Background = new SolidColorBrush(Colors.White), // }
Foreground = new SolidColorBrush(Colors.Black) //
}; // var (args, errorMsg) = _appSettings.IsShutdownEnabled
// ? ("/s /t 0", "Windows konnte nicht heruntergefahren werden.")
if (await confirmDialog.ShowAsync() != ContentDialogResult.Primary) return; // : ("/l", "Abmeldung fehlgeschlagen.");
//
try // try
{ // {
_cameraService.CloseSession(); // Process.Start(new ProcessStartInfo
} // {
catch // FileName = "shutdown",
{ // Arguments = args,
} // CreateNoWindow = true,
// UseShellExecute = false
var (args, errorMsg) = _appSettings.IsShutdownEnabled // });
? ("/s /t 0", "Windows konnte nicht heruntergefahren werden.") // }
: ("/l", "Abmeldung fehlgeschlagen."); // catch (Exception ex)
// {
try // _logger.Error(ex.Message);
{ // System.Windows.MessageBox.Show(errorMsg);
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) private void ShowQRCode(object sender, MouseButtonEventArgs e)
{ {
@ -235,9 +223,24 @@ public partial class MainWindow : Window
return; return;
} }
var qrWindow = new PhotoPrismQRCodeDisplayWindow(); var content = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center };
qrWindow.SetQRCode(qrCodeImage); content.Children.Add(new System.Windows.Controls.Image
qrWindow.ShowDialog(); {
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) catch (Exception ex)
{ {
@ -299,14 +302,14 @@ public partial class MainWindow : Window
_pictureGalleryService.ResetNewPhotoCount(); _pictureGalleryService.ResetNewPhotoCount();
ButtonPanel.Visibility = Visibility.Hidden; ButtonPanel.Visibility = Visibility.Hidden;
ActionButtonsContainer.Visibility = Visibility.Hidden; ActionButtonsContainer.Visibility = Visibility.Hidden;
ShutdownDock.Visibility = Visibility.Hidden; //ShutdownDock.Visibility = Visibility.Hidden;
} }
else else
{ {
PicturePanel.ClearValue(ContentProperty); PicturePanel.ClearValue(ContentProperty);
ButtonPanel.Visibility = Visibility.Visible; ButtonPanel.Visibility = Visibility.Visible;
ActionButtonsContainer.Visibility = Visibility.Visible; ActionButtonsContainer.Visibility = Visibility.Visible;
ShutdownDock.Visibility = Visibility.Visible; //ShutdownDock.Visibility = Visibility.Visible;
} }
_isPicturePanelVisible = visible; _isPicturePanelVisible = visible;