import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/auth/bloc/auth_bloc.dart'; import '../../../../core/auth/bloc/auth_event.dart'; import '../../../../core/auth/models/auth_state.dart'; import '../../../../core/auth/models/login_request.dart'; import '../../../../shared/theme/app_theme.dart'; import '../../../../shared/widgets/buttons/buttons.dart'; import '../widgets/login_form.dart'; import '../widgets/login_header.dart'; import '../widgets/login_footer.dart'; /// Écran de connexion avec interface sophistiquée class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State with TickerProviderStateMixin { late AnimationController _animationController; late AnimationController _shakeController; late Animation _fadeAnimation; late Animation _slideAnimation; late Animation _shakeAnimation; final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); bool _obscurePassword = true; bool _rememberMe = false; bool _isLoading = false; @override void initState() { super.initState(); _setupAnimations(); _startEntryAnimation(); } void _setupAnimations() { _animationController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _shakeController = AnimationController( duration: const Duration(milliseconds: 600), 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: 50.0, end: 0.0, ).animate(CurvedAnimation( parent: _animationController, curve: const Interval(0.2, 0.8, curve: Curves.easeOut), )); _shakeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _shakeController, curve: Curves.elasticInOut, )); } void _startEntryAnimation() { Future.delayed(const Duration(milliseconds: 100), () { if (mounted) { _animationController.forward(); } }); } @override void dispose() { _animationController.dispose(); _shakeController.dispose(); _emailController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.backgroundLight, body: BlocListener( listener: _handleAuthStateChange, child: SafeArea( child: AnimatedBuilder( animation: _animationController, builder: (context, child) { return FadeTransition( opacity: _fadeAnimation, child: Transform.translate( offset: Offset(0, _slideAnimation.value), child: _buildLoginContent(), ), ); }, ), ), ), ); } Widget _buildLoginContent() { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( children: [ const SizedBox(height: 60), // Header avec logo et titre LoginHeader( onAnimationComplete: () {}, ), const SizedBox(height: 60), // Formulaire de connexion AnimatedBuilder( animation: _shakeAnimation, builder: (context, child) { return Transform.translate( offset: Offset( _shakeAnimation.value * 10 * (1 - _shakeAnimation.value) * (1 - _shakeAnimation.value), 0, ), child: LoginForm( formKey: _formKey, emailController: _emailController, passwordController: _passwordController, obscurePassword: _obscurePassword, rememberMe: _rememberMe, isLoading: _isLoading, onObscureToggle: () { setState(() { _obscurePassword = !_obscurePassword; }); HapticFeedback.selectionClick(); }, onRememberMeToggle: (value) { setState(() { _rememberMe = value; }); HapticFeedback.selectionClick(); }, onSubmit: _handleLogin, ), ), ), ), const SizedBox(height: 40), // Footer avec liens et informations const LoginFooter(), const SizedBox(height: 20), ], ), ); } void _handleAuthStateChange(BuildContext context, AuthState state) { setState(() { _isLoading = state.isLoading; }); if (state.status == AuthStatus.authenticated) { // Connexion réussie - navigation gérée par l'app principal _showSuccessMessage(); HapticFeedback.heavyImpact(); } else if (state.status == AuthStatus.error) { // Erreur de connexion _handleLoginError(state.errorMessage ?? 'Erreur inconnue'); } else if (state.status == AuthStatus.unauthenticated && state.errorMessage != null) { // Échec de connexion _handleLoginError(state.errorMessage!); } } void _handleLogin() { if (!_formKey.currentState!.validate()) { _triggerShakeAnimation(); HapticFeedback.mediumImpact(); return; } final email = _emailController.text.trim(); final password = _passwordController.text; if (email.isEmpty || password.isEmpty) { _showErrorMessage('Veuillez remplir tous les champs'); _triggerShakeAnimation(); return; } // Déclencher la connexion final loginRequest = LoginRequest( email: email, password: password, rememberMe: _rememberMe, ); context.read().add(AuthLoginRequested(loginRequest)); // Feedback haptique HapticFeedback.lightImpact(); } void _handleLoginError(String errorMessage) { _showErrorMessage(errorMessage); _triggerShakeAnimation(); HapticFeedback.mediumImpact(); // Effacer l'erreur après affichage Future.delayed(const Duration(seconds: 3), () { if (mounted) { context.read().add(const AuthErrorCleared()); } }); } void _triggerShakeAnimation() { _shakeController.reset(); _shakeController.forward(); } void _showSuccessMessage() { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon( Icons.check_circle, color: Colors.white, size: 24, ), const SizedBox(width: 12), const Text( 'Connexion réussie !', style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, ), ), ], ), backgroundColor: AppTheme.successColor, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), margin: const EdgeInsets.all(16), duration: const Duration(seconds: 2), ), ); } void _showErrorMessage(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon( Icons.error_outline, color: Colors.white, size: 24, ), const SizedBox(width: 12), Expanded( child: Text( message, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), ), ], ), backgroundColor: AppTheme.errorColor, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), margin: const EdgeInsets.all(16), duration: const Duration(seconds: 4), action: SnackBarAction( label: 'OK', textColor: Colors.white, onPressed: () { ScaffoldMessenger.of(context).hideCurrentSnackBar(); }, ), ), ); } }