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:
@@ -1,5 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
id("com.google.gms.google-services")
|
||||||
|
// END: FlutterFire Configuration
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
|||||||
54
android/app/google-services.json
Normal file
54
android/app/google-services.json
Normal 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"
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.9.1" apply false
|
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
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
firebase.json
Normal file
1
firebase.json
Normal 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"}}}}}}
|
||||||
36
ios/Runner/GoogleService-Info.plist
Normal file
36
ios/Runner/GoogleService-Info.plist
Normal 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>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../providers/user_provider.dart';
|
import '../../providers/user_provider.dart';
|
||||||
|
import '../../services/auth_service.dart';
|
||||||
|
|
||||||
class ProfileContent extends StatelessWidget {
|
class ProfileContent extends StatelessWidget {
|
||||||
const ProfileContent({super.key});
|
const ProfileContent({super.key});
|
||||||
@@ -143,12 +144,34 @@ class ProfileContent extends StatelessWidget {
|
|||||||
child: Text('Annuler'),
|
child: Text('Annuler'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO: Implémenter la mise à jour
|
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();
|
Navigator.of(context).pop();
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
if (success) {
|
||||||
).showSnackBar(SnackBar(content: Text('Profil mis à jour !')));
|
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'),
|
child: Text('Sauvegarder'),
|
||||||
),
|
),
|
||||||
@@ -159,6 +182,11 @@ class ProfileContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showChangePasswordDialog(BuildContext context) {
|
void _showChangePasswordDialog(BuildContext context) {
|
||||||
|
final currentPasswordController = TextEditingController();
|
||||||
|
final newPasswordController = TextEditingController();
|
||||||
|
final confirmPasswordController = TextEditingController();
|
||||||
|
final authService = AuthService();
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
@@ -168,16 +196,19 @@ class ProfileContent extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: currentPasswordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: InputDecoration(labelText: 'Mot de passe actuel'),
|
decoration: InputDecoration(labelText: 'Mot de passe actuel'),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: newPasswordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: InputDecoration(labelText: 'Nouveau mot de passe'),
|
decoration: InputDecoration(labelText: 'Nouveau mot de passe'),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: confirmPasswordController,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Confirmer le mot de passe',
|
labelText: 'Confirmer le mot de passe',
|
||||||
@@ -191,12 +222,59 @@ class ProfileContent extends StatelessWidget {
|
|||||||
child: Text('Annuler'),
|
child: Text('Annuler'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO: Implémenter le changement de mot de passe
|
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();
|
Navigator.of(context).pop();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
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'),
|
child: Text('Changer'),
|
||||||
),
|
),
|
||||||
@@ -207,29 +285,70 @@ class ProfileContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showDeleteAccountDialog(BuildContext context) {
|
void _showDeleteAccountDialog(BuildContext context) {
|
||||||
|
final passwordController = TextEditingController();
|
||||||
|
final authService = AuthService();
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Supprimer le compte'),
|
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.',
|
'Ê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: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text('Annuler'),
|
child: Text('Annuler'),
|
||||||
),
|
),
|
||||||
TextButton(
|
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();
|
Navigator.of(context).pop();
|
||||||
// TODO: Implémenter la suppression
|
|
||||||
Provider.of<UserProvider>(context, listen: false).logout();
|
Provider.of<UserProvider>(context, listen: false).logout();
|
||||||
Navigator.pushNamedAndRemoveUntil(
|
Navigator.pushNamedAndRemoveUntil(
|
||||||
context,
|
context,
|
||||||
'/login',
|
'/login',
|
||||||
(route) => false,
|
(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),
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
child: Text('Supprimer'),
|
child: Text('Supprimer'),
|
||||||
|
|||||||
77
lib/firebase_options.dart
Normal file
77
lib/firebase_options.dart
Normal 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',
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,8 +6,14 @@ import 'pages/login.dart';
|
|||||||
import 'pages/home.dart';
|
import 'pages/home.dart';
|
||||||
import 'providers/theme_provider.dart';
|
import 'providers/theme_provider.dart';
|
||||||
import 'providers/user_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(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ class User {
|
|||||||
final String nom;
|
final String nom;
|
||||||
final String prenom;
|
final String prenom;
|
||||||
final String email;
|
final String email;
|
||||||
final String password;
|
|
||||||
|
|
||||||
User({
|
User({
|
||||||
this.id,
|
this.id,
|
||||||
required this.nom,
|
required this.nom,
|
||||||
required this.prenom,
|
required this.prenom,
|
||||||
required this.email,
|
required this.email,
|
||||||
required this.password,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Constructeur pour créer un User depuis un Map (utile pour Firebase)
|
// Constructeur pour créer un User depuis un Map (utile pour Firebase)
|
||||||
@@ -22,7 +20,6 @@ class User {
|
|||||||
nom: map['nom'] ?? '',
|
nom: map['nom'] ?? '',
|
||||||
prenom: map['prenom'] ?? '',
|
prenom: map['prenom'] ?? '',
|
||||||
email: map['email'] ?? '',
|
email: map['email'] ?? '',
|
||||||
password: map['password'] ?? '',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +36,6 @@ class User {
|
|||||||
'nom': nom,
|
'nom': nom,
|
||||||
'prenom': prenom,
|
'prenom': prenom,
|
||||||
'email': email,
|
'email': email,
|
||||||
'password': password,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,14 +53,12 @@ class User {
|
|||||||
String? nom,
|
String? nom,
|
||||||
String? prenom,
|
String? prenom,
|
||||||
String? email,
|
String? email,
|
||||||
String? password,
|
|
||||||
}) {
|
}) {
|
||||||
return User(
|
return User(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
nom: nom ?? this.nom,
|
nom: nom ?? this.nom,
|
||||||
prenom: prenom ?? this.prenom,
|
prenom: prenom ?? this.prenom,
|
||||||
email: email ?? this.email,
|
email: email ?? this.email,
|
||||||
password: password ?? this.password,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,3 +76,4 @@ class User {
|
|||||||
@override
|
@override
|
||||||
int get hashCode => email.hashCode;
|
int get hashCode => email.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/user_service.dart';
|
import '../services/auth_service.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../providers/user_provider.dart';
|
import '../providers/user_provider.dart';
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _userService = UserService();
|
final _authService = AuthService();
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
@@ -26,26 +26,6 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
super.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
|
// Méthode de connexion
|
||||||
Future<void> _login() async {
|
Future<void> _login() async {
|
||||||
if (!_formKey.currentState!.validate()) {
|
if (!_formKey.currentState!.validate()) {
|
||||||
@@ -57,24 +37,26 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final user = await _userService.authenticateUser(
|
final userCredential = await _authService.signIn(
|
||||||
_emailController.text.trim(),
|
email: _emailController.text.trim(),
|
||||||
_passwordController.text,
|
password: _passwordController.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted && userCredential.user != null) {
|
||||||
if (user != null) {
|
// Récupérer les données utilisateur depuis Firestore
|
||||||
// Naviguer vers la page d'accueil
|
final userProvider = Provider.of<UserProvider>(context, listen: false);
|
||||||
Provider.of<UserProvider>(context, listen: false).setCurrentUser(user);
|
final userData = await userProvider.getUserData(userCredential.user!.uid);
|
||||||
|
|
||||||
|
if (userData != null) {
|
||||||
|
userProvider.setCurrentUser(userData);
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
Navigator.pushReplacementNamed(context, '/home');
|
||||||
} else {
|
} else {
|
||||||
// Échec de la connexion
|
_showErrorMessage('Données utilisateur non trouvées');
|
||||||
_showErrorMessage('Email ou mot de passe incorrect');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_showErrorMessage('Erreur lors de la connexion: ${e.toString()}');
|
_showErrorMessage('Email ou mot de passe incorrect');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:bcrypt/bcrypt.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../models/user.dart';
|
import '../models/user.dart';
|
||||||
import '../services/user_service.dart';
|
import '../services/auth_service.dart';
|
||||||
|
import '../providers/user_provider.dart';
|
||||||
|
|
||||||
class SignUpPage extends StatefulWidget {
|
class SignUpPage extends StatefulWidget {
|
||||||
const SignUpPage({super.key});
|
const SignUpPage({super.key});
|
||||||
@@ -17,7 +18,7 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
final _emailController = TextEditingController();
|
final _emailController = TextEditingController();
|
||||||
final _passwordController = TextEditingController();
|
final _passwordController = TextEditingController();
|
||||||
final _confirmPasswordController = TextEditingController();
|
final _confirmPasswordController = TextEditingController();
|
||||||
final _userService = UserService();
|
final _authService = AuthService();
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _obscurePassword = true;
|
bool _obscurePassword = true;
|
||||||
@@ -84,39 +85,29 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Vérifier si l'email existe déjà
|
// Créer le compte avec Firebase Auth
|
||||||
bool emailExists = await _userService.emailExists(
|
final userCredential = await _authService.createAccount(
|
||||||
_emailController.text.trim(),
|
email: _emailController.text.trim(),
|
||||||
);
|
password: _passwordController.text,
|
||||||
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
|
// Créer l'objet User
|
||||||
User newUser = User(
|
User newUser = User(
|
||||||
|
id: userCredential.user!.uid,
|
||||||
nom: _nomController.text.trim(),
|
nom: _nomController.text.trim(),
|
||||||
prenom: _prenomController.text.trim(),
|
prenom: _prenomController.text.trim(),
|
||||||
email: _emailController.text.trim().toLowerCase(),
|
email: _emailController.text.trim(),
|
||||||
password: hashedPassword,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sauvegarder l'utilisateur
|
// Sauvegarder les données utilisateur dans Firestore
|
||||||
bool success = await _userService.addUser(newUser);
|
await Provider.of<UserProvider>(context, listen: false).saveUserData(newUser);
|
||||||
|
|
||||||
|
// Mettre à jour le displayName
|
||||||
|
await _authService.updateDisplayName(displayName: newUser.fullName);
|
||||||
|
|
||||||
if (success) {
|
|
||||||
_showSuccessDialog();
|
_showSuccessDialog();
|
||||||
} else {
|
|
||||||
_showErrorDialog('Erreur lors de la création du compte');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_showErrorDialog('Une erreur est survenue: ${e.toString()}');
|
_showErrorDialog('Erreur lors de la création du compte: ${e.toString()}');
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
@@ -351,3 +342,4 @@ class _SignUpPageState extends State<SignUpPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
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 '../models/user.dart';
|
||||||
|
import '../services/auth_service.dart';
|
||||||
|
|
||||||
class UserProvider extends ChangeNotifier {
|
class UserProvider extends ChangeNotifier {
|
||||||
User? _currentUser;
|
User? _currentUser;
|
||||||
|
final AuthService _authService = AuthService();
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
User? get currentUser => _currentUser;
|
User? get currentUser => _currentUser;
|
||||||
|
|
||||||
@@ -11,10 +16,65 @@ class UserProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void logout() {
|
Future<void> logout() async {
|
||||||
|
await _authService.signOut();
|
||||||
_currentUser = null;
|
_currentUser = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isLoggedIn => _currentUser != null;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
lib/services/auth_service.dart
Normal file
68
lib/services/auth_service.dart
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
88
pubspec.lock
88
pubspec.lock
@@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_flutterfire_internals:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _flutterfire_internals
|
||||||
|
sha256: "23d16f00a2da8ffa997c782453c73867b0609bd90435195671a54de38a3566df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.62"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +49,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
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:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -89,6 +121,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -176,6 +256,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.6"
|
version: "0.15.6"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
google_maps_flutter: ^2.5.0
|
google_maps_flutter: ^2.5.0
|
||||||
|
firebase_core: ^4.1.1
|
||||||
|
firebase_auth: ^6.1.0
|
||||||
|
cloud_firestore: ^6.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user