import 'package:flutter/material.dart'; import 'dart:math' as math; import 'dart:convert'; import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode, debugPrint; import 'package:universal_html/html.dart' as html; import 'package:geosector_app/core/services/js_stub.dart' if (dart.library.js) 'dart:js' as js; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/services/app_info_service.dart'; import 'package:geosector_app/core/services/current_user_service.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/presentation/widgets/custom_button.dart'; import 'package:geosector_app/presentation/widgets/custom_text_field.dart'; import 'package:geosector_app/presentation/widgets/connectivity_indicator.dart'; import 'package:geosector_app/presentation/widgets/loading_spin_overlay.dart'; import 'package:geosector_app/presentation/widgets/result_dialog.dart'; import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:geosector_app/core/services/hive_service.dart'; // Pour vérifier l'initialisation class LoginPage extends StatefulWidget { final String? loginType; const LoginPage({super.key, this.loginType}); @override State createState() => _LoginPageState(); } // Class pour dessiner les petits points blancs sur le fond class DotsPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.white.withOpacity(0.5) ..style = PaintingStyle.fill; final random = math.Random(42); // Seed fixe pour consistance final numberOfDots = (size.width * size.height) ~/ 1500; for (int i = 0; i < numberOfDots; i++) { final x = random.nextDouble() * size.width; final y = random.nextDouble() * size.height; final radius = 1.0 + random.nextDouble() * 2.0; canvas.drawCircle(Offset(x, y), radius, paint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } class _LoginPageState extends State { final _formKey = GlobalKey(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); final _usernameFocusNode = FocusNode(); bool _obscurePassword = true; String _appVersion = ''; bool _isCleaningCache = false; // Type de connexion (utilisateur ou administrateur) late String _loginType; // État de la connexion Internet bool _isConnected = true; // Par défaut, on suppose qu'il y a une connexion Future _getAppVersion() async { // Utilise directement AppInfoService (remplace package_info_plus) if (mounted) { setState(() { _appVersion = AppInfoService.version; }); } } Future _checkConnectivity() async { await connectivityService.checkConnectivity(); if (mounted) { setState(() { _isConnected = connectivityService.isConnected; }); } } @override void initState() { super.initState(); // VÉRIFICATION CRITIQUE : S'assurer que Hive est initialisé // Cette vérification DOIT se faire avant tout accès aux repositories if (!HiveService.instance.areBoxesInitialized()) { debugPrint('⚠️ LoginPage: Boxes Hive non initialisées, redirection vers SplashPage'); // Construire les paramètres pour la redirection après initialisation final loginType = widget.loginType ?? 'admin'; // Rediriger immédiatement vers SplashPage avec les bons paramètres // pour que SplashPage puisse rediriger automatiquement après l'initialisation WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { context.go('/?action=login&type=$loginType'); } }); // Initialiser avec des valeurs par défaut pour éviter les erreurs _loginType = ''; return; // IMPORTANT : Arrêter l'exécution du reste de initState } // NOUVELLE VÉRIFICATION : S'assurer que la réinitialisation complète a été effectuée // Vérifier la clé 'hive_initialized' dans la box settings try { if (Hive.isBoxOpen(AppKeys.settingsBoxName)) { final settingsBox = Hive.box(AppKeys.settingsBoxName); final isInitialized = settingsBox.get('hive_initialized', defaultValue: false); if (isInitialized != true) { debugPrint('⚠️ LoginPage: Réinitialisation Hive requise (hive_initialized=$isInitialized)'); // Construire les paramètres pour la redirection après initialisation final loginType = widget.loginType ?? 'admin'; // Forcer une réinitialisation complète via SplashPage WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { context.go('/?action=login&type=$loginType'); } }); // Initialiser avec des valeurs par défaut pour éviter les erreurs _loginType = ''; return; // IMPORTANT : Arrêter l'exécution du reste de initState } debugPrint('✅ LoginPage: Hive correctement initialisé'); } } catch (e) { debugPrint('❌ LoginPage: Erreur lors de la vérification de hive_initialized: $e'); // En cas d'erreur, forcer la réinitialisation final loginType = widget.loginType ?? 'admin'; WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { context.go('/?action=login&type=$loginType'); } }); _loginType = ''; return; } // Vérification du type de connexion (seulement si Hive est initialisé) if (widget.loginType == null) { // Si aucun type n'est spécifié, naviguer vers la splash page debugPrint( 'LoginPage: Aucun type de connexion spécifié, navigation vers splash page'); WidgetsBinding.instance.addPostFrameCallback((_) { GoRouter.of(context).go('/'); }); _loginType = ''; } else { _loginType = widget.loginType!; debugPrint('LoginPage: Type de connexion utilisé: $_loginType'); } // En mode web, essayer de détecter le paramètre dans l'URL directement // UNIQUEMENT si le loginType n'est pas déjà 'user' if (kIsWeb && _loginType != 'user') { try { final uri = Uri.parse(Uri.base.toString()); // 1. Vérifier d'abord si nous avons déjà le paramètre 'type=user' final typeParam = uri.queryParameters['type']; if (typeParam != null && typeParam.trim().toLowerCase() == 'user') { setState(() { _loginType = 'user'; }); } // 2. Sinon, vérifier le fragment d'URL (hash) else if (uri.fragment.trim().toLowerCase() == 'user') { setState(() { _loginType = 'user'; }); } // 3. Enfin, si toujours pas de type 'user', vérifier le sessionStorage if (_loginType != 'user') { WidgetsBinding.instance.addPostFrameCallback((_) { try { // Utiliser une approche plus robuste pour accéder au sessionStorage // Éviter d'utiliser hasProperty qui peut causer des erreurs final result = js.context.callMethod('eval', [ ''' (function() { try { if (window.sessionStorage) { var value = sessionStorage.getItem('loginType'); return value; } return null; } catch (e) { console.error('Error accessing sessionStorage:', e); return null; } })() ''' ]); if (result != null && result is String && result.toLowerCase() == 'user') { setState(() { _loginType = 'user'; debugPrint( 'LoginPage: Type détecté depuis sessionStorage: $_loginType'); }); } } catch (e) { debugPrint('LoginPage: Erreur lors de l\'accès au sessionStorage: $e'); } }); } } catch (e) { debugPrint('Erreur lors de la récupération des paramètres d\'URL: $e'); } } // Les permissions sont maintenant vérifiées dans splash_page // Initialiser l'état de la connexion WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _isConnected = connectivityService.isConnected; }); } }); // Récupérer la version de l'application _getAppVersion(); // Vérification de connectivité au démarrage _checkConnectivity(); // Pré-remplir le champ username avec l'identifiant du dernier utilisateur connecté // seulement si le rôle correspond au type de login WidgetsBinding.instance.addPostFrameCallback((_) { // Vérifier à nouveau que les boxes sont disponibles if (!HiveService.instance.areBoxesInitialized()) { debugPrint('⚠️ Boxes non disponibles pour pré-remplir le username'); return; } try { final users = userRepository.getAllUsers(); if (users.isNotEmpty) { // Trouver l'utilisateur le plus récent (celui avec la date de dernière connexion la plus récente) users.sort((a, b) => (b.lastSyncedAt).compareTo(a.lastSyncedAt)); final lastUser = users.first; // Convertir le rôle en int si nécessaire int roleValue; if (lastUser.role is String) { roleValue = int.tryParse(lastUser.role as String) ?? 0; } else { roleValue = lastUser.role; } // Vérifier si le rôle correspond au type de login bool roleMatches = false; if (_loginType == 'user' && roleValue == 1) { roleMatches = true; debugPrint('Rôle utilisateur (1) correspond au type de login (user)'); } else if (_loginType == 'admin' && roleValue > 1) { roleMatches = true; debugPrint( 'Rôle administrateur ($roleValue) correspond au type de login (admin)'); } // Pré-remplir le champ username seulement si le rôle correspond if (roleMatches) { // Utiliser le username s'il existe, sinon utiliser l'email comme fallback if (lastUser.username != null && lastUser.username!.isNotEmpty) { _usernameController.text = lastUser.username!; // Déplacer le focus sur le champ mot de passe puisque le username est déjà rempli _usernameFocusNode.unfocus(); debugPrint('Champ username pré-rempli avec: ${lastUser.username}'); } else if (lastUser.email.isNotEmpty) { _usernameController.text = lastUser.email; _usernameFocusNode.unfocus(); debugPrint( 'Champ username pré-rempli avec email: ${lastUser.email}'); } } else { debugPrint( 'Le rôle ($roleValue) ne correspond pas au type de login ($_loginType), champ username non pré-rempli'); } } } catch (e) { debugPrint('Erreur lors du pré-remplissage: $e'); // Continuer sans pré-remplir en cas d'erreur } }); } @override void dispose() { _usernameController.dispose(); _passwordController.dispose(); _usernameFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { debugPrint('DEBUG BUILD: Reconstruction de LoginPage avec type: $_loginType'); // Utiliser l'instance globale de userRepository final theme = Theme.of(context); // Les permissions sont maintenant gérées dans splash_page // On n'a plus besoin de ces vérifications ici return Scaffold( body: Stack( children: [ // Fond dégradé avec petits points blancs AnimatedContainer( duration: const Duration(milliseconds: 500), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: _loginType == 'user' ? [Colors.white, Colors.green.shade300] : [Colors.white, Colors.red.shade300], ), ), child: CustomPaint( painter: DotsPainter(), child: const SizedBox( width: double.infinity, height: double.infinity), ), ), // Bouton "Nettoyer le cache" en bas à gauche Positioned( bottom: 20, left: 20, child: SafeArea( child: TextButton.icon( onPressed: _isCleaningCache ? null : () async { // Confirmation avant nettoyage final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Nettoyer le cache ?'), content: const Text( 'Cette action va :\n' '• Supprimer toutes les données locales\n' '• Forcer le rechargement de l\'application\n\n' 'Continuer ?' ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, ), child: const Text('Nettoyer'), ), ], ), ); if (confirm == true) { setState(() => _isCleaningCache = true); debugPrint('👤 Utilisateur a demandé un nettoyage du cache'); // Nettoyer le cache Hive await HiveService.instance.cleanDataOnLogout(); // Forcer le rechargement complet de la page sur Web if (kIsWeb) { html.window.location.reload(); } else { // Sur mobile, rediriger vers splash setState(() => _isCleaningCache = false); if (context.mounted) { context.go('/'); } } } }, icon: _isCleaningCache ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.cleaning_services, size: 18, color: Colors.black87), label: Text( _isCleaningCache ? 'Nettoyage...' : 'Nettoyer le cache', style: const TextStyle( color: Colors.black87, fontWeight: FontWeight.w500, ), ), ), ), ), SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), child: Card( elevation: 8, shadowColor: _loginType == 'user' ? Colors.green.withOpacity(0.5) : Colors.red.withOpacity(0.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.0)), child: Padding( padding: const EdgeInsets.all(20.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Logo simplifié avec chemin direct Image.asset( 'assets/images/logo-geosector-1024.png', height: 140, ), const SizedBox(height: 24), Text( _loginType == 'user' ? 'Connexion Utilisateur' : 'Connexion Administrateur', style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: _loginType == 'user' ? Colors.green : Colors.red, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), // Ajouter un texte de débogage uniquement en mode développement if (kDebugMode) Text( 'Type de connexion: $_loginType', style: const TextStyle( fontSize: 10, color: Colors.grey), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'Bienvenue sur GEOSECTOR', style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface.withOpacity(0.7), ), textAlign: TextAlign.center, ), const SizedBox(height: 16), // Indicateur de connectivité const ConnectivityIndicator(), const SizedBox(height: 16), if (!kIsWeb && !_isConnected) Container( margin: const EdgeInsets.only(top: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.error.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: theme.colorScheme.error.withOpacity(0.3), ), ), child: Column( children: [ Icon(Icons.signal_wifi_off, color: theme.colorScheme.error, size: 32), const SizedBox(height: 8), Text('Connexion Internet requise', style: theme.textTheme.titleMedium ?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.error)), const SizedBox(height: 8), const Text( 'Veuillez vous connecter à Internet (WiFi ou données mobiles) pour pouvoir vous connecter.'), ], ), ), const SizedBox(height: 16), // Formulaire de connexion Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ CustomTextField( controller: _usernameController, label: 'Identifiant', hintText: 'Entrez votre identifiant', prefixIcon: Icons.person_outline, keyboardType: TextInputType.text, autofocus: true, focusNode: _usernameFocusNode, validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer votre identifiant'; } return null; }, ), const SizedBox(height: 16), CustomTextField( controller: _passwordController, label: 'Mot de passe', hintText: 'Entrez votre mot de passe', prefixIcon: Icons.lock_outline, obscureText: _obscurePassword, suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer votre mot de passe'; } return null; }, onFieldSubmitted: (_) async { if (!userRepository.isLoading && _formKey.currentState!.validate()) { // Vérifier que le type de connexion est spécifié if (_loginType.isEmpty) { debugPrint( 'Login: Type non spécifié, redirection vers la page de démarrage'); context.go('/'); return; } debugPrint( 'Login: Tentative avec type: $_loginType'); final success = await userRepository.loginWithSpinner( context, _usernameController.text, // NIST: ne pas faire de trim _passwordController.text, type: _loginType, ); if (success && mounted) { // Récupérer directement le rôle de l'utilisateur final user = userRepository.getCurrentUser(); if (user == null) { debugPrint( 'ERREUR: Utilisateur non trouvé après connexion réussie'); if (context.mounted) { await ResultDialog.show( context: context, success: false, message: 'Erreur de connexion. Veuillez réessayer.', ); } return; } // Convertir le rôle en int si nécessaire int roleValue; if (user.role is String) { roleValue = int.tryParse( user.role as String) ?? 1; } else { roleValue = user.role; } debugPrint( 'Role de l\'utilisateur: $roleValue'); // Définir le mode d'affichage selon le type de connexion if (_loginType == 'user') { // Connexion en mode user : toujours mode user await CurrentUserService.instance.setDisplayMode('user'); debugPrint('Mode d\'affichage défini: user'); if (context.mounted) { context.go('/user'); } } else { // Connexion en mode admin if (roleValue >= 2) { await CurrentUserService.instance.setDisplayMode('admin'); debugPrint('Mode d\'affichage défini: admin'); if (context.mounted) { context.go('/admin'); } } else { // Un user (rôle 1) ne peut pas se connecter en mode admin debugPrint('Erreur: User (rôle 1) tentant de se connecter en mode admin'); if (context.mounted) { await ResultDialog.show( context: context, success: false, message: 'Accès administrateur non autorisé pour ce compte.', ); } return; } } } else if (context.mounted) { await ResultDialog.show( context: context, success: false, message: 'Échec de la connexion. Vérifiez vos identifiants.', ); } } }, ), const SizedBox(height: 8), // Mot de passe oublié Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { _showForgotPasswordDialog(context); }, child: Text( 'Mot de passe oublié ?', style: TextStyle( color: theme.colorScheme.primary, ), ), ), ), const SizedBox(height: 24), // Bouton de connexion CustomButton( onPressed: (userRepository.isLoading || !_isConnected) ? null : () async { if (_formKey.currentState! .validate()) { // Les permissions sont déjà vérifiées dans splash_page // Vérifier la connexion Internet await connectivityService .checkConnectivity(); if (!connectivityService .isConnected) { if (context.mounted) { ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: const Text( 'Aucune connexion Internet. La connexion n\'est pas possible hors ligne.'), backgroundColor: theme.colorScheme.error, duration: const Duration( seconds: 3), action: SnackBarAction( label: 'Réessayer', onPressed: () async { await connectivityService .checkConnectivity(); if (connectivityService .isConnected && context.mounted) { await ResultDialog.show( context: context, success: true, message: 'Connexion Internet ${connectivityService.connectionType} détectée.', ); } }, ), ), ); } return; } // Vérifier que le type de connexion est spécifié if (_loginType.isEmpty) { debugPrint( 'Login: Type non spécifié, redirection vers la page de démarrage'); if (context.mounted) { context.go('/'); } return; } debugPrint( 'Login: Tentative avec type: $_loginType'); // Utiliser le nouveau spinner moderne pour la connexion if (!mounted) return; final success = await userRepository .loginWithSpinner( context, _usernameController.text, // NIST: ne pas faire de trim _passwordController.text, type: _loginType, ); if (success && mounted) { debugPrint( 'Connexion réussie, tentative de redirection...'); // Récupérer directement le rôle de l'utilisateur final user = userRepository .getCurrentUser(); if (user == null) { debugPrint( 'ERREUR: Utilisateur non trouvé après connexion réussie'); if (context.mounted) { await ResultDialog.show( context: context, success: false, message: 'Erreur de connexion. Veuillez réessayer.', ); } return; } // Convertir le rôle en int si nécessaire int roleValue; if (user.role is String) { roleValue = int.tryParse( user.role as String) ?? 1; } else { roleValue = user.role; } debugPrint( 'Role de l\'utilisateur: $roleValue'); // Définir le mode d'affichage selon le type de connexion if (_loginType == 'user') { // Connexion en mode user : toujours mode user await CurrentUserService.instance.setDisplayMode('user'); debugPrint('Mode d\'affichage défini: user'); if (context.mounted) { context.go('/user'); } } else { // Connexion en mode admin if (roleValue >= 2) { await CurrentUserService.instance.setDisplayMode('admin'); debugPrint('Mode d\'affichage défini: admin'); if (context.mounted) { context.go('/admin'); } } else { // Un user (rôle 1) ne peut pas se connecter en mode admin debugPrint('Erreur: User (rôle 1) tentant de se connecter en mode admin'); if (context.mounted) { await ResultDialog.show( context: context, success: false, message: 'Accès administrateur non autorisé pour ce compte.', ); } return; } } } else if (context.mounted) { await ResultDialog.show( context: context, success: false, message: 'Échec de la connexion. Vérifiez vos identifiants.', ); } } }, text: _isConnected ? 'Se connecter' : 'Connexion Internet requise', isLoading: userRepository.isLoading, ), const SizedBox(height: 24), // Inscription administrateur uniquement en mode admin if (_loginType == 'admin') ...[ // Détecter si on est sur mobile LayoutBuilder( builder: (context, constraints) { final isMobile = constraints.maxWidth < 400; if (isMobile) { // Sur mobile : afficher sur deux lignes return Column( children: [ Text( 'Pas encore de compte ?', style: theme.textTheme.bodyMedium, textAlign: TextAlign.center, ), const SizedBox(height: 4), TextButton( onPressed: () { context.go('/register'); }, child: const Text( 'Inscription Administrateur', style: TextStyle( color: Colors.blue, fontWeight: FontWeight.bold, ), ), ), ], ); } else { // Sur desktop : afficher sur une ligne return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Pas encore de compte ?', style: theme.textTheme.bodyMedium, ), TextButton( onPressed: () { context.go('/register'); }, child: const Text( 'Inscription Administrateur', style: TextStyle( color: Colors.blue, fontWeight: FontWeight.bold, ), ), ), ], ); } }, ), ], // Lien vers la page d'accueil TextButton( onPressed: () { context.go('/'); }, child: Text( 'Retour à l\'accueil', style: TextStyle( color: theme.colorScheme.secondary, ), ), ), // Badge de version dans la card if (_appVersion.isNotEmpty) ...[ const SizedBox(height: 16), Center( child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: theme.colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: theme.colorScheme.primary.withOpacity(0.3), width: 1, ), ), child: Text( 'v$_appVersion', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.primary.withOpacity(0.8), fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ), ], ], ), ), ], ), ), ), ), ), ), ), ], ), ); } // Affiche la boîte de dialogue pour la récupération de mot de passe void _showForgotPasswordDialog(BuildContext context) { final emailController = TextEditingController(); final formKey = GlobalKey(); bool isLoading = false; showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return StatefulBuilder(builder: (context, setState) { return AlertDialog( title: const Row( children: [ Icon(Icons.lock_reset, color: Colors.blue), SizedBox(width: 10), Text('Récupération de mot de passe'), ], ), content: Form( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Veuillez entrer votre adresse email pour recevoir un nouveau mot de passe.', style: TextStyle(fontSize: 14), ), const SizedBox(height: 16), CustomTextField( controller: emailController, label: 'Email', hintText: 'Entrez votre email', prefixIcon: Icons.email_outlined, keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer votre email'; } if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') .hasMatch(value)) { return 'Veuillez entrer un email valide'; } return null; }, ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Annuler'), ), ElevatedButton( onPressed: isLoading ? null : () async { if (formKey.currentState!.validate()) { setState(() { isLoading = true; }); // Afficher le loading overlay final overlay = LoadingSpinOverlayUtils.show( context: context, message: 'Envoi en cours...', ); try { // Vérifier la connexion Internet await connectivityService.checkConnectivity(); if (!connectivityService.isConnected) { throw Exception('Aucune connexion Internet'); } // Construire l'URL de l'API final baseUrl = Uri.base.origin; final apiUrl = '$baseUrl/api/lostpassword'; debugPrint('Envoi de la requête à: $apiUrl'); debugPrint('Email: ${emailController.text.trim()}'); http.Response? response; try { // Envoyer la requête à l'API response = await http.post( Uri.parse(apiUrl), headers: {'Content-Type': 'application/json'}, body: json.encode({ 'email': emailController.text.trim(), }), ); debugPrint('Réponse reçue: ${response.statusCode}'); debugPrint('Corps de la réponse: ${response.body}'); // Si la réponse est 404, c'est peut-être un problème de route if (response.statusCode == 404) { // Essayer avec une URL alternative final alternativeUrl = '$baseUrl/api/index.php/lostpassword'; debugPrint( 'Tentative avec URL alternative: $alternativeUrl'); final alternativeResponse = await http.post( Uri.parse(alternativeUrl), headers: {'Content-Type': 'application/json'}, body: json.encode({ 'email': emailController.text.trim(), }), ); debugPrint( 'Réponse alternative reçue: ${alternativeResponse.statusCode}'); debugPrint( 'Corps de la réponse alternative: ${alternativeResponse.body}'); // Si la réponse alternative est un succès, utiliser cette réponse if (alternativeResponse.statusCode == 200) { response = alternativeResponse; } } } catch (e) { debugPrint( 'Erreur lors de l\'envoi de la requête: $e'); throw Exception('Erreur de connexion: $e'); } // Traiter la réponse if (response.statusCode == 200) { // Masquer le loading LoadingSpinOverlayUtils.hideSpecific(overlay); // Modifier le contenu de la boîte de dialogue pour afficher le message de succès setState(() { isLoading = false; }); // Remplacer le contenu de la boîte de dialogue par un message de succès if (context.mounted) { showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { // Fermer automatiquement la boîte de dialogue après 2 secondes Future.delayed(const Duration(seconds: 2), () { if (context.mounted && Navigator.of(context).canPop()) { Navigator.of(context).pop(); } }); return const AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.check_circle, color: Colors.green, size: 48, ), SizedBox(height: 16), Text( 'Vous recevrez un nouveau mot de passe par email', textAlign: TextAlign.center, style: TextStyle(fontSize: 16), ), ], ), ); }, ); } } else { // Masquer le loading LoadingSpinOverlayUtils.hideSpecific(overlay); // Fermer la boîte de dialogue actuelle if (context.mounted) { Navigator.of(context).pop(); } // Afficher un message d'erreur final responseData = json.decode(response.body); throw Exception(responseData['message'] ?? 'Erreur lors de la récupération du mot de passe'); } } catch (e) { // Masquer le loading LoadingSpinOverlayUtils.hideSpecific(overlay); // Afficher un message d'erreur if (context.mounted) { await ResultDialog.show( context: context, success: false, message: e.toString().contains('Exception:') ? e.toString().split('Exception: ')[1] : 'Erreur lors de la récupération du mot de passe', ); } } finally { if (mounted) { setState(() { isLoading = false; }); } } } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, ), child: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Text('Recevoir un nouveau mot de passe'), ), ], ); }); }, ); } }