402 lines
12 KiB
Dart
402 lines
12 KiB
Dart
/// 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/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<LoginPage> createState() => _LoginPageState();
|
||
}
|
||
|
||
class _LoginPageState extends State<LoginPage>
|
||
with TickerProviderStateMixin {
|
||
|
||
late AnimationController _animationController;
|
||
late Animation<double> _fadeAnimation;
|
||
late Animation<Offset> _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<double>(
|
||
begin: 0.0,
|
||
end: 1.0,
|
||
).animate(CurvedAnimation(
|
||
parent: _animationController,
|
||
curve: const Interval(0.0, 0.6, curve: Curves.easeOut),
|
||
));
|
||
|
||
_slideAnimation = Tween<Offset>(
|
||
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<void>(
|
||
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<AuthBloc>().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<AuthBloc, AuthState>(
|
||
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<AuthBloc, AuthState>(
|
||
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<AuthBloc>().add(const AuthLoginRequested());
|
||
}
|
||
}
|