- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
948 lines
43 KiB
Dart
Executable File
948 lines
43 KiB
Dart
Executable File
import 'package:flutter/material.dart';
|
|
import 'dart:math' as math;
|
|
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode;
|
|
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:geosector_app/core/services/app_info_service.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:package_info_plus/package_info_plus.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<LoginPage> 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<LoginPage> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _usernameController = TextEditingController();
|
|
final _passwordController = TextEditingController();
|
|
final _usernameFocusNode = FocusNode();
|
|
bool _obscurePassword = true;
|
|
String _appVersion = '';
|
|
|
|
// Type de connexion (utilisateur ou administrateur)
|
|
late String _loginType;
|
|
|
|
|
|
// État de la connexion Internet
|
|
bool _isConnected = false;
|
|
|
|
Future<void> _getAppVersion() async {
|
|
try {
|
|
final packageInfo = await PackageInfo.fromPlatform();
|
|
if (mounted) {
|
|
setState(() {
|
|
_appVersion = packageInfo.version;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Erreur lors de la récupération de la version: $e');
|
|
// Fallback sur la version du AppInfoService si elle existe
|
|
if (mounted) {
|
|
setState(() {
|
|
_appVersion = AppInfoService.fullVersion
|
|
.split(' ')
|
|
.last; // Extraire juste le numéro
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _checkConnectivity() async {
|
|
await connectivityService.checkConnectivity();
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
_isConnected = connectivityService.isConnected;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
// Vérification du type de connexion
|
|
if (widget.loginType == null) {
|
|
// Si aucun type n'est spécifié, naviguer vers la splash page
|
|
print(
|
|
'LoginPage: Aucun type de connexion spécifié, navigation vers splash page');
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
GoRouter.of(context).go('/');
|
|
});
|
|
_loginType = '';
|
|
} else {
|
|
_loginType = widget.loginType!;
|
|
print('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';
|
|
print(
|
|
'LoginPage: Type détecté depuis sessionStorage: $_loginType');
|
|
});
|
|
}
|
|
} catch (e) {
|
|
print('LoginPage: Erreur lors de l\'accès au sessionStorage: $e');
|
|
}
|
|
});
|
|
}
|
|
} catch (e) {
|
|
print('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((_) {
|
|
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');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
@override
|
|
void dispose() {
|
|
_usernameController.dispose();
|
|
_passwordController.dispose();
|
|
_usernameFocusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
|
|
@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;
|
|
|
|
// 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),
|
|
),
|
|
),
|
|
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) {
|
|
print(
|
|
'Login: Type non spécifié, redirection vers la page de démarrage');
|
|
context.go('/');
|
|
return;
|
|
}
|
|
|
|
print(
|
|
'Login: Tentative avec type: $_loginType');
|
|
|
|
final success =
|
|
await userRepository.login(
|
|
_usernameController.text.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');
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(
|
|
const SnackBar(
|
|
content: Text(
|
|
'Erreur de connexion. Veuillez réessayer.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
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');
|
|
|
|
// Redirection simple basée sur le rôle
|
|
if (roleValue > 1) {
|
|
debugPrint(
|
|
'Redirection vers /admin (rôle > 1)');
|
|
context.go('/admin');
|
|
} else {
|
|
debugPrint(
|
|
'Redirection vers /user (rôle = 1)');
|
|
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: () {
|
|
_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) {
|
|
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;
|
|
}
|
|
|
|
// Vérifier que le type de connexion est spécifié
|
|
if (_loginType.isEmpty) {
|
|
print(
|
|
'Login: Type non spécifié, redirection vers la page de démarrage');
|
|
context.go('/');
|
|
return;
|
|
}
|
|
|
|
print(
|
|
'Login: Tentative avec type: $_loginType');
|
|
|
|
// Utiliser directement userRepository avec l'overlay de chargement
|
|
final success = await userRepository
|
|
.loginWithUI(
|
|
context,
|
|
_usernameController.text.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');
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(
|
|
const SnackBar(
|
|
content: Text(
|
|
'Erreur de connexion. Veuillez réessayer.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
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');
|
|
|
|
// Redirection simple basée sur le rôle
|
|
if (roleValue > 1) {
|
|
debugPrint(
|
|
'Redirection vers /admin (rôle > 1)');
|
|
context.go('/admin');
|
|
} else {
|
|
debugPrint(
|
|
'Redirection vers /user (rôle = 1)');
|
|
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: 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 en bas à droite
|
|
if (_appVersion.isNotEmpty)
|
|
Positioned(
|
|
bottom: 16,
|
|
right: 16,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
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: 10,
|
|
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<FormState>();
|
|
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;
|
|
});
|
|
|
|
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';
|
|
|
|
print('Envoi de la requête à: $apiUrl');
|
|
print('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(),
|
|
}),
|
|
);
|
|
|
|
print('Réponse reçue: ${response.statusCode}');
|
|
print('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';
|
|
print(
|
|
'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(),
|
|
}),
|
|
);
|
|
|
|
print(
|
|
'Réponse alternative reçue: ${alternativeResponse.statusCode}');
|
|
print(
|
|
'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) {
|
|
print(
|
|
'Erreur lors de l\'envoi de la requête: $e');
|
|
throw Exception('Erreur de connexion: $e');
|
|
}
|
|
|
|
// Traiter la réponse
|
|
if (response.statusCode == 200) {
|
|
// 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
|
|
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 (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 {
|
|
// Fermer la boîte de dialogue actuelle
|
|
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) {
|
|
// Afficher un message d'erreur
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(e
|
|
.toString()
|
|
.contains('Exception:')
|
|
? e.toString().split('Exception: ')[1]
|
|
: 'Erreur lors de la récupération du mot de passe'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
} 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<Color>(Colors.white),
|
|
),
|
|
)
|
|
: const Text('Recevoir un nouveau mot de passe'),
|
|
),
|
|
],
|
|
);
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|