Implement login and signup test with json.

This commit is contained in:
Van Leemput Dayron
2025-10-01 00:48:06 +02:00
parent b3d463e1fb
commit 9fb81f793b
8 changed files with 1004 additions and 362 deletions

View File

@@ -1,21 +1,28 @@
PODS:
- Flutter (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e

84
lib/models/user.dart Normal file
View File

@@ -0,0 +1,84 @@
import 'dart:convert';
class User {
final String? id;
final String nom;
final String prenom;
final String email;
final String password;
User({
this.id,
required this.nom,
required this.prenom,
required this.email,
required this.password,
});
// Constructeur pour créer un User depuis un Map (utile pour Firebase)
factory User.fromMap(Map<String, dynamic> map) {
return User(
id: map['id'],
nom: map['nom'] ?? '',
prenom: map['prenom'] ?? '',
email: map['email'] ?? '',
password: map['password'] ?? '',
);
}
// Constructeur pour créer un User depuis JSON
factory User.fromJson(String jsonStr) {
Map<String, dynamic> map = json.decode(jsonStr);
return User.fromMap(map);
}
// Méthode pour convertir un User en Map (utile pour Firebase)
Map<String, dynamic> toMap() {
return {
'id': id,
'nom': nom,
'prenom': prenom,
'email': email,
'password': password,
};
}
// Méthode pour convertir un User en JSON
String toJson() {
return json.encode(toMap());
}
// Méthode pour obtenir le nom complet
String get fullName => '$prenom $nom';
// Méthode pour créer une copie avec des modifications
User copyWith({
String? id,
String? nom,
String? prenom,
String? email,
String? password,
}) {
return User(
id: id ?? this.id,
nom: nom ?? this.nom,
prenom: prenom ?? this.prenom,
email: email ?? this.email,
password: password ?? this.password,
);
}
@override
String toString() {
return 'User(id: $id, nom: $nom, prenom: $prenom, email: $email)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User && other.email == email;
}
@override
int get hashCode => email.hashCode;
}

View File

@@ -1,102 +1,231 @@
//import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../models/user.dart';
import '../services/user_service.dart';
class LoginPage extends StatelessWidget {
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _userService = UserService();
bool _isLoading = false;
bool _obscurePassword = true;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
// Validation de l'email
String? _validateEmail(String? value) {
if (value == null || value.trim().isEmpty) {
return 'Email requis';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value.trim())) {
return 'Email invalide';
}
return null;
}
// Validation du mot de passe
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Mot de passe requis';
}
return null;
}
// Méthode de connexion
Future<void> _login() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
final user = await _userService.authenticateUser(
_emailController.text.trim(),
_passwordController.text,
);
if (user != null) {
// Connexion réussie
_showSuccessMessage('Connexion réussie !');
// Naviguer vers la page d'accueil
Navigator.pushReplacementNamed(context, '/home');
} else {
// Échec de la connexion
_showErrorMessage('Email ou mot de passe incorrect');
}
} catch (e) {
_showErrorMessage('Erreur lors de la connexion: ${e.toString()}');
} finally {
setState(() {
_isLoading = false;
});
}
}
void _showSuccessMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
}
void _showErrorMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
duration: Duration(seconds: 3),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
const Text(
'Bienvenue sur Travel Mate',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
// Titre
Text(
'Travel Mate',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
const Text(
// Sous-titre
Text(
'Connectez-vous pour continuer',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 80),
const SizedBox(height: 32),
const TextField(
// Champ email
TextFormField(
controller: _emailController,
validator: _validateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.email),
),
),
const SizedBox(height: 20),
const TextField(
obscureText: true,
const SizedBox(height: 16),
// Champ mot de passe
TextFormField(
controller: _passwordController,
validator: _validatePassword,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
labelText: 'Mot de passe',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Logique de connexion
Navigator.pushReplacementNamed(context, '/home');
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
),
const SizedBox(height: 8),
// Lien "Mot de passe oublié"
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
Navigator.pushNamed(context, '/forgot');
},
child: Text('Mot de passe oublié?'),
),
),
const SizedBox(height: 24),
// Bouton de connexion
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _isLoading ? null : _login,
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text('Login', style: TextStyle(fontSize: 18)),
child: _isLoading
? CircularProgressIndicator(color: Colors.white)
: Text(
'Se connecter',
style: TextStyle(fontSize: 16),
),
),
const SizedBox(height: 20),
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/forgot');
},
child: const Text('Mot de passe oublié ?'),
),
const SizedBox(height: 20),
const SizedBox(height: 24),
// Lien d'inscription
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Pas encore inscrit ? "),
GestureDetector(
onTap: () {
// Logique d'inscription
Text("Vous n'avez pas de compte?"),
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/signup');
},
child: const Text(
'Inscrivez-vous !',
style: TextStyle(
color: Color.fromARGB(255, 37, 109, 167),
decoration: TextDecoration.underline,
),
),
child: Text('S\'inscrire'),
),
],
),
const SizedBox(height: 40),
// Séparateur
Container(
width: double.infinity,
height: 1,
@@ -114,14 +243,16 @@ class LoginPage extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
// Bouton Google avec fond
// GOOGLE
GestureDetector(
onTap: () {
// Logique de connexion avec Google
// TODO: Implémenter la connexion Google
_showErrorMessage(
'Connexion Google non implémentée',
);
},
child: Container(
width: 50,
@@ -131,7 +262,7 @@ class LoginPage extends StatelessWidget {
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
color: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 3,
offset: Offset(0, 1),
@@ -151,7 +282,6 @@ class LoginPage extends StatelessWidget {
),
),
),
const SizedBox(height: 8),
const Text('Google'),
],
@@ -164,7 +294,10 @@ class LoginPage extends StatelessWidget {
// APPLE
GestureDetector(
onTap: () {
// Logique de connexion avec Google
// TODO: Implémenter la connexion Apple
_showErrorMessage(
'Connexion Apple non implémentée',
);
},
child: Container(
width: 50,
@@ -174,7 +307,7 @@ class LoginPage extends StatelessWidget {
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
color: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 3,
offset: Offset(0, 1),
@@ -190,22 +323,25 @@ class LoginPage extends StatelessWidget {
'assets/icons/apple_white.png',
width: 24,
height: 24,
color: Colors.white,
),
),
),
),
const SizedBox(height: 8),
const Text('Apple'),
],
),
],
),
const SizedBox(height: 40),
],
),
),
),
),
),
);
}
}

View File

@@ -1,8 +1,170 @@
import 'package:flutter/material.dart';
import 'package:bcrypt/bcrypt.dart';
import '../models/user.dart';
import '../services/user_service.dart';
class SignUpPage extends StatelessWidget {
class SignUpPage extends StatefulWidget {
const SignUpPage({super.key});
@override
State<SignUpPage> createState() => _SignUpPageState();
}
class _SignUpPageState extends State<SignUpPage> {
final _formKey = GlobalKey<FormState>();
final _nomController = TextEditingController();
final _prenomController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
final _userService = UserService();
bool _isLoading = false;
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
@override
void dispose() {
_nomController.dispose();
_prenomController.dispose();
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
super.dispose();
}
// Méthode de validation
String? _validateField(String? value, String fieldName) {
if (value == null || value.trim().isEmpty) {
return '$fieldName est requis';
}
return null;
}
String? _validateEmail(String? value) {
if (value == null || value.trim().isEmpty) {
return 'Email est requis';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value.trim())) {
return 'Email invalide';
}
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Mot de passe est requis';
}
if (value.length < 8) {
return 'Le mot de passe doit contenir au moins 8 caractères';
}
return null;
}
String? _validateConfirmPassword(String? value) {
if (value == null || value.isEmpty) {
return 'Confirmation du mot de passe requise';
}
if (value != _passwordController.text) {
return 'Les mots de passe ne correspondent pas';
}
return null;
}
// Méthode d'enregistrement
Future<void> _signUp() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
// Vérifier si l'email existe déjà
bool emailExists = await _userService.emailExists(
_emailController.text.trim(),
);
if (emailExists) {
_showErrorDialog('Cet email est déjà utilisé');
return;
}
// Hasher le mot de passe
String hashedPassword = BCrypt.hashpw(
_passwordController.text,
BCrypt.gensalt(),
);
// Créer l'utilisateur
User newUser = User(
nom: _nomController.text.trim(),
prenom: _prenomController.text.trim(),
email: _emailController.text.trim().toLowerCase(),
password: hashedPassword,
);
// Sauvegarder l'utilisateur
bool success = await _userService.addUser(newUser);
if (success) {
_showSuccessDialog();
} else {
_showErrorDialog('Erreur lors de la création du compte');
}
} catch (e) {
_showErrorDialog('Une erreur est survenue: ${e.toString()}');
} finally {
setState(() {
_isLoading = false;
});
}
}
void _showSuccessDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Succès'),
content: Text('Votre compte a été créé avec succès !'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Fermer la dialog
Navigator.of(context).pop(); // Retourner à la page de login
},
child: Text('OK'),
),
],
);
},
);
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Erreur'),
content: Text(message),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('OK'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -17,106 +179,157 @@ class SignUpPage extends StatelessWidget {
elevation: 0,
),
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Form(
key: _formKey,
child: Column(
children: [
const SizedBox(height: 40),
const Text(
'Bienvenue sur Travel Mate',
'Créer un compte',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
const Text(
'Créez un compte pour continuer',
'Rejoignez Travel Mate',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 20),
const SizedBox(height: 40),
const TextField(
// Champ Nom
TextFormField(
controller: _nomController,
validator: (value) => _validateField(value, 'Nom'),
decoration: InputDecoration(
labelText: 'Nom',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.person),
),
),
const SizedBox(height: 12),
const SizedBox(height: 20),
const TextField(
// Champ Prénom
TextFormField(
controller: _prenomController,
validator: (value) => _validateField(value, 'Prénom'),
decoration: InputDecoration(
labelText: 'Prénom',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.person_outline),
),
),
const SizedBox(height: 20),
const TextField(
// Champ Email
TextFormField(
controller: _emailController,
validator: _validateEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.email),
),
),
const SizedBox(height: 20),
const TextField(
obscureText: true,
// Champ Mot de passe
TextFormField(
controller: _passwordController,
validator: _validatePassword,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'Mot de passe',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
),
const SizedBox(height: 20),
const TextField(
obscureText: true,
// Champ Confirmation mot de passe
TextFormField(
controller: _confirmPasswordController,
validator: _validateConfirmPassword,
obscureText: _obscureConfirmPassword,
decoration: InputDecoration(
labelText: 'Confirmez le mot de passe',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword
? Icons.visibility
: Icons.visibility_off,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Logique d'inscription
Navigator.pushNamed(context, '/home');
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
),
),
const SizedBox(height: 30),
// Bouton d'inscription
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: _isLoading ? null : _signUp,
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'S\'inscrire',
style: TextStyle(fontSize: 18),
child: _isLoading
? CircularProgressIndicator(color: Colors.white)
: Text('S\'inscrire', style: TextStyle(fontSize: 18)),
),
),
const SizedBox(height: 20),
// Lien vers login
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Déjà un compte ? "),
GestureDetector(
onTap: () {
// Logique de navigation vers la page de connexion
Navigator.pop(context);
},
child: const Text(
@@ -129,114 +342,8 @@ class SignUpPage extends StatelessWidget {
),
],
),
const SizedBox(height: 40),
Container(
width: double.infinity,
height: 1,
color: Colors.grey.shade300,
),
const SizedBox(height: 40),
Text(
'Ou inscrivez-vous avec',
style: TextStyle(color: Colors.grey.shade600),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
// Bouton Google avec fond
GestureDetector(
onTap: () {
// Logique de connexion avec Google
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
spreadRadius: 1,
blurRadius: 3,
offset: Offset(0, 1),
),
],
border: Border.all(
color: Colors.grey.shade300,
width: 1,
),
),
child: Center(
child: Image.asset(
'assets/icons/google.png',
width: 24,
height: 24,
),
),
),
),
const SizedBox(height: 8),
const Text('Google'),
],
),
const SizedBox(width: 40),
Column(
children: [
// APPLE
GestureDetector(
onTap: () {
// Logique de connexion avec Google
},
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
spreadRadius: 1,
blurRadius: 3,
offset: Offset(0, 1),
),
],
border: Border.all(
color: Colors.grey.shade300,
width: 1,
),
),
child: Center(
child: Image.asset(
'assets/icons/apple_white.png',
width: 24,
height: 24,
),
),
),
),
const SizedBox(height: 8),
const Text('Apple'),
],
),
],
),
],
),
),
),

View File

@@ -0,0 +1,272 @@
import 'dart:io';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';
import 'package:bcrypt/bcrypt.dart';
import '../models/user.dart';
class UserService {
static const String _fileName = 'users.json';
// Obtenir le fichier JSON
Future<File> _getUserFile() async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/$_fileName');
}
// Charger tous les utilisateurs
Future<List<User>> loadUsers() async {
try {
final file = await _getUserFile();
if (!await file.exists()) return [];
final contents = await file.readAsString();
if (contents.isEmpty) return [];
final List<dynamic> jsonList = json.decode(contents);
return jsonList.map((json) => User.fromMap(json)).toList();
} catch (e) {
print('Erreur lors du chargement des utilisateurs: $e');
return [];
}
}
// Sauvegarder tous les utilisateurs
Future<void> saveUsers(List<User> users) async {
try {
final file = await _getUserFile();
final jsonList = users.map((user) => user.toMap()).toList();
await file.writeAsString(json.encode(jsonList));
} catch (e) {
print('Erreur lors de la sauvegarde des utilisateurs: $e');
throw Exception('Erreur de sauvegarde');
}
}
// Ajouter un nouvel utilisateur
Future<bool> addUser(User user) async {
try {
final users = await loadUsers();
// Vérifier si l'email existe déjà
if (users.any((u) => u.email.toLowerCase() == user.email.toLowerCase())) {
return false; // Email déjà utilisé
}
// Générer un ID unique
final newUser = user.copyWith(
id: DateTime.now().millisecondsSinceEpoch.toString(),
);
users.add(newUser);
await saveUsers(users);
return true;
} catch (e) {
print('Erreur lors de l\'ajout de l\'utilisateur: $e');
return false;
}
}
// Authentifier un utilisateur avec bcrypt
Future<User?> authenticateUser(String email, String password) async {
try {
final users = await loadUsers();
// Trouver l'utilisateur par email (insensible à la casse)
User? user;
try {
user = users.firstWhere(
(u) => u.email.toLowerCase() == email.toLowerCase(),
);
} catch (e) {
return null; // Utilisateur non trouvé
}
// Vérifier le mot de passe avec bcrypt
if (BCrypt.checkpw(password, user.password)) {
return user;
}
return null; // Mot de passe incorrect
} catch (e) {
print('Erreur lors de l\'authentification: $e');
return null;
}
}
// Vérifier si un email existe
Future<bool> emailExists(String email) async {
try {
final users = await loadUsers();
return users.any(
(user) => user.email.toLowerCase() == email.toLowerCase(),
);
} catch (e) {
print('Erreur lors de la vérification de l\'email: $e');
return false;
}
}
// Obtenir un utilisateur par ID
Future<User?> getUserById(String id) async {
try {
final users = await loadUsers();
return users.firstWhere((user) => user.id == id);
} catch (e) {
print('Utilisateur avec l\'ID $id non trouvé');
return null;
}
}
// Obtenir un utilisateur par email
Future<User?> getUserByEmail(String email) async {
try {
final users = await loadUsers();
return users.firstWhere(
(user) => user.email.toLowerCase() == email.toLowerCase(),
);
} catch (e) {
print('Utilisateur avec l\'email $email non trouvé');
return null;
}
}
// Mettre à jour un utilisateur
Future<bool> updateUser(User updatedUser) async {
try {
final users = await loadUsers();
final index = users.indexWhere((user) => user.id == updatedUser.id);
if (index != -1) {
users[index] = updatedUser;
await saveUsers(users);
return true;
}
return false; // Utilisateur non trouvé
} catch (e) {
print('Erreur lors de la mise à jour de l\'utilisateur: $e');
return false;
}
}
// Supprimer un utilisateur
Future<bool> deleteUser(String id) async {
try {
final users = await loadUsers();
final initialLength = users.length;
users.removeWhere((user) => user.id == id);
if (users.length < initialLength) {
await saveUsers(users);
return true;
}
return false; // Utilisateur non trouvé
} catch (e) {
print('Erreur lors de la suppression de l\'utilisateur: $e');
return false;
}
}
// Changer le mot de passe d'un utilisateur
Future<bool> changePassword(
String userId,
String oldPassword,
String newPassword,
) async {
try {
final users = await loadUsers();
final userIndex = users.indexWhere((user) => user.id == userId);
if (userIndex == -1) return false; // Utilisateur non trouvé
final user = users[userIndex];
// Vérifier l'ancien mot de passe
if (!BCrypt.checkpw(oldPassword, user.password)) {
return false; // Ancien mot de passe incorrect
}
// Hasher le nouveau mot de passe
final hashedNewPassword = BCrypt.hashpw(newPassword, BCrypt.gensalt());
// Mettre à jour l'utilisateur
users[userIndex] = user.copyWith(password: hashedNewPassword);
await saveUsers(users);
return true;
} catch (e) {
print('Erreur lors du changement de mot de passe: $e');
return false;
}
}
// Réinitialiser le mot de passe (pour la fonctionnalité "mot de passe oublié")
Future<bool> resetPassword(String email, String newPassword) async {
try {
final users = await loadUsers();
final userIndex = users.indexWhere(
(user) => user.email.toLowerCase() == email.toLowerCase(),
);
if (userIndex == -1) return false; // Utilisateur non trouvé
// Hasher le nouveau mot de passe
final hashedNewPassword = BCrypt.hashpw(newPassword, BCrypt.gensalt());
// Mettre à jour l'utilisateur
users[userIndex] = users[userIndex].copyWith(password: hashedNewPassword);
await saveUsers(users);
return true;
} catch (e) {
print('Erreur lors de la réinitialisation du mot de passe: $e');
return false;
}
}
// Obtenir le nombre total d'utilisateurs
Future<int> getUserCount() async {
try {
final users = await loadUsers();
return users.length;
} catch (e) {
print('Erreur lors du comptage des utilisateurs: $e');
return 0;
}
}
// Vider la base de données (utile pour les tests)
Future<void> clearAllUsers() async {
try {
await saveUsers([]);
} catch (e) {
print('Erreur lors du vidage de la base de données: $e');
}
}
// Méthode pour créer des utilisateurs de test
Future<void> createTestUsers() async {
try {
final testUsers = [
User(
nom: 'Dupont',
prenom: 'Jean',
email: 'jean.dupont@test.com',
password: BCrypt.hashpw('password123', BCrypt.gensalt()),
),
User(
nom: 'Martin',
prenom: 'Marie',
email: 'marie.martin@test.com',
password: BCrypt.hashpw('password123', BCrypt.gensalt()),
),
];
for (final user in testUsers) {
await addUser(user);
}
} catch (e) {
print('Erreur lors de la création des utilisateurs de test: $e');
}
}
}

View File

@@ -5,8 +5,10 @@
import FlutterMacOS
import Foundation
import path_provider_foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.13.0"
bcrypt:
dependency: "direct main"
description:
name: bcrypt
sha256: "9dc3f234d5935a76917a6056613e1a6d9b53f7fa56f98e24cd49b8969307764b"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
boolean_selector:
dependency: transitive
description:
@@ -168,6 +176,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
url: "https://pub.dev"
source: hosted
version: "2.2.18"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
path_provider_linux:
dependency: transitive
description:

View File

@@ -32,6 +32,8 @@ dependencies:
sdk: flutter
provider: ^6.1.1
shared_preferences: ^2.2.2
path_provider: ^2.1.1
bcrypt: ^1.1.3
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.