/// Page de Connexion Adaptative - Démonstration des Rôles /// Interface de connexion avec sélection de rôles pour démonstration library login_page; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/auth/bloc/auth_bloc.dart'; import '../../../../core/auth/models/user_role.dart'; import '../../../../core/design_system/tokens/typography_tokens.dart'; import 'keycloak_webview_auth_page.dart'; /// Page de connexion avec démonstration des rôles class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State with TickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _initializeAnimations(); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _initializeAnimations() { _animationController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _animationController, curve: const Interval(0.0, 0.6, curve: Curves.easeOut), )); _slideAnimation = Tween( begin: const Offset(0.0, 0.3), end: Offset.zero, ).animate(CurvedAnimation( parent: _animationController, curve: const Interval(0.3, 1.0, curve: Curves.easeOutCubic), )); _animationController.forward(); } /// Ouvre la page WebView d'authentification void _openWebViewAuth(BuildContext context, AuthWebViewRequired state) { debugPrint('🚀 Ouverture WebView avec URL: ${state.authUrl}'); debugPrint('🔑 State: ${state.state}'); debugPrint('🔐 Code verifier: ${state.codeVerifier.substring(0, 10)}...'); debugPrint('📱 Tentative de navigation vers KeycloakWebViewAuthPage...'); Navigator.of(context).push( MaterialPageRoute( builder: (context) => KeycloakWebViewAuthPage( onAuthSuccess: (user) { debugPrint('✅ Authentification réussie pour: ${user.fullName}'); debugPrint('🔄 Notification du BLoC avec les données utilisateur...'); // Notifier le BLoC du succès avec les données utilisateur context.read().add(AuthWebViewCallback( 'success', user: user, )); // Fermer la WebView - la navigation sera gérée par le BlocListener Navigator.of(context).pop(); }, onAuthError: (error) { debugPrint('❌ Erreur d\'authentification: $error'); // Fermer la WebView et afficher l'erreur Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur d\'authentification: $error'), backgroundColor: Colors.red, duration: const Duration(seconds: 5), ), ); }, onAuthCancel: () { debugPrint('❌ Authentification annulée par l\'utilisateur'); // Fermer la WebView Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Authentification annulée'), backgroundColor: Colors.orange, ), ); }, ), ), ); debugPrint('✅ Navigation vers KeycloakWebViewAuthPage lancée'); } @override Widget build(BuildContext context) { return Scaffold( body: BlocConsumer( listener: (context, state) { debugPrint('🔄 État BLoC reçu: ${state.runtimeType}'); if (state is AuthAuthenticated) { debugPrint('✅ Utilisateur authentifié, navigation vers dashboard'); Navigator.of(context).pushReplacementNamed('/dashboard'); } else if (state is AuthError) { debugPrint('❌ Erreur d\'authentification: ${state.message}'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: Colors.red, ), ); } else if (state is AuthWebViewRequired) { debugPrint('🚀 État AuthWebViewRequired reçu, ouverture WebView...'); // Ouvrir la page WebView d'authentification immédiatement WidgetsBinding.instance.addPostFrameCallback((_) { _openWebViewAuth(context, state); }); } else if (state is AuthLoading) { debugPrint('⏳ État de chargement...'); } else { debugPrint('ℹ️ État non géré: ${state.runtimeType}'); } }, builder: (context, state) { // Vérification supplémentaire dans le builder if (state is AuthWebViewRequired) { debugPrint('🔄 Builder détecte AuthWebViewRequired, ouverture WebView...'); WidgetsBinding.instance.addPostFrameCallback((_) { _openWebViewAuth(context, state); }); } return _buildLoginContent(context, state); }, ), ); } Widget _buildLoginContent(BuildContext context, AuthState state) { return Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFF6C5CE7), Color(0xFF5A4FCF), ], ), ), child: SafeArea( child: AnimatedBuilder( animation: _animationController, builder: (context, child) { return FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: _buildLoginUI(), ), ); }, ), ), ); } Widget _buildLoginUI() { return Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // Logo et titre _buildHeader(), const SizedBox(height: 48), // Information Keycloak _buildKeycloakInfo(), const SizedBox(height: 32), // Bouton de connexion _buildLoginButton(), const SizedBox(height: 32), // Informations de démonstration _buildDemoInfo(), ], ), ), ); } Widget _buildHeader() { return Column( children: [ Container( width: 100, height: 100, decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(50), border: Border.all( color: Colors.white.withOpacity(0.3), width: 2, ), ), child: const Icon( Icons.account_circle, size: 60, color: Colors.white, ), ), const SizedBox(height: 24), Text( 'UnionFlow', style: TypographyTokens.headlineLarge.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( 'Dashboard Adaptatif Révolutionnaire', style: TypographyTokens.bodyLarge.copyWith( color: Colors.white.withOpacity(0.9), ), textAlign: TextAlign.center, ), ], ); } Widget _buildKeycloakInfo() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( Icons.security, color: Colors.white.withOpacity(0.9), size: 32, ), const SizedBox(height: 12), Text( 'Authentification Keycloak', style: TypographyTokens.bodyLarge.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( 'Connectez-vous avec vos identifiants UnionFlow', style: TypographyTokens.bodyMedium.copyWith( color: Colors.white.withOpacity(0.8), ), textAlign: TextAlign.center, ), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( 'localhost:8180/realms/unionflow', style: TypographyTokens.bodySmall.copyWith( color: Colors.white.withOpacity(0.7), fontFamily: 'monospace', ), ), ), ], ), ); } Widget _buildLoginButton() { return BlocBuilder( builder: (context, state) { final isLoading = state is AuthLoading; return SizedBox( width: double.infinity, height: 56, child: ElevatedButton( onPressed: isLoading ? null : _handleLogin, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: const Color(0xFF6C5CE7), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), elevation: 0, ), child: isLoading ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.login, size: 20), const SizedBox(width: 8), Text( 'Se Connecter avec Keycloak', style: TypographyTokens.bodyLarge.copyWith( fontWeight: FontWeight.bold, ), ), ], ), ), ); }, ); } Widget _buildDemoInfo() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: Column( children: [ Icon( Icons.info_outline, color: Colors.white.withOpacity(0.8), size: 24, ), const SizedBox(height: 8), Text( 'Mode Démonstration', style: TypographyTokens.bodyMedium.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( 'Sélectionnez un rôle ci-dessus pour voir le dashboard adaptatif correspondant. Chaque rôle affiche une interface unique !', style: TypographyTokens.bodySmall.copyWith( color: Colors.white.withOpacity(0.8), ), textAlign: TextAlign.center, ), ], ), ); } void _handleLogin() { // Démarrer l'authentification Keycloak context.read().add(const AuthLoginRequested()); } }