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 '../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.elasticIn, )); } void _startEntryAnimation() { _animationController.forward(); } void _startShakeAnimation() { _shakeController.reset(); _shakeController.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: BlocConsumer( listener: (context, state) { setState(() { _isLoading = state.status == AuthStatus.checking; }); if (state.status == AuthStatus.error) { _startShakeAnimation(); _showErrorSnackBar(state.errorMessage ?? 'Erreur de connexion'); } else if (state.status == AuthStatus.authenticated) { _showSuccessSnackBar('Connexion réussie !'); } }, builder: (context, state) { return SafeArea( child: _buildLoginContent(), ); }, ), ); } Widget _buildLoginContent() { return AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform.translate( offset: Offset(0, _slideAnimation.value), child: Opacity( opacity: _fadeAnimation.value, child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ const SizedBox(height: 60), // Header avec logo et titre const LoginHeader(), const SizedBox(height: 40), // Formulaire de connexion AnimatedBuilder( animation: _shakeAnimation, builder: (context, child) { return Transform.translate( offset: Offset( _shakeAnimation.value * 10 * (1 - _shakeAnimation.value) * ((_shakeAnimation.value * 10).round() % 2 == 0 ? 1 : -1), 0, ), child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Padding( padding: const EdgeInsets.all(24), 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 _handleLogin() { if (!_formKey.currentState!.validate()) { _startShakeAnimation(); return; } HapticFeedback.lightImpact(); final loginRequest = LoginRequest( email: _emailController.text.trim(), password: _passwordController.text, rememberMe: _rememberMe, ); context.read().add(AuthLoginRequested(loginRequest)); } void _showErrorSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon( Icons.error_outline, color: Colors.white, ), const SizedBox(width: 12), Expanded( child: Text( message, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ), ], ), 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: 'Fermer', textColor: Colors.white, onPressed: () { ScaffoldMessenger.of(context).hideCurrentSnackBar(); }, ), ), ); } void _showSuccessSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon( Icons.check_circle_outline, color: Colors.white, ), const SizedBox(width: 12), Expanded( child: Text( message, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ), ], ), backgroundColor: AppTheme.successColor, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), margin: const EdgeInsets.all(16), duration: const Duration(seconds: 2), ), ); } }