import 'package:cloud_firestore/cloud_firestore.dart'; /// Modèle représentant une activité touristique liée à un voyage class Activity { final String id; final String tripId; final String name; final String description; final String category; final String? imageUrl; final double? rating; final String? priceLevel; final String? address; final double? latitude; final double? longitude; final String? placeId; // Google Places ID final String? website; final String? phoneNumber; final List openingHours; final Map votes; // userId -> vote (1 pour pour, -1 pour contre) final DateTime createdAt; final DateTime updatedAt; Activity({ required this.id, required this.tripId, required this.name, required this.description, required this.category, this.imageUrl, this.rating, this.priceLevel, this.address, this.latitude, this.longitude, this.placeId, this.website, this.phoneNumber, this.openingHours = const [], this.votes = const {}, required this.createdAt, required this.updatedAt, }); /// Calcule le score total des votes int get totalVotes { return votes.values.fold(0, (sum, vote) => sum + vote); } /// Calcule le nombre de votes positifs int get positiveVotes { return votes.values.where((vote) => vote > 0).length; } /// Calcule le nombre de votes négatifs int get negativeVotes { return votes.values.where((vote) => vote < 0).length; } /// Vérifie si l'utilisateur a voté bool hasUserVoted(String userId) { return votes.containsKey(userId); } /// Récupère le vote d'un utilisateur (-1, 0, 1) int getUserVote(String userId) { return votes[userId] ?? 0; } /// Vérifie si tous les participants du voyage ont voté positivement pour cette activité bool isApprovedByAllParticipants(List tripParticipants) { if (tripParticipants.isEmpty) return false; // Tous les participants doivent avoir voté for (String participantId in tripParticipants) { if (!votes.containsKey(participantId)) { return false; // Quelqu'un n'a pas encore voté } if (votes[participantId] != 1) { return false; // Quelqu'un a voté négativement ou neutre } } return true; // Tous ont voté positivement } /// Crée une copie avec des modifications Activity copyWith({ String? id, String? tripId, String? name, String? description, String? category, String? imageUrl, double? rating, String? priceLevel, String? address, double? latitude, double? longitude, String? placeId, String? website, String? phoneNumber, List? openingHours, Map? votes, DateTime? createdAt, DateTime? updatedAt, }) { return Activity( id: id ?? this.id, tripId: tripId ?? this.tripId, name: name ?? this.name, description: description ?? this.description, category: category ?? this.category, imageUrl: imageUrl ?? this.imageUrl, rating: rating ?? this.rating, priceLevel: priceLevel ?? this.priceLevel, address: address ?? this.address, latitude: latitude ?? this.latitude, longitude: longitude ?? this.longitude, placeId: placeId ?? this.placeId, website: website ?? this.website, phoneNumber: phoneNumber ?? this.phoneNumber, openingHours: openingHours ?? this.openingHours, votes: votes ?? this.votes, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); } /// Conversion vers Map pour Firestore Map toMap() { return { 'id': id, 'tripId': tripId, 'name': name, 'description': description, 'category': category, 'imageUrl': imageUrl, 'rating': rating, 'priceLevel': priceLevel, 'address': address, 'latitude': latitude, 'longitude': longitude, 'placeId': placeId, 'website': website, 'phoneNumber': phoneNumber, 'openingHours': openingHours, 'votes': votes, 'createdAt': Timestamp.fromDate(createdAt), 'updatedAt': Timestamp.fromDate(updatedAt), }; } /// Création depuis Map Firestore factory Activity.fromMap(Map map) { return Activity( id: map['id'] ?? '', tripId: map['tripId'] ?? '', name: map['name'] ?? '', description: map['description'] ?? '', category: map['category'] ?? '', imageUrl: map['imageUrl'], rating: map['rating']?.toDouble(), priceLevel: map['priceLevel'], address: map['address'], latitude: map['latitude']?.toDouble(), longitude: map['longitude']?.toDouble(), placeId: map['placeId'], website: map['website'], phoneNumber: map['phoneNumber'], openingHours: List.from(map['openingHours'] ?? []), votes: Map.from(map['votes'] ?? {}), createdAt: (map['createdAt'] as Timestamp).toDate(), updatedAt: (map['updatedAt'] as Timestamp).toDate(), ); } /// Création depuis snapshot Firestore factory Activity.fromSnapshot(DocumentSnapshot snapshot) { final data = snapshot.data() as Map; return Activity.fromMap({...data, 'id': snapshot.id}); } @override String toString() { return 'Activity(id: $id, name: $name, category: $category, votes: $totalVotes)'; } @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is Activity && other.id == id; } @override int get hashCode => id.hashCode; } /// Énumération des catégories d'activités enum ActivityCategory { museum('Musée', 'museum'), restaurant('Restaurant', 'restaurant'), attraction('Attraction', 'tourist_attraction'), entertainment('Divertissement', 'amusement_park'), shopping('Shopping', 'shopping_mall'), nature('Nature', 'park'), culture('Culture', 'establishment'), nightlife('Vie nocturne', 'night_club'), sports('Sports', 'gym'), relaxation('Détente', 'spa'); const ActivityCategory(this.displayName, this.googlePlaceType); final String displayName; final String googlePlaceType; static ActivityCategory? fromGoogleType(String type) { for (final category in ActivityCategory.values) { if (category.googlePlaceType == type) { return category; } } return null; } } /// Énumération des niveaux de prix enum PriceLevel { free('Gratuit', 0), inexpensive('Bon marché', 1), moderate('Modéré', 2), expensive('Cher', 3), veryExpensive('Très cher', 4); const PriceLevel(this.displayName, this.level); final String displayName; final int level; static PriceLevel? fromLevel(int level) { for (final price in PriceLevel.values) { if (price.level == level) { return price; } } return null; } }