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