fix: Geräte-Pairing - Fehlschlag beim Einlösen war unsichtbar
RedeemPairingCode() rief nach einem Fehlschlag sofort RestartApplication() auf, direkt nach dem Setzen von PairingStatus - die Fehlermeldung konnte nie gerendert werden, und wurde auch nirgends geloggt. Ein Fehlschlag (falscher/abgelaufener Code, Server nicht erreichbar) wirkte dadurch wie ein kommentarloser Absturz ohne jede Spur. - SettingsViewModel loggt den Fehler jetzt über AppLogger und zeigt vor dem Neustart einen Bestätigungsdialog mit der echten Fehlermeldung an. - AppBootstrapper.RestartApplication() gibt unter dotnet run/IDE-Debug (wo ProcessPath auf den dotnet-Host statt die App zeigt) die ursprünglichen Kommandozeilenargumente beim Neustart mit, statt nur die dotnet-CLI-Hilfe zu öffnen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,7 +82,17 @@ public static class AppBootstrapper
|
||||
{
|
||||
var exePath = Environment.ProcessPath;
|
||||
if (!string.IsNullOrEmpty(exePath))
|
||||
System.Diagnostics.Process.Start(exePath);
|
||||
{
|
||||
var psi = new System.Diagnostics.ProcessStartInfo(exePath) { UseShellExecute = false };
|
||||
// Unter "dotnet run"/IDE-Debug zeigt ProcessPath auf den SDK-Host (dotnet[.exe]) statt
|
||||
// auf die App selbst - ein Neustart ohne Argumente würde nur die dotnet-CLI-Hilfe
|
||||
// anzeigen statt die App neu zu starten. Die ursprünglichen Kommandozeilenargumente
|
||||
// (u.a. der DLL-Pfad) erneut mitgeben deckt auch diesen Fall ab.
|
||||
if (Path.GetFileNameWithoutExtension(exePath).Equals("dotnet", StringComparison.OrdinalIgnoreCase))
|
||||
foreach (var arg in Environment.GetCommandLineArgs())
|
||||
psi.ArgumentList.Add(arg);
|
||||
System.Diagnostics.Process.Start(psi);
|
||||
}
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -212,6 +212,12 @@ public partial class SettingsViewModel : ObservableObject
|
||||
/// den Stand des anderen Geräts ersetzt wird.
|
||||
public Func<Task<bool>>? OnConfirmPairingRestore { get; set; }
|
||||
|
||||
/// Vom Code-Behind gesetzt: zeigt einen Fehlerdialog, den der Nutzer aktiv wegklicken muss —
|
||||
/// notwendig, weil RedeemPairingCode() nach einem Fehlschlag sofort neu startet (_dbContext ist
|
||||
/// zu dem Zeitpunkt bereits disposed, siehe dort) und PairingStatus dadurch sonst nie sichtbar
|
||||
/// gerendert würde.
|
||||
public Func<string, Task>? OnShowPairingError { get; set; }
|
||||
|
||||
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||||
|
||||
private readonly ISchoolHolidayRepository _schoolHolidays;
|
||||
@@ -225,6 +231,7 @@ public partial class SettingsViewModel : ObservableObject
|
||||
private readonly EventQueue _eventQueue;
|
||||
private readonly SnapshotService? _snapshotService;
|
||||
private readonly CompetencyCatalogImportService _catalogImport;
|
||||
private readonly AppLogger _logger;
|
||||
|
||||
public SettingsViewModel(ISubjectRepository subjects, ICompetencyDomainRepository domainRepo,
|
||||
IGradingKeyTemplateRepository gradingKeyTemplates, IGradingSchemeRepository gradingSchemes,
|
||||
@@ -236,8 +243,9 @@ public partial class SettingsViewModel : ObservableObject
|
||||
ISupervisionDutyRepository supervisionDuties, LetterTemplateService letterTemplates,
|
||||
AiSettingsService aiSettings, AiPlanningService aiPlanning,
|
||||
SyncSettingsService syncSettings, SyncAuthService syncAuth, EventQueue eventQueue,
|
||||
SnapshotService? snapshotService = null)
|
||||
AppLogger logger, SnapshotService? snapshotService = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_subjects = subjects;
|
||||
_domainRepo = domainRepo;
|
||||
_gradingKeyTemplates = gradingKeyTemplates;
|
||||
@@ -564,6 +572,11 @@ public partial class SettingsViewModel : ObservableObject
|
||||
// _dbContext ist bereits disposed, PairingStatus ist aber ein reines ViewModel-Feld
|
||||
// ohne DB-Zugriff - das Setzen ist unabhängig davon noch sicher.
|
||||
PairingStatus = $"Fehlgeschlagen: {ex.Message}";
|
||||
_logger.Error("Geräte-Pairing (Redeem) fehlgeschlagen", ex);
|
||||
// Ohne diesen Dialog würde PairingStatus nie gerendert: der direkt folgende Neustart
|
||||
// (unten, auch im Fehlerfall nötig, siehe Kommentar oben) beendet den Prozess, bevor
|
||||
// Avalonia den nächsten Frame zeichnen könnte.
|
||||
if (OnShowPairingError is not null) await OnShowPairingError(ex.Message);
|
||||
}
|
||||
AppBootstrapper.RestartApplication();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public partial class SettingsView : UserControl
|
||||
vm.OnAppLockChanged = () => App.Services.GetRequiredService<AppLockViewModel>().ApplyConfig();
|
||||
vm.OnConfirmHardDelete = ShowHardDeleteConfirmDialog;
|
||||
vm.OnConfirmPairingRestore = ShowPairingRestoreConfirmDialog;
|
||||
vm.OnShowPairingError = ShowPairingErrorDialog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +67,19 @@ public partial class SettingsView : UserControl
|
||||
return owner is not null && await dialog.ShowDialog<bool>(owner);
|
||||
}
|
||||
|
||||
private async Task ShowPairingErrorDialog(string message)
|
||||
{
|
||||
var info = new ConfirmDialogInfo
|
||||
{
|
||||
Title = "Kopplung fehlgeschlagen",
|
||||
Message = $"{message}\n\nDie App wird jetzt neu gestartet (unveränderte Datenbank).",
|
||||
ConfirmText = "OK",
|
||||
};
|
||||
var dialog = new ConfirmDialog { DataContext = info };
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner is not null) await dialog.ShowDialog<bool>(owner);
|
||||
}
|
||||
|
||||
private async void OnImportClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
|
||||
Reference in New Issue
Block a user