feat: Implement activity management feature with Firestore integration
- Added AddActivityBottomSheet for adding custom activities to trips. - Created Activity model to represent tourist activities. - Developed ActivityRepository for managing activities in Firestore. - Integrated ActivityPlacesService for searching activities via Google Places API. - Updated ShowTripDetailsContent to navigate to activities page. - Enhanced main.dart to include ActivityBloc and necessary repositories.
This commit is contained in:
231
lib/models/activity.dart
Normal file
231
lib/models/activity.dart
Normal file
@@ -0,0 +1,231 @@
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}) {
|
||||
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<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),
|
||||
};
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user