- 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.
347 lines
12 KiB
Dart
347 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../services/auth_service.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../providers/user_provider.dart';
|
|
|
|
class LoginPage extends StatefulWidget {
|
|
const LoginPage({super.key});
|
|
|
|
@override
|
|
State<LoginPage> createState() => _LoginPageState();
|
|
}
|
|
|
|
class _LoginPageState extends State<LoginPage> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _emailController = TextEditingController();
|
|
final _passwordController = TextEditingController();
|
|
final _authService = AuthService();
|
|
|
|
bool _isLoading = false;
|
|
bool _obscurePassword = true;
|
|
|
|
@override
|
|
void dispose() {
|
|
_emailController.dispose();
|
|
_passwordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
// Méthode de connexion
|
|
Future<void> _login() async {
|
|
if (!_formKey.currentState!.validate()) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final userCredential = await _authService.signIn(
|
|
email: _emailController.text.trim(),
|
|
password: _passwordController.text,
|
|
);
|
|
|
|
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 {
|
|
_showErrorMessage('Données utilisateur non trouvées');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
_showErrorMessage('Email ou mot de passe incorrect');
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
|
|
// Titre
|
|
Text(
|
|
'Travel Mate',
|
|
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// Sous-titre
|
|
Text(
|
|
'Connectez-vous pour continuer',
|
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
|
),
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
// Champ email
|
|
TextFormField(
|
|
controller: _emailController,
|
|
validator: _validateEmail,
|
|
keyboardType: TextInputType.emailAddress,
|
|
decoration: InputDecoration(
|
|
labelText: 'Email',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
),
|
|
prefixIcon: Icon(Icons.email),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Champ mot de passe
|
|
TextFormField(
|
|
controller: _passwordController,
|
|
validator: _validatePassword,
|
|
obscureText: _obscurePassword,
|
|
decoration: InputDecoration(
|
|
labelText: 'Mot de passe',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
),
|
|
prefixIcon: Icon(Icons.lock),
|
|
suffixIcon: IconButton(
|
|
icon: Icon(
|
|
_obscurePassword
|
|
? Icons.visibility
|
|
: Icons.visibility_off,
|
|
),
|
|
onPressed: () {
|
|
setState(() {
|
|
_obscurePassword = !_obscurePassword;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
// Lien "Mot de passe oublié"
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: TextButton(
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, '/forgot');
|
|
},
|
|
child: Text('Mot de passe oublié?'),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Bouton de connexion
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 50,
|
|
child: ElevatedButton(
|
|
onPressed: _isLoading ? null : _login,
|
|
style: ElevatedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: _isLoading
|
|
? CircularProgressIndicator(color: Colors.white)
|
|
: Text(
|
|
'Se connecter',
|
|
style: TextStyle(fontSize: 16),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Lien d'inscription
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text("Vous n'avez pas de compte?"),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pushNamed(context, '/signup');
|
|
},
|
|
child: Text('S\'inscrire'),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
|
|
// Séparateur
|
|
Container(
|
|
width: double.infinity,
|
|
height: 1,
|
|
color: Colors.grey.shade300,
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
|
|
Text(
|
|
'Ou connectez-vous avec',
|
|
style: TextStyle(color: Colors.grey.shade600),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
// GOOGLE
|
|
GestureDetector(
|
|
onTap: () {
|
|
// TODO: Implémenter la connexion Google
|
|
_showErrorMessage(
|
|
'Connexion Google non implémentée',
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: Colors.black,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withValues(alpha: 0.3),
|
|
spreadRadius: 1,
|
|
blurRadius: 3,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
border: Border.all(
|
|
color: Colors.grey.shade300,
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Image.asset(
|
|
'assets/icons/google.png',
|
|
width: 24,
|
|
height: 24,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text('Google'),
|
|
],
|
|
),
|
|
|
|
const SizedBox(width: 40),
|
|
|
|
Column(
|
|
children: [
|
|
// APPLE
|
|
GestureDetector(
|
|
onTap: () {
|
|
// TODO: Implémenter la connexion Apple
|
|
_showErrorMessage(
|
|
'Connexion Apple non implémentée',
|
|
);
|
|
},
|
|
child: Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(
|
|
color: Colors.black,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withValues(alpha: 0.3),
|
|
spreadRadius: 1,
|
|
blurRadius: 3,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
border: Border.all(
|
|
color: Colors.grey.shade300,
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Image.asset(
|
|
'assets/icons/apple_white.png',
|
|
width: 24,
|
|
height: 24,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text('Apple'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 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),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|