Files
unionflow-server-api/unionflow-mobile-apps/lib/features/auth/presentation/pages/login_page.dart

403 lines
12 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// 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<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());
}
}