Enhance User model with optional fields and update authentication methods to include profile picture and phone number

This commit is contained in:
Van Leemput Dayron
2025-11-03 01:29:39 +01:00
parent 745f2597d9
commit de52dae0f4
3 changed files with 75 additions and 44 deletions

View File

@@ -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> {
), ),
), ),
), ),
) ),
], ],
), ),
), ),
); );
} }
} }

View File

@@ -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;
} }

View File

@@ -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());