Files
LehrerApp/LehrerApp.Desktop/Views/Settings/SettingsView.axaml
T
adminandClaude Sonnet 5 48d07a2b99 Datensicherheit: Backup, Verschlüsselung, App-Sperre (Kapitel 13.3)
Automatisches rollierendes Backup der Datenbank beim Start mit
Wiederherstellung über die Einstellungen, versionierte Schema-Migration,
optionale Passwort-Verschlüsselung der LiteDB-Datei und eine App-Sperre
nach Inaktivität mit eigenem Passwort.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 13:07:36 +02:00

384 lines
21 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:LehrerApp.Desktop.ViewModels.Settings"
x:Class="LehrerApp.Desktop.Views.Settings.SettingsView"
x:DataType="vm:SettingsViewModel">
<Grid RowDefinitions="Auto,*">
<TextBlock Grid.Row="0" Text="Einstellungen" FontSize="22" FontWeight="SemiBold"
Margin="32,28,32,0"/>
<TabbedPage Grid.Row="1" TabPlacement="Top">
<!-- Tab: Fächer -->
<ContentPage Header="Fächer">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="640">
<ItemsControl ItemsSource="{Binding Subjects}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:SubjectListItem">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1" Padding="0,7">
<Grid ColumnDefinitions="*,80,Auto">
<TextBlock Grid.Column="0" Text="{Binding Name}"
VerticalAlignment="Center" FontSize="14"/>
<TextBlock Grid.Column="1" Text="{Binding ShortName}"
VerticalAlignment="Center" Opacity="0.5" FontSize="13"/>
<Button Grid.Column="2" Content="Löschen" FontSize="12" Padding="10,4"
Command="{Binding $parent[ItemsControl].((vm:SettingsViewModel)DataContext).DeleteSubjectCommand}"
CommandParameter="{Binding}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1" CornerRadius="6" Padding="14,12">
<StackPanel Spacing="10">
<TextBlock Text="Neues Fach" FontSize="12" FontWeight="SemiBold" Opacity="0.7"/>
<Grid ColumnDefinitions="*,12,100,12,Auto">
<TextBox Grid.Column="0" Text="{Binding NewName}"
PlaceholderText="Name (z.B. Chemie)"/>
<TextBox Grid.Column="2" Text="{Binding NewShort}"
PlaceholderText="Kürzel (z.B. Ch)"/>
<Button Grid.Column="4" Content="Hinzufügen"
Command="{Binding AddSubjectCommand}"/>
</Grid>
<TextBlock Text="{Binding NewNameError}" Foreground="Red" FontSize="12"
IsVisible="{Binding NewNameError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Tab: Kompetenzen -->
<ContentPage Header="Kompetenzen">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="640">
<!-- Fach + Klassenstufe Auswahl + Import/Export -->
<Grid ColumnDefinitions="*,12,120,12,Auto,8,Auto">
<ComboBox Grid.Column="0"
ItemsSource="{Binding Subjects}"
SelectedItem="{Binding CatalogSubject}"
PlaceholderText="Fach auswählen"
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate DataType="vm:SubjectListItem">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<NumericUpDown Grid.Column="2"
Value="{Binding CatalogGradeLevel}"
Minimum="1" Maximum="13" FormatString="0"/>
<Button Grid.Column="4" Content="JSON importieren" Click="OnImportClick"
IsEnabled="{Binding CatalogSubject, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<Button Grid.Column="6" Content="JSON exportieren" Click="OnExportClick"
IsEnabled="{Binding CatalogSubject, Converter={x:Static ObjectConverters.IsNotNull}}"/>
</Grid>
<!-- Katalog-Validierungsmeldung -->
<TextBlock Text="{Binding CatalogValidation}" Foreground="Red" FontSize="12"
IsVisible="{Binding CatalogValidation, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<!-- Leerer Zustand -->
<TextBlock Text="Kein Fach ausgewählt." Opacity="0.4" FontSize="13"
IsVisible="{Binding CatalogSubject, Converter={x:Static ObjectConverters.IsNull}}"/>
<!-- Domain-Liste -->
<ItemsControl ItemsSource="{Binding Domains}"
IsVisible="{Binding CatalogSubject, Converter={x:Static ObjectConverters.IsNotNull}}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:DomainEditItem">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1" CornerRadius="6" Margin="0,0,0,8" Padding="14,12">
<StackPanel Spacing="8">
<!-- Bereichs-Header -->
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="{Binding DisplayName}"
FontWeight="SemiBold" FontSize="14" VerticalAlignment="Center"/>
<Button Grid.Column="1" Content="Bereich löschen" FontSize="12" Padding="10,4"
Command="{Binding $parent[ItemsControl].((vm:SettingsViewModel)DataContext).DeleteDomainCommand}"
CommandParameter="{Binding}"/>
</Grid>
<!-- Kompetenz-Items -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:CompetencyItemVm">
<Grid ColumnDefinitions="*,Auto" Margin="0,3">
<TextBlock Grid.Column="0" Text="{Binding Display}"
TextWrapping="Wrap" VerticalAlignment="Center"
FontSize="13"/>
<Button Grid.Column="1" Content="×" Padding="7,2" FontSize="13"
Command="{Binding DeleteCommand}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Kompetenz hinzufügen -->
<Grid ColumnDefinitions="80,8,*,8,Auto">
<TextBox Grid.Column="0" Text="{Binding NewItemCode}"
PlaceholderText="EG1" FontSize="12"/>
<TextBox Grid.Column="2" Text="{Binding NewItemDesc}"
PlaceholderText="Kompetenz-Beschreibung" FontSize="12"/>
<Button Grid.Column="4" Content=""
Command="{Binding AddItemCommand}"/>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Neuen Bereich hinzufügen -->
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1" CornerRadius="6" Padding="14,12"
IsVisible="{Binding CatalogSubject, Converter={x:Static ObjectConverters.IsNotNull}}">
<StackPanel Spacing="10">
<TextBlock Text="Neuer Bereich" FontSize="12" FontWeight="SemiBold" Opacity="0.7"/>
<Grid ColumnDefinitions="*,12,90,12,Auto">
<TextBox Grid.Column="0" Text="{Binding NewDomainName}"
PlaceholderText="Bereichsname (z.B. Erkenntnisgewinnung)"/>
<TextBox Grid.Column="2" Text="{Binding NewDomainCode}"
PlaceholderText="Kürzel (z.B. EG)"/>
<Button Grid.Column="4" Content="Hinzufügen"
Command="{Binding AddDomainCommand}"/>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Tab: Notenschlüssel-Vorlagen -->
<ContentPage Header="Notenschlüssel-Vorlagen">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="640">
<TextBlock Text="Noch keine Vorlagen angelegt." Opacity="0.4" FontSize="13"
IsVisible="{Binding !GradingKeyTemplateList.Count}"/>
<ItemsControl ItemsSource="{Binding GradingKeyTemplateList}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:GradingKeyTemplateEditItem">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1" Padding="0,9">
<Grid ColumnDefinitions="*,Auto,Auto">
<StackPanel Grid.Column="0">
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="14"/>
<TextBlock FontSize="12" Opacity="0.5">
<Run Text="{Binding GradingSystemLabel}"/>
<Run Text=" · "/>
<Run Text="{Binding Entries.Count}"/>
<Run Text=" Stufen"/>
</TextBlock>
<TextBlock Text="{Binding CompletenessWarning}" Foreground="Red" FontSize="11"
TextWrapping="Wrap"
IsVisible="{Binding CompletenessWarning, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
<Button Grid.Column="1" Content="Bearbeiten" FontSize="12" Padding="10,4"
Margin="8,0,0,0" Tag="{Binding}" Click="OnEditGradingKeyTemplateClick"/>
<Button Grid.Column="2" Content="Löschen" FontSize="12" Padding="10,4"
Margin="8,0,0,0"
Command="{Binding $parent[ItemsControl].((vm:SettingsViewModel)DataContext).DeleteGradingKeyTemplateCommand}"
CommandParameter="{Binding}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Neue Vorlage anlegen -->
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1" CornerRadius="6" Padding="14,12">
<StackPanel Spacing="10">
<TextBlock Text="Neue Vorlage" FontSize="12" FontWeight="SemiBold" Opacity="0.7"/>
<Grid ColumnDefinitions="*,12,140,12,Auto">
<TextBox Grid.Column="0" Text="{Binding NewTemplateName}"
PlaceholderText="Vorlagenname (z.B. Standard Oberstufe)"/>
<ComboBox Grid.Column="2" ItemsSource="{Binding GradingSystemOptions}"
SelectedItem="{Binding NewTemplateGradingSystemName}"
HorizontalAlignment="Stretch"/>
<Button Grid.Column="4" Content="Anlegen"
Command="{Binding AddGradingKeyTemplateCommand}"/>
</Grid>
<TextBlock Text="{Binding NewTemplateNameError}" Foreground="Red" FontSize="12"
IsVisible="{Binding NewTemplateNameError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Tab: Notenschema -->
<ContentPage Header="Notenschema">
<ContentPage.Resources>
<DataTemplate x:Key="GradingSchemeTemplate" DataType="vm:GradingSchemeEditItem">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="1" CornerRadius="6" Padding="14,12">
<StackPanel Spacing="8">
<TextBlock Text="{Binding Label}" FontWeight="SemiBold" FontSize="14"/>
<Grid ColumnDefinitions="Auto,90,Auto,90,Auto,90" ColumnSpacing="6">
<TextBlock Grid.Column="0" Text="Klausuren %" VerticalAlignment="Center" FontSize="12"/>
<NumericUpDown Grid.Column="1" Value="{Binding ExamsPercent}" Minimum="0" Maximum="100"
FormatString="0.#" ShowButtonSpinner="False"/>
<TextBlock Grid.Column="2" Text="Mitarbeit %" VerticalAlignment="Center" FontSize="12"/>
<NumericUpDown Grid.Column="3" Value="{Binding ParticipationPercent}" Minimum="0" Maximum="100"
FormatString="0.#" ShowButtonSpinner="False"/>
<TextBlock Grid.Column="4" Text="Sonstige %" VerticalAlignment="Center" FontSize="12"/>
<NumericUpDown Grid.Column="5" Value="{Binding OtherPercent}" Minimum="0" Maximum="100"
FormatString="0.#" ShowButtonSpinner="False"/>
</Grid>
<TextBlock Text="{Binding ValidationMessage}" Foreground="Red" FontSize="12"
IsVisible="{Binding ValidationMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="{Binding StatusMessage}" Foreground="Green" FontSize="12"
VerticalAlignment="Center"
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<Button Grid.Column="1" Content="Speichern" Command="{Binding SaveCommand}"/>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ContentPage.Resources>
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="14" MaxWidth="480">
<TextBlock Text="Voreinstellung der Gewichtung für die Zeugnisnote (Klausuren / Mitarbeit / Sonstige), je Gruppentyp. Kann pro Lerngruppe überschrieben werden."
FontSize="12" Opacity="0.6" TextWrapping="Wrap"/>
<ContentControl Content="{Binding ClassScheme}" ContentTemplate="{StaticResource GradingSchemeTemplate}"/>
<ContentControl Content="{Binding CourseScheme}" ContentTemplate="{StaticResource GradingSchemeTemplate}"/>
</StackPanel>
</ScrollViewer>
</ContentPage>
<!-- Tab: Sicherheit (13.3) -->
<ContentPage Header="Sicherheit">
<ScrollViewer>
<StackPanel Margin="32,20,32,28" Spacing="24" MaxWidth="560">
<!-- Datensicherung -->
<StackPanel Spacing="10">
<TextBlock Text="Datensicherung" FontSize="16" FontWeight="SemiBold"/>
<TextBlock Text="Bei jedem Start wird automatisch ein Backup der Datenbank angelegt (die letzten 10 werden behalten)."
FontSize="12" Opacity="0.6" TextWrapping="Wrap"/>
<Button Content="Jetzt sichern" Command="{Binding CreateBackupNowCommand}"
HorizontalAlignment="Left"/>
<TextBlock Text="{Binding BackupStatus}" Foreground="Green" FontSize="12"
IsVisible="{Binding BackupStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<ItemsControl ItemsSource="{Binding Backups}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:BackupListItem">
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
BorderThickness="0,0,0,1" Padding="0,7">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Text="{Binding Display}"
VerticalAlignment="Center" FontSize="13"/>
<Button Grid.Column="1" Content="Wiederherstellen" FontSize="12" Padding="10,4"
Command="{Binding $parent[ItemsControl].((vm:SettingsViewModel)DataContext).RestoreBackupCommand}"
CommandParameter="{Binding}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Noch keine Backups vorhanden." Opacity="0.35" FontSize="12"
IsVisible="{Binding !Backups.Count}"/>
</StackPanel>
<Separator/>
<!-- Datenbank-Verschlüsselung -->
<StackPanel Spacing="10">
<TextBlock Text="Datenbank-Verschlüsselung" FontSize="16" FontWeight="SemiBold"/>
<TextBlock Text="Schützt die Datenbankdatei auf der Festplatte mit einem Passwort. Ohne das Passwort kann die App die Datenbank beim Start nicht öffnen — bei Verlust sind die Daten nicht mehr zugänglich."
FontSize="12" Opacity="0.6" TextWrapping="Wrap"/>
<TextBlock FontSize="13" FontWeight="SemiBold">
<Run Text="Status: "/>
<Run Text="{Binding DbEncryptionStatusLabel}"/>
</TextBlock>
<StackPanel Spacing="4" IsVisible="{Binding IsDbEncrypted}">
<TextBlock Text="Aktuelles Passwort" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding CurrentDbPassword}" PasswordChar="●"/>
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Neues Passwort" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding NewDbPassword}" PasswordChar="●"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Wiederholen" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding NewDbPasswordConfirm}" PasswordChar="●"/>
</StackPanel>
</Grid>
<TextBlock Text="{Binding DbPasswordError}" Foreground="Red" FontSize="12"
IsVisible="{Binding DbPasswordError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="Passwort speichern" Command="{Binding SaveDbPasswordCommand}"/>
<Button Content="Verschlüsselung entfernen" Command="{Binding RemoveDbPasswordCommand}"
IsVisible="{Binding IsDbEncrypted}"/>
</StackPanel>
<TextBlock Text="Nach dem Ändern wird die App automatisch neu gestartet."
FontSize="11" Opacity="0.5"/>
</StackPanel>
<Separator/>
<!-- App-Sperre nach Inaktivität -->
<StackPanel Spacing="10">
<TextBlock Text="App-Sperre nach Inaktivität" FontSize="16" FontWeight="SemiBold"/>
<TextBlock Text="Sperrt die Oberfläche nach einer Zeit ohne Maus-/Tastatureingabe. Eigenes Passwort, unabhängig von der Datenbank-Verschlüsselung."
FontSize="12" Opacity="0.6" TextWrapping="Wrap"/>
<CheckBox Content="App-Sperre aktivieren" IsChecked="{Binding AppLockEnabled}"/>
<StackPanel Spacing="4" MaxWidth="200">
<TextBlock Text="Zeit bis zur Sperre (Minuten)" FontSize="12" Opacity="0.7"/>
<NumericUpDown Value="{Binding AppLockTimeoutMinutes}" Minimum="1" Maximum="120" FormatString="0"/>
</StackPanel>
<Grid ColumnDefinitions="*,12,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Passwort (leer lassen, um es zu behalten)" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding AppLockNewPassword}" PasswordChar="●"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Wiederholen" FontSize="12" Opacity="0.7"/>
<TextBox Text="{Binding AppLockNewPasswordConfirm}" PasswordChar="●"/>
</StackPanel>
</Grid>
<TextBlock Text="{Binding AppLockPasswordError}" Foreground="Red" FontSize="12"
IsVisible="{Binding AppLockPasswordError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<Button Content="Speichern" Command="{Binding SaveAppLockSettingsCommand}" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding AppLockStatus}" Foreground="Green" FontSize="12"
IsVisible="{Binding AppLockStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
</ContentPage>
</TabbedPage>
</Grid>
</UserControl>