Enhance User model with optional fields and update authentication methods to include profile picture and phone number
This commit is contained in:
@@ -21,9 +21,8 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => MapContent(
|
builder: (context) =>
|
||||||
initialSearchQuery: widget.trip.location,
|
MapContent(initialSearchQuery: widget.trip.location),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -32,7 +31,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
Future<void> _openGoogleMaps() async {
|
Future<void> _openGoogleMaps() async {
|
||||||
final location = Uri.encodeComponent(widget.trip.location);
|
final location = Uri.encodeComponent(widget.trip.location);
|
||||||
final url = 'https://www.google.com/maps/search/?api=1&query=$location';
|
final url = 'https://www.google.com/maps/search/?api=1&query=$location';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (await canLaunchUrl(uri)) {
|
if (await canLaunchUrl(uri)) {
|
||||||
@@ -48,7 +47,9 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Impossible d\'ouvrir Google Maps. Vérifiez que l\'application est installée.'),
|
content: Text(
|
||||||
|
'Impossible d\'ouvrir Google Maps. Vérifiez que l\'application est installée.',
|
||||||
|
),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -69,15 +70,12 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
final textColor = isDarkMode ? Colors.white : Colors.black;
|
final textColor = isDarkMode ? Colors.white : Colors.black;
|
||||||
final secondaryTextColor = isDarkMode ? Colors.white70 : Colors.grey[600];
|
final secondaryTextColor = isDarkMode ? Colors.white70 : Colors.grey[600];
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: Text(widget.trip.title)),
|
||||||
title: Text(widget.trip.title),
|
|
||||||
),
|
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -92,10 +90,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
SizedBox(height: 8),
|
||||||
Text(
|
Text(widget.trip.description, style: TextStyle(color: textColor)),
|
||||||
widget.trip.description,
|
|
||||||
style: TextStyle(color: textColor),
|
|
||||||
),
|
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -125,10 +120,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'${widget.trip.participants.length} participant${widget.trip.participants.length > 1 ? 's' : ''}',
|
'${widget.trip.participants.length} participant${widget.trip.participants.length > 1 ? 's' : ''}',
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 14, color: secondaryTextColor),
|
||||||
fontSize: 14,
|
|
||||||
color: secondaryTextColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -166,7 +158,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: const Color.fromARGB(255, 102, 102, 102),
|
||||||
padding: EdgeInsets.symmetric(vertical: 12),
|
padding: EdgeInsets.symmetric(vertical: 12),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -175,7 +167,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 12),
|
SizedBox(width: 12),
|
||||||
|
|
||||||
// Bouton Google Maps
|
// Bouton Google Maps
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
@@ -189,7 +181,7 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.green[700],
|
backgroundColor: const Color.fromARGB(255, 102, 102, 102),
|
||||||
padding: EdgeInsets.symmetric(vertical: 12),
|
padding: EdgeInsets.symmetric(vertical: 12),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -211,13 +203,11 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
final result = await Navigator.push(
|
final result = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => CreateTripContent(
|
builder: (context) =>
|
||||||
tripToEdit: widget.trip,
|
CreateTripContent(tripToEdit: widget.trip),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
if (result == true && mounted) {
|
if (result == true && mounted) {
|
||||||
Navigator.pop(context, true); // Retour avec flag
|
Navigator.pop(context, true); // Retour avec flag
|
||||||
}
|
}
|
||||||
@@ -249,7 +239,8 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: Text('Confirmer la suppression'),
|
title: Text('Confirmer la suppression'),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Êtes-vous sûr de vouloir supprimer ce voyage ? Cette action est irréversible.'),
|
'Êtes-vous sûr de vouloir supprimer ce voyage ? Cette action est irréversible.',
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
@@ -257,9 +248,14 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<TripBloc>().add(TripDeleteRequested(tripId: widget.trip.id!));
|
context.read<TripBloc>().add(
|
||||||
|
TripDeleteRequested(tripId: widget.trip.id!),
|
||||||
|
);
|
||||||
Navigator.pop(context); // Fermer le dialogue
|
Navigator.pop(context); // Fermer le dialogue
|
||||||
Navigator.pop(context, true); // Retourner à l'écran précédent
|
Navigator.pop(
|
||||||
|
context,
|
||||||
|
true,
|
||||||
|
); // Retourner à l'écran précédent
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Supprimer',
|
'Supprimer',
|
||||||
@@ -285,10 +281,10 @@ class _ShowTripDetailsContentState extends State<ShowTripDetailsContent> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,49 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
/// Model representing a user in the travel mate application.
|
/// Model representing a user in the travel mate application.
|
||||||
///
|
///
|
||||||
/// This class encapsulates user information including personal details
|
/// This class encapsulates user information including personal details
|
||||||
/// and provides methods for serialization/deserialization with Firebase
|
/// and provides methods for serialization/deserialization with Firebase
|
||||||
/// and JSON operations.
|
/// and JSON operations.
|
||||||
class User {
|
class User {
|
||||||
/// Unique identifier for the user (usually Firebase UID).
|
/// Unique identifier for the user (usually Firebase UID).
|
||||||
final String? id;
|
final String? id;
|
||||||
|
|
||||||
/// User's last name.
|
/// User's last name.
|
||||||
final String nom;
|
final String nom;
|
||||||
|
|
||||||
/// User's first name.
|
/// User's first name.
|
||||||
final String prenom;
|
final String prenom;
|
||||||
|
|
||||||
/// User's email address.
|
/// User's email address.
|
||||||
final String email;
|
final String email;
|
||||||
|
|
||||||
|
/// User's profile picture URL (optional).
|
||||||
|
final String? profilePictureUrl;
|
||||||
|
|
||||||
|
/// User's phone number (optional).
|
||||||
|
final String? phoneNumber;
|
||||||
|
|
||||||
|
/// Platform used for authentication (e.g., 'google', 'apple', 'email').
|
||||||
|
final String platform;
|
||||||
|
|
||||||
/// Creates a new [User] instance.
|
/// Creates a new [User] instance.
|
||||||
///
|
///
|
||||||
/// [nom], [prenom], and [email] are required fields.
|
/// [nom], [prenom], [email] and [platform] are required fields.
|
||||||
|
/// [profilePictureUrl] and [phoneNumber] are optional.
|
||||||
/// [id] is optional and typically assigned by Firebase.
|
/// [id] is optional and typically assigned by Firebase.
|
||||||
User({
|
User({
|
||||||
this.id,
|
this.id,
|
||||||
required this.nom,
|
required this.nom,
|
||||||
required this.prenom,
|
required this.prenom,
|
||||||
required this.email,
|
required this.email,
|
||||||
|
this.profilePictureUrl,
|
||||||
|
this.phoneNumber,
|
||||||
|
required this.platform,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Creates a [User] instance from a Map (useful for Firebase operations).
|
/// Creates a [User] instance from a Map (useful for Firebase operations).
|
||||||
///
|
///
|
||||||
/// Handles null values gracefully by providing empty string defaults.
|
/// Handles null values gracefully by providing empty string defaults.
|
||||||
factory User.fromMap(Map<String, dynamic> map) {
|
factory User.fromMap(Map<String, dynamic> map) {
|
||||||
return User(
|
return User(
|
||||||
@@ -38,11 +51,14 @@ class User {
|
|||||||
nom: map['nom'] ?? '',
|
nom: map['nom'] ?? '',
|
||||||
prenom: map['prenom'] ?? '',
|
prenom: map['prenom'] ?? '',
|
||||||
email: map['email'] ?? '',
|
email: map['email'] ?? '',
|
||||||
|
profilePictureUrl: map['profilePictureUrl'],
|
||||||
|
phoneNumber: map['phoneNumber'],
|
||||||
|
platform: map['platform'] ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [User] instance from a JSON string.
|
/// Creates a [User] instance from a JSON string.
|
||||||
///
|
///
|
||||||
/// Parses the JSON and delegates to [fromMap] for object creation.
|
/// Parses the JSON and delegates to [fromMap] for object creation.
|
||||||
factory User.fromJson(String jsonStr) {
|
factory User.fromJson(String jsonStr) {
|
||||||
Map<String, dynamic> map = json.decode(jsonStr);
|
Map<String, dynamic> map = json.decode(jsonStr);
|
||||||
@@ -50,7 +66,7 @@ class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the [User] instance to a Map (useful for Firebase operations).
|
/// Converts the [User] instance to a Map (useful for Firebase operations).
|
||||||
///
|
///
|
||||||
/// Returns a map with all user properties for database storage.
|
/// Returns a map with all user properties for database storage.
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
@@ -58,11 +74,14 @@ class User {
|
|||||||
'nom': nom,
|
'nom': nom,
|
||||||
'prenom': prenom,
|
'prenom': prenom,
|
||||||
'email': email,
|
'email': email,
|
||||||
|
'profilePictureUrl': profilePictureUrl,
|
||||||
|
'phoneNumber': phoneNumber,
|
||||||
|
'platform': platform,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the [User] instance to a JSON string.
|
/// Converts the [User] instance to a JSON string.
|
||||||
///
|
///
|
||||||
/// Useful for API communications and data serialization.
|
/// Useful for API communications and data serialization.
|
||||||
String toJson() {
|
String toJson() {
|
||||||
return json.encode(toMap());
|
return json.encode(toMap());
|
||||||
@@ -72,7 +91,7 @@ class User {
|
|||||||
String get fullName => '$prenom $nom';
|
String get fullName => '$prenom $nom';
|
||||||
|
|
||||||
/// Creates a copy of this user with optionally modified properties.
|
/// Creates a copy of this user with optionally modified properties.
|
||||||
///
|
///
|
||||||
/// Allows updating specific fields while preserving others.
|
/// Allows updating specific fields while preserving others.
|
||||||
/// Useful for state management and partial updates.
|
/// Useful for state management and partial updates.
|
||||||
User copyWith({
|
User copyWith({
|
||||||
@@ -80,12 +99,18 @@ class User {
|
|||||||
String? nom,
|
String? nom,
|
||||||
String? prenom,
|
String? prenom,
|
||||||
String? email,
|
String? email,
|
||||||
|
String? profilePictureUrl,
|
||||||
|
String? phoneNumber,
|
||||||
|
String? platform,
|
||||||
}) {
|
}) {
|
||||||
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,
|
||||||
|
profilePictureUrl: profilePictureUrl ?? this.profilePictureUrl,
|
||||||
|
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||||
|
platform: platform ?? this.platform,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +121,7 @@ class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compares users based on email address.
|
/// Compares users based on email address.
|
||||||
///
|
///
|
||||||
/// Two users are considered equal if they have the same email.
|
/// Two users are considered equal if they have the same email.
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
@@ -108,4 +133,3 @@ class User {
|
|||||||
@override
|
@override
|
||||||
int get hashCode => email.hashCode;
|
int get hashCode => email.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class AuthRepository {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
_errorService.showError(message: 'Utilisateur ou mot de passe incorrect');
|
_errorService.showError(message: 'Utilisateur ou mot de passe incorrect');
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new user account with email and password.
|
/// Creates a new user account with email and password.
|
||||||
@@ -93,6 +94,9 @@ class AuthRepository {
|
|||||||
email: email,
|
email: email,
|
||||||
nom: nom,
|
nom: nom,
|
||||||
prenom: prenom,
|
prenom: prenom,
|
||||||
|
phoneNumber: 'Uknown',
|
||||||
|
platform: 'email',
|
||||||
|
profilePictureUrl: '',
|
||||||
);
|
);
|
||||||
|
|
||||||
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
@@ -100,6 +104,7 @@ class AuthRepository {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
_errorService.showError(message: 'Erreur lors de la création du compte');
|
_errorService.showError(message: 'Erreur lors de la création du compte');
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs in a user using Google authentication.
|
/// Signs in a user using Google authentication.
|
||||||
@@ -127,6 +132,9 @@ class AuthRepository {
|
|||||||
email: firebaseUser.user!.email ?? '',
|
email: firebaseUser.user!.email ?? '',
|
||||||
nom: '',
|
nom: '',
|
||||||
prenom: firebaseUser.user!.displayName ?? 'User',
|
prenom: firebaseUser.user!.displayName ?? 'User',
|
||||||
|
phoneNumber: firebaseUser.user!.phoneNumber ?? 'Uknown',
|
||||||
|
profilePictureUrl: firebaseUser.user!.photoURL,
|
||||||
|
platform: 'google',
|
||||||
);
|
);
|
||||||
|
|
||||||
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
@@ -162,6 +170,9 @@ class AuthRepository {
|
|||||||
email: firebaseUser.user!.email ?? '',
|
email: firebaseUser.user!.email ?? '',
|
||||||
nom: '',
|
nom: '',
|
||||||
prenom: firebaseUser.user!.displayName ?? 'User',
|
prenom: firebaseUser.user!.displayName ?? 'User',
|
||||||
|
phoneNumber: firebaseUser.user!.phoneNumber ?? 'Uknown',
|
||||||
|
profilePictureUrl: firebaseUser.user!.photoURL,
|
||||||
|
platform: 'apple',
|
||||||
);
|
);
|
||||||
|
|
||||||
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
await _firestore.collection('users').doc(user.id).set(user.toMap());
|
||||||
|
|||||||
Reference in New Issue
Block a user