import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:go_router/go_router.dart'; import 'package:go_router/src/state.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/presentation/widgets/custom_button.dart'; import 'package:geosector_app/presentation/widgets/custom_text_field.dart'; import 'package:geosector_app/core/services/location_service.dart'; import 'package:geosector_app/core/services/connectivity_service.dart'; import 'package:geosector_app/presentation/widgets/connectivity_indicator.dart'; import 'package:geosector_app/core/services/auth_service.dart'; import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales class LoginPage extends StatefulWidget { final String? loginType; const LoginPage({super.key, this.loginType}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final _formKey = GlobalKey(); final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); final _usernameFocusNode = FocusNode(); bool _obscurePassword = true; // Type de connexion (utilisateur ou administrateur) String? _loginType; // État des permissions de géolocalisation bool _checkingPermission = true; bool _hasLocationPermission = false; String? _locationErrorMessage; // État de la connexion Internet bool _isConnected = false; @override void initState() { super.initState(); // Récupérer le type de connexion depuis les paramètres du widget _loginType = widget.loginType ?? 'admin'; // Par défaut admin print('DEBUG: LoginType initial depuis widget: $_loginType'); // Vérifier explicitement si le type est 'user' if (_loginType != null && _loginType!.trim().toLowerCase() == 'user') { _loginType = 'user'; print('DEBUG: LoginType confirmé comme user'); } else { _loginType = 'admin'; print('DEBUG: LoginType confirmé comme admin'); } // Vérifier les permissions de géolocalisation au démarrage seulement sur mobile if (!kIsWeb) { _checkLocationPermission(); } else { // En version web, on considère que les permissions sont accordées setState(() { _checkingPermission = false; _hasLocationPermission = true; }); } // Initialiser l'état de la connexion WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _isConnected = connectivityService.isConnected; }); } }); // Pré-remplir le champ username avec l'identifiant du dernier utilisateur connecté WidgetsBinding.instance.addPostFrameCallback((_) { 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; // 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(); } else if (lastUser.email.isNotEmpty) { _usernameController.text = lastUser.email; _usernameFocusNode.unfocus(); } } }); } /// Vérifie les permissions de géolocalisation Future _checkLocationPermission() async { // Ne pas vérifier les permissions en version web if (kIsWeb) { setState(() { _hasLocationPermission = true; _checkingPermission = false; }); return; } setState(() { _checkingPermission = true; }); // Vérifier si les services de localisation sont activés et si l'application a la permission final hasPermission = await LocationService.checkAndRequestPermission(); final errorMessage = await LocationService.getLocationErrorMessage(); setState(() { _hasLocationPermission = hasPermission; _locationErrorMessage = errorMessage; _checkingPermission = false; }); } @override void dispose() { _usernameController.dispose(); _passwordController.dispose(); _usernameFocusNode.dispose(); super.dispose(); } /// Construit l'écran de chargement pendant la vérification des permissions Widget _buildLoadingScreen(ThemeData theme) { return Scaffold( body: SafeArea( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/images/geosector-logo-200.png', height: 140, fit: BoxFit.contain, ), const SizedBox(height: 32), const CircularProgressIndicator(), const SizedBox(height: 24), Text( 'Vérification des permissions...', style: theme.textTheme.titleMedium, textAlign: TextAlign.center, ), ], ), ), ), ); } /// Construit l'écran de demande de permission de géolocalisation Widget _buildLocationPermissionScreen(ThemeData theme) { return Scaffold( body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Logo et titre Image.asset( 'assets/images/geosector-logo-200.png', height: 140, fit: BoxFit.contain, ), const SizedBox(height: 24), Text( 'Accès à la localisation requis', style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), textAlign: TextAlign.center, ), const SizedBox(height: 24), // Message d'erreur Container( 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.location_disabled, color: theme.colorScheme.error, size: 48, ), const SizedBox(height: 16), Text( _locationErrorMessage ?? 'L\'accès à la localisation est nécessaire pour utiliser cette application.', style: theme.textTheme.bodyLarge, textAlign: TextAlign.center, ), const SizedBox(height: 16), Text( 'Cette application utilise votre position pour enregistrer les passages et assurer le suivi des secteurs géographiques. Sans cette permission, l\'application ne peut pas fonctionner correctement.', style: theme.textTheme.bodyMedium, textAlign: TextAlign.center, ), ], ), ), const SizedBox(height: 32), // Instructions pour activer la localisation Text( 'Comment activer la localisation :', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const SizedBox(height: 16), _buildInstructionStep( theme, 1, 'Ouvrez les paramètres de votre appareil'), _buildInstructionStep(theme, 2, 'Accédez aux paramètres de confidentialité ou de localisation'), _buildInstructionStep(theme, 3, 'Recherchez GEOSECTOR dans la liste des applications'), _buildInstructionStep(theme, 4, 'Activez l\'accès à la localisation pour cette application'), const SizedBox(height: 32), // Boutons d'action CustomButton( onPressed: () async { // Ouvrir les paramètres de l'application await LocationService.openAppSettings(); }, text: 'Ouvrir les paramètres de l\'application', icon: Icons.settings, ), const SizedBox(height: 16), CustomButton( onPressed: () async { // Ouvrir les paramètres de localisation await LocationService.openLocationSettings(); }, text: 'Ouvrir les paramètres de localisation', icon: Icons.location_on, backgroundColor: theme.colorScheme.secondary, ), const SizedBox(height: 16), CustomButton( onPressed: () { // Vérifier à nouveau les permissions _checkLocationPermission(); }, text: 'Vérifier à nouveau', icon: Icons.refresh, backgroundColor: theme.colorScheme.tertiary, ), ], ), ), ), ), ), ); } /// Construit une étape d'instruction pour activer la localisation Widget _buildInstructionStep( ThemeData theme, int stepNumber, String instruction) { return Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: theme.colorScheme.primary, shape: BoxShape.circle, ), child: Center( child: Text( '$stepNumber', style: TextStyle( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 12), Expanded( child: Text( instruction, style: theme.textTheme.bodyMedium, ), ), ], ), ); } @override Widget build(BuildContext context) { print('DEBUG BUILD: Reconstruction de LoginPage avec type: $_loginType'); // Utiliser l'instance globale de userRepository final theme = Theme.of(context); final size = MediaQuery.of(context).size; // Afficher l'écran de permission de géolocalisation si l'utilisateur n'a pas accordé la permission (sauf en version web) if (!kIsWeb && _checkingPermission) { return _buildLoadingScreen(theme); } else if (!kIsWeb && !_hasLocationPermission) { return _buildLocationPermissionScreen(theme); } return Scaffold( body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Logo et titre Image.asset( 'assets/images/geosector-logo-200.png', height: 140, fit: BoxFit.contain, ), const SizedBox(height: 24), Text( (_loginType != null && _loginType!.trim().toLowerCase() == 'user') ? 'Connexion Utilisateur' : 'Connexion Administrateur', style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: (_loginType != null && _loginType!.trim().toLowerCase() == 'user') ? Colors.green : Colors.red, ), textAlign: TextAlign.center, ), // Ajouter un texte de débogage Text( 'Type de connexion détecté: $_loginType', style: 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.onBackground.withOpacity(0.7), ), textAlign: TextAlign.center, ), const SizedBox(height: 16), // Indicateur de connectivité ConnectivityIndicator(), 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()) { // S'assurer que le type est toujours défini final loginType = _loginType ?? 'admin'; final actualType = (loginType.trim().toLowerCase() == 'user') ? 'user' : 'admin'; print('DEBUG: Login avec type: $actualType'); final success = await userRepository.login( _usernameController.text.trim(), _passwordController.text, type: actualType, ); if (success && mounted) { if (userRepository.isAdmin()) { context.go('/admin'); } else { context.go('/user'); } } else if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Échec de la connexion. Vérifiez vos identifiants.'), backgroundColor: Colors.red, ), ); } } }, ), const SizedBox(height: 8), // Mot de passe oublié Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { // Naviguer vers la page de récupération de mot de passe }, 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()) { // Vérifier à nouveau les permissions de géolocalisation avant de se connecter (sauf en version web) if (!kIsWeb) { await _checkLocationPermission(); // Si l'utilisateur n'a toujours pas accordé les permissions, ne pas continuer if (!_hasLocationPermission) { ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( content: Text( 'L\'accès à la localisation est nécessaire pour utiliser cette application.'), backgroundColor: Colors.red, ), ); return; } } // Vérifier la connexion Internet await connectivityService .checkConnectivity(); if (!connectivityService.isConnected) { 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 && mounted) { ScaffoldMessenger.of(context) .showSnackBar( SnackBar( content: Text( 'Connexion Internet ${connectivityService.connectionType} détectée.'), backgroundColor: Colors.green, ), ); } }, ), ), ); return; } // S'assurer que le type est toujours défini final loginType = _loginType ?? 'admin'; final actualType = (loginType.trim().toLowerCase() == 'user') ? 'user' : 'admin'; print( 'DEBUG: Login bouton avec type: $actualType'); // Utiliser le service d'authentification avec l'overlay de chargement final authService = AuthService(userRepository); final success = await authService.login( context, _usernameController.text.trim(), _passwordController.text, type: actualType, ); if (success && mounted) { if (userRepository.isAdmin()) { context.go('/admin'); } else { context.go('/user'); } } else if (mounted) { ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( content: Text( 'Échec de la connexion. Vérifiez vos identifiants.'), backgroundColor: Colors.red, ), ); } } }, text: _isConnected ? 'Se connecter' : 'Connexion Internet requise', isLoading: userRepository.isLoading, ), const SizedBox(height: 24), // Inscription administrateur uniquement Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Pas encore de compte ?', style: theme.textTheme.bodyMedium, ), TextButton( onPressed: () { context.go('/register'); }, child: Text( 'Inscription Administrateur', style: TextStyle( color: theme.colorScheme.tertiary, fontWeight: FontWeight.bold, ), ), ), ], ), // Lien vers la page publique TextButton( onPressed: () { context.go('/public'); }, child: Text( 'Retour au site GEOSECTOR', style: TextStyle( color: theme.colorScheme.secondary, ), ), ), ], ), ), ], ), ), ), ), ), ); } }