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:
@@ -36,6 +36,8 @@ public enum SettingsTab
|
||||
Privacy = 10,
|
||||
Sync = 11,
|
||||
Ai = 12,
|
||||
Appearance = 13,
|
||||
Trash = 14,
|
||||
}
|
||||
|
||||
// ── Haupt-ViewModel ───────────────────────────────────────────────────────────
|
||||
@@ -237,6 +239,27 @@ public partial class SettingsViewModel : ObservableObject
|
||||
/// gerendert würde.
|
||||
public Func<string, Task>? OnShowPairingError { get; set; }
|
||||
|
||||
// ── Darstellung (12.4) ──────────────────────────────────────────────────────
|
||||
|
||||
[ObservableProperty] private AppTheme _selectedTheme;
|
||||
public string[] ThemeOptions { get; } = AppThemeDisplay.Options;
|
||||
public string SelectedThemeName
|
||||
{
|
||||
get => AppThemeDisplay.Label(SelectedTheme);
|
||||
set => SelectedTheme = AppThemeDisplay.FromLabel(value);
|
||||
}
|
||||
|
||||
/// Vom Code-Behind gesetzt: wendet die Theme-Variante sofort an (Application.Current, siehe
|
||||
/// App.ApplyTheme) — ViewModels fassen Avalonia-Framework-Typen nicht direkt an, gleiches
|
||||
/// Muster wie die übrigen Code-Behind-Hooks in dieser Klasse.
|
||||
public Action<AppTheme>? OnThemeChanged { get; set; }
|
||||
|
||||
partial void OnSelectedThemeChanged(AppTheme value)
|
||||
{
|
||||
_appearance.Save(value);
|
||||
OnThemeChanged?.Invoke(value);
|
||||
}
|
||||
|
||||
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||||
|
||||
private readonly ISchoolHolidayRepository _schoolHolidays;
|
||||
@@ -251,9 +274,12 @@ public partial class SettingsViewModel : ObservableObject
|
||||
private readonly SyncEngine? _syncEngine;
|
||||
private readonly SnapshotService? _snapshotService;
|
||||
private readonly SyncKeyRecoveryService _syncKeyRecovery;
|
||||
private readonly AppearanceSettingsService _appearance;
|
||||
private readonly CompetencyCatalogImportService _catalogImport;
|
||||
private readonly AppLogger _logger;
|
||||
|
||||
public TrashViewModel TrashTab { get; }
|
||||
|
||||
public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo,
|
||||
IGradingKeyTemplateRepository gradingKeyTemplates, IGradingSchemeRepository gradingSchemes,
|
||||
GradingService grading, BackupService backups, DatabaseEncryptionService dbEncryption,
|
||||
@@ -265,11 +291,15 @@ public partial class SettingsViewModel : ObservableObject
|
||||
AiSettingsService aiSettings, AiPlanningService aiPlanning,
|
||||
SyncSettingsService syncSettings, SyncAuthService syncAuth, EventQueue eventQueue,
|
||||
AppLogger logger, SyncKeyStatus syncKeyStatus, SyncKeyRecoveryService syncKeyRecovery,
|
||||
AppearanceSettingsService appearance, TrashViewModel trashTab,
|
||||
SnapshotService? snapshotService = null, SyncEngine? syncEngine = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_syncKeyRecovery = syncKeyRecovery;
|
||||
SyncKeyWasRegenerated = syncKeyStatus.KeyWasRegenerated;
|
||||
_appearance = appearance;
|
||||
_selectedTheme = appearance.Load();
|
||||
TrashTab = trashTab;
|
||||
_subjects = subjects;
|
||||
_domainRepo = domainRepo;
|
||||
_gradingKeyTemplates = gradingKeyTemplates;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Papierkorb (14.3): zeigt gelöschte Datensätze der abgedeckten Modelle (siehe
|
||||
/// TODO.md 14.3 für die vollständige Liste und die bewusst ausgeschlossenen, kaskadierenden
|
||||
/// Löschvorgänge wie eine ganze Lerngruppe) und erlaubt das Wiederherstellen. Rein lokal — der
|
||||
/// Papierkorb selbst wird nicht synchronisiert (siehe TrashRepository-Klassenkommentar);
|
||||
/// wiederhergestellte Einträge synchronisieren sich danach ganz normal wie jede andere Änderung.
|
||||
/// </summary>
|
||||
public partial class TrashViewModel : ObservableObject
|
||||
{
|
||||
private readonly ITrashRepository _trash;
|
||||
private readonly IGradeRepository _grades;
|
||||
private readonly IWorkTaskRepository _tasks;
|
||||
private readonly ITimeEntryRepository _timeEntries;
|
||||
private readonly ISeatingPlanRepository _seatingPlans;
|
||||
private readonly IGradingKeyTemplateRepository _gradingKeyTemplates;
|
||||
|
||||
[ObservableProperty] private string _status = "";
|
||||
|
||||
public ObservableCollection<TrashItemViewModel> Items { get; } = [];
|
||||
public bool HasItems => Items.Count > 0;
|
||||
|
||||
public TrashViewModel(ITrashRepository trash, IGradeRepository grades, IWorkTaskRepository tasks,
|
||||
ITimeEntryRepository timeEntries, ISeatingPlanRepository seatingPlans,
|
||||
IGradingKeyTemplateRepository gradingKeyTemplates)
|
||||
{
|
||||
_trash = trash;
|
||||
_grades = grades;
|
||||
_tasks = tasks;
|
||||
_timeEntries = timeEntries;
|
||||
_seatingPlans = seatingPlans;
|
||||
_gradingKeyTemplates = gradingKeyTemplates;
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
Items.Clear();
|
||||
foreach (var item in _trash.GetAll())
|
||||
Items.Add(new TrashItemViewModel(item, TypeLabel(item.EntityType)));
|
||||
OnPropertyChanged(nameof(HasItems));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Restore(TrashItemViewModel? item)
|
||||
{
|
||||
if (item is null) return;
|
||||
switch (item.EntityType)
|
||||
{
|
||||
case nameof(Grade): _grades.Restore(item.Id); break;
|
||||
case nameof(WorkTask): _tasks.Restore(item.Id); break;
|
||||
case nameof(TimeEntry): _timeEntries.Restore(item.Id); break;
|
||||
case nameof(SeatingPlan): _seatingPlans.Restore(item.Id); break;
|
||||
case nameof(GradingKeyTemplate): _gradingKeyTemplates.Restore(item.Id); break;
|
||||
default: return; // Unbekannter Typ (sollte nicht vorkommen) - Eintrag unverändert lassen.
|
||||
}
|
||||
Status = $"„{item.Summary}“ wiederhergestellt.";
|
||||
Load();
|
||||
}
|
||||
|
||||
private static string TypeLabel(string entityType) => entityType switch
|
||||
{
|
||||
nameof(Grade) => "Note",
|
||||
nameof(WorkTask) => "Aufgabe",
|
||||
nameof(TimeEntry) => "Zeiteintrag",
|
||||
nameof(SeatingPlan) => "Sitzplan",
|
||||
nameof(GradingKeyTemplate) => "Notenschlüssel-Vorlage",
|
||||
_ => entityType,
|
||||
};
|
||||
}
|
||||
|
||||
public class TrashItemViewModel
|
||||
{
|
||||
private const int RetentionDays = 30;
|
||||
|
||||
public Guid Id { get; }
|
||||
public string EntityType { get; }
|
||||
public string TypeLabel { get; }
|
||||
public string Summary { get; }
|
||||
public string DeletedAtDisplay { get; }
|
||||
public string ExpiresInDisplay { get; }
|
||||
|
||||
public TrashItemViewModel(TrashedItem item, string typeLabel)
|
||||
{
|
||||
Id = item.Id;
|
||||
EntityType = item.EntityType;
|
||||
TypeLabel = typeLabel;
|
||||
Summary = item.Summary;
|
||||
DeletedAtDisplay = item.DeletedAt.ToLocalTime().ToString("dd.MM.yyyy HH:mm");
|
||||
var daysLeft = RetentionDays - (DateTime.UtcNow - item.DeletedAt).Days;
|
||||
ExpiresInDisplay = daysLeft <= 0 ? "läuft heute ab" : $"noch {daysLeft} Tag(e)";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user