feat: Integrate Firebase Authentication and Firestore

- Added Firebase configuration to Android and iOS projects.
- Created `google-services.json` and `GoogleService-Info.plist` for Firebase setup.
- Implemented `AuthService` for handling user authentication with Firebase.
- Updated `UserProvider` to manage user data with Firestore.
- Refactored `ProfileContent`, `LoginPage`, and `SignUpPage` to use the new authentication service.
- Removed the old `UserService` and replaced it with Firestore-based user management.
- Added Firebase options in `firebase_options.dart` for platform-specific configurations.
- Updated dependencies in `pubspec.yaml` for Firebase packages.
This commit is contained in:
Dayron
2025-10-06 18:57:30 +02:00
parent 0b9a140620
commit ec0bc59a70
16 changed files with 609 additions and 374 deletions

View File

@@ -1,5 +1,8 @@
plugins {
id("com.android.application")
// START: FlutterFire Configuration
id("com.google.gms.google-services")
// END: FlutterFire Configuration
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")

View File

@@ -0,0 +1,54 @@
{
"project_info": {
"project_number": "521527250907",
"project_id": "travelmate-a47f5",
"storage_bucket": "travelmate-a47f5.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:521527250907:android:be3db7fc84f053ec7da1fe",
"android_client_info": {
"package_name": "com.example.travel_mate"
}
},
"oauth_client": [
{
"client_id": "521527250907-lqgj1lmfcsjusm2be9r6kpuanq3jvjcd.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.example.travel_mate",
"certificate_hash": "9b7e3f14f0fcae0034ed977b5d40305b1812308d"
}
},
{
"client_id": "521527250907-j0kt1hc8hc7qc2kedp4akehau754cn5d.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAON_ol0Jr34tKbETvdDK9JCQdKNawxBeQ"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "521527250907-j0kt1hc8hc7qc2kedp4akehau754cn5d.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.example.travelMate"
}
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -20,6 +20,9 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
// START: FlutterFire Configuration
id("com.google.gms.google-services") version("4.3.15") apply false
// END: FlutterFire Configuration
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}

1
firebase.json Normal file
View File

@@ -0,0 +1 @@
{"flutter":{"platforms":{"android":{"default":{"projectId":"travelmate-a47f5","appId":"1:521527250907:android:be3db7fc84f053ec7da1fe","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"travelmate-a47f5","configurations":{"android":"1:521527250907:android:be3db7fc84f053ec7da1fe","ios":"1:521527250907:ios:64b41be39c54db1c7da1fe","windows":"1:521527250907:web:53ff98bcdb8c218f7da1fe"}}}}}}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m</string>
<key>ANDROID_CLIENT_ID</key>
<string>521527250907-lqgj1lmfcsjusm2be9r6kpuanq3jvjcd.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>AIzaSyBFaSzvcvO8qg7El-2jRf7WctZIMKNA4-I</string>
<key>GCM_SENDER_ID</key>
<string>521527250907</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.travelMate</string>
<key>PROJECT_ID</key>
<string>travelmate-a47f5</string>
<key>STORAGE_BUCKET</key>
<string>travelmate-a47f5.firebasestorage.app</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:521527250907:ios:64b41be39c54db1c7da1fe</string>
</dict>
</plist>

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/user_provider.dart';
import '../../services/auth_service.dart';
class ProfileContent extends StatelessWidget {
const ProfileContent({super.key});
@@ -143,12 +144,34 @@ class ProfileContent extends StatelessWidget {
child: Text('Annuler'),
),
TextButton(
onPressed: () {
// TODO: Implémenter la mise à jour
onPressed: () async {
if (prenomController.text.trim().isNotEmpty &&
nomController.text.trim().isNotEmpty) {
final updatedUser = user.copyWith(
nom: nomController.text.trim(),
prenom: prenomController.text.trim(),
);
final success = await Provider.of<UserProvider>(context,
listen: false)
.updateUser(updatedUser);
Navigator.of(context).pop();
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Profil mis à jour !')));
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Profil mis à jour !'),
backgroundColor: Colors.green),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la mise à jour'),
backgroundColor: Colors.red),
);
}
}
},
child: Text('Sauvegarder'),
),
@@ -159,6 +182,11 @@ class ProfileContent extends StatelessWidget {
}
void _showChangePasswordDialog(BuildContext context) {
final currentPasswordController = TextEditingController();
final newPasswordController = TextEditingController();
final confirmPasswordController = TextEditingController();
final authService = AuthService();
showDialog(
context: context,
builder: (BuildContext context) {
@@ -168,16 +196,19 @@ class ProfileContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: currentPasswordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Mot de passe actuel'),
),
SizedBox(height: 16),
TextField(
controller: newPasswordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Nouveau mot de passe'),
),
SizedBox(height: 16),
TextField(
controller: confirmPasswordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Confirmer le mot de passe',
@@ -191,12 +222,59 @@ class ProfileContent extends StatelessWidget {
child: Text('Annuler'),
),
TextButton(
onPressed: () {
// TODO: Implémenter le changement de mot de passe
onPressed: () async {
if (currentPasswordController.text.isEmpty ||
newPasswordController.text.isEmpty ||
confirmPasswordController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Tous les champs sont requis'),
backgroundColor: Colors.red),
);
return;
}
if (newPasswordController.text != confirmPasswordController.text) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Les mots de passe ne correspondent pas'),
backgroundColor: Colors.red),
);
return;
}
if (newPasswordController.text.length < 8) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Le mot de passe doit contenir au moins 8 caractères'),
backgroundColor: Colors.red),
);
return;
}
try {
final user = Provider.of<UserProvider>(context, listen: false)
.currentUser;
await authService.resetPasswordFromCurrentPassword(
currentPassword: currentPasswordController.text,
newPassword: newPasswordController.text,
email: user!.email,
);
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Mot de passe changé !')),
SnackBar(
content: Text('Mot de passe changé !'),
backgroundColor: Colors.green),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur: Mot de passe actuel incorrect'),
backgroundColor: Colors.red),
);
}
},
child: Text('Changer'),
),
@@ -207,29 +285,70 @@ class ProfileContent extends StatelessWidget {
}
void _showDeleteAccountDialog(BuildContext context) {
final passwordController = TextEditingController();
final authService = AuthService();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Supprimer le compte'),
content: Text(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Êtes-vous sûr de vouloir supprimer votre compte ? Cette action est irréversible.',
),
SizedBox(height: 16),
TextField(
controller: passwordController,
obscureText: true,
decoration: InputDecoration(
labelText: 'Confirmez votre mot de passe',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Annuler'),
),
TextButton(
onPressed: () {
onPressed: () async {
if (passwordController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Mot de passe requis'),
backgroundColor: Colors.red),
);
return;
}
try {
final user = Provider.of<UserProvider>(context, listen: false)
.currentUser;
await authService.deleteAccount(
password: passwordController.text,
email: user!.email,
);
Navigator.of(context).pop();
// TODO: Implémenter la suppression
Provider.of<UserProvider>(context, listen: false).logout();
Navigator.pushNamedAndRemoveUntil(
context,
'/login',
(route) => false,
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Erreur lors de la suppression: Mot de passe incorrect'),
backgroundColor: Colors.red),
);
}
},
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: Text('Supprimer'),

77
lib/firebase_options.dart Normal file
View File

@@ -0,0 +1,77 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for web - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
return windows;
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyAON_ol0Jr34tKbETvdDK9JCQdKNawxBeQ',
appId: '1:521527250907:android:be3db7fc84f053ec7da1fe',
messagingSenderId: '521527250907',
projectId: 'travelmate-a47f5',
storageBucket: 'travelmate-a47f5.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyBFaSzvcvO8qg7El-2jRf7WctZIMKNA4-I',
appId: '1:521527250907:ios:64b41be39c54db1c7da1fe',
messagingSenderId: '521527250907',
projectId: 'travelmate-a47f5',
storageBucket: 'travelmate-a47f5.firebasestorage.app',
iosClientId: '521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com',
iosBundleId: 'com.example.travelMate',
);
static const FirebaseOptions windows = FirebaseOptions(
apiKey: 'AIzaSyC4t-WOvp22zns9b9t58urznsNAhSHRAag',
appId: '1:521527250907:web:53ff98bcdb8c218f7da1fe',
messagingSenderId: '521527250907',
projectId: 'travelmate-a47f5',
authDomain: 'travelmate-a47f5.firebaseapp.com',
storageBucket: 'travelmate-a47f5.firebasestorage.app',
measurementId: 'G-J246Y7J61M',
);
}

View File

@@ -6,8 +6,14 @@ import 'pages/login.dart';
import 'pages/home.dart';
import 'providers/theme_provider.dart';
import 'providers/user_provider.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(
MultiProvider(
providers: [

View File

@@ -5,14 +5,12 @@ class User {
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)
@@ -22,7 +20,6 @@ class User {
nom: map['nom'] ?? '',
prenom: map['prenom'] ?? '',
email: map['email'] ?? '',
password: map['password'] ?? '',
);
}
@@ -39,7 +36,6 @@ class User {
'nom': nom,
'prenom': prenom,
'email': email,
'password': password,
};
}
@@ -57,14 +53,12 @@ class User {
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,
);
}
@@ -82,3 +76,4 @@ class User {
@override
int get hashCode => email.hashCode;
}

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import '../services/user_service.dart';
import '../services/auth_service.dart';
import 'package:provider/provider.dart';
import '../providers/user_provider.dart';
@@ -14,7 +14,7 @@ class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _userService = UserService();
final _authService = AuthService();
bool _isLoading = false;
bool _obscurePassword = true;
@@ -26,26 +26,6 @@ class _LoginPageState extends State<LoginPage> {
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()) {
@@ -57,24 +37,26 @@ class _LoginPageState extends State<LoginPage> {
});
try {
final user = await _userService.authenticateUser(
_emailController.text.trim(),
_passwordController.text,
final userCredential = await _authService.signIn(
email: _emailController.text.trim(),
password: _passwordController.text,
);
if (mounted) {
if (user != null) {
// Naviguer vers la page d'accueil
Provider.of<UserProvider>(context, listen: false).setCurrentUser(user);
if (mounted && userCredential.user != null) {
// Récupérer les données utilisateur depuis Firestore
final userProvider = Provider.of<UserProvider>(context, listen: false);
final userData = await userProvider.getUserData(userCredential.user!.uid);
if (userData != null) {
userProvider.setCurrentUser(userData);
Navigator.pushReplacementNamed(context, '/home');
} else {
// Échec de la connexion
_showErrorMessage('Email ou mot de passe incorrect');
_showErrorMessage('Données utilisateur non trouvées');
}
}
} catch (e) {
if (mounted) {
_showErrorMessage('Erreur lors de la connexion: ${e.toString()}');
_showErrorMessage('Email ou mot de passe incorrect');
}
} finally {
if (mounted) {
@@ -85,18 +67,6 @@ class _LoginPageState extends State<LoginPage> {
}
}
void _showErrorMessage(String message) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -341,4 +311,36 @@ class _LoginPageState extends State<LoginPage> {
),
);
}
// 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;
}
void _showErrorMessage(String message) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
),
);
}
}
}

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:bcrypt/bcrypt.dart';
import 'package:provider/provider.dart';
import '../models/user.dart';
import '../services/user_service.dart';
import '../services/auth_service.dart';
import '../providers/user_provider.dart';
class SignUpPage extends StatefulWidget {
const SignUpPage({super.key});
@@ -17,7 +18,7 @@ class _SignUpPageState extends State<SignUpPage> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
final _userService = UserService();
final _authService = AuthService();
bool _isLoading = false;
bool _obscurePassword = true;
@@ -84,39 +85,29 @@ class _SignUpPageState extends State<SignUpPage> {
});
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 le compte avec Firebase Auth
final userCredential = await _authService.createAccount(
email: _emailController.text.trim(),
password: _passwordController.text,
);
// Créer l'utilisateur
// Créer l'objet User
User newUser = User(
id: userCredential.user!.uid,
nom: _nomController.text.trim(),
prenom: _prenomController.text.trim(),
email: _emailController.text.trim().toLowerCase(),
password: hashedPassword,
email: _emailController.text.trim(),
);
// Sauvegarder l'utilisateur
bool success = await _userService.addUser(newUser);
// Sauvegarder les données utilisateur dans Firestore
await Provider.of<UserProvider>(context, listen: false).saveUserData(newUser);
// Mettre à jour le displayName
await _authService.updateDisplayName(displayName: newUser.fullName);
if (success) {
_showSuccessDialog();
} else {
_showErrorDialog('Erreur lors de la création du compte');
}
} catch (e) {
_showErrorDialog('Une erreur est survenue: ${e.toString()}');
_showErrorDialog('Erreur lors de la création du compte: ${e.toString()}');
} finally {
setState(() {
_isLoading = false;
@@ -351,3 +342,4 @@ class _SignUpPageState extends State<SignUpPage> {
);
}
}

View File

@@ -1,8 +1,13 @@
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/user.dart';
import '../services/auth_service.dart';
class UserProvider extends ChangeNotifier {
User? _currentUser;
final AuthService _authService = AuthService();
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
User? get currentUser => _currentUser;
@@ -11,10 +16,65 @@ class UserProvider extends ChangeNotifier {
notifyListeners();
}
void logout() {
Future<void> logout() async {
await _authService.signOut();
_currentUser = null;
notifyListeners();
}
bool get isLoggedIn => _currentUser != null;
// Méthode pour récupérer les données utilisateur depuis Firestore
Future<User?> getUserData(String uid) async {
try {
DocumentSnapshot doc = await _firestore.collection('users').doc(uid).get();
if (doc.exists) {
Map<String, dynamic> data = doc.data() as Map<String, dynamic>;
return User.fromMap({...data, 'id': uid});
}
return null;
} catch (e) {
print('Erreur lors de la récupération des données utilisateur: $e');
return null;
}
}
// Méthode pour sauvegarder les données utilisateur dans Firestore
Future<void> saveUserData(User user) async {
try {
await _firestore.collection('users').doc(user.id).set(user.toMap());
} catch (e) {
print('Erreur lors de la sauvegarde des données utilisateur: $e');
throw e;
}
}
// Méthode pour mettre à jour les données utilisateur
Future<bool> updateUser(User updatedUser) async {
try {
await _firestore.collection('users').doc(updatedUser.id).update(updatedUser.toMap());
// Mettre à jour le displayName dans Firebase Auth
await _authService.updateDisplayName(displayName: updatedUser.fullName);
_currentUser = updatedUser;
notifyListeners();
return true;
} catch (e) {
print('Erreur lors de la mise à jour de l\'utilisateur: $e');
return false;
}
}
// Initialiser l'utilisateur connecté
Future<void> initializeUser() async {
firebase_auth.User? firebaseUser = _authService.currentUser;
if (firebaseUser != null) {
User? userData = await getUserData(firebaseUser.uid);
if (userData != null) {
_currentUser = userData;
notifyListeners();
}
}
}
}

View File

@@ -0,0 +1,68 @@
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
User? get currentUser => firebaseAuth.currentUser;
Stream<User?> get authStateChanges => firebaseAuth.authStateChanges();
Future<UserCredential> signIn({
required String email,
required String password
}) async {
return await firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
}
Future<UserCredential> createAccount({
required String email,
required String password
}) async {
return await firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
}
Future<void> signOut() async {
await firebaseAuth.signOut();
}
Future<void> resetPassword(String email) async {
await firebaseAuth.sendPasswordResetEmail(email: email);
}
Future<void> updateDisplayName({
required String displayName,
}) async {
await currentUser!.updateDisplayName(displayName);
}
Future<void> deleteAccount({
required String password,
required String email,
}) async {
// Re-authenticate the user
AuthCredential credential =
EmailAuthProvider.credential(email: email, password: password);
await currentUser!.reauthenticateWithCredential(credential);
// Delete the user
await currentUser!.delete();
await firebaseAuth.signOut();
}
Future<void> resetPasswordFromCurrentPassword({
required String currentPassword,
required String newPassword,
required String email,
}) async {
// Re-authenticate the user
AuthCredential credential =
EmailAuthProvider.credential(email: email, password: currentPassword);
await currentUser!.reauthenticateWithCredential(credential);
// Update the password
await currentUser!.updatePassword(newPassword);
}
}

View File

@@ -1,272 +0,0 @@
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

@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "23d16f00a2da8ffa997c782453c73867b0609bd90435195671a54de38a3566df"
url: "https://pub.dev"
source: hosted
version: "1.3.62"
async:
dependency: transitive
description:
@@ -41,6 +49,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.2"
cloud_firestore:
dependency: "direct main"
description:
name: cloud_firestore
sha256: af66aeffe5943d582c0f655ec860433dbd773ac4a4ffc62c11960b52a18833fe
url: "https://pub.dev"
source: hosted
version: "6.0.2"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
sha256: "494dd3d275a0259e3ba08b442b54e64839b0cf58352a50fe97eb67cacf3bad28"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
sha256: "85b7b071c23eecbbbb47a9fbd2cfdb1b6af20f2323663fd90234d91a064f0584"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
collection:
dependency: transitive
description:
@@ -89,6 +121,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: "735f857c9363376eeb585e7ba57e67e5f495202cd3f609902b8769795fd823bc"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: "5badda0ea5048ffbb1726169cf5530539490de8055c3bd43f4f9cd5fcef8e556"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: "07c889d2c56e648ed30225e819801d7e45542747a658d9c385520de35d312dec"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "4dd96f05015c0dcceaa47711394c32971aee70169625d5e2477e7676c01ce0ee"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "5873a370f0d232918e23a5a6137dbe4c2c47cf017301f4ea02d9d636e52f60f0"
url: "https://pub.dev"
source: hosted
version: "6.0.1"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "61a51037312dac781f713308903bb7a1762a7f92f7bc286a3a0947fb2a713b82"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
flutter:
dependency: "direct main"
description: flutter
@@ -176,6 +256,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.15.6"
http:
dependency: transitive
description:
name: http
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
url: "https://pub.dev"
source: hosted
version: "1.5.0"
http_parser:
dependency: transitive
description:

View File

@@ -40,6 +40,9 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
google_maps_flutter: ^2.5.0
firebase_core: ^4.1.1
firebase_auth: ^6.1.0
cloud_firestore: ^6.0.2
dev_dependencies:
flutter_test: