diff --git a/lib/components/home/create_trip_content.dart b/lib/components/home/create_trip_content.dart index 63978f8..39b9f0d 100644 --- a/lib/components/home/create_trip_content.dart +++ b/lib/components/home/create_trip_content.dart @@ -875,7 +875,7 @@ class _CreateTripContentState extends State { Future _updateGroupMembers( String tripId, user_state.UserModel currentUser, - List> participantsData, + List> participantsData, ) async { final groupBloc = context.read(); try { @@ -933,12 +933,14 @@ class _CreateTripContentState extends State { firstName: currentUser.prenom, pseudo: currentUser.prenom, role: 'admin', + profilePictureUrl: currentUser.profilePictureUrl, ), ...participantsData.map((p) => GroupMember( userId: p['id'] as String, firstName: p['firstName'] as String, pseudo: p['firstName'] as String, role: 'member', + profilePictureUrl: p['profilePictureUrl'] as String?, )), ]; return groupMembers; @@ -1087,11 +1089,14 @@ class _CreateTripContentState extends State { // Mode mise à jour tripBloc.add(TripUpdateRequested(trip: tripWithCoordinates)); - await _updateGroupMembers( - widget.tripToEdit!.id!, - currentUser, - participantsData, - ); + // Vérifier que l'ID du voyage existe avant de mettre à jour le groupe + if (widget.tripToEdit != null && widget.tripToEdit!.id != null && widget.tripToEdit!.id!.isNotEmpty) { + await _updateGroupMembers( + widget.tripToEdit!.id!, + currentUser, + participantsData, + ); + } } else { // Mode création - Le groupe sera créé dans le listener TripCreated @@ -1113,8 +1118,8 @@ class _CreateTripContentState extends State { } } - Future>> _getParticipantsData(List emails) async { - List> participantsData = []; + Future>> _getParticipantsData(List emails) async { + List> participantsData = []; for (String email in emails) { try { @@ -1122,10 +1127,12 @@ class _CreateTripContentState extends State { if (userId != null) { final userDoc = await _userService.getUserById(userId); final firstName = userDoc?.prenom ?? 'Utilisateur'; + final profilePictureUrl = userDoc?.profilePictureUrl; participantsData.add({ 'id': userId, 'firstName': firstName, + 'profilePictureUrl': profilePictureUrl, }); } else { if (mounted) { diff --git a/lib/components/home/show_trip_details_content.dart b/lib/components/home/show_trip_details_content.dart index 906699c..3e5499d 100644 --- a/lib/components/home/show_trip_details_content.dart +++ b/lib/components/home/show_trip_details_content.dart @@ -10,6 +10,7 @@ import 'package:travel_mate/models/trip.dart'; import 'package:travel_mate/components/map/map_content.dart'; import 'package:travel_mate/services/error_service.dart'; import 'package:travel_mate/services/activity_cache_service.dart'; +import 'package:travel_mate/repositories/group_repository.dart'; import 'package:travel_mate/components/activities/activities_page.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -24,6 +25,7 @@ class ShowTripDetailsContent extends StatefulWidget { class _ShowTripDetailsContentState extends State { final ErrorService _errorService = ErrorService(); final ActivityCacheService _cacheService = ActivityCacheService(); + final GroupRepository _groupRepository = GroupRepository(); @override void initState() { @@ -386,46 +388,8 @@ class _ShowTripDetailsContentState extends State { ), const SizedBox(height: 12), - Row( - children: [ - // Avatars des participants (limité à 4 + bouton add) - ...List.generate( - widget.trip.totalParticipants > 4 ? 4 : widget.trip.totalParticipants, - (index) => Container( - margin: const EdgeInsets.only(right: 8), - child: CircleAvatar( - radius: 25, - backgroundColor: Colors.blue[100], - child: Icon( - Icons.person, - color: Colors.blue[600], - size: 24, - ), - ), - ), - ), - - // Bouton d'ajout si moins de 4 participants affichés - if (widget.trip.totalParticipants <= 4) - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: Colors.grey[300]!, - width: 2, - style: BorderStyle.solid, - ), - ), - child: Icon( - Icons.add, - color: Colors.grey[400], - size: 24, - ), - ), - ], - ), + // Afficher les participants avec leurs images + _buildParticipantsSection(), const SizedBox(height: 32), @@ -593,18 +557,15 @@ class _ShowTripDetailsContentState extends State { color: theme.colorScheme.onSurface, ), ), - onTap: () async { + onTap: () { Navigator.pop(context); - final result = await Navigator.push( + Navigator.push( context, MaterialPageRoute( builder: (context) => CreateTripContent(tripToEdit: widget.trip), ), ); - if (result == true && mounted) { - Navigator.pop(context, true); - } }, ), ListTile( @@ -671,6 +632,112 @@ class _ShowTripDetailsContentState extends State { ); } + /// Construire la section des participants avec leurs images de profil + Widget _buildParticipantsSection() { + // Vérifier que le trip a un ID + if (widget.trip.id == null || widget.trip.id!.isEmpty) { + return const Center(child: Text('Aucun participant')); + } + + return FutureBuilder( + future: _groupRepository.getGroupByTripId(widget.trip.id!), + builder: (context, snapshot) { + // En attente + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + // Erreur + if (snapshot.hasError) { + return Center( + child: Text( + 'Erreur: ${snapshot.error}', + style: TextStyle(color: Colors.red), + ), + ); + } + + // Pas de groupe trouvé + if (!snapshot.hasData || snapshot.data == null) { + return const Center(child: Text('Aucun participant')); + } + + final group = snapshot.data!; + final members = group.members; + + if (members.isEmpty) { + return const Center(child: Text('Aucun participant')); + } + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ...List.generate( + members.length, + (index) { + final member = members[index]; + return Padding( + padding: const EdgeInsets.only(right: 12), + child: _buildParticipantAvatar(member), + ); + }, + ), + ], + ), + ); + }, + ); + } + + /// Construire un avatar pour un participant + Widget _buildParticipantAvatar(dynamic member) { + final theme = Theme.of(context); + final initials = member.pseudo.isNotEmpty + ? member.pseudo[0].toUpperCase() + : (member.firstName.isNotEmpty ? member.firstName[0].toUpperCase() : '?'); + + final name = member.pseudo.isNotEmpty ? member.pseudo : member.firstName; + + return Tooltip( + message: name, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: theme.colorScheme.primary.withValues(alpha: 0.3), + width: 2, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: CircleAvatar( + radius: 28, + backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.2), + backgroundImage: (member.profilePictureUrl != null && + member.profilePictureUrl!.isNotEmpty) + ? NetworkImage(member.profilePictureUrl!) + : null, + child: (member.profilePictureUrl == null || member.profilePictureUrl!.isEmpty) + ? Text( + initials, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: theme.colorScheme.primary, + ), + ) + : null, + ), + ), + ); + } + void _navigateToActivities() { Navigator.push( context, diff --git a/lib/models/group_member.dart b/lib/models/group_member.dart index c63b70d..1d27f47 100644 --- a/lib/models/group_member.dart +++ b/lib/models/group_member.dart @@ -4,6 +4,7 @@ class GroupMember { final String pseudo; // Pseudo du membre (par défaut = prénom) final String role; // 'admin' ou 'member' final DateTime joinedAt; + final String? profilePictureUrl; // URL de la photo de profil GroupMember({ required this.userId, @@ -11,6 +12,7 @@ class GroupMember { String? pseudo, this.role = 'member', DateTime? joinedAt, + this.profilePictureUrl, }) : pseudo = pseudo ?? firstName, // Par défaut, pseudo = prénom joinedAt = joinedAt ?? DateTime.now(); @@ -21,6 +23,7 @@ class GroupMember { pseudo: map['pseudo'] ?? map['firstName'] ?? '', role: map['role'] ?? 'member', joinedAt: DateTime.fromMillisecondsSinceEpoch(map['joinedAt'] ?? 0), + profilePictureUrl: map['profilePictureUrl'], ); } @@ -30,6 +33,7 @@ class GroupMember { 'pseudo': pseudo, 'role': role, 'joinedAt': joinedAt.millisecondsSinceEpoch, + 'profilePictureUrl': profilePictureUrl, }; } @@ -39,6 +43,7 @@ class GroupMember { String? pseudo, String? role, DateTime? joinedAt, + String? profilePictureUrl, }) { return GroupMember( userId: userId ?? this.userId, @@ -46,6 +51,7 @@ class GroupMember { pseudo: pseudo ?? this.pseudo, role: role ?? this.role, joinedAt: joinedAt ?? this.joinedAt, + profilePictureUrl: profilePictureUrl ?? this.profilePictureUrl, ); } } \ No newline at end of file