using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Services; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels.ClassTeacher; using LehrerApp.Desktop.ViewModels.Groups; using LehrerApp.Desktop.ViewModels.Planning; using LehrerApp.Desktop.ViewModels.Settings; using LehrerApp.Desktop.ViewModels.Students; using LehrerApp.Desktop.ViewModels.Workload; using Microsoft.Extensions.DependencyInjection; using System.Collections.ObjectModel; namespace LehrerApp.Desktop.ViewModels; public partial class MainWindowViewModel : ObservableObject { private readonly IServiceProvider _services; [ObservableProperty] private ObservableObject? _currentPage; [ObservableProperty] private NavItem _activeNavItem = NavItem.Dashboard; [ObservableProperty] private string _currentSchoolYear = ""; public SyncStatusViewModel SyncStatus { get; } public ObservableCollection Toasts { get; } public AppLockViewModel AppLock { get; } public bool IsDashboardActive => ActiveNavItem == NavItem.Dashboard; public bool IsGroupsActive => ActiveNavItem == NavItem.Groups; public bool IsStudentsActive => ActiveNavItem == NavItem.Students; public bool IsExamsActive => ActiveNavItem == NavItem.Exams; public bool IsPlannerActive => ActiveNavItem == NavItem.Planner; public bool IsWorkloadActive => ActiveNavItem == NavItem.Workload; public bool IsClassTeacherActive => ActiveNavItem == NavItem.ClassTeacher; public bool IsSettingsActive => ActiveNavItem == NavItem.Settings; public MainWindowViewModel(IServiceProvider services, DashboardViewModel dashboard, SchoolYearService sy, SyncStatusViewModel syncStatus, NotificationService notifications, AppLockViewModel appLock) { _services = services; SyncStatus = syncStatus; Toasts = notifications.Toasts; AppLock = appLock; CurrentSchoolYear = sy.CurrentSchoolYear(); CurrentPage = dashboard; AppLock.ApplyConfig(); SyncStatus.DataChanged += OnSyncDataChanged; } // EventApplier schreibt bei eingehenden Sync-Ereignissen absichtlich direkt auf die rohe // LiteDB-Collection, an jedem ViewModel vorbei (Ping-Pong-Vermeidung, siehe EventApplier- // Klassenkommentar) - ohne diesen Hook blieb die gerade sichtbare Seite bis zum nächsten // manuellen Neuladen (z.B. Tab-Wechsel) auf dem alten Stand (TODO 10.1.11). Lädt bewusst nur // die aktuell sichtbare Seite über ihren eigenen, längst vorhandenen Lade-Einstieg neu - keine // neue Lade-Logik, keine Navigation weg von einer offenen Detailansicht. SyncEngine.DataChanged // feuert aus dem Sync-Timer-Hintergrundthread - daher der Dispatcher-Sprung zurück auf den // UI-Thread (gleiches Muster wie NotificationService.Show). private void OnSyncDataChanged() { if (Dispatcher.UIThread.CheckAccess()) RefreshCurrentPage(); else Dispatcher.UIThread.Post(RefreshCurrentPage); } private void RefreshCurrentPage() { switch (CurrentPage) { case DashboardViewModel vm: vm.RefreshCommand.Execute(null); break; case GroupListViewModel vm: vm.RefreshCommand.Execute(null); break; case StudentListViewModel vm: vm.LoadStudents(); break; case TimetableViewModel vm: vm.Load(); break; case WorkloadViewModel vm: vm.Tasks.Load(); vm.TimeTracking.Load(); vm.Evaluation.Load(); break; case ClassTeacherOverviewViewModel vm: vm.LoadCommand.Execute(null); break; case GroupDetailViewModel { Group: { } group } vm: vm.LoadGroup(group.Id); break; // Inline-Bearbeitung (IsEditing) nicht überschreiben - anders als die Gruppenansicht // laufen Namens-/Geschlechtsänderungen hier nicht über einen Dialog. case StudentDetailViewModel { Student: { } student, IsEditing: false } vm: vm.LoadStudent(student.Id); break; } } partial void OnActiveNavItemChanged(NavItem value) { OnPropertyChanged(nameof(IsDashboardActive)); OnPropertyChanged(nameof(IsGroupsActive)); OnPropertyChanged(nameof(IsStudentsActive)); OnPropertyChanged(nameof(IsExamsActive)); OnPropertyChanged(nameof(IsPlannerActive)); OnPropertyChanged(nameof(IsWorkloadActive)); OnPropertyChanged(nameof(IsClassTeacherActive)); OnPropertyChanged(nameof(IsSettingsActive)); } [RelayCommand] private void NavigateTo(NavItem item) { ActiveNavItem = item; CurrentPage = item switch { NavItem.Dashboard => GetDashboard(), NavItem.Groups => _services.GetRequiredService(), NavItem.Students => GetStudents(), NavItem.Exams => new PlaceholderViewModel { Title = "Klausuren", Icon = "📝" }, NavItem.Planner => GetTimetable(), NavItem.Workload => GetWorkload(), NavItem.ClassTeacher => GetClassTeacherOverview(), NavItem.Settings => _services.GetRequiredService(), _ => CurrentPage, }; } private DashboardViewModel GetDashboard() { var dashboard = _services.GetRequiredService(); dashboard.RefreshCommand.Execute(null); return dashboard; } private StudentListViewModel GetStudents() { var students = _services.GetRequiredService(); students.SelectedStudent = null; students.LoadStudents(); return students; } private TimetableViewModel GetTimetable() { var timetable = _services.GetRequiredService(); timetable.WeekOffset = 0; timetable.Load(); timetable.ActiveTabIndex = 0; return timetable; } private WorkloadViewModel GetWorkload() { var workload = _services.GetRequiredService(); workload.Tasks.Load(); workload.TimeTracking.Load(); workload.Evaluation.Load(); workload.ActiveTabIndex = 0; return workload; } private ClassTeacherOverviewViewModel GetClassTeacherOverview() { var vm = _services.GetRequiredService(); vm.OnNavigateToSettings = () => { NavigateToSettings(SettingsTab.WebUntis); return Task.CompletedTask; }; vm.LoadCommand.Execute(null); return vm; } public void NavigateToGroupDetail(Guid groupId, int initialTab = 0) { ActiveNavItem = NavItem.Groups; var vm = _services.GetRequiredService(); vm.ActiveTabIndex = initialTab; CurrentPage = vm; // View bindet zuerst → Callbacks werden registriert vm.LoadGroup(groupId); // dann Daten laden } public void NavigateToSettings(SettingsTab initialTab = SettingsTab.Subjects) { ActiveNavItem = NavItem.Settings; var vm = _services.GetRequiredService(); vm.ActiveTabIndex = (int)initialTab; CurrentPage = vm; } public void NavigateToStudent(Guid studentId) { ActiveNavItem = NavItem.Students; var vm = _services.GetRequiredService(); vm.OnReturnToStudentList = NavigateToStudents; vm.LoadStudent(studentId); CurrentPage = vm; } public void NavigateToStudents() => NavigateTo(NavItem.Students); /// Sprungziel aus dem Kurs-Dashboard ("Anstehende Aufgaben für diese Klasse") in den /// Aufgaben-Bereich — anders als NavigateToGroupDetail/NavigateToSettings kein Tab innerhalb /// einer Detailansicht, sondern ein eigener Top-Level-Bereich (NavItem.Workload). public void NavigateToWorkload() => NavigateTo(NavItem.Workload); } public enum NavItem { Dashboard, Groups, Students, Exams, Planner, Workload, ClassTeacher, Settings } public partial class PlaceholderViewModel : ObservableObject { [ObservableProperty] private string _title = ""; [ObservableProperty] private string _icon = ""; }