import 'dart:async'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:provider/provider.dart'; import '../../../core/constants/env_config.dart'; import '../../../core/theme/theme_provider.dart'; import '../../../core/utils/validators.dart'; import '../../../data/datasources/event_remote_data_source.dart'; import '../../../data/datasources/user_remote_data_source.dart'; import '../../../data/models/user_model.dart'; import '../../../data/providers/user_provider.dart'; import '../../../data/services/preferences_helper.dart'; import '../../../data/services/secure_storage.dart'; import '../../../domain/entities/user.dart'; import '../../widgets/custom_button.dart'; import '../home/home_screen.dart'; import '../signup/SignUpScreen.dart'; /// Écran de connexion avec design moderne et compact. /// /// Cet écran permet aux utilisateurs de s'authentifier avec leur email /// et mot de passe. Il inclut la validation des champs, la gestion d'erreurs, /// et une interface utilisateur optimisée. /// /// **Fonctionnalités:** /// - Validation des champs en temps réel /// - Affichage/masquage du mot de passe /// - Gestion d'erreurs robuste /// - Support du thème clair/sombre /// - Navigation vers l'inscription class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State with SingleTickerProviderStateMixin { // ============================================================================ // FORMULAIRE // ============================================================================ final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); // ============================================================================ // ÉTATS // ============================================================================ bool _isPasswordVisible = false; bool _isSubmitting = false; String? _errorMessage; // ============================================================================ // SERVICES // ============================================================================ late final UserRemoteDataSource _userRemoteDataSource; late final SecureStorage _secureStorage; late final PreferencesHelper _preferencesHelper; // ============================================================================ // ANIMATIONS // ============================================================================ late AnimationController _animationController; late Animation _fadeAnimation; @override void initState() { super.initState(); _initializeServices(); _initializeAnimations(); } void _initializeServices() { _userRemoteDataSource = UserRemoteDataSource(http.Client()); _secureStorage = SecureStorage(); _preferencesHelper = PreferencesHelper(); } void _initializeAnimations() { _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); _fadeAnimation = CurvedAnimation( parent: _animationController, curve: Curves.easeIn, ); _animationController.forward(); } @override void dispose() { _emailController.dispose(); _passwordController.dispose(); _animationController.dispose(); super.dispose(); } // ============================================================================ // ACTIONS // ============================================================================ /// Bascule la visibilité du mot de passe. void _togglePasswordVisibility() { setState(() { _isPasswordVisible = !_isPasswordVisible; }); } /// Soumet le formulaire de connexion. Future _handleSubmit() async { if (!_formKey.currentState!.validate()) { return; } setState(() { _isSubmitting = true; _errorMessage = null; }); try { final email = _emailController.text.trim(); final password = _passwordController.text; final user = await _userRemoteDataSource.authenticateUser(email, password); if (user.userId.isEmpty) { throw Exception('ID utilisateur manquant dans la réponse'); } await _saveUserData(user); await _navigateToHome(user); } catch (e) { _handleError(e); } finally { if (mounted) { setState(() { _isSubmitting = false; }); } } } /// Sauvegarde les données utilisateur. Future _saveUserData(UserModel user) async { await _secureStorage.saveUserId(user.userId); await _preferencesHelper.saveUserName(user.userLastName); await _preferencesHelper.saveUserLastName(user.userFirstName); final userProvider = Provider.of(context, listen: false); userProvider.setUser( User( userId: user.userId, userLastName: user.userLastName, userFirstName: user.userFirstName, email: user.email, motDePasse: 'motDePasseHashé', profileImageUrl: user.profileImageUrl, eventsCount: user.eventsCount ?? 0, friendsCount: user.friendsCount ?? 0, postsCount: user.postsCount ?? 0, visitedPlacesCount: user.visitedPlacesCount ?? 0, ), ); } /// Navigue vers l'écran d'accueil. Future _navigateToHome(UserModel user) async { if (!mounted) return; Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => HomeScreen( userId: user.userId, userFirstName: user.userFirstName, userLastName: user.userLastName, userProfileImage: user.profileImageUrl, eventRemoteDataSource: EventRemoteDataSource(http.Client()), ), ), ); } /// Gère les erreurs de connexion. void _handleError(Object error) { String message = 'Erreur lors de la connexion'; if (error.toString().contains('Unauthorized') || error.toString().contains('incorrect')) { message = 'Email ou mot de passe incorrect'; } else if (error.toString().contains('network') || error.toString().contains('timeout')) { message = 'Problème de connexion. Vérifiez votre réseau'; } else { message = error.toString().replaceAll('Exception: ', ''); } setState(() { _errorMessage = message; }); if (EnvConfig.enableDetailedLogs) { debugPrint('[LoginScreen] Erreur: $error'); } } /// Navigue vers l'écran d'inscription. void _navigateToSignUp() { Navigator.push( context, MaterialPageRoute( builder: (context) => const SignUpScreen(), ), ); } /// Gère la réinitialisation du mot de passe. Future _handleForgotPassword() async { final emailController = TextEditingController(); final formKey = GlobalKey(); final result = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Réinitialisation du mot de passe'), content: Form( key: formKey, child: TextFormField( controller: emailController, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Email', hintText: 'Entrez votre email', ), validator: Validators.validateEmail, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Annuler'), ), TextButton( onPressed: () { if (formKey.currentState!.validate()) { Navigator.pop(context, true); } }, child: const Text('Envoyer'), ), ], ), ); if (result == true && emailController.text.isNotEmpty) { try { await _userRemoteDataSource.requestPasswordReset( emailController.text.trim(), ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Un email de réinitialisation a été envoyé à votre adresse', ), behavior: SnackBarBehavior.floating, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: ${e.toString()}'), behavior: SnackBarBehavior.floating, ), ); } } } } // ============================================================================ // BUILD // ============================================================================ @override Widget build(BuildContext context) { final theme = Theme.of(context); final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0; return Scaffold( body: Stack( children: [ _buildBackground(theme), _buildContent(theme, isKeyboardVisible), _buildThemeToggle(theme), if (_isSubmitting) _buildLoadingOverlay(theme), ], ), ); } /// Construit l'arrière-plan avec dégradé. Widget _buildBackground(ThemeData theme) { return AnimatedContainer( duration: const Duration(seconds: 3), decoration: BoxDecoration( gradient: LinearGradient( colors: [ theme.colorScheme.primary, theme.colorScheme.secondary, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), ); } /// Construit le contenu principal. Widget _buildContent(ThemeData theme, bool isKeyboardVisible) { return FadeTransition( opacity: _fadeAnimation, child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (!isKeyboardVisible) _buildLogo(theme), if (!isKeyboardVisible) const SizedBox(height: 32), _buildTitle(theme), const SizedBox(height: 40), _buildEmailField(theme), const SizedBox(height: 16), _buildPasswordField(theme), const SizedBox(height: 24), _buildSubmitButton(theme), const SizedBox(height: 16), _buildSignUpLink(theme), _buildForgotPasswordLink(theme), if (_errorMessage != null) _buildErrorMessage(theme), ], ), ), ), ), ); } /// Construit le logo. Widget _buildLogo(ThemeData theme) { return Image.asset( 'lib/assets/images/logo.png', height: 120, errorBuilder: (context, error, stackTrace) { return Icon( Icons.event, size: 80, color: theme.colorScheme.onPrimary, ); }, ); } /// Construit le titre. Widget _buildTitle(ThemeData theme) { return Text( 'Bienvenue sur AfterWork', style: theme.textTheme.headlineSmall?.copyWith( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ); } /// Construit le champ email. Widget _buildEmailField(ThemeData theme) { return TextFormField( controller: _emailController, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, decoration: InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email, color: theme.colorScheme.onPrimary), filled: true, fillColor: theme.colorScheme.onPrimary.withOpacity(0.1), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: theme.colorScheme.onPrimary.withOpacity(0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: theme.colorScheme.onPrimary, width: 2, ), ), labelStyle: TextStyle(color: theme.colorScheme.onPrimary), ), style: TextStyle(color: theme.colorScheme.onPrimary), validator: Validators.validateEmail, ); } /// Construit le champ mot de passe. Widget _buildPasswordField(ThemeData theme) { return TextFormField( controller: _passwordController, obscureText: !_isPasswordVisible, textInputAction: TextInputAction.done, onFieldSubmitted: (_) => _handleSubmit(), decoration: InputDecoration( labelText: 'Mot de passe', prefixIcon: Icon(Icons.lock, color: theme.colorScheme.onPrimary), suffixIcon: IconButton( icon: Icon( _isPasswordVisible ? Icons.visibility : Icons.visibility_off, color: theme.colorScheme.onPrimary.withOpacity(0.7), ), onPressed: _togglePasswordVisibility, ), filled: true, fillColor: theme.colorScheme.onPrimary.withOpacity(0.1), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: theme.colorScheme.onPrimary.withOpacity(0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: theme.colorScheme.onPrimary, width: 2, ), ), labelStyle: TextStyle(color: theme.colorScheme.onPrimary), ), style: TextStyle(color: theme.colorScheme.onPrimary), validator: Validators.validatePassword, ); } /// Construit le bouton de soumission (compact). Widget _buildSubmitButton(ThemeData theme) { return SizedBox( width: double.infinity, child: CustomButton( text: 'Connexion', icon: Icons.login, onPressed: () => _handleSubmit(), isLoading: _isSubmitting, isEnabled: !_isSubmitting, variant: ButtonVariant.primary, size: ButtonSize.medium, ), ); } /// Construit le lien vers l'inscription. Widget _buildSignUpLink(ThemeData theme) { return TextButton( onPressed: _navigateToSignUp, child: Text( 'Pas encore de compte ? Inscrivez-vous', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onPrimary.withOpacity(0.9), decoration: TextDecoration.underline, ), ), ); } /// Construit le lien mot de passe oublié. Widget _buildForgotPasswordLink(ThemeData theme) { return TextButton( onPressed: _handleForgotPassword, child: Text( 'Mot de passe oublié ?', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onPrimary.withOpacity(0.7), ), ), ); } /// Construit le message d'erreur. Widget _buildErrorMessage(ThemeData theme) { return Padding( padding: const EdgeInsets.only(top: 16), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: theme.colorScheme.error.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: theme.colorScheme.error.withOpacity(0.3), ), ), child: Row( children: [ Icon( Icons.error_outline, color: theme.colorScheme.error, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( _errorMessage!, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.error, ), ), ), ], ), ), ); } /// Construit le bouton de basculement de thème. Widget _buildThemeToggle(ThemeData theme) { return Positioned( top: MediaQuery.of(context).padding.top + 8, right: 16, child: IconButton( icon: Icon( Provider.of(context).isDarkMode ? Icons.light_mode : Icons.dark_mode, color: theme.colorScheme.onPrimary, ), onPressed: () { Provider.of(context, listen: false).toggleTheme(); }, tooltip: 'Changer le thème', ), ); } /// Construit l'overlay de chargement. Widget _buildLoadingOverlay(ThemeData theme) { return Container( color: Colors.black.withOpacity(0.3), child: Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( theme.colorScheme.onPrimary, ), ), ), ); } }