/// A service that handles trip-related business logic and data operations. /// /// This service provides functionality for: /// - Trip creation, updating, and deletion /// - Trip validation and business rule enforcement /// - Location-based trip suggestions /// - Trip statistics and analytics /// - Integration with Firebase Firestore for data persistence /// /// The service ensures data integrity by validating trip information /// before database operations and provides comprehensive error handling /// and logging through the ErrorService. /// /// Example usage: /// ```dart /// final tripService = TripService(); /// /// // Create a new trip /// final tripId = await tripService.createTrip(newTrip); /// /// // Get trip suggestions for a location /// final suggestions = await tripService.getTripSuggestions('Paris'); /// /// // Calculate trip statistics /// final stats = await tripService.getTripStatistics(tripId); /// ``` import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:travel_mate/services/error_service.dart'; import '../models/trip.dart'; /// Service for managing trip-related operations and business logic. class TripService { /// Service for error handling and logging final _errorService = ErrorService(); /// Firestore instance for database operations final FirebaseFirestore _firestore = FirebaseFirestore.instance; /// Collection name for trips in Firestore static const String _tripsCollection = 'trips'; // Charger tous les voyages Future> loadTrips() async { try { final QuerySnapshot querySnapshot = await _firestore .collection(_tripsCollection) .orderBy('createdAt', descending: true) .get(); return querySnapshot.docs.map((doc) { final data = doc.data() as Map; return Trip.fromMap({...data, 'id': doc.id}, doc.id); }).toList(); } catch (e) { _errorService.logError('Erreur lors du chargement des voyages: $e', StackTrace.current); return []; } } // Ajouter un nouveau voyage Future addTrip(Trip trip) async { try { final tripData = trip.toMap(); // Retirer l'ID vide du map tripData.remove('id'); // Convertir les dates en Timestamp pour Firestore tripData['startDate'] = Timestamp.fromDate(trip.startDate); tripData['endDate'] = Timestamp.fromDate(trip.endDate); tripData['createdAt'] = FieldValue.serverTimestamp(); tripData['updatedAt'] = FieldValue.serverTimestamp(); await _firestore.collection(_tripsCollection).add(tripData); return true; } catch (e) { _errorService.logError('Erreur lors de l\'ajout du voyage: $e', StackTrace.current); return false; } } // Stream pour écouter les voyages d'un utilisateur en temps réel Stream> getTripsStreamByUser(String userId, String userEmail) { return _firestore .collection(_tripsCollection) .snapshots() .map((snapshot) { final List trips = []; for (int i = 0; i < snapshot.docs.length; i++) { var doc = snapshot.docs[i]; try { final data = doc.data(); // Vérifier si l'utilisateur est impliqué dans ce voyage final String createdBy = data['createdBy']?.toString() ?? ''; final List participants = data['participants'] ?? []; bool userIsInvolved = false; String reason = ''; // L'utilisateur est le créateur if (createdBy == userId) { userIsInvolved = true; reason = 'Créateur du voyage'; } // L'utilisateur est dans la liste des participants (par ID uniquement) if (participants.contains(userId)) { userIsInvolved = true; reason = reason.isEmpty ? 'Participant par ID' : '$reason + Participant par ID'; } if (userIsInvolved) { final trip = _convertDocumentToTrip(doc.id, data); if (trip != null) { trips.add(trip); } } } catch (e, stackTrace) { _errorService.logError('Erreur lors du traitement du document ${doc.id}: $e', stackTrace); } } // Trier par date de création (les plus récents en premier) trips.sort((a, b) { try { return b.createdAt.compareTo(a.createdAt); } catch (e) { _errorService.logError('Erreur lors du tri: $e', StackTrace.current); return 0; } }); return trips; }).handleError((error, stackTrace) { _errorService.logError('Erreur dans le stream: $error', stackTrace); return []; }); } // Obtenir les voyages d'un utilisateur (version simplifiée) Future> getTripsByUser(String userId) async { try { // Récupérer d'abord les voyages créés par l'utilisateur final QuerySnapshot createdTrips = await _firestore .collection(_tripsCollection) .where('createdBy', isEqualTo: userId) .get(); final List trips = []; for (var doc in createdTrips.docs) { try { final data = doc.data() as Map; final trip = _convertDocumentToTrip(doc.id, data); if (trip != null) { trips.add(trip); } } catch (e) { _errorService.logError('Erreur lors de la conversion du voyage créé ${doc.id}: $e', StackTrace.current); } } // Trier par date de création trips.sort((a, b) { try { return b.createdAt.compareTo(a.createdAt); } catch (e) { return 0; } }); return trips; } catch (e) { _errorService.logError('Erreur lors de la récupération des voyages: $e', StackTrace.current); return []; } } // Méthode helper pour convertir un document Firestore en Trip Trip? _convertDocumentToTrip(String docId, Map data) { try { // Créer une copie des données pour ne pas modifier l'original Map processedData = Map.from(data); // Convertir les Timestamps Firestore en String ISO if (processedData['createdAt'] is Timestamp) { processedData['createdAt'] = (processedData['createdAt'] as Timestamp).toDate().toIso8601String(); } if (processedData['updatedAt'] is Timestamp) { processedData['updatedAt'] = (processedData['updatedAt'] as Timestamp).toDate().toIso8601String(); } if (processedData['startDate'] is Timestamp) { processedData['startDate'] = (processedData['startDate'] as Timestamp).toDate().toIso8601String(); } if (processedData['endDate'] is Timestamp) { processedData['endDate'] = (processedData['endDate'] as Timestamp).toDate().toIso8601String(); } // Assurer que tous les champs requis sont présents processedData['id'] = docId; processedData['participants'] = processedData['participants'] ?? []; processedData['budget'] = (processedData['budget'] is num) ? (processedData['budget'] as num).toDouble() : 0.0; processedData['description'] = processedData['description'] ?? ''; processedData['status'] = processedData['status'] ?? 'draft'; final trip = Trip.fromMap(processedData, docId); return trip; } catch (e, stackTrace) { _errorService.logError('Erreur lors de la conversion du document $docId: $e', stackTrace); return null; } } // Mettre à jour un voyage Future updateTrip(Trip updatedTrip) async { try { final tripData = updatedTrip.toMap(); tripData['updatedAt'] = FieldValue.serverTimestamp(); tripData.remove('id'); // Retirer l'ID des données à mettre à jour await _firestore .collection(_tripsCollection) .doc(updatedTrip.id) .update(tripData); return true; } catch (e) { _errorService.logError('Erreur lors de la mise à jour du voyage: $e', StackTrace.current); return false; } } // Supprimer un voyage Future deleteTrip(String tripId) async { try { await _firestore.collection(_tripsCollection).doc(tripId).delete(); return true; } catch (e) { _errorService.logError('Erreur lors de la suppression du voyage: $e', StackTrace.current); return false; } } // Obtenir un voyage par son ID Future getTripById(String tripId) async { try { final DocumentSnapshot doc = await _firestore .collection(_tripsCollection) .doc(tripId) .get(); if (doc.exists) { final data = doc.data() as Map; return Trip.fromMap({...data, 'id': doc.id}, doc.id); } return null; } catch (e) { _errorService.logError('Erreur lors de la récupération du voyage: $e', StackTrace.current); return null; } } // Ajouter un participant à un voyage Future addParticipant(String tripId, String userId) async { try { await _firestore.collection(_tripsCollection).doc(tripId).update({ 'participants': FieldValue.arrayUnion([userId]), 'updatedAt': FieldValue.serverTimestamp(), }); return true; } catch (e) { _errorService.logError('Erreur lors de l\'ajout du participant: $e', StackTrace.current); return false; } } // Retirer un participant d'un voyage Future removeParticipant(String tripId, String userId) async { try { await _firestore.collection(_tripsCollection).doc(tripId).update({ 'participants': FieldValue.arrayRemove([userId]), 'updatedAt': FieldValue.serverTimestamp(), }); return true; } catch (e) { _errorService.logError('Erreur lors du retrait du participant: $e', StackTrace.current); return false; } } }