import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:hl_lieferservice/bloc/app_bloc.dart'; import 'package:hl_lieferservice/bloc/app_states.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_bloc.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_event.dart'; import 'package:hl_lieferservice/feature/authentication/bloc/auth_state.dart'; import 'package:hl_lieferservice/feature/settings/presentation/environment_settings_page.dart'; /// Login-Page nach der Migration auf Keycloak OIDC (Phase B). /// /// Der eigentliche Flow läuft komplett im `AuthBloc` → /// `KeycloakOidcTokenProvider.login()`: `flutter_appauth` öffnet einen /// Browser-Tab, der RedirectURI `holzleitner://oauth2redirect` kommt /// zurück, der Code wird gegen Tokens getauscht. class LoginPage extends StatelessWidget { const LoginPage({ super.key, this.sessionExpired = false, this.loginTimedOut = false, }); final bool sessionExpired; /// Hinweis-Banner, dass der vorherige Login-Versuch ins 10-s-Timeout /// gelaufen ist (typisch: Verbindungsabbruch oder hängender Issuer). /// Wird vom `LoginEnforcer` aus `Unauthenticated.loginTimedOut` gefüttert. final bool loginTimedOut; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ // Während eines laufenden Logins gesperrt: Ein Umgebungswechsel // würde den Token-Provider mitten im Browser-Flow austauschen. BlocBuilder( builder: (context, state) => IconButton( tooltip: 'Einstellungen', icon: const Icon(Icons.settings), onPressed: state is Authenticating ? null : () => Navigator.of(context).push( MaterialPageRoute( builder: (_) => const EnvironmentSettingsPage(), ), ), ), ), ], ), body: Column( children: [ if (sessionExpired) MaterialBanner( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), content: const Text( 'Deine Sitzung ist abgelaufen. Bitte melde dich erneut an.', style: TextStyle(color: Colors.white), ), backgroundColor: Colors.orange.shade800, leading: const Icon( Icons.warning_amber_rounded, color: Colors.white, ), actions: const [SizedBox.shrink()], ), if (loginTimedOut) MaterialBanner( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), content: const Text( 'Einloggen nicht möglich. Später erneut versuchen.', style: TextStyle(color: Colors.white), ), backgroundColor: Colors.red.shade700, leading: const Icon( Icons.cloud_off, color: Colors.white, ), actions: const [SizedBox.shrink()], ), Expanded( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(50), child: Column( children: [ Image.asset( 'assets/holzleitner_Logo_2017_RZ_transparent.png', ), const Padding( padding: EdgeInsets.only(top: 20), child: Text( 'Auslieferservice', style: TextStyle( fontWeight: FontWeight.w400, fontSize: 20, ), ), ), ], ), ), FractionallySizedBox( widthFactor: 0.8, child: Padding( padding: const EdgeInsets.only(top: 15, bottom: 15), child: BlocBuilder( builder: (context, state) { if (state is Authenticating) { return Column( children: const [ CircularProgressIndicator(), SizedBox(height: 16), Text('Anmeldung wird abgeschlossen…'), ], ); } return OutlinedButton( onPressed: () => context.read().add( const LoginRequested(), ), child: const Text( 'Anmelden mit Holzleitner Login', ), ); }, ), ), ), const _ActiveEnvironmentHint(), ], ), ), ), ], ), ); } } /// Zeigt, gegen welchen Server die Anmeldung läuft — damit niemand /// versehentlich in der Testumgebung ausliefert. class _ActiveEnvironmentHint extends StatelessWidget { const _ActiveEnvironmentHint(); @override Widget build(BuildContext context) { final state = context.watch().state; if (state is! AppConfigLoaded) return const SizedBox.shrink(); final env = state.active; return Text( 'Umgebung: ${env.name} (${env.hostAndPort})', style: TextStyle( fontSize: 13, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ); } }