- Dashboard: Fehlzeiten-Warnung lädt Mitarbeitssitzungen einmal vorab statt pro Schüler/Eintrag einzeln nachzuschlagen (N+1 vermieden); neues IParticipationSessionRepository.GetAll() dafür. - CI: .gitea/workflows/ci.yml baut und testet bei jedem Push/PR. Dabei fehlende Release|Any CPU-Konfiguration für 6 Projekte in der .sln behoben (LehrerApp.Data.Tests wurde bei Release-Builds der Solution bislang stillschweigend übersprungen). TreatWarningsAsErrors jetzt aktiv. - SettingsViewModel (1986 Zeilen) als partial class auf 20 Themen- dateien aufgeteilt, Verhalten unverändert. - Backup: optionaler zweiter Sicherungsordner (USB-Stick/Netzlaufwerk, best-effort) und Integritätsprüfung nach jedem Backup (DatabaseEncryptionService.CanOpenAndRead). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
171 lines
6.5 KiB
C#
171 lines
6.5 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using LehrerApp.Core.Interfaces;
|
|
using LehrerApp.Core.Models;
|
|
using LehrerApp.Core.Services;
|
|
using LehrerApp.Data;
|
|
using LehrerApp.Desktop.Services;
|
|
using LehrerApp.Desktop.ViewModels.Planning;
|
|
using LehrerApp.Sync;
|
|
using LehrerApp.Sync.Crypto;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace LehrerApp.Desktop.ViewModels.Settings;
|
|
|
|
public partial class SettingsViewModel
|
|
{
|
|
// ── WebUntis-iCal-Abgleich (Nutzer-Feedback) ──────────────────────────────
|
|
|
|
[ObservableProperty] private bool _untisEnabled;
|
|
[ObservableProperty] private bool _untisIsConfigured;
|
|
[ObservableProperty] private string _untisIcalUrlInput = "";
|
|
[ObservableProperty] private string _untisUrlError = "";
|
|
[ObservableProperty] private string _untisStatusDisplay = "";
|
|
[ObservableProperty] private bool _untisFetchBusy;
|
|
[ObservableProperty] private bool _untisApiIsConfigured;
|
|
[ObservableProperty] private string _untisSchool = "";
|
|
[ObservableProperty] private string _untisHost = "";
|
|
[ObservableProperty] private string _untisUsername = "";
|
|
[ObservableProperty] private string _untisPassword = "";
|
|
[ObservableProperty] private string _untisApiStatus = "";
|
|
[ObservableProperty] private bool _untisApiBusy;
|
|
[ObservableProperty] private string? _untisHomeroomClassName;
|
|
public string UntisHomeroomClassDisplay => UntisHomeroomClassName is { Length: > 0 } name
|
|
? $"Ausgewählt: {name}"
|
|
: "Keine Klasse ausgewählt.";
|
|
partial void OnUntisHomeroomClassNameChanged(string? value) => OnPropertyChanged(nameof(UntisHomeroomClassDisplay));
|
|
public Func<Task>? OnReviewUntisMapping { get; set; }
|
|
/// Vom Code-Behind gesetzt: öffnet den WebUntis-Klassenauswahldialog, liefert null bei Abbruch.
|
|
public Func<Task<(int UntisId, string Name)?>>? OnPickHomeroomClass { get; set; }
|
|
|
|
// ── WebUntis-iCal-Abgleich: Laden / Speichern / Entfernen / Jetzt abrufen ────
|
|
//
|
|
// Wie bei Sync deckt ein Neustart das Registrieren von UntisSyncService ab
|
|
// (AppBootstrapper registriert es nur einmalig beim Start, wenn URL+Enabled vorliegen).
|
|
|
|
private void LoadUntisSettings()
|
|
{
|
|
UntisEnabled = _untisSettings.Enabled;
|
|
UntisIsConfigured = _untisSettings.IsConfigured;
|
|
UntisStatusDisplay = _untisSettings.LastSyncAt is { } at
|
|
? $"Letzter Abgleich: {at.ToLocalTime():dd.MM.yyyy HH:mm} — {_untisSettings.LastSyncStatus}"
|
|
: "Noch kein Abgleich durchgeführt.";
|
|
UntisApiIsConfigured = _untisSettings.ApiIsConfigured;
|
|
if (_untisSettings.GetApiCredentials() is { } credentials)
|
|
{
|
|
UntisSchool = credentials.School;
|
|
UntisHost = credentials.Host;
|
|
UntisUsername = credentials.Username;
|
|
UntisApiStatus = $"API-Zugang für {credentials.Username} ist lokal verschlüsselt gespeichert.";
|
|
}
|
|
UntisHomeroomClassName = _untisSettings.HomeroomClassName;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task UntisSaveApi()
|
|
{
|
|
UntisApiStatus = "";
|
|
if (_untisIntegration is null)
|
|
{
|
|
UntisApiStatus = "Die WebUntis-Integration ist nicht verfügbar.";
|
|
return;
|
|
}
|
|
if (string.IsNullOrWhiteSpace(UntisSchool) || string.IsNullOrWhiteSpace(UntisUsername) ||
|
|
string.IsNullOrWhiteSpace(UntisPassword))
|
|
{
|
|
UntisApiStatus = "Schule, Benutzername und Passwort sind erforderlich.";
|
|
return;
|
|
}
|
|
UntisApiBusy = true;
|
|
try
|
|
{
|
|
var credentials = new WebUntisCredentials(UntisSchool.Trim(), UntisHost.Trim(),
|
|
UntisUsername.Trim(), UntisPassword);
|
|
await _untisIntegration.ConnectAsync(credentials);
|
|
_untisSettings.SetApiCredentials(credentials);
|
|
UntisPassword = "";
|
|
UntisApiIsConfigured = true;
|
|
UntisApiStatus = "Anmeldung erfolgreich. Die WebUntis-Session bleibt bei Nutzung bis zu 10 Minuten offen.";
|
|
}
|
|
catch (WebUntisIntegrationException ex) { UntisApiStatus = ex.Message; }
|
|
finally { UntisApiBusy = false; }
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task UntisRemoveApi()
|
|
{
|
|
UntisApiBusy = true;
|
|
try { if (_untisIntegration is not null) await _untisIntegration.DisconnectAsync(); }
|
|
catch (WebUntisIntegrationException) { /* lokale Zugangsdaten trotzdem sicher entfernen */ }
|
|
finally
|
|
{
|
|
_untisSettings.ClearApiCredentials();
|
|
UntisPassword = "";
|
|
UntisApiIsConfigured = false;
|
|
UntisApiStatus = "WebUntis-API-Zugang entfernt.";
|
|
UntisApiBusy = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void UntisSaveUrl()
|
|
{
|
|
UntisUrlError = "";
|
|
if (string.IsNullOrWhiteSpace(UntisIcalUrlInput)) { UntisUrlError = "iCal-URL erforderlich."; return; }
|
|
if (!Uri.TryCreate(UntisIcalUrlInput, UriKind.Absolute, out _)) { UntisUrlError = "Ungültige URL."; return; }
|
|
|
|
_untisSettings.SetIcalUrl(UntisIcalUrlInput.Trim());
|
|
_untisSettings.SetEnabled(true);
|
|
UntisIcalUrlInput = "";
|
|
AppBootstrapper.RestartApplication();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void UntisRemove()
|
|
{
|
|
_untisSettings.ClearIcalUrl();
|
|
LoadUntisSettings();
|
|
AppBootstrapper.RestartApplication();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task UntisFetchNow()
|
|
{
|
|
if (_untisSync is null) { UntisStatusDisplay = "Abgleich nicht aktiv — App neu starten."; return; }
|
|
UntisFetchBusy = true;
|
|
try
|
|
{
|
|
await _untisSync.PollAsync();
|
|
LoadUntisSettings();
|
|
}
|
|
finally { UntisFetchBusy = false; }
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task UntisReviewMapping()
|
|
{
|
|
if (OnReviewUntisMapping is not null) await OnReviewUntisMapping();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task UntisPickHomeroomClass()
|
|
{
|
|
if (OnPickHomeroomClass is null) return;
|
|
var result = await OnPickHomeroomClass();
|
|
if (result is null) return;
|
|
_untisSettings.SetHomeroomClass(result.Value.UntisId, result.Value.Name);
|
|
LoadUntisSettings();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void UntisClearHomeroomClass()
|
|
{
|
|
_untisSettings.SetHomeroomClass(null, null);
|
|
LoadUntisSettings();
|
|
}
|
|
}
|