diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 746a639..7d93efd 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -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") diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..070c1b2 --- /dev/null +++ b/android/app/google-services.json @@ -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" +} \ No newline at end of file diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index fb605bc..ff284ff 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -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 } diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..4c11011 --- /dev/null +++ b/firebase.json @@ -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"}}}}}} \ No newline at end of file diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..a1f738f --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.521527250907-3i1qe2656eojs8k9hjdi573j09i9p41m + ANDROID_CLIENT_ID + 521527250907-lqgj1lmfcsjusm2be9r6kpuanq3jvjcd.apps.googleusercontent.com + API_KEY + AIzaSyBFaSzvcvO8qg7El-2jRf7WctZIMKNA4-I + GCM_SENDER_ID + 521527250907 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.travelMate + PROJECT_ID + travelmate-a47f5 + STORAGE_BUCKET + travelmate-a47f5.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:521527250907:ios:64b41be39c54db1c7da1fe + + \ No newline at end of file diff --git a/lib/components/profile/profile_content.dart b/lib/components/profile/profile_content.dart index 4431e52..4f589c6 100644 --- a/lib/components/profile/profile_content.dart +++ b/lib/components/profile/profile_content.dart @@ -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 - Navigator.of(context).pop(); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Profil mis à 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(context, + listen: false) + .updateUser(updatedUser); + + Navigator.of(context).pop(); + + 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 - Navigator.of(context).pop(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Mot de passe changé !')), - ); + 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(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é !'), + 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,13 +285,30 @@ 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( - 'Êtes-vous sûr de vouloir supprimer votre compte ? Cette action est irréversible.', + 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( @@ -221,15 +316,39 @@ class ProfileContent extends StatelessWidget { child: Text('Annuler'), ), TextButton( - onPressed: () { - Navigator.of(context).pop(); - // TODO: Implémenter la suppression - Provider.of(context, listen: false).logout(); - Navigator.pushNamedAndRemoveUntil( - context, - '/login', - (route) => false, - ); + 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(context, listen: false) + .currentUser; + await authService.deleteAccount( + password: passwordController.text, + email: user!.email, + ); + + Navigator.of(context).pop(); + Provider.of(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'), diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..9cede1f --- /dev/null +++ b/lib/firebase_options.dart @@ -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', + ); + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 7ff439c..65e5e85 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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: [ diff --git a/lib/models/user.dart b/lib/models/user.dart index 4d74e44..13ca0f0 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -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; } + diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 31073be..a2bdd9c 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -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 { final _formKey = GlobalKey(); 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 { 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 _login() async { if (!_formKey.currentState!.validate()) { @@ -57,24 +37,26 @@ class _LoginPageState extends State { }); 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(context, listen: false).setCurrentUser(user); + if (mounted && userCredential.user != null) { + // Récupérer les données utilisateur depuis Firestore + final userProvider = Provider.of(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 { } } - 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 { ), ); } + + // 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), + ), + ); + } + } } diff --git a/lib/pages/signup.dart b/lib/pages/signup.dart index 981686f..bc2e19f 100644 --- a/lib/pages/signup.dart +++ b/lib/pages/signup.dart @@ -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 { 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 { }); 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(context, listen: false).saveUserData(newUser); - if (success) { - _showSuccessDialog(); - } else { - _showErrorDialog('Erreur lors de la création du compte'); - } + // Mettre à jour le displayName + await _authService.updateDisplayName(displayName: newUser.fullName); + + _showSuccessDialog(); } 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 { ); } } + \ No newline at end of file diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index 9a8b455..3161b89 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -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 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 getUserData(String uid) async { + try { + DocumentSnapshot doc = await _firestore.collection('users').doc(uid).get(); + if (doc.exists) { + Map data = doc.data() as Map; + 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 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 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 initializeUser() async { + firebase_auth.User? firebaseUser = _authService.currentUser; + if (firebaseUser != null) { + User? userData = await getUserData(firebaseUser.uid); + if (userData != null) { + _currentUser = userData; + notifyListeners(); + } + } + } } diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart new file mode 100644 index 0000000..de5d11e --- /dev/null +++ b/lib/services/auth_service.dart @@ -0,0 +1,68 @@ +import 'package:firebase_auth/firebase_auth.dart'; + +class AuthService { + final FirebaseAuth firebaseAuth = FirebaseAuth.instance; + + User? get currentUser => firebaseAuth.currentUser; + + Stream get authStateChanges => firebaseAuth.authStateChanges(); + + Future signIn({ + required String email, + required String password + }) async { + return await firebaseAuth.signInWithEmailAndPassword( + email: email, password: password); + } + + Future createAccount({ + required String email, + required String password + }) async { + return await firebaseAuth.createUserWithEmailAndPassword( + email: email, password: password); + } + + Future signOut() async { + await firebaseAuth.signOut(); + } + + Future resetPassword(String email) async { + await firebaseAuth.sendPasswordResetEmail(email: email); + } + + Future updateDisplayName({ + required String displayName, + }) async { + await currentUser!.updateDisplayName(displayName); + } + + Future 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 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); + } + +} \ No newline at end of file diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart deleted file mode 100644 index f85afcd..0000000 --- a/lib/services/user_service.dart +++ /dev/null @@ -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 _getUserFile() async { - final directory = await getApplicationDocumentsDirectory(); - return File('${directory.path}/$_fileName'); - } - - // Charger tous les utilisateurs - Future> loadUsers() async { - try { - final file = await _getUserFile(); - if (!await file.exists()) return []; - - final contents = await file.readAsString(); - if (contents.isEmpty) return []; - - final List 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 saveUsers(List 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 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 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 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 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 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 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 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 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 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 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 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 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'); - } - } -} diff --git a/pubspec.lock b/pubspec.lock index 22e2dbf..7b06651 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 87cca5c..5c87b6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: