Phase B+1: Bootstrap-Splash + Logout im Drawer

- AuthBootstrapping als neuer Initial-State im AuthBloc. Beim Cold-Start
  bleibt die App im Splash, bis restoreSession entweder Authenticated
  oder Unauthenticated emittiert — kein sichtbarer LoginPage-Flash mehr
  für Nutzer mit gespeicherter Session.
- LoginEnforcer rendert für AuthBootstrapping ein eigenes Splash-Widget
  mit Logo + Spinner, für Unauthenticated weiterhin die LoginPage.
- AuthBloc._handleRestore emittiert Unauthenticated explizit, wenn
  restoreSession false liefert oder wirft — sonst bliebe der Bootstrap-
  State hängen.
- HomeAppDrawer zeigt jetzt displayName + Personalnummer aus dem
  Authenticated-State im Header und bekommt einen Abmelden-Eintrag
  unten (rot, Confirm-Dialog), der LogoutRequested feuert. Der
  Provider löscht den Refresh-Token aus der Secure Storage und der
  LoginEnforcer routet automatisch zurück auf die LoginPage.
This commit is contained in:
Dennis Nemec
2026-05-15 11:21:57 +02:00
parent cb22fff407
commit f074d53f3d
4 changed files with 116 additions and 12 deletions

View File

@ -1,10 +1,18 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/feature/authentication/presentation/login_page.dart';
import '../bloc/auth_bloc.dart';
import '../bloc/auth_state.dart';
/// Routet die App zwischen Bootstrap-Splash, Login-Page und der
/// eigentlichen UI:
/// * `AuthBootstrapping` → Splash (verhindert Login-Page-Flash beim
/// Cold-Start, während der Refresh-Token gegen Keycloak gegengeprüft
/// wird).
/// * `Authenticated` → `child` (= reguläre App).
/// * sonst → LoginPage; `sessionExpired`-Banner wenn ein Refresh
/// serverseitig abgewiesen wurde.
class LoginEnforcer extends StatelessWidget {
final Widget child;
@ -17,10 +25,37 @@ class LoginEnforcer extends StatelessWidget {
if (state is Authenticated) {
return child;
}
if (state is AuthBootstrapping) {
return const _AuthBootstrapSplash();
}
final expired = state is Unauthenticated && state.sessionExpired;
return LoginPage(sessionExpired: expired);
},
);
}
}
class _AuthBootstrapSplash extends StatelessWidget {
const _AuthBootstrapSplash();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 60),
child: Image.asset(
'assets/holzleitner_Logo_2017_RZ_transparent.png',
),
),
const SizedBox(height: 32),
const CircularProgressIndicator(),
],
),
),
);
}
}