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/user_remote_data_source.dart'; import '../../../data/models/user_model.dart'; import '../../../data/services/preferences_helper.dart'; import '../../../data/services/secure_storage.dart'; import '../../widgets/custom_button.dart'; import '../login/login_screen.dart'; /// Écran d'inscription avec design moderne et compact. /// /// Cet écran permet aux utilisateurs de créer un nouveau compte avec /// validation des champs, gestion d'erreurs, et interface optimisée. /// /// **Fonctionnalités:** /// - Validation des champs en temps réel /// - Vérification de correspondance des mots de passe /// - Affichage/masquage du mot de passe /// - Gestion d'erreurs robuste /// - Support du thème clair/sombre class SignUpScreen extends StatefulWidget { const SignUpScreen({super.key}); @override State createState() => _SignUpScreenState(); } class _SignUpScreenState extends State with SingleTickerProviderStateMixin { // ============================================================================ // FORMULAIRE // ============================================================================ final _formKey = GlobalKey(); final _lastNameController = TextEditingController(); final _firstNameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = 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() { _lastNameController.dispose(); _firstNameController.dispose(); _emailController.dispose(); _passwordController.dispose(); _confirmPasswordController.dispose(); _animationController.dispose(); super.dispose(); } // ============================================================================ // ACTIONS // ============================================================================ /// Bascule la visibilité du mot de passe. void _togglePasswordVisibility() { setState(() { _isPasswordVisible = !_isPasswordVisible; }); } /// Soumet le formulaire d'inscription. Future _handleSubmit() async { if (!_formKey.currentState!.validate()) { return; } // Vérifier la correspondance des mots de passe if (_passwordController.text != _confirmPasswordController.text) { setState(() { _errorMessage = 'Les mots de passe ne correspondent pas'; }); return; } setState(() { _isSubmitting = true; _errorMessage = null; }); try { final user = UserModel( userId: '', userLastName: _lastNameController.text.trim(), userFirstName: _firstNameController.text.trim(), email: _emailController.text.trim(), motDePasse: _passwordController.text, profileImageUrl: '', ); final createdUser = await _userRemoteDataSource.createUser(user); await _saveUserData(createdUser); await _navigateToLogin(); } 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); } /// Navigue vers l'écran de connexion. Future _navigateToLogin() async { if (!mounted) return; Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const LoginScreen(), ), ); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Compte créé avec succès ! Connectez-vous maintenant'), behavior: SnackBarBehavior.floating, ), ); } /// Gère les erreurs d'inscription. void _handleError(Object error) { String message = 'Erreur lors de la création du compte'; if (error.toString().contains('Conflict') || error.toString().contains('existe déjà')) { message = 'Un compte avec cet email existe déjà'; } 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('[SignUpScreen] Erreur: $error'); } } // ============================================================================ // 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: 32), _buildLastNameField(theme), const SizedBox(height: 16), _buildFirstNameField(theme), const SizedBox(height: 16), _buildEmailField(theme), const SizedBox(height: 16), _buildPasswordField(theme), const SizedBox(height: 16), _buildConfirmPasswordField(theme), const SizedBox(height: 24), _buildSubmitButton(theme), const SizedBox(height: 16), _buildLoginLink(theme), if (_errorMessage != null) _buildErrorMessage(theme), ], ), ), ), ), ); } /// Construit le logo. Widget _buildLogo(ThemeData theme) { return Image.asset( 'lib/assets/images/logo.png', height: 100, errorBuilder: (context, error, stackTrace) { return Icon( Icons.person_add, size: 64, color: theme.colorScheme.onPrimary, ); }, ); } /// Construit le titre. Widget _buildTitle(ThemeData theme) { return Text( 'Créer un compte', style: theme.textTheme.headlineSmall?.copyWith( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ); } /// Construit le champ nom. Widget _buildLastNameField(ThemeData theme) { return TextFormField( controller: _lastNameController, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( theme, 'Nom', Icons.person, ), style: TextStyle(color: theme.colorScheme.onPrimary), validator: (value) => Validators.validateName(value, fieldName: 'le nom'), ); } /// Construit le champ prénom. Widget _buildFirstNameField(ThemeData theme) { return TextFormField( controller: _firstNameController, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( theme, 'Prénoms', Icons.person_outline, ), style: TextStyle(color: theme.colorScheme.onPrimary), validator: (value) => Validators.validateName(value, fieldName: 'le prénom'), ); } /// Construit le champ email. Widget _buildEmailField(ThemeData theme) { return TextFormField( controller: _emailController, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, decoration: _buildInputDecoration( theme, 'Email', Icons.email, ), 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.next, decoration: _buildInputDecoration( theme, 'Mot de passe', Icons.lock, suffixIcon: IconButton( icon: Icon( _isPasswordVisible ? Icons.visibility : Icons.visibility_off, color: theme.colorScheme.onPrimary.withOpacity(0.7), ), onPressed: _togglePasswordVisibility, ), ), style: TextStyle(color: theme.colorScheme.onPrimary), validator: (value) => Validators.validatePassword(value, minLength: 6), ); } /// Construit le champ confirmation mot de passe. Widget _buildConfirmPasswordField(ThemeData theme) { return TextFormField( controller: _confirmPasswordController, obscureText: !_isPasswordVisible, textInputAction: TextInputAction.done, onFieldSubmitted: (_) => _handleSubmit(), decoration: _buildInputDecoration( theme, 'Confirmer le mot de passe', Icons.lock_outline, suffixIcon: IconButton( icon: Icon( _isPasswordVisible ? Icons.visibility : Icons.visibility_off, color: theme.colorScheme.onPrimary.withOpacity(0.7), ), onPressed: _togglePasswordVisibility, ), ), style: TextStyle(color: theme.colorScheme.onPrimary), validator: (value) => Validators.validatePasswordMatch( _passwordController.text, value, ), ); } /// Construit le bouton de soumission (compact). Widget _buildSubmitButton(ThemeData theme) { return SizedBox( width: double.infinity, child: CustomButton( text: 'Créer un compte', icon: Icons.person_add, onPressed: () => _handleSubmit(), isLoading: _isSubmitting, isEnabled: !_isSubmitting, variant: ButtonVariant.primary, size: ButtonSize.medium, ), ); } /// Construit le lien vers la connexion. Widget _buildLoginLink(ThemeData theme) { return TextButton( onPressed: () => Navigator.pop(context), child: Text( 'Déjà un compte ? Connectez-vous', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onPrimary.withOpacity(0.9), decoration: TextDecoration.underline, ), ), ); } /// 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, ), ), ), ); } // ============================================================================ // UTILITAIRES // ============================================================================ /// Construit la décoration d'un champ de texte. InputDecoration _buildInputDecoration( ThemeData theme, String label, IconData icon, { Widget? suffixIcon, }) { return InputDecoration( labelText: label, prefixIcon: Icon(icon, color: theme.colorScheme.onPrimary), suffixIcon: suffixIcon, 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), ); } }