feat: Enhance trip creation and management features with user validation, improved error handling, and Firestore integration
This commit is contained in:
@@ -1,89 +1,239 @@
|
||||
import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import '../models/trip.dart';
|
||||
|
||||
class TripService {
|
||||
static const String _tripsKey = 'trips_data';
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
static const String _tripsCollection = 'trips';
|
||||
|
||||
// Charger tous les voyages
|
||||
Future<List<Trip>> loadTrips() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final tripsJson = prefs.getString(_tripsKey);
|
||||
final QuerySnapshot querySnapshot = await _firestore
|
||||
.collection(_tripsCollection)
|
||||
.orderBy('createdAt', descending: true)
|
||||
.get();
|
||||
|
||||
if (tripsJson == null || tripsJson.isEmpty) return [];
|
||||
|
||||
final List<dynamic> jsonList = json.decode(tripsJson);
|
||||
return jsonList.map((json) => Trip.fromMap(json)).toList();
|
||||
return querySnapshot.docs.map((doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return Trip.fromMap({...data, 'id': doc.id});
|
||||
}).toList();
|
||||
} catch (e) {
|
||||
//print('Erreur lors du chargement des voyages: $e');
|
||||
print('Erreur lors du chargement des voyages: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Sauvegarder tous les voyages
|
||||
Future<void> saveTrips(List<Trip> trips) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonList = trips.map((trip) => trip.toMap()).toList();
|
||||
await prefs.setString(_tripsKey, json.encode(jsonList));
|
||||
} catch (e) {
|
||||
//print('Erreur lors de la sauvegarde des voyages: $e');
|
||||
throw Exception('Erreur de sauvegarde');
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter un nouveau voyage
|
||||
Future<bool> addTrip(Trip trip) async {
|
||||
try {
|
||||
final trips = await loadTrips();
|
||||
|
||||
// Générer un ID unique
|
||||
final newTrip = trip.copyWith(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
trips.add(newTrip);
|
||||
await saveTrips(trips);
|
||||
final tripData = trip.toMap();
|
||||
// Retirer l'ID vide du map
|
||||
tripData.remove('id');
|
||||
|
||||
// Les participants contiennent déjà uniquement des IDs
|
||||
// Pas besoin d'ajouter le créateur car il est déjà inclus
|
||||
|
||||
// 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) {
|
||||
//print('Erreur lors de l\'ajout du voyage: $e');
|
||||
print('Erreur lors de l\'ajout du voyage: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir les voyages d'un utilisateur
|
||||
// Stream pour écouter les voyages d'un utilisateur en temps réel
|
||||
Stream<List<Trip>> getTripsStreamByUser(String userId, String userEmail) {
|
||||
print('=== STREAM CRÉÉ ===');
|
||||
print('UserId: $userId');
|
||||
|
||||
return _firestore
|
||||
.collection(_tripsCollection)
|
||||
.snapshots()
|
||||
.map((snapshot) {
|
||||
print('=== NOUVEAU SNAPSHOT ===');
|
||||
print('Nombre de documents: ${snapshot.docs.length}');
|
||||
|
||||
final List<Trip> trips = [];
|
||||
|
||||
for (int i = 0; i < snapshot.docs.length; i++) {
|
||||
var doc = snapshot.docs[i];
|
||||
print('\n--- Document $i (${doc.id}) ---');
|
||||
|
||||
try {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
|
||||
// Vérifier si l'utilisateur est impliqué dans ce voyage
|
||||
final String createdBy = data['createdBy']?.toString() ?? '';
|
||||
final List<dynamic> participants = data['participants'] ?? [];
|
||||
|
||||
print('CreatedBy: "$createdBy"');
|
||||
print('UserId: "$userId"');
|
||||
print('Participants: $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';
|
||||
}
|
||||
|
||||
print('Utilisateur impliqué: $userIsInvolved');
|
||||
print('Raison: $reason');
|
||||
|
||||
if (userIsInvolved) {
|
||||
print('Tentative de conversion du trip...');
|
||||
final trip = _convertDocumentToTrip(doc.id, data);
|
||||
if (trip != null) {
|
||||
trips.add(trip);
|
||||
print('Trip ajouté: ${trip.title}');
|
||||
} else {
|
||||
print('Échec de la conversion du trip');
|
||||
}
|
||||
} else {
|
||||
print('Utilisateur non impliqué dans ce voyage');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('Erreur lors du traitement du document ${doc.id}: $e');
|
||||
print('StackTrace: $stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
print('\n=== RÉSUMÉ ===');
|
||||
print('Trips trouvés: ${trips.length}');
|
||||
if (trips.isNotEmpty) {
|
||||
for (int i = 0; i < trips.length; i++) {
|
||||
print(' ${i+1}. ${trips[i].title} (${trips[i].id})');
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
print('Erreur lors du tri: $e');
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return trips;
|
||||
}).handleError((error, stackTrace) {
|
||||
print('Erreur dans le stream: $error');
|
||||
print('StackTrace: $stackTrace');
|
||||
return <Trip>[];
|
||||
});
|
||||
}
|
||||
|
||||
// Obtenir les voyages d'un utilisateur (version simplifiée)
|
||||
Future<List<Trip>> getTripsByUser(String userId) async {
|
||||
try {
|
||||
final trips = await loadTrips();
|
||||
return trips
|
||||
.where(
|
||||
(trip) =>
|
||||
trip.createdBy == userId || trip.participants.contains(userId),
|
||||
)
|
||||
.toList();
|
||||
print('Récupération des voyages pour userId: $userId');
|
||||
|
||||
// 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<Trip> trips = [];
|
||||
|
||||
for (var doc in createdTrips.docs) {
|
||||
try {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
final trip = _convertDocumentToTrip(doc.id, data);
|
||||
if (trip != null) {
|
||||
trips.add(trip);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Erreur lors de la conversion du voyage créé ${doc.id}: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
//print('Erreur lors de la récupération des voyages: $e');
|
||||
print('Erreur lors de la récupération des voyages: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode helper pour convertir un document Firestore en Trip
|
||||
Trip? _convertDocumentToTrip(String docId, Map<String, dynamic> data) {
|
||||
print('\n=== CONVERSION TRIP $docId ===');
|
||||
|
||||
try {
|
||||
// Créer une copie des données pour ne pas modifier l'original
|
||||
Map<String, dynamic> processedData = Map<String, dynamic>.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);
|
||||
return trip;
|
||||
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ Erreur lors de la conversion du document $docId: $e');
|
||||
print('StackTrace: $stackTrace');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour un voyage
|
||||
Future<bool> updateTrip(Trip updatedTrip) async {
|
||||
try {
|
||||
final trips = await loadTrips();
|
||||
final index = trips.indexWhere((trip) => trip.id == updatedTrip.id);
|
||||
final tripData = updatedTrip.toMap();
|
||||
tripData['updatedAt'] = FieldValue.serverTimestamp();
|
||||
tripData.remove('id'); // Retirer l'ID des données à mettre à jour
|
||||
|
||||
if (index != -1) {
|
||||
trips[index] = updatedTrip.copyWith(updatedAt: DateTime.now());
|
||||
await saveTrips(trips);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
await _firestore
|
||||
.collection(_tripsCollection)
|
||||
.doc(updatedTrip.id)
|
||||
.update(tripData);
|
||||
return true;
|
||||
} catch (e) {
|
||||
//print('Erreur lors de la mise à jour du voyage: $e');
|
||||
print('Erreur lors de la mise à jour du voyage: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -91,18 +241,59 @@ class TripService {
|
||||
// Supprimer un voyage
|
||||
Future<bool> deleteTrip(String tripId) async {
|
||||
try {
|
||||
final trips = await loadTrips();
|
||||
final initialLength = trips.length;
|
||||
trips.removeWhere((trip) => trip.id == tripId);
|
||||
|
||||
if (trips.length < initialLength) {
|
||||
await saveTrips(trips);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
await _firestore.collection(_tripsCollection).doc(tripId).delete();
|
||||
return true;
|
||||
} catch (e) {
|
||||
//print('Erreur lors de la suppression du voyage: $e');
|
||||
print('Erreur lors de la suppression du voyage: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir un voyage par son ID
|
||||
Future<Trip?> getTripById(String tripId) async {
|
||||
try {
|
||||
final DocumentSnapshot doc = await _firestore
|
||||
.collection(_tripsCollection)
|
||||
.doc(tripId)
|
||||
.get();
|
||||
|
||||
if (doc.exists) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return Trip.fromMap({...data, 'id': doc.id});
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
print('Erreur lors de la récupération du voyage: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter un participant à un voyage
|
||||
Future<bool> 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) {
|
||||
print('Erreur lors de l\'ajout du participant: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Retirer un participant d'un voyage
|
||||
Future<bool> 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) {
|
||||
print('Erreur lors du retrait du participant: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user