import 'package:hl_lieferservice/feature/authentication/model/user.dart'; abstract class AuthState {} /// Initialer State beim App-Start: der Refresh-Token aus der Secure /// Storage wird gerade gegen das Issuer-Endpoint geprüft. Die UI zeigt /// in dieser Zeit einen Splash statt LoginPage, damit Nutzer mit /// gespeicherter Session keinen sichtbaren Login-Flash sehen. class AuthBootstrapping extends AuthState {} class Unauthenticated extends AuthState { final bool sessionExpired; /// `true`, wenn der letzte Login-Versuch in das 10-s-Timeout im /// [AuthBloc] gelaufen ist (z. B. Verbindungsabbruch während /// `tokenProvider.login()` oder hängender Issuer). Die [LoginPage] /// blendet daraufhin einen Hinweis ein. final bool loginTimedOut; Unauthenticated({this.sessionExpired = false, this.loginTimedOut = false}); } /// Transient state während dem PKCE-Flow (Browser-Tab offen, /// Token-Tausch läuft). class Authenticating extends AuthState {} /// Erfolgreiche Authentifizierung. Trägt sowohl die "neuen" /// Token-/Claim-Felder als auch das Legacy-User-Objekt — Letzteres /// wird aus den Claims befüllt und mit Phase D komplett entfernt. class Authenticated extends AuthState { Authenticated({ required this.personalnummer, required this.displayName, required this.email, required this.user, required this.sessionId, }); /// Personalnummer aus dem `personalnummer`-Custom-Claim. Das /// Backend nutzt diese als Account-Identifier. final int personalnummer; /// Beste verfügbare Beschriftung für den Fahrer: /// `name` > `given_name + family_name` > `preferred_username`. final String displayName; /// E-Mail aus dem `email`-Claim, falls vorhanden. final String? email; /// Legacy: das alte `User`-Domänenobjekt, aus den Claims befüllt. /// Wird in Phase D entfernt, sobald keine Aufrufer mehr da sind. final User user; /// Legacy: füllt für die alten `getSessionOrThrow()`-Aufrufer /// einen Wert — die laufen sowieso gegen ERPframe und werden in /// Phase D abgelöst. final String sessionId; /// Konstruiert den State aus dem dekodierten ID-Token-Payload. factory Authenticated.fromClaims({ required Map claims, required String accessToken, }) { final personalnummerRaw = claims['personalnummer']; final personalnummer = switch (personalnummerRaw) { int v => v, String v => int.parse(v), _ => throw FormatException( 'personalnummer-Claim fehlt oder hat unerwarteten Typ ' '(${personalnummerRaw.runtimeType})', ), }; final given = (claims['given_name'] as String?) ?? ''; final family = (claims['family_name'] as String?) ?? ''; final preferredUsername = (claims['preferred_username'] as String?) ?? 'fahrer'; final fullName = (claims['name'] as String?)?.trim(); final displayName = fullName != null && fullName.isNotEmpty ? fullName : (given.isNotEmpty || family.isNotEmpty ? '$given $family'.trim() : preferredUsername); final email = claims['email'] as String?; final user = User( number: personalnummer.toString(), firstName: given, lastName: family, mail: email ?? '', ); return Authenticated( personalnummer: personalnummer, displayName: displayName, email: email, user: user, sessionId: accessToken, ); } }