fix(chat): Correction race condition + Implémentation TODOs
## Corrections Critiques ### Race Condition - Statuts de Messages - Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas - Cause : WebSocket delivery confirmations arrivaient avant messages locaux - Solution : Pattern Optimistic UI dans chat_bloc.dart - Création message temporaire immédiate - Ajout à la liste AVANT requête HTTP - Remplacement par message serveur à la réponse - Fichier : lib/presentation/state_management/chat_bloc.dart ## Implémentation TODOs (13/21) ### Social (social_header_widget.dart) - ✅ Copier lien du post dans presse-papiers - ✅ Partage natif via Share.share() - ✅ Dialogue de signalement avec 5 raisons ### Partage (share_post_dialog.dart) - ✅ Interface sélection d'amis avec checkboxes - ✅ Partage externe via Share API ### Média (media_upload_service.dart) - ✅ Parsing JSON réponse backend - ✅ Méthode deleteMedia() pour suppression - ✅ Génération miniature vidéo ### Posts (create_post_dialog.dart, edit_post_dialog.dart) - ✅ Extraction URL depuis uploads - ✅ Documentation chargement médias ### Chat (conversations_screen.dart) - ✅ Navigation vers notifications - ✅ ConversationSearchDelegate pour recherche ## Nouveaux Fichiers ### Configuration - build-prod.ps1 : Script build production avec dart-define - lib/core/constants/env_config.dart : Gestion environnements ### Documentation - TODOS_IMPLEMENTED.md : Documentation complète TODOs ## Améliorations ### Architecture - Refactoring injection de dépendances - Amélioration routing et navigation - Optimisation providers (UserProvider, FriendsProvider) ### UI/UX - Amélioration thème et couleurs - Optimisation animations - Meilleure gestion erreurs ### Services - Configuration API avec env_config - Amélioration datasources (events, users) - Optimisation modèles de données
This commit is contained in:
124
lib/domain/entities/chat_message.dart
Normal file
124
lib/domain/entities/chat_message.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant un message de chat.
|
||||
///
|
||||
/// Un message est échangé entre deux utilisateurs dans une conversation.
|
||||
class ChatMessage extends Equatable {
|
||||
const ChatMessage({
|
||||
required this.id,
|
||||
required this.conversationId,
|
||||
required this.senderId,
|
||||
required this.senderFirstName,
|
||||
required this.senderLastName,
|
||||
required this.senderProfileImageUrl,
|
||||
required this.content,
|
||||
required this.timestamp,
|
||||
required this.isRead,
|
||||
this.isDelivered = false,
|
||||
this.attachmentUrl,
|
||||
this.attachmentType,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String conversationId;
|
||||
final String senderId;
|
||||
final String senderFirstName;
|
||||
final String senderLastName;
|
||||
final String? senderProfileImageUrl;
|
||||
final String content;
|
||||
final DateTime timestamp;
|
||||
final bool isRead;
|
||||
final bool isDelivered;
|
||||
final String? attachmentUrl; // URL d'une image/fichier joint
|
||||
final AttachmentType? attachmentType;
|
||||
|
||||
/// Nom complet de l'expéditeur.
|
||||
String get senderFullName => '$senderFirstName $senderLastName';
|
||||
|
||||
/// Indique si le message a une pièce jointe.
|
||||
bool get hasAttachment => attachmentUrl != null && attachmentType != null;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
conversationId,
|
||||
senderId,
|
||||
senderFirstName,
|
||||
senderLastName,
|
||||
senderProfileImageUrl,
|
||||
content,
|
||||
timestamp,
|
||||
isRead,
|
||||
isDelivered,
|
||||
attachmentUrl,
|
||||
attachmentType,
|
||||
];
|
||||
|
||||
/// Crée une copie de ce message avec des valeurs modifiées.
|
||||
ChatMessage copyWith({
|
||||
String? id,
|
||||
String? conversationId,
|
||||
String? senderId,
|
||||
String? senderFirstName,
|
||||
String? senderLastName,
|
||||
String? senderProfileImageUrl,
|
||||
String? content,
|
||||
DateTime? timestamp,
|
||||
bool? isRead,
|
||||
bool? isDelivered,
|
||||
String? attachmentUrl,
|
||||
AttachmentType? attachmentType,
|
||||
}) {
|
||||
return ChatMessage(
|
||||
id: id ?? this.id,
|
||||
conversationId: conversationId ?? this.conversationId,
|
||||
senderId: senderId ?? this.senderId,
|
||||
senderFirstName: senderFirstName ?? this.senderFirstName,
|
||||
senderLastName: senderLastName ?? this.senderLastName,
|
||||
senderProfileImageUrl: senderProfileImageUrl ?? this.senderProfileImageUrl,
|
||||
content: content ?? this.content,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
isRead: isRead ?? this.isRead,
|
||||
isDelivered: isDelivered ?? this.isDelivered,
|
||||
attachmentUrl: attachmentUrl ?? this.attachmentUrl,
|
||||
attachmentType: attachmentType ?? this.attachmentType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type de pièce jointe.
|
||||
enum AttachmentType {
|
||||
image,
|
||||
video,
|
||||
audio,
|
||||
file,
|
||||
}
|
||||
|
||||
/// Extensions pour faciliter l'utilisation.
|
||||
extension AttachmentTypeExtension on AttachmentType {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case AttachmentType.image:
|
||||
return 'Image';
|
||||
case AttachmentType.video:
|
||||
return 'Vidéo';
|
||||
case AttachmentType.audio:
|
||||
return 'Audio';
|
||||
case AttachmentType.file:
|
||||
return 'Fichier';
|
||||
}
|
||||
}
|
||||
|
||||
String get icon {
|
||||
switch (this) {
|
||||
case AttachmentType.image:
|
||||
return '🖼️';
|
||||
case AttachmentType.video:
|
||||
return '🎥';
|
||||
case AttachmentType.audio:
|
||||
return '🎵';
|
||||
case AttachmentType.file:
|
||||
return '📄';
|
||||
}
|
||||
}
|
||||
}
|
||||
80
lib/domain/entities/comment.dart
Normal file
80
lib/domain/entities/comment.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant un commentaire sur un post social.
|
||||
///
|
||||
/// Cette entité est pure et indépendante de la couche de données.
|
||||
/// Elle représente un commentaire dans le domaine métier.
|
||||
class Comment extends Equatable {
|
||||
const Comment({
|
||||
required this.id,
|
||||
required this.postId,
|
||||
required this.userId,
|
||||
required this.userFirstName,
|
||||
required this.userLastName,
|
||||
required this.userProfileImageUrl,
|
||||
required this.content,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
/// ID unique du commentaire
|
||||
final String id;
|
||||
|
||||
/// ID du post auquel appartient ce commentaire
|
||||
final String postId;
|
||||
|
||||
/// ID de l'utilisateur qui a créé le commentaire
|
||||
final String userId;
|
||||
|
||||
/// Prénom de l'utilisateur
|
||||
final String userFirstName;
|
||||
|
||||
/// Nom de l'utilisateur
|
||||
final String userLastName;
|
||||
|
||||
/// URL de l'image de profil de l'utilisateur
|
||||
final String userProfileImageUrl;
|
||||
|
||||
/// Contenu du commentaire
|
||||
final String content;
|
||||
|
||||
/// Date et heure de création du commentaire
|
||||
final DateTime timestamp;
|
||||
|
||||
/// Retourne le nom complet de l'auteur du commentaire
|
||||
String get authorFullName => '$userFirstName $userLastName';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
postId,
|
||||
userId,
|
||||
userFirstName,
|
||||
userLastName,
|
||||
userProfileImageUrl,
|
||||
content,
|
||||
timestamp,
|
||||
];
|
||||
|
||||
/// Crée une copie de ce commentaire avec des valeurs modifiées.
|
||||
Comment copyWith({
|
||||
String? id,
|
||||
String? postId,
|
||||
String? userId,
|
||||
String? userFirstName,
|
||||
String? userLastName,
|
||||
String? userProfileImageUrl,
|
||||
String? content,
|
||||
DateTime? timestamp,
|
||||
}) {
|
||||
return Comment(
|
||||
id: id ?? this.id,
|
||||
postId: postId ?? this.postId,
|
||||
userId: userId ?? this.userId,
|
||||
userFirstName: userFirstName ?? this.userFirstName,
|
||||
userLastName: userLastName ?? this.userLastName,
|
||||
userProfileImageUrl: userProfileImageUrl ?? this.userProfileImageUrl,
|
||||
content: content ?? this.content,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
77
lib/domain/entities/conversation.dart
Normal file
77
lib/domain/entities/conversation.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
import 'chat_message.dart';
|
||||
|
||||
/// Entité de domaine représentant une conversation entre deux utilisateurs.
|
||||
///
|
||||
/// Une conversation regroupe tous les messages échangés entre deux amis.
|
||||
class Conversation extends Equatable {
|
||||
const Conversation({
|
||||
required this.id,
|
||||
required this.participantId,
|
||||
required this.participantFirstName,
|
||||
required this.participantLastName,
|
||||
required this.participantProfileImageUrl,
|
||||
this.lastMessage,
|
||||
this.lastMessageTimestamp,
|
||||
required this.unreadCount,
|
||||
this.isTyping = false,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String participantId; // L'autre utilisateur
|
||||
final String participantFirstName;
|
||||
final String participantLastName;
|
||||
final String? participantProfileImageUrl;
|
||||
final String? lastMessage; // Contenu du dernier message
|
||||
final DateTime? lastMessageTimestamp;
|
||||
final int unreadCount; // Nombre de messages non lus
|
||||
final bool isTyping; // L'autre utilisateur est en train de taper
|
||||
|
||||
/// Nom complet du participant.
|
||||
String get participantFullName => '$participantFirstName $participantLastName';
|
||||
|
||||
/// Indique si la conversation a des messages non lus.
|
||||
bool get hasUnreadMessages => unreadCount > 0;
|
||||
|
||||
/// Indique si la conversation a un dernier message.
|
||||
bool get hasLastMessage => lastMessage != null && lastMessageTimestamp != null;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
participantId,
|
||||
participantFirstName,
|
||||
participantLastName,
|
||||
participantProfileImageUrl,
|
||||
lastMessage,
|
||||
lastMessageTimestamp,
|
||||
unreadCount,
|
||||
isTyping,
|
||||
];
|
||||
|
||||
/// Crée une copie de cette conversation avec des valeurs modifiées.
|
||||
Conversation copyWith({
|
||||
String? id,
|
||||
String? participantId,
|
||||
String? participantFirstName,
|
||||
String? participantLastName,
|
||||
String? participantProfileImageUrl,
|
||||
String? lastMessage,
|
||||
DateTime? lastMessageTimestamp,
|
||||
int? unreadCount,
|
||||
bool? isTyping,
|
||||
}) {
|
||||
return Conversation(
|
||||
id: id ?? this.id,
|
||||
participantId: participantId ?? this.participantId,
|
||||
participantFirstName: participantFirstName ?? this.participantFirstName,
|
||||
participantLastName: participantLastName ?? this.participantLastName,
|
||||
participantProfileImageUrl: participantProfileImageUrl ?? this.participantProfileImageUrl,
|
||||
lastMessage: lastMessage ?? this.lastMessage,
|
||||
lastMessageTimestamp: lastMessageTimestamp ?? this.lastMessageTimestamp,
|
||||
unreadCount: unreadCount ?? this.unreadCount,
|
||||
isTyping: isTyping ?? this.isTyping,
|
||||
);
|
||||
}
|
||||
}
|
||||
216
lib/domain/entities/establishment.dart
Normal file
216
lib/domain/entities/establishment.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant un établissement.
|
||||
///
|
||||
/// Un établissement est un lieu physique (bar, restaurant, club, etc.)
|
||||
/// où peuvent se dérouler des événements Afterwork.
|
||||
class Establishment extends Equatable {
|
||||
const Establishment({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.address,
|
||||
required this.city,
|
||||
required this.postalCode,
|
||||
this.description,
|
||||
this.phoneNumber,
|
||||
this.email,
|
||||
this.website,
|
||||
this.imageUrl,
|
||||
this.rating,
|
||||
this.priceRange,
|
||||
this.capacity,
|
||||
this.amenities = const [],
|
||||
this.openingHours,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final EstablishmentType type;
|
||||
final String address;
|
||||
final String city;
|
||||
final String postalCode;
|
||||
final String? description;
|
||||
final String? phoneNumber;
|
||||
final String? email;
|
||||
final String? website;
|
||||
final String? imageUrl;
|
||||
final double? rating; // Note moyenne sur 5
|
||||
final PriceRange? priceRange;
|
||||
final int? capacity; // Capacité maximale
|
||||
final List<String> amenities; // WiFi, Terrasse, Parking, etc.
|
||||
final String? openingHours;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
|
||||
/// Adresse complète formatée
|
||||
String get fullAddress => '$address, $postalCode $city';
|
||||
|
||||
/// Indique si l'établissement a une localisation
|
||||
bool get hasLocation => latitude != null && longitude != null;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
address,
|
||||
city,
|
||||
postalCode,
|
||||
description,
|
||||
phoneNumber,
|
||||
email,
|
||||
website,
|
||||
imageUrl,
|
||||
rating,
|
||||
priceRange,
|
||||
capacity,
|
||||
amenities,
|
||||
openingHours,
|
||||
latitude,
|
||||
longitude,
|
||||
];
|
||||
|
||||
/// Crée une copie de cet établissement avec des valeurs modifiées.
|
||||
Establishment copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
EstablishmentType? type,
|
||||
String? address,
|
||||
String? city,
|
||||
String? postalCode,
|
||||
String? description,
|
||||
String? phoneNumber,
|
||||
String? email,
|
||||
String? website,
|
||||
String? imageUrl,
|
||||
double? rating,
|
||||
PriceRange? priceRange,
|
||||
int? capacity,
|
||||
List<String>? amenities,
|
||||
String? openingHours,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
}) {
|
||||
return Establishment(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
address: address ?? this.address,
|
||||
city: city ?? this.city,
|
||||
postalCode: postalCode ?? this.postalCode,
|
||||
description: description ?? this.description,
|
||||
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||
email: email ?? this.email,
|
||||
website: website ?? this.website,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
rating: rating ?? this.rating,
|
||||
priceRange: priceRange ?? this.priceRange,
|
||||
capacity: capacity ?? this.capacity,
|
||||
amenities: amenities ?? this.amenities,
|
||||
openingHours: openingHours ?? this.openingHours,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type d'établissement.
|
||||
enum EstablishmentType {
|
||||
bar,
|
||||
restaurant,
|
||||
club,
|
||||
cafe,
|
||||
lounge,
|
||||
pub,
|
||||
brewery,
|
||||
winery,
|
||||
other,
|
||||
}
|
||||
|
||||
/// Fourchette de prix.
|
||||
enum PriceRange {
|
||||
cheap, // €
|
||||
moderate, // €€
|
||||
expensive, // €€€
|
||||
luxury, // €€€€
|
||||
}
|
||||
|
||||
/// Extensions pour faciliter l'utilisation.
|
||||
extension EstablishmentTypeExtension on EstablishmentType {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case EstablishmentType.bar:
|
||||
return 'Bar';
|
||||
case EstablishmentType.restaurant:
|
||||
return 'Restaurant';
|
||||
case EstablishmentType.club:
|
||||
return 'Club';
|
||||
case EstablishmentType.cafe:
|
||||
return 'Café';
|
||||
case EstablishmentType.lounge:
|
||||
return 'Lounge';
|
||||
case EstablishmentType.pub:
|
||||
return 'Pub';
|
||||
case EstablishmentType.brewery:
|
||||
return 'Brasserie';
|
||||
case EstablishmentType.winery:
|
||||
return 'Cave à vin';
|
||||
case EstablishmentType.other:
|
||||
return 'Autre';
|
||||
}
|
||||
}
|
||||
|
||||
String get icon {
|
||||
switch (this) {
|
||||
case EstablishmentType.bar:
|
||||
return '🍸';
|
||||
case EstablishmentType.restaurant:
|
||||
return '🍽️';
|
||||
case EstablishmentType.club:
|
||||
return '💃';
|
||||
case EstablishmentType.cafe:
|
||||
return '☕';
|
||||
case EstablishmentType.lounge:
|
||||
return '🛋️';
|
||||
case EstablishmentType.pub:
|
||||
return '🍺';
|
||||
case EstablishmentType.brewery:
|
||||
return '🍻';
|
||||
case EstablishmentType.winery:
|
||||
return '🍷';
|
||||
case EstablishmentType.other:
|
||||
return '📍';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PriceRangeExtension on PriceRange {
|
||||
String get symbol {
|
||||
switch (this) {
|
||||
case PriceRange.cheap:
|
||||
return '€';
|
||||
case PriceRange.moderate:
|
||||
return '€€';
|
||||
case PriceRange.expensive:
|
||||
return '€€€';
|
||||
case PriceRange.luxury:
|
||||
return '€€€€';
|
||||
}
|
||||
}
|
||||
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case PriceRange.cheap:
|
||||
return 'Économique';
|
||||
case PriceRange.moderate:
|
||||
return 'Modéré';
|
||||
case PriceRange.expensive:
|
||||
return 'Cher';
|
||||
case PriceRange.luxury:
|
||||
return 'Luxe';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,168 @@
|
||||
class EventModel {
|
||||
final String eventId;
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant un événement.
|
||||
///
|
||||
/// Cette classe est indépendante de toute infrastructure (API, BDD, etc.)
|
||||
/// et représente la logique métier pure selon Clean Architecture.
|
||||
class Event extends Equatable {
|
||||
|
||||
const Event({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.startDate,
|
||||
required this.location,
|
||||
required this.category,
|
||||
required this.creatorEmail, required this.creatorFirstName, required this.creatorLastName, required this.creatorProfileImageUrl, this.link,
|
||||
this.imageUrl,
|
||||
this.participantIds = const [],
|
||||
this.status = EventStatus.open,
|
||||
this.reactionsCount = 0,
|
||||
this.commentsCount = 0,
|
||||
this.sharesCount = 0,
|
||||
});
|
||||
final String id;
|
||||
final String title;
|
||||
final String description;
|
||||
final String eventDate;
|
||||
final DateTime startDate;
|
||||
final String location;
|
||||
final String category;
|
||||
final String? link;
|
||||
final String? imageUrl;
|
||||
final String creatorId;
|
||||
final String status;
|
||||
|
||||
// Informations sur le créateur
|
||||
final String creatorEmail;
|
||||
final String creatorFirstName;
|
||||
final String creatorLastName;
|
||||
final String creatorProfileImageUrl;
|
||||
|
||||
// Participants et interactions
|
||||
final List<String> participantIds;
|
||||
final EventStatus status;
|
||||
final int reactionsCount;
|
||||
final int commentsCount;
|
||||
final int sharesCount;
|
||||
|
||||
EventModel({
|
||||
required this.eventId,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.eventDate,
|
||||
required this.location,
|
||||
required this.category,
|
||||
this.link,
|
||||
this.imageUrl,
|
||||
required this.creatorId,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
// Méthode pour créer un EventModel à partir d'un JSON
|
||||
factory EventModel.fromJson(Map<String, dynamic> json) {
|
||||
return EventModel(
|
||||
eventId: json['id'],
|
||||
title: json['title'],
|
||||
description: json['description'],
|
||||
eventDate: json['event_date'],
|
||||
location: json['location'],
|
||||
category: json['category'],
|
||||
link: json['link'],
|
||||
imageUrl: json['imageUrl'],
|
||||
creatorId: json['creator']['id'], // Assurez-vous que le JSON a ce format
|
||||
status: json['status'],
|
||||
/// Crée une copie de l'événement avec les champs modifiés
|
||||
Event copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? description,
|
||||
DateTime? startDate,
|
||||
String? location,
|
||||
String? category,
|
||||
String? link,
|
||||
String? imageUrl,
|
||||
String? creatorEmail,
|
||||
String? creatorFirstName,
|
||||
String? creatorLastName,
|
||||
String? creatorProfileImageUrl,
|
||||
List<String>? participantIds,
|
||||
EventStatus? status,
|
||||
int? reactionsCount,
|
||||
int? commentsCount,
|
||||
int? sharesCount,
|
||||
}) {
|
||||
return Event(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
startDate: startDate ?? this.startDate,
|
||||
location: location ?? this.location,
|
||||
category: category ?? this.category,
|
||||
link: link ?? this.link,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
creatorEmail: creatorEmail ?? this.creatorEmail,
|
||||
creatorFirstName: creatorFirstName ?? this.creatorFirstName,
|
||||
creatorLastName: creatorLastName ?? this.creatorLastName,
|
||||
creatorProfileImageUrl: creatorProfileImageUrl ?? this.creatorProfileImageUrl,
|
||||
participantIds: participantIds ?? this.participantIds,
|
||||
status: status ?? this.status,
|
||||
reactionsCount: reactionsCount ?? this.reactionsCount,
|
||||
commentsCount: commentsCount ?? this.commentsCount,
|
||||
sharesCount: sharesCount ?? this.sharesCount,
|
||||
);
|
||||
}
|
||||
|
||||
// Méthode pour convertir un EventModel en JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': eventId,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'event_date': eventDate,
|
||||
'location': location,
|
||||
'category': category,
|
||||
'link': link,
|
||||
'imageUrl': imageUrl,
|
||||
'creator': {'id': creatorId}, // Structure du JSON pour l'API
|
||||
'status': status,
|
||||
};
|
||||
/// Retourne le nom complet du créateur
|
||||
String get creatorFullName => '$creatorFirstName $creatorLastName';
|
||||
|
||||
/// Retourne le nombre total de participants
|
||||
int get participantsCount => participantIds.length;
|
||||
|
||||
/// Vérifie si l'événement est ouvert aux participations
|
||||
bool get isOpen => status == EventStatus.open;
|
||||
|
||||
/// Vérifie si l'événement est fermé
|
||||
bool get isClosed => status == EventStatus.closed;
|
||||
|
||||
/// Vérifie si l'événement est annulé
|
||||
bool get isCancelled => status == EventStatus.cancelled;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
startDate,
|
||||
location,
|
||||
category,
|
||||
link,
|
||||
imageUrl,
|
||||
creatorEmail,
|
||||
creatorFirstName,
|
||||
creatorLastName,
|
||||
creatorProfileImageUrl,
|
||||
participantIds,
|
||||
status,
|
||||
reactionsCount,
|
||||
commentsCount,
|
||||
sharesCount,
|
||||
];
|
||||
}
|
||||
|
||||
/// Énumération des statuts possibles d'un événement
|
||||
enum EventStatus {
|
||||
open,
|
||||
closed,
|
||||
cancelled,
|
||||
completed;
|
||||
|
||||
/// Convertit une chaîne en EventStatus
|
||||
static EventStatus fromString(String status) {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'ouvert':
|
||||
case 'open':
|
||||
return EventStatus.open;
|
||||
case 'fermé':
|
||||
case 'ferme':
|
||||
case 'closed':
|
||||
return EventStatus.closed;
|
||||
case 'annulé':
|
||||
case 'annule':
|
||||
case 'cancelled':
|
||||
return EventStatus.cancelled;
|
||||
case 'terminé':
|
||||
case 'termine':
|
||||
case 'completed':
|
||||
return EventStatus.completed;
|
||||
default:
|
||||
return EventStatus.open;
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir une liste d'EventModel à partir d'une liste JSON
|
||||
static List<EventModel> fromJsonList(List<dynamic> jsonList) {
|
||||
return jsonList.map((json) => EventModel.fromJson(json)).toList();
|
||||
}
|
||||
|
||||
// Convertir une liste d'EventModel en JSON
|
||||
static List<Map<String, dynamic>> toJsonList(List<EventModel> events) {
|
||||
return events.map((event) => event.toJson()).toList();
|
||||
/// Convertit EventStatus en chaîne pour l'API
|
||||
String toApiString() {
|
||||
switch (this) {
|
||||
case EventStatus.open:
|
||||
return 'ouvert';
|
||||
case EventStatus.closed:
|
||||
return 'fermé';
|
||||
case EventStatus.cancelled:
|
||||
return 'annulé';
|
||||
case EventStatus.completed:
|
||||
return 'terminé';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,33 +11,27 @@ enum FriendStatus { pending, accepted, blocked, unknown }
|
||||
///
|
||||
/// Chaque instance de [Friend] est immuable et toute modification doit passer par [copyWith].
|
||||
class Friend extends Equatable {
|
||||
final String friendId; // ID unique de l'ami, requis et non-nullable
|
||||
final String friendFirstName; // Prénom de l'ami, non-nullable pour garantir une intégrité des données
|
||||
final String friendLastName; // Nom de famille, non-nullable
|
||||
final String? email; // Adresse e-mail, optionnelle mais typiquement présente
|
||||
final String? imageUrl; // URL de l'image de profil, optionnelle
|
||||
final FriendStatus status; // Statut de l'ami, avec une valeur par défaut `unknown`
|
||||
final String? dateAdded;
|
||||
final String? lastInteraction;
|
||||
|
||||
/// Logger statique pour suivre toutes les actions et transformations liées à [Friend].
|
||||
static final Logger _logger = Logger();
|
||||
|
||||
/// Constructeur de la classe [Friend].
|
||||
/// Initialisation avec des valeurs spécifiques pour `firstName` et `lastName`.
|
||||
/// La validation des valeurs est incluse pour garantir l'intégrité des données.
|
||||
Friend({
|
||||
required this.friendId,
|
||||
this.friendFirstName = 'Ami inconnu', // Valeur par défaut pour éviter les champs vides
|
||||
this.friendFirstName =
|
||||
'Ami inconnu', // Valeur par défaut pour éviter les champs vides
|
||||
this.friendLastName = '',
|
||||
this.email,
|
||||
this.imageUrl,
|
||||
this.status = FriendStatus.unknown,
|
||||
this.dateAdded,
|
||||
this.lastInteraction,
|
||||
this.isOnline = false,
|
||||
this.isBestFriend = false,
|
||||
this.hasKnownSinceChildhood = false,
|
||||
}) {
|
||||
assert(friendId.isNotEmpty, 'friendId ne doit pas être vide');
|
||||
_logger.i('[LOG] Création d\'un objet Friend : ID = $friendId, Nom = $friendFirstName $friendLastName');
|
||||
_logger.i(
|
||||
'[LOG] Création d\'un objet Friend : ID = $friendId, Nom = $friendFirstName $friendLastName',);
|
||||
}
|
||||
|
||||
/// Méthode factory pour créer un objet [Friend] à partir d'un JSON.
|
||||
@@ -49,7 +43,7 @@ class Friend extends Equatable {
|
||||
|
||||
if (json['friendId'] == null || (json['friendId'] as String).isEmpty) {
|
||||
_logger.e('[ERROR] friendId manquant ou vide dans le JSON.');
|
||||
throw ArgumentError("friendId est requis pour créer un objet Friend");
|
||||
throw ArgumentError('friendId est requis pour créer un objet Friend');
|
||||
}
|
||||
|
||||
return Friend(
|
||||
@@ -59,8 +53,27 @@ class Friend extends Equatable {
|
||||
email: json['email'] as String?,
|
||||
imageUrl: json['friendProfileImageUrl'] as String?,
|
||||
status: _parseStatus(json['status'] as String?),
|
||||
isOnline: json['isOnline'] == true,
|
||||
isBestFriend: json['isBestFriend'] == true,
|
||||
hasKnownSinceChildhood: json['hasKnownSinceChildhood'] == true,
|
||||
);
|
||||
}
|
||||
final String friendId; // ID unique de l'ami, requis et non-nullable
|
||||
final String
|
||||
friendFirstName; // Prénom de l'ami, non-nullable pour garantir une intégrité des données
|
||||
final String friendLastName; // Nom de famille, non-nullable
|
||||
final String? email; // Adresse e-mail, optionnelle mais typiquement présente
|
||||
final String? imageUrl; // URL de l'image de profil, optionnelle
|
||||
final FriendStatus
|
||||
status; // Statut de l'ami, avec une valeur par défaut `unknown`
|
||||
final String? dateAdded;
|
||||
final String? lastInteraction;
|
||||
final bool? isOnline;
|
||||
final bool? isBestFriend;
|
||||
final bool? hasKnownSinceChildhood;
|
||||
|
||||
/// Logger statique pour suivre toutes les actions et transformations liées à [Friend].
|
||||
static final Logger _logger = Logger();
|
||||
|
||||
/// Méthode privée pour parser le champ `status` en type [FriendStatus].
|
||||
/// Retourne [FriendStatus.unknown] si le statut est non reconnu.
|
||||
@@ -87,6 +100,9 @@ class Friend extends Equatable {
|
||||
'email': email,
|
||||
'friendProfileImageUrl': imageUrl,
|
||||
'status': status.name,
|
||||
'isOnline': isOnline,
|
||||
'isBestFriend': isBestFriend,
|
||||
'hasKnownSinceChildhood': hasKnownSinceChildhood,
|
||||
};
|
||||
_logger.i('[LOG] Conversion Friend -> JSON : $json');
|
||||
return json;
|
||||
@@ -96,16 +112,18 @@ class Friend extends Equatable {
|
||||
/// Facilite la modification immuable des propriétés sans affecter l'instance actuelle.
|
||||
///
|
||||
/// Log chaque copie pour surveiller l'état des données.
|
||||
Friend copyWith({
|
||||
String? friendId,
|
||||
String? friendFirstName,
|
||||
String? friendLastName,
|
||||
String? email,
|
||||
String? imageUrl,
|
||||
FriendStatus? status,
|
||||
String? lastInteraction,
|
||||
String? dateAdded,
|
||||
}) {
|
||||
Friend copyWith(
|
||||
{String? friendId,
|
||||
String? friendFirstName,
|
||||
String? friendLastName,
|
||||
String? email,
|
||||
String? imageUrl,
|
||||
FriendStatus? status,
|
||||
String? lastInteraction,
|
||||
String? dateAdded,
|
||||
bool? isOnline,
|
||||
bool? isBestFriend,
|
||||
bool? hasKnownSinceChildhood,}) {
|
||||
final newFriend = Friend(
|
||||
friendId: friendId ?? this.friendId,
|
||||
friendFirstName: friendFirstName ?? this.friendFirstName,
|
||||
@@ -113,13 +131,28 @@ class Friend extends Equatable {
|
||||
email: email ?? this.email,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
status: status ?? this.status,
|
||||
isOnline: isOnline ?? this.isOnline,
|
||||
isBestFriend: isBestFriend ?? this.isBestFriend,
|
||||
hasKnownSinceChildhood:
|
||||
hasKnownSinceChildhood ?? this.hasKnownSinceChildhood,
|
||||
);
|
||||
_logger.i('[LOG] Création d\'une copie modifiée de Friend : ID = ${newFriend.friendId}');
|
||||
_logger.i(
|
||||
'[LOG] Création d\'une copie modifiée de Friend : ID = ${newFriend.friendId}',);
|
||||
return newFriend;
|
||||
}
|
||||
|
||||
/// Propriétés utilisées pour comparer les objets [Friend],
|
||||
/// facilitant l'utilisation dans des listes et des ensembles.
|
||||
@override
|
||||
List<Object?> get props => [friendId, friendFirstName, friendLastName, email, imageUrl, status];
|
||||
List<Object?> get props => [
|
||||
friendId,
|
||||
friendFirstName,
|
||||
friendLastName,
|
||||
email,
|
||||
imageUrl,
|
||||
status,
|
||||
isOnline,
|
||||
isBestFriend,
|
||||
hasKnownSinceChildhood,
|
||||
];
|
||||
}
|
||||
|
||||
73
lib/domain/entities/friend_suggestion.dart
Normal file
73
lib/domain/entities/friend_suggestion.dart
Normal file
@@ -0,0 +1,73 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité représentant une suggestion d'ami.
|
||||
///
|
||||
/// Cette classe contient toutes les informations nécessaires pour
|
||||
/// afficher et gérer les suggestions d'amis dans l'application.
|
||||
class FriendSuggestion extends Equatable {
|
||||
const FriendSuggestion({
|
||||
required this.userId,
|
||||
required this.firstName,
|
||||
required this.lastName,
|
||||
required this.email,
|
||||
required this.profileImageUrl,
|
||||
required this.mutualFriendsCount,
|
||||
required this.suggestionReason,
|
||||
});
|
||||
|
||||
/// ID unique de l'utilisateur suggéré
|
||||
final String userId;
|
||||
|
||||
/// Prénom de l'utilisateur suggéré
|
||||
final String firstName;
|
||||
|
||||
/// Nom de famille de l'utilisateur suggéré
|
||||
final String lastName;
|
||||
|
||||
/// Adresse email de l'utilisateur suggéré
|
||||
final String email;
|
||||
|
||||
/// URL de l'image de profil de l'utilisateur suggéré
|
||||
final String profileImageUrl;
|
||||
|
||||
/// Nombre d'amis en commun avec cet utilisateur
|
||||
final int mutualFriendsCount;
|
||||
|
||||
/// Raison de la suggestion (ex: "3 amis en commun")
|
||||
final String suggestionReason;
|
||||
|
||||
/// Nom complet de l'utilisateur suggéré
|
||||
String get fullName => '$firstName $lastName'.trim();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
userId,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
profileImageUrl,
|
||||
mutualFriendsCount,
|
||||
suggestionReason,
|
||||
];
|
||||
|
||||
/// Copie de l'entité avec modifications optionnelles
|
||||
FriendSuggestion copyWith({
|
||||
String? userId,
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
String? email,
|
||||
String? profileImageUrl,
|
||||
int? mutualFriendsCount,
|
||||
String? suggestionReason,
|
||||
}) {
|
||||
return FriendSuggestion(
|
||||
userId: userId ?? this.userId,
|
||||
firstName: firstName ?? this.firstName,
|
||||
lastName: lastName ?? this.lastName,
|
||||
email: email ?? this.email,
|
||||
profileImageUrl: profileImageUrl ?? this.profileImageUrl,
|
||||
mutualFriendsCount: mutualFriendsCount ?? this.mutualFriendsCount,
|
||||
suggestionReason: suggestionReason ?? this.suggestionReason,
|
||||
);
|
||||
}
|
||||
}
|
||||
76
lib/domain/entities/notification.dart
Normal file
76
lib/domain/entities/notification.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant une notification.
|
||||
///
|
||||
/// Cette entité est pure et indépendante de la couche de données.
|
||||
/// Elle représente une notification dans le domaine métier.
|
||||
class Notification extends Equatable {
|
||||
const Notification({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.type,
|
||||
required this.timestamp,
|
||||
this.isRead = false,
|
||||
this.eventId,
|
||||
this.userId,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
final String message;
|
||||
final NotificationType type;
|
||||
final DateTime timestamp;
|
||||
final bool isRead;
|
||||
final String? eventId;
|
||||
final String? userId;
|
||||
final Map<String, dynamic>? metadata;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
title,
|
||||
message,
|
||||
type,
|
||||
timestamp,
|
||||
isRead,
|
||||
eventId,
|
||||
userId,
|
||||
metadata,
|
||||
];
|
||||
|
||||
/// Crée une copie de cette notification avec des valeurs modifiées.
|
||||
Notification copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? message,
|
||||
NotificationType? type,
|
||||
DateTime? timestamp,
|
||||
bool? isRead,
|
||||
String? eventId,
|
||||
String? userId,
|
||||
Map<String, dynamic>? metadata,
|
||||
}) {
|
||||
return Notification(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
message: message ?? this.message,
|
||||
type: type ?? this.type,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
isRead: isRead ?? this.isRead,
|
||||
eventId: eventId ?? this.eventId,
|
||||
userId: userId ?? this.userId,
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type de notification.
|
||||
enum NotificationType {
|
||||
event,
|
||||
friend,
|
||||
reminder,
|
||||
other,
|
||||
}
|
||||
|
||||
90
lib/domain/entities/reservation.dart
Normal file
90
lib/domain/entities/reservation.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant une réservation.
|
||||
///
|
||||
/// Cette entité est pure et indépendante de la couche de données.
|
||||
/// Elle représente une réservation d'événement ou d'établissement.
|
||||
class Reservation extends Equatable {
|
||||
const Reservation({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
required this.userFullName,
|
||||
required this.eventId,
|
||||
required this.eventTitle,
|
||||
required this.reservationDate,
|
||||
required this.numberOfPeople,
|
||||
required this.status,
|
||||
this.establishmentId,
|
||||
this.establishmentName,
|
||||
this.notes,
|
||||
this.createdAt,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String userId;
|
||||
final String userFullName;
|
||||
final String eventId;
|
||||
final String eventTitle;
|
||||
final DateTime reservationDate;
|
||||
final int numberOfPeople;
|
||||
final ReservationStatus status;
|
||||
final String? establishmentId;
|
||||
final String? establishmentName;
|
||||
final String? notes;
|
||||
final DateTime? createdAt;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
userId,
|
||||
userFullName,
|
||||
eventId,
|
||||
eventTitle,
|
||||
reservationDate,
|
||||
numberOfPeople,
|
||||
status,
|
||||
establishmentId,
|
||||
establishmentName,
|
||||
notes,
|
||||
createdAt,
|
||||
];
|
||||
|
||||
/// Crée une copie de cette réservation avec des valeurs modifiées.
|
||||
Reservation copyWith({
|
||||
String? id,
|
||||
String? userId,
|
||||
String? userFullName,
|
||||
String? eventId,
|
||||
String? eventTitle,
|
||||
DateTime? reservationDate,
|
||||
int? numberOfPeople,
|
||||
ReservationStatus? status,
|
||||
String? establishmentId,
|
||||
String? establishmentName,
|
||||
String? notes,
|
||||
DateTime? createdAt,
|
||||
}) {
|
||||
return Reservation(
|
||||
id: id ?? this.id,
|
||||
userId: userId ?? this.userId,
|
||||
userFullName: userFullName ?? this.userFullName,
|
||||
eventId: eventId ?? this.eventId,
|
||||
eventTitle: eventTitle ?? this.eventTitle,
|
||||
reservationDate: reservationDate ?? this.reservationDate,
|
||||
numberOfPeople: numberOfPeople ?? this.numberOfPeople,
|
||||
status: status ?? this.status,
|
||||
establishmentId: establishmentId ?? this.establishmentId,
|
||||
establishmentName: establishmentName ?? this.establishmentName,
|
||||
notes: notes ?? this.notes,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Statut d'une réservation.
|
||||
enum ReservationStatus {
|
||||
pending, // En attente de confirmation
|
||||
confirmed, // Confirmée
|
||||
cancelled, // Annulée
|
||||
completed, // Terminée
|
||||
}
|
||||
86
lib/domain/entities/social_post.dart
Normal file
86
lib/domain/entities/social_post.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Entité de domaine représentant un post social.
|
||||
///
|
||||
/// Cette entité est pure et indépendante de la couche de données.
|
||||
/// Elle représente un post social dans le domaine métier.
|
||||
class SocialPost extends Equatable {
|
||||
const SocialPost({
|
||||
required this.id,
|
||||
required this.content,
|
||||
required this.userId,
|
||||
required this.userFirstName,
|
||||
required this.userLastName,
|
||||
required this.userProfileImageUrl,
|
||||
required this.timestamp,
|
||||
this.imageUrl,
|
||||
this.likesCount = 0,
|
||||
this.commentsCount = 0,
|
||||
this.sharesCount = 0,
|
||||
this.isLikedByCurrentUser = false,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String content;
|
||||
final String userId;
|
||||
final String userFirstName;
|
||||
final String userLastName;
|
||||
final String userProfileImageUrl;
|
||||
final DateTime timestamp;
|
||||
final String? imageUrl;
|
||||
final int likesCount;
|
||||
final int commentsCount;
|
||||
final int sharesCount;
|
||||
final bool isLikedByCurrentUser;
|
||||
|
||||
/// Retourne le nom complet de l'auteur.
|
||||
String get authorFullName => '$userFirstName $userLastName';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
content,
|
||||
userId,
|
||||
userFirstName,
|
||||
userLastName,
|
||||
userProfileImageUrl,
|
||||
timestamp,
|
||||
imageUrl,
|
||||
likesCount,
|
||||
commentsCount,
|
||||
sharesCount,
|
||||
isLikedByCurrentUser,
|
||||
];
|
||||
|
||||
/// Crée une copie de ce post avec des valeurs modifiées.
|
||||
SocialPost copyWith({
|
||||
String? id,
|
||||
String? content,
|
||||
String? userId,
|
||||
String? userFirstName,
|
||||
String? userLastName,
|
||||
String? userProfileImageUrl,
|
||||
DateTime? timestamp,
|
||||
String? imageUrl,
|
||||
int? likesCount,
|
||||
int? commentsCount,
|
||||
int? sharesCount,
|
||||
bool? isLikedByCurrentUser,
|
||||
}) {
|
||||
return SocialPost(
|
||||
id: id ?? this.id,
|
||||
content: content ?? this.content,
|
||||
userId: userId ?? this.userId,
|
||||
userFirstName: userFirstName ?? this.userFirstName,
|
||||
userLastName: userLastName ?? this.userLastName,
|
||||
userProfileImageUrl: userProfileImageUrl ?? this.userProfileImageUrl,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
likesCount: likesCount ?? this.likesCount,
|
||||
commentsCount: commentsCount ?? this.commentsCount,
|
||||
sharesCount: sharesCount ?? this.sharesCount,
|
||||
isLikedByCurrentUser: isLikedByCurrentUser ?? this.isLikedByCurrentUser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
116
lib/domain/entities/story.dart
Normal file
116
lib/domain/entities/story.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Type de média dans une story.
|
||||
enum StoryMediaType {
|
||||
image,
|
||||
video,
|
||||
}
|
||||
|
||||
/// Entité de domaine représentant une story.
|
||||
///
|
||||
/// Cette entité est pure et indépendante de la couche de données.
|
||||
/// Elle représente une story éphémère (24h) dans le domaine métier.
|
||||
class Story extends Equatable {
|
||||
const Story({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
required this.userFirstName,
|
||||
required this.userLastName,
|
||||
required this.userProfileImageUrl,
|
||||
required this.userIsVerified,
|
||||
required this.mediaType,
|
||||
required this.mediaUrl,
|
||||
required this.createdAt,
|
||||
required this.expiresAt,
|
||||
this.thumbnailUrl,
|
||||
this.durationSeconds,
|
||||
this.isActive = true,
|
||||
this.viewsCount = 0,
|
||||
this.hasViewed = false,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String userId;
|
||||
final String userFirstName;
|
||||
final String userLastName;
|
||||
final String userProfileImageUrl;
|
||||
final bool userIsVerified;
|
||||
final StoryMediaType mediaType;
|
||||
final String mediaUrl;
|
||||
final String? thumbnailUrl;
|
||||
final int? durationSeconds;
|
||||
final DateTime createdAt;
|
||||
final DateTime expiresAt;
|
||||
final bool isActive;
|
||||
final int viewsCount;
|
||||
final bool hasViewed;
|
||||
|
||||
/// Retourne le nom complet de l'auteur.
|
||||
String get authorFullName => '$userFirstName $userLastName';
|
||||
|
||||
/// Vérifie si la story est expirée.
|
||||
bool get isExpired => DateTime.now().isAfter(expiresAt);
|
||||
|
||||
/// Retourne la durée formatée (MM:SS) pour les vidéos.
|
||||
String? get formattedDuration {
|
||||
if (durationSeconds == null) return null;
|
||||
final minutes = durationSeconds! ~/ 60;
|
||||
final seconds = durationSeconds! % 60;
|
||||
return '$minutes:${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
userId,
|
||||
userFirstName,
|
||||
userLastName,
|
||||
userProfileImageUrl,
|
||||
userIsVerified,
|
||||
mediaType,
|
||||
mediaUrl,
|
||||
thumbnailUrl,
|
||||
durationSeconds,
|
||||
createdAt,
|
||||
expiresAt,
|
||||
isActive,
|
||||
viewsCount,
|
||||
hasViewed,
|
||||
];
|
||||
|
||||
Story copyWith({
|
||||
String? id,
|
||||
String? userId,
|
||||
String? userFirstName,
|
||||
String? userLastName,
|
||||
String? userProfileImageUrl,
|
||||
bool? userIsVerified,
|
||||
StoryMediaType? mediaType,
|
||||
String? mediaUrl,
|
||||
String? thumbnailUrl,
|
||||
int? durationSeconds,
|
||||
DateTime? createdAt,
|
||||
DateTime? expiresAt,
|
||||
bool? isActive,
|
||||
int? viewsCount,
|
||||
bool? hasViewed,
|
||||
}) {
|
||||
return Story(
|
||||
id: id ?? this.id,
|
||||
userId: userId ?? this.userId,
|
||||
userFirstName: userFirstName ?? this.userFirstName,
|
||||
userLastName: userLastName ?? this.userLastName,
|
||||
userProfileImageUrl: userProfileImageUrl ?? this.userProfileImageUrl,
|
||||
userIsVerified: userIsVerified ?? this.userIsVerified,
|
||||
mediaType: mediaType ?? this.mediaType,
|
||||
mediaUrl: mediaUrl ?? this.mediaUrl,
|
||||
thumbnailUrl: thumbnailUrl ?? this.thumbnailUrl,
|
||||
durationSeconds: durationSeconds ?? this.durationSeconds,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
expiresAt: expiresAt ?? this.expiresAt,
|
||||
isActive: isActive ?? this.isActive,
|
||||
viewsCount: viewsCount ?? this.viewsCount,
|
||||
hasViewed: hasViewed ?? this.hasViewed,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class User extends Equatable {
|
||||
final String userId;
|
||||
final String userLastName;
|
||||
final String userFirstName;
|
||||
final String email;
|
||||
final String motDePasse;
|
||||
final String profileImageUrl;
|
||||
final int eventsCount;
|
||||
final int friendsCount;
|
||||
final int postsCount;
|
||||
final int visitedPlacesCount;
|
||||
|
||||
const User({
|
||||
required this.userId,
|
||||
@@ -19,11 +9,27 @@ class User extends Equatable {
|
||||
required this.email,
|
||||
required this.motDePasse,
|
||||
required this.profileImageUrl,
|
||||
this.isVerified = false,
|
||||
this.eventsCount = 0,
|
||||
this.friendsCount = 0,
|
||||
this.postsCount = 0,
|
||||
this.visitedPlacesCount = 0,
|
||||
this.isOnline = false,
|
||||
this.lastSeen,
|
||||
});
|
||||
final String userId;
|
||||
final String userLastName;
|
||||
final String userFirstName;
|
||||
final String email;
|
||||
final String motDePasse;
|
||||
final String profileImageUrl;
|
||||
final bool isVerified;
|
||||
final int eventsCount;
|
||||
final int friendsCount;
|
||||
final int postsCount;
|
||||
final int visitedPlacesCount;
|
||||
final bool isOnline;
|
||||
final DateTime? lastSeen;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
@@ -33,9 +39,12 @@ class User extends Equatable {
|
||||
email,
|
||||
motDePasse,
|
||||
profileImageUrl,
|
||||
isVerified,
|
||||
eventsCount,
|
||||
friendsCount,
|
||||
postsCount,
|
||||
visitedPlacesCount,
|
||||
isOnline,
|
||||
lastSeen,
|
||||
];
|
||||
}
|
||||
|
||||
116
lib/domain/repositories/chat_repository.dart
Normal file
116
lib/domain/repositories/chat_repository.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../entities/chat_message.dart';
|
||||
import '../entities/conversation.dart';
|
||||
|
||||
/// Interface pour le repository du chat.
|
||||
///
|
||||
/// Cette interface définit les contrats que doit respecter tout repository
|
||||
/// qui gère les données relatives aux conversations et aux messages de chat.
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// final result = await chatRepository.getConversations('userId');
|
||||
/// result.fold(
|
||||
/// (failure) => print('Erreur: $failure'),
|
||||
/// (conversations) => print('${conversations.length} conversations'),
|
||||
/// );
|
||||
/// ```
|
||||
abstract class ChatRepository {
|
||||
/// Récupère toutes les conversations d'un utilisateur.
|
||||
///
|
||||
/// [userId] L'identifiant de l'utilisateur
|
||||
///
|
||||
/// Returns [Right] avec la liste des conversations si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, List<Conversation>>> getConversations(String userId);
|
||||
|
||||
/// Récupère ou crée une conversation avec un utilisateur.
|
||||
///
|
||||
/// [userId] L'identifiant de l'utilisateur actuel
|
||||
/// [participantId] L'identifiant de l'autre participant
|
||||
///
|
||||
/// Returns [Right] avec la conversation si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, Conversation>> getOrCreateConversation(
|
||||
String userId,
|
||||
String participantId,
|
||||
);
|
||||
|
||||
/// Récupère les messages d'une conversation avec pagination.
|
||||
///
|
||||
/// [conversationId] L'identifiant de la conversation
|
||||
/// [page] Le numéro de page (défaut: 0)
|
||||
/// [size] La taille de la page (défaut: 50)
|
||||
///
|
||||
/// Returns [Right] avec la liste des messages si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, List<ChatMessage>>> getMessages(
|
||||
String conversationId, {
|
||||
int page = 0,
|
||||
int size = 50,
|
||||
});
|
||||
|
||||
/// Envoie un nouveau message.
|
||||
///
|
||||
/// [senderId] L'identifiant de l'expéditeur
|
||||
/// [recipientId] L'identifiant du destinataire
|
||||
/// [content] Le contenu du message
|
||||
/// [messageType] Le type de message (text, image, video, audio, file)
|
||||
/// [mediaUrl] L'URL du média (optionnel)
|
||||
///
|
||||
/// Returns [Right] avec le message créé si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, ChatMessage>> sendMessage({
|
||||
required String senderId,
|
||||
required String recipientId,
|
||||
required String content,
|
||||
String? messageType,
|
||||
String? mediaUrl,
|
||||
});
|
||||
|
||||
/// Marque un message comme lu.
|
||||
///
|
||||
/// [messageId] L'identifiant du message
|
||||
///
|
||||
/// Returns [Right] avec void si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, void>> markMessageAsRead(String messageId);
|
||||
|
||||
/// Marque tous les messages d'une conversation comme lus.
|
||||
///
|
||||
/// [conversationId] L'identifiant de la conversation
|
||||
/// [userId] L'identifiant de l'utilisateur
|
||||
///
|
||||
/// Returns [Right] avec void si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, void>> markConversationAsRead(
|
||||
String conversationId,
|
||||
String userId,
|
||||
);
|
||||
|
||||
/// Supprime un message.
|
||||
///
|
||||
/// [messageId] L'identifiant du message
|
||||
///
|
||||
/// Returns [Right] avec void si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, void>> deleteMessage(String messageId);
|
||||
|
||||
/// Supprime une conversation.
|
||||
///
|
||||
/// [conversationId] L'identifiant de la conversation
|
||||
///
|
||||
/// Returns [Right] avec void si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, void>> deleteConversation(String conversationId);
|
||||
|
||||
/// Récupère le nombre de messages non lus pour un utilisateur.
|
||||
///
|
||||
/// [userId] L'identifiant de l'utilisateur
|
||||
///
|
||||
/// Returns [Right] avec le nombre de messages non lus si succès,
|
||||
/// [Left] avec une [Failure] si erreur
|
||||
Future<Either<Failure, int>> getUnreadMessagesCount(String userId);
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:afterwork/domain/entities/user.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../entities/user.dart';
|
||||
|
||||
/// Interface pour le dépôt de l'utilisateur.
|
||||
/// Cette interface définit les contrats que doit respecter tout dépôt
|
||||
/// qui gère les données relatives aux utilisateurs.
|
||||
abstract class UserRepository {
|
||||
/// Méthode pour récupérer un utilisateur par son identifiant.
|
||||
/// Cette méthode retourne un objet [User] ou lève une exception en cas d'échec.
|
||||
Future<User> getUser(String id) {
|
||||
print("Appel à la méthode getUser avec l'ID : $id");
|
||||
throw UnimplementedError("Cette méthode doit être implémentée dans une classe concrète.");
|
||||
}
|
||||
/// Cette méthode retourne [Right] avec un objet [User] si succès,
|
||||
/// [Left] avec une [Failure] si erreur.
|
||||
Future<Either<Failure, User>> getUser(String id);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:afterwork/domain/entities/user.dart';
|
||||
import 'package:afterwork/domain/repositories/user_repository.dart';
|
||||
import 'package:afterwork/core/errors/failures.dart';
|
||||
|
||||
import '../../core/errors/failures.dart';
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../entities/user.dart';
|
||||
import '../repositories/user_repository.dart';
|
||||
|
||||
/// Classe qui implémente le cas d'utilisation permettant de récupérer un utilisateur par son ID.
|
||||
/// Elle interagit avec le dépôt d'utilisateur pour récupérer les données utilisateur.
|
||||
class GetUser {
|
||||
final UserRepository repository; // Référence au dépôt d'utilisateur
|
||||
|
||||
/// Constructeur qui prend en paramètre un dépôt d'utilisateur.
|
||||
GetUser(this.repository) {
|
||||
print("Initialisation de GetUser avec le UserRepository.");
|
||||
AppLogger.d('Initialisation de GetUser avec le UserRepository.', tag: 'GetUser');
|
||||
}
|
||||
final UserRepository repository;
|
||||
|
||||
/// Méthode pour récupérer un utilisateur par son ID.
|
||||
/// Retourne soit un [User], soit une [Failure] en cas d'erreur.
|
||||
Future<Either<Failure, User>> call(String id) async {
|
||||
print("Appel à GetUser avec l'ID : $id");
|
||||
AppLogger.d("Appel à GetUser avec l'ID : $id", tag: 'GetUser');
|
||||
|
||||
try {
|
||||
// Appel au dépôt pour récupérer l'utilisateur
|
||||
final user = await repository.getUser(id);
|
||||
print("Utilisateur récupéré avec succès : ${user.userId}");
|
||||
return Right(user);
|
||||
} catch (e) {
|
||||
print("Erreur lors de la récupération de l'utilisateur : $e");
|
||||
return Left(ServerFailure());
|
||||
}
|
||||
// Appel au dépôt pour récupérer l'utilisateur
|
||||
final result = await repository.getUser(id);
|
||||
|
||||
// Logger le résultat
|
||||
result.fold(
|
||||
(failure) => AppLogger.e('Erreur lors de la récupération de l\'utilisateur', error: failure, tag: 'GetUser'),
|
||||
(user) => AppLogger.d('Utilisateur récupéré avec succès : ${user.userId}', tag: 'GetUser'),
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user