Files
TravelMate/lib/repositories/activity_repository.dart

405 lines
11 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/activity.dart';
import '../services/error_service.dart';
/// Repository pour la gestion des activités dans Firestore
class ActivityRepository {
static final ActivityRepository _instance = ActivityRepository._internal();
factory ActivityRepository() => _instance;
ActivityRepository._internal();
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final ErrorService _errorService = ErrorService();
static const String _collection = 'activities';
/// Ajoute une nouvelle activité
Future<String?> addActivity(Activity activity) async {
try {
_errorService.logInfo(
'ActivityRepository',
'Ajout d\'une activité: ${activity.name}',
);
final docRef = await _firestore
.collection(_collection)
.add(activity.toMap());
// Mettre à jour l'activité avec l'ID généré
await docRef.update({'id': docRef.id});
_errorService.logSuccess(
'ActivityRepository',
'Activité ajoutée avec ID: ${docRef.id}',
);
return docRef.id;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur lors de l\'ajout: $e',
);
_errorService.logError(
'activity_repository',
'Erreur ajout activité: $e',
);
return null;
}
}
/// Récupère toutes les activités d'un voyage
Future<List<Activity>> getActivitiesByTrip(String tripId) async {
try {
_errorService.logInfo(
'ActivityRepository',
'Récupération des activités pour le voyage: $tripId',
);
// Modifié pour éviter l'erreur d'index composite
// On récupère d'abord par tripId, puis on trie en mémoire
final querySnapshot = await _firestore
.collection(_collection)
.where('tripId', isEqualTo: tripId)
.get();
final activities = querySnapshot.docs
.map((doc) => Activity.fromSnapshot(doc))
.toList();
// Tri en mémoire par date de mise à jour (plus récent en premier)
activities.sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
_errorService.logInfo(
'ActivityRepository',
'${activities.length} activités trouvées',
);
return activities;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur lors de la récupération: $e',
);
_errorService.logError(
'activity_repository',
'Erreur récupération activités: $e',
);
return [];
}
}
/// Récupère une activité par son ID (alias pour getActivityById pour compatibilité)
Future<Activity?> getActivity(String tripId, String activityId) async {
return getActivityById(activityId);
}
/// Récupère une activité par son ID
Future<Activity?> getActivityById(String activityId) async {
try {
final doc = await _firestore
.collection(_collection)
.doc(activityId)
.get();
if (doc.exists) {
return Activity.fromSnapshot(doc);
}
return null;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur récupération activité: $e',
);
_errorService.logError(
'activity_repository',
'Erreur récupération activité: $e',
);
return null;
}
}
/// Met à jour une activité
Future<bool> updateActivity(Activity activity) async {
try {
_errorService.logInfo(
'ActivityRepository',
'Mise à jour de l\'activité: ${activity.id}',
);
await _firestore
.collection(_collection)
.doc(activity.id)
.update(activity.copyWith(updatedAt: DateTime.now()).toMap());
_errorService.logSuccess(
'ActivityRepository',
'Activité mise à jour avec succès',
);
return true;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur lors de la mise à jour: $e',
);
_errorService.logError(
'activity_repository',
'Erreur mise à jour activité: $e',
);
return false;
}
}
/// Supprime une activité
Future<bool> deleteActivity(String activityId) async {
try {
_errorService.logInfo(
'ActivityRepository',
'Suppression de l\'activité: $activityId',
);
await _firestore.collection(_collection).doc(activityId).delete();
_errorService.logSuccess(
'ActivityRepository',
'Activité supprimée avec succès',
);
return true;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur lors de la suppression: $e',
);
_errorService.logError(
'activity_repository',
'Erreur suppression activité: $e',
);
return false;
}
}
/// Vote pour une activité
Future<bool> voteForActivity(
String activityId,
String userId,
int vote,
) async {
try {
// Validation des paramètres
if (activityId.isEmpty) {
_errorService.logError('ActivityRepository', 'ID d\'activité vide');
_errorService.logError(
'activity_repository',
'ID d\'activité vide pour le vote',
);
return false;
}
if (userId.isEmpty) {
_errorService.logError('ActivityRepository', 'ID d\'utilisateur vide');
_errorService.logError(
'activity_repository',
'ID d\'utilisateur vide pour le vote',
);
return false;
}
_errorService.logInfo(
'ActivityRepository',
'Vote pour l\'activité $activityId: $vote',
);
// vote: 1 pour positif, -1 pour négatif, 0 pour supprimer le vote
final activityRef = _firestore.collection(_collection).doc(activityId);
await _firestore.runTransaction((transaction) async {
final snapshot = await transaction.get(activityRef);
if (!snapshot.exists) {
throw Exception('Activité non trouvée');
}
final activity = Activity.fromSnapshot(snapshot);
final newVotes = Map<String, int>.from(activity.votes);
if (vote == 0) {
// Supprimer le vote
newVotes.remove(userId);
} else {
// Ajouter ou modifier le vote
newVotes[userId] = vote;
}
transaction.update(activityRef, {
'votes': newVotes,
'updatedAt': Timestamp.fromDate(DateTime.now()),
});
});
_errorService.logSuccess(
'ActivityRepository',
'Vote enregistré avec succès',
);
return true;
} catch (e) {
_errorService.logError('ActivityRepository', 'Erreur lors du vote: $e');
_errorService.logError('activity_repository', 'Erreur vote: $e');
return false;
}
}
/// Récupère les activités avec un stream pour les mises à jour en temps réel
Stream<List<Activity>> getActivitiesStream(String tripId) {
try {
return _firestore
.collection(_collection)
.where('tripId', isEqualTo: tripId)
.snapshots()
.map((snapshot) {
final activities = snapshot.docs
.map((doc) => Activity.fromSnapshot(doc))
.toList();
// Tri en mémoire par date de mise à jour (plus récent en premier)
activities.sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
return activities;
});
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur stream activités: $e',
);
_errorService.logError(
'activity_repository',
'Erreur stream activités: $e',
);
return Stream.value([]);
}
}
/// Ajoute plusieurs activités en lot
Future<List<String>> addActivitiesBatch(List<Activity> activities) async {
try {
_errorService.logInfo(
'ActivityRepository',
'Ajout en lot de ${activities.length} activités',
);
final batch = _firestore.batch();
final addedIds = <String>[];
for (final activity in activities) {
final docRef = _firestore.collection(_collection).doc();
final activityWithId = activity.copyWith(id: docRef.id);
batch.set(docRef, activityWithId.toMap());
addedIds.add(docRef.id);
}
await batch.commit();
_errorService.logSuccess(
'ActivityRepository',
'${addedIds.length} activités ajoutées en lot',
);
return addedIds;
} catch (e) {
_errorService.logError('ActivityRepository', 'Erreur ajout en lot: $e');
_errorService.logError('activity_repository', 'Erreur ajout en lot: $e');
return [];
}
}
/// Recherche des activités par catégorie
Future<List<Activity>> getActivitiesByCategory(
String tripId,
String category,
) async {
try {
_errorService.logInfo(
'ActivityRepository',
'Recherche par catégorie: $category pour le voyage: $tripId',
);
// Récupérer toutes les activités du voyage puis filtrer en mémoire
final querySnapshot = await _firestore
.collection(_collection)
.where('tripId', isEqualTo: tripId)
.get();
final activities = querySnapshot.docs
.map((doc) => Activity.fromSnapshot(doc))
.where((activity) => activity.category == category)
.toList();
// Tri en mémoire par date de mise à jour (plus récent en premier)
activities.sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
return activities;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur recherche par catégorie: $e',
);
_errorService.logError(
'activity_repository',
'Erreur recherche par catégorie: $e',
);
return [];
}
}
/// Récupère les activités les mieux notées d'un voyage
Future<List<Activity>> getTopRatedActivities(
String tripId, {
int limit = 10,
}) async {
try {
final activities = await getActivitiesByTrip(tripId);
// Trier par nombre de votes positifs puis par note Google
activities.sort((a, b) {
final aScore = a.totalVotes;
final bScore = b.totalVotes;
if (aScore != bScore) {
return bScore.compareTo(aScore);
}
return (b.rating ?? 0).compareTo(a.rating ?? 0);
});
return activities.take(limit).toList();
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur activités top rated: $e',
);
_errorService.logError('activity_repository', 'Erreur top rated: $e');
return [];
}
}
/// Vérifie si une activité existe déjà pour un voyage (basé sur placeId)
Future<Activity?> findExistingActivity(String tripId, String placeId) async {
try {
final querySnapshot = await _firestore
.collection(_collection)
.where('tripId', isEqualTo: tripId)
.where('placeId', isEqualTo: placeId)
.limit(1)
.get();
if (querySnapshot.docs.isNotEmpty) {
return Activity.fromSnapshot(querySnapshot.docs.first);
}
return null;
} catch (e) {
_errorService.logError(
'ActivityRepository',
'Erreur recherche activité existante: $e',
);
return null;
}
}
}