using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels; using LehrerApp.Sync; namespace LehrerApp.Desktop.Views; public partial class MainWindow : Window { private static readonly TimeSpan FinalSyncDelay = TimeSpan.FromMilliseconds(350); private SyncEngine? _syncEngine; private bool _finalSyncStarted; private bool _closeAfterFinalSync; public MainWindow() { InitializeComponent(); PointerMoved += (_, _) => NotifyActivity(); PointerPressed += (_, _) => NotifyActivity(); KeyDown += (_, _) => NotifyActivity(); KeyDown += OnWindowKeyDown; } private void OnWindowKeyDown(object? sender, KeyEventArgs e) { if (DataContext is not MainWindowViewModel vm) return; var commandModifier = (e.KeyModifiers & (KeyModifiers.Control | KeyModifiers.Meta)) != 0; if (commandModifier && e.Key == Key.K) { vm.OpenCommandPaletteCommand.Execute(null); FocusCommandPalette(); e.Handled = true; return; } if (!vm.IsCommandPaletteOpen) return; if (e.Key == Key.Escape) { vm.CloseCommandPaletteCommand.Execute(null); e.Handled = true; } else if (e.Key == Key.Enter) { vm.CommandPalette.ExecuteCommand.Execute(vm.CommandPalette.SelectedResult); e.Handled = true; } else if (e.Key is Key.Down or Key.Up) { MoveCommandPaletteSelection(vm, e.Key == Key.Down ? 1 : -1); e.Handled = true; } } private void OnOpenCommandPaletteClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { if (DataContext is MainWindowViewModel vm) vm.OpenCommandPaletteCommand.Execute(null); FocusCommandPalette(); } private void FocusCommandPalette() => Dispatcher.UIThread.Post(() => { if (this.FindControl("CommandPaletteSearchBox") is { } search) { search.Focus(); search.SelectAll(); } }); private static void MoveCommandPaletteSelection(MainWindowViewModel vm, int delta) { var results = vm.CommandPalette.Results; if (results.Count == 0) return; var current = vm.CommandPalette.SelectedResult is { } selected ? results.IndexOf(selected) : -1; vm.CommandPalette.SelectedResult = results[Math.Clamp(current + delta, 0, results.Count - 1)]; } public void EnableFinalSync(SyncEngine syncEngine) { _syncEngine = syncEngine; Closing += OnClosing; } /// Wendet die zuletzt gespeicherte Fenstergröße an und merkt sich die aktuelle beim /// Schließen (14.6). Eigenständig von verdrahtet (mehrere /// Closing-Handler sind unproblematisch) und immer aktiv, auch ohne konfigurierten /// Sync-Server. public void EnableWindowSizePersistence(WindowSettingsService settings) { var saved = settings.Load(); Width = saved.Width; Height = saved.Height; if (saved.IsMaximized) WindowState = WindowState.Maximized; Closing += (_, _) => { var current = settings.Load(); settings.Save(new WindowSettings { IsMaximized = WindowState == WindowState.Maximized, // Im maximierten Zustand spiegeln Width/Height die Bildschirmgröße wider, nicht // die zuletzt genutzte Normalgröße - dann den vorherigen Wert beibehalten statt // ihn zu überschreiben. Width = WindowState == WindowState.Normal ? Width : current.Width, Height = WindowState == WindowState.Normal ? Height : current.Height, }); }; } private async void OnClosing(object? sender, WindowClosingEventArgs e) { if (_closeAfterFinalSync || _finalSyncStarted || _syncEngine is null) return; e.Cancel = true; _finalSyncStarted = true; await _syncEngine.SyncBeforeShutdownAsync(FinalSyncDelay); _closeAfterFinalSync = true; Close(); } private void NotifyActivity() { if (DataContext is MainWindowViewModel vm) vm.AppLock.NotifyActivity(); } private void OnLockScreenKeyDown(object? sender, KeyEventArgs e) { if (e.Key == Key.Enter && DataContext is MainWindowViewModel vm) vm.AppLock.UnlockCommand.Execute(null); } }