256 lines
7.1 KiB
Dart
256 lines
7.1 KiB
Dart
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<String> openingHours;
|
|
final Map<String, int> votes; // userId -> vote (1 pour pour, -1 pour contre)
|
|
final DateTime createdAt;
|
|
final DateTime updatedAt;
|
|
final DateTime? date; // Date prévue pour l'activité
|
|
|
|
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,
|
|
this.date,
|
|
});
|
|
|
|
/// Calcule le score total des votes
|
|
int get totalVotes {
|
|
return votes.values.fold(0, (total, vote) => total + 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<String> 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<String>? openingHours,
|
|
Map<String, int>? votes,
|
|
DateTime? createdAt,
|
|
DateTime? updatedAt,
|
|
DateTime? date,
|
|
bool clearDate = false,
|
|
}) {
|
|
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,
|
|
date: clearDate ? null : (date ?? this.date),
|
|
);
|
|
}
|
|
|
|
/// Conversion vers Map pour Firestore
|
|
Map<String, dynamic> 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),
|
|
'date': date != null ? Timestamp.fromDate(date!) : null,
|
|
};
|
|
}
|
|
|
|
/// Création depuis Map Firestore
|
|
factory Activity.fromMap(Map<String, dynamic> 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<String>.from(map['openingHours'] ?? []),
|
|
votes: Map<String, int>.from(map['votes'] ?? {}),
|
|
createdAt: (map['createdAt'] as Timestamp).toDate(),
|
|
updatedAt: (map['updatedAt'] as Timestamp).toDate(),
|
|
date: map['date'] != null ? (map['date'] as Timestamp).toDate() : null,
|
|
);
|
|
}
|
|
|
|
/// Création depuis snapshot Firestore
|
|
factory Activity.fromSnapshot(DocumentSnapshot snapshot) {
|
|
final data = snapshot.data() as Map<String, dynamic>;
|
|
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;
|
|
}
|
|
}
|