feat: Dunkelmodus, Fenstergröße merken, Papierkorb für Löschvorgänge (12.4, 14.3, 14.6)

Dunkelmodus über neuen Einstellungen-Tab "Darstellung" (Systemvorgabe/Hell/Dunkel),
Fenstergröße/Maximiert-Status wird über Sitzungen hinweg gemerkt (bewusst ohne
Fensterposition), und ein generischer Snapshot-basierter Papierkorb (30 Tage) für
Sitzpläne, Noten, Notenschlüssel-Vorlagen, Aufgaben und Zeiteinträge. Details und
bewusste Scope-Entscheidungen (Spaltenbreiten zurückgestellt, Farb-Audit für
Dunkelmodus offen, welche Entitäten der Papierkorb abdeckt) in TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:26:10 +02:00
co-authored by Claude Sonnet 5
parent d9d007cd4b
commit 331033db5c
20 changed files with 1023 additions and 12 deletions
@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Input;
using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels;
using LehrerApp.Sync;
@@ -26,6 +27,32 @@ public partial class MainWindow : Window
Closing += OnClosing;
}
/// <summary>Wendet die zuletzt gespeicherte Fenstergröße an und merkt sich die aktuelle beim
/// Schließen (14.6). Eigenständig von <see cref="EnableFinalSync"/> verdrahtet (mehrere
/// Closing-Handler sind unproblematisch) und immer aktiv, auch ohne konfigurierten
/// Sync-Server.</summary>
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)
@@ -920,6 +920,68 @@
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Tab: Darstellung (12.4) -->
<ContentPage Header="Darstellung">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="420">
<TextBlock Text="Darstellung" FontSize="16" FontWeight="SemiBold"/>
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
Text="Wechselt sofort, ohne Neustart."/>
<StackPanel Spacing="4">
<TextBlock Text="Design" FontSize="12" Opacity="0.7"/>
<ComboBox ItemsSource="{Binding ThemeOptions}" SelectedItem="{Binding SelectedThemeName}"
HorizontalAlignment="Left" MinWidth="180"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Tab: Papierkorb (14.3) -->
<ContentPage Header="Papierkorb">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="560" DataContext="{Binding TrashTab}"
x:DataType="vm:TrashViewModel">
<TextBlock Text="Papierkorb" FontSize="16" FontWeight="SemiBold"/>
<TextBlock FontSize="12" Opacity="0.6" TextWrapping="Wrap"
Text="Gelöschte Noten, Aufgaben, Zeiteinträge, Sitzpläne und Notenschlüssel-Vorlagen bleiben 30 Tage wiederherstellbar, danach werden sie endgültig entfernt. Andere Löschvorgänge (z. B. eine ganze Lerngruppe) sind davon nicht erfasst."/>
<TextBlock Text="{Binding Status}" Foreground="Green" FontSize="12"
IsVisible="{Binding Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<TextBlock Text="Der Papierkorb ist leer." Classes="emptyhint"
IsVisible="{Binding !HasItems}"/>
<ItemsControl ItemsSource="{Binding Items}" IsVisible="{Binding HasItems}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:TrashItemViewModel">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1" Padding="0,8">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="2">
<TextBlock FontSize="13">
<Run Text="{Binding TypeLabel}" FontWeight="SemiBold"/><Run Text=": "/><Run Text="{Binding Summary}"/>
</TextBlock>
<TextBlock FontSize="11" Opacity="0.6">
<Run Text="Gelöscht am "/><Run Text="{Binding DeletedAtDisplay}"/><Run Text=" · "/><Run Text="{Binding ExpiresInDisplay}"/>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Content="Wiederherstellen" FontSize="12" Padding="10,4"
Command="{Binding $parent[ItemsControl].((vm:TrashViewModel)DataContext).RestoreCommand}"
CommandParameter="{Binding}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</ContentPage>
</TabbedPage>
</Grid>
</UserControl>
@@ -25,6 +25,7 @@ public partial class SettingsView : UserControl
vm.OnSaveRecoveryFile = SaveRecoveryFile;
vm.OnPickRecoveryFile = PickRecoveryFile;
vm.OnConfirmRecoveryRestore = ShowRecoveryRestoreConfirmDialog;
vm.OnThemeChanged = App.ApplyTheme;
}
}