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 {
|
||||
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")
|
||||
|
||||
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 {
|
||||
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
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: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
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 '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: [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
# 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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user