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:
136
lib/data/models/chat_message_model.dart
Normal file
136
lib/data/models/chat_message_model.dart
Normal file
@@ -0,0 +1,136 @@
|
||||
import '../../domain/entities/chat_message.dart';
|
||||
|
||||
/// Modèle de données pour les messages de chat (Data Transfer Object).
|
||||
class ChatMessageModel {
|
||||
ChatMessageModel({
|
||||
required this.id,
|
||||
required this.conversationId,
|
||||
required this.senderId,
|
||||
required this.senderFirstName,
|
||||
required this.senderLastName,
|
||||
this.senderProfileImageUrl,
|
||||
required this.content,
|
||||
required this.timestamp,
|
||||
required this.isRead,
|
||||
this.isDelivered = false,
|
||||
this.attachmentUrl,
|
||||
this.attachmentType,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [ChatMessageModel] à partir d'un JSON.
|
||||
factory ChatMessageModel.fromJson(Map<String, dynamic> json) {
|
||||
return ChatMessageModel(
|
||||
id: _parseId(json, 'id', ''),
|
||||
conversationId: _parseString(json, 'conversationId', ''),
|
||||
senderId: _parseId(json, 'senderId', ''),
|
||||
senderFirstName: _parseString(json, 'senderFirstName', ''),
|
||||
senderLastName: _parseString(json, 'senderLastName', ''),
|
||||
senderProfileImageUrl: json['senderProfileImageUrl'] as String?,
|
||||
content: _parseString(json, 'content', ''),
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
isRead: json['isRead'] as bool? ?? false,
|
||||
isDelivered: json['isDelivered'] as bool? ?? false,
|
||||
attachmentUrl: json['attachmentUrl'] as String?,
|
||||
attachmentType: _parseAttachmentType(json['attachmentType'] as String?),
|
||||
);
|
||||
}
|
||||
|
||||
/// Factory pour créer un [ChatMessageModel] à partir d'une entité.
|
||||
factory ChatMessageModel.fromEntity(ChatMessage message) {
|
||||
return ChatMessageModel(
|
||||
id: message.id,
|
||||
conversationId: message.conversationId,
|
||||
senderId: message.senderId,
|
||||
senderFirstName: message.senderFirstName,
|
||||
senderLastName: message.senderLastName,
|
||||
senderProfileImageUrl: message.senderProfileImageUrl,
|
||||
content: message.content,
|
||||
timestamp: message.timestamp,
|
||||
isRead: message.isRead,
|
||||
isDelivered: message.isDelivered,
|
||||
attachmentUrl: message.attachmentUrl,
|
||||
attachmentType: message.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;
|
||||
final AttachmentType? attachmentType;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'conversationId': conversationId,
|
||||
'senderId': senderId,
|
||||
'senderFirstName': senderFirstName,
|
||||
'senderLastName': senderLastName,
|
||||
if (senderProfileImageUrl != null) 'senderProfileImageUrl': senderProfileImageUrl,
|
||||
'content': content,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'isRead': isRead,
|
||||
'isDelivered': isDelivered,
|
||||
if (attachmentUrl != null) 'attachmentUrl': attachmentUrl,
|
||||
if (attachmentType != null) 'attachmentType': _attachmentTypeToString(attachmentType!),
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [ChatMessage].
|
||||
ChatMessage toEntity() {
|
||||
return ChatMessage(
|
||||
id: id,
|
||||
conversationId: conversationId,
|
||||
senderId: senderId,
|
||||
senderFirstName: senderFirstName,
|
||||
senderLastName: senderLastName,
|
||||
senderProfileImageUrl: senderProfileImageUrl,
|
||||
content: content,
|
||||
timestamp: timestamp,
|
||||
isRead: isRead,
|
||||
isDelivered: isDelivered,
|
||||
attachmentUrl: attachmentUrl,
|
||||
attachmentType: attachmentType,
|
||||
);
|
||||
}
|
||||
|
||||
// Méthodes de parsing
|
||||
static String _parseString(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
static String _parseId(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
static AttachmentType? _parseAttachmentType(String? type) {
|
||||
if (type == null) return null;
|
||||
|
||||
switch (type.toLowerCase()) {
|
||||
case 'image':
|
||||
return AttachmentType.image;
|
||||
case 'video':
|
||||
return AttachmentType.video;
|
||||
case 'audio':
|
||||
return AttachmentType.audio;
|
||||
case 'file':
|
||||
return AttachmentType.file;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String _attachmentTypeToString(AttachmentType type) {
|
||||
return type.toString().split('.').last;
|
||||
}
|
||||
}
|
||||
128
lib/data/models/comment_model.dart
Normal file
128
lib/data/models/comment_model.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import '../../domain/entities/comment.dart';
|
||||
|
||||
/// Modèle de données pour les commentaires (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine Comment.
|
||||
class CommentModel {
|
||||
CommentModel({
|
||||
required this.id,
|
||||
required this.postId,
|
||||
required this.userId,
|
||||
required this.userFirstName,
|
||||
required this.userLastName,
|
||||
required this.userProfileImageUrl,
|
||||
required this.content,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [CommentModel] à partir d'un JSON.
|
||||
factory CommentModel.fromJson(Map<String, dynamic> json) {
|
||||
return CommentModel(
|
||||
id: _parseId(json, 'id', ''),
|
||||
postId: _parseId(json, 'postId', ''),
|
||||
userId: _parseId(json, 'userId', ''),
|
||||
userFirstName: _parseString(json, 'userFirstName', ''),
|
||||
userLastName: _parseString(json, 'userLastName', ''),
|
||||
userProfileImageUrl: _parseString(json, 'userProfileImageUrl', ''),
|
||||
content: _parseString(json, 'content', ''),
|
||||
timestamp: _parseTimestamp(json['timestamp']),
|
||||
);
|
||||
}
|
||||
|
||||
/// Crée un [CommentModel] depuis une entité de domaine [Comment].
|
||||
factory CommentModel.fromEntity(Comment comment) {
|
||||
return CommentModel(
|
||||
id: comment.id,
|
||||
postId: comment.postId,
|
||||
userId: comment.userId,
|
||||
userFirstName: comment.userFirstName,
|
||||
userLastName: comment.userLastName,
|
||||
userProfileImageUrl: comment.userProfileImageUrl,
|
||||
content: comment.content,
|
||||
timestamp: comment.timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String postId;
|
||||
final String userId;
|
||||
final String userFirstName;
|
||||
final String userLastName;
|
||||
final String userProfileImageUrl;
|
||||
final String content;
|
||||
final DateTime timestamp;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'postId': postId,
|
||||
'userId': userId,
|
||||
'userFirstName': userFirstName,
|
||||
'userLastName': userLastName,
|
||||
'userProfileImageUrl': userProfileImageUrl,
|
||||
'content': content,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [Comment].
|
||||
Comment toEntity() {
|
||||
return Comment(
|
||||
id: id,
|
||||
postId: postId,
|
||||
userId: userId,
|
||||
userFirstName: userFirstName,
|
||||
userLastName: userLastName,
|
||||
userProfileImageUrl: userProfileImageUrl,
|
||||
content: content,
|
||||
timestamp: timestamp,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse une valeur string depuis le JSON avec valeur par défaut.
|
||||
static String _parseString(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
/// Parse un timestamp depuis le JSON.
|
||||
static DateTime _parseTimestamp(dynamic timestamp) {
|
||||
if (timestamp == null) return DateTime.now();
|
||||
|
||||
if (timestamp is String) {
|
||||
try {
|
||||
return DateTime.parse(timestamp);
|
||||
} catch (e) {
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp is int) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
}
|
||||
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
/// Parse un ID (UUID) depuis le JSON.
|
||||
///
|
||||
/// [json] Le JSON à parser
|
||||
/// [key] La clé de l'ID
|
||||
/// [defaultValue] La valeur par défaut si l'ID est null
|
||||
///
|
||||
/// Returns l'ID parsé ou la valeur par défaut
|
||||
static String _parseId(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
99
lib/data/models/conversation_model.dart
Normal file
99
lib/data/models/conversation_model.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import '../../domain/entities/conversation.dart';
|
||||
|
||||
/// Modèle de données pour les conversations (Data Transfer Object).
|
||||
class ConversationModel {
|
||||
ConversationModel({
|
||||
required this.id,
|
||||
required this.participantId,
|
||||
required this.participantFirstName,
|
||||
required this.participantLastName,
|
||||
this.participantProfileImageUrl,
|
||||
this.lastMessage,
|
||||
this.lastMessageTimestamp,
|
||||
required this.unreadCount,
|
||||
this.isTyping = false,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [ConversationModel] à partir d'un JSON.
|
||||
factory ConversationModel.fromJson(Map<String, dynamic> json) {
|
||||
return ConversationModel(
|
||||
id: _parseId(json, 'id', ''),
|
||||
participantId: _parseId(json, 'participantId', ''),
|
||||
participantFirstName: _parseString(json, 'participantFirstName', ''),
|
||||
participantLastName: _parseString(json, 'participantLastName', ''),
|
||||
participantProfileImageUrl: json['participantProfileImageUrl'] as String?,
|
||||
lastMessage: json['lastMessage'] as String?,
|
||||
lastMessageTimestamp: json['lastMessageTimestamp'] != null
|
||||
? DateTime.parse(json['lastMessageTimestamp'] as String)
|
||||
: null,
|
||||
unreadCount: json['unreadCount'] as int? ?? 0,
|
||||
isTyping: json['isTyping'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Factory pour créer un [ConversationModel] à partir d'une entité.
|
||||
factory ConversationModel.fromEntity(Conversation conversation) {
|
||||
return ConversationModel(
|
||||
id: conversation.id,
|
||||
participantId: conversation.participantId,
|
||||
participantFirstName: conversation.participantFirstName,
|
||||
participantLastName: conversation.participantLastName,
|
||||
participantProfileImageUrl: conversation.participantProfileImageUrl,
|
||||
lastMessage: conversation.lastMessage,
|
||||
lastMessageTimestamp: conversation.lastMessageTimestamp,
|
||||
unreadCount: conversation.unreadCount,
|
||||
isTyping: conversation.isTyping,
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String participantId;
|
||||
final String participantFirstName;
|
||||
final String participantLastName;
|
||||
final String? participantProfileImageUrl;
|
||||
final String? lastMessage;
|
||||
final DateTime? lastMessageTimestamp;
|
||||
final int unreadCount;
|
||||
final bool isTyping;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'participantId': participantId,
|
||||
'participantFirstName': participantFirstName,
|
||||
'participantLastName': participantLastName,
|
||||
if (participantProfileImageUrl != null) 'participantProfileImageUrl': participantProfileImageUrl,
|
||||
if (lastMessage != null) 'lastMessage': lastMessage,
|
||||
if (lastMessageTimestamp != null) 'lastMessageTimestamp': lastMessageTimestamp!.toIso8601String(),
|
||||
'unreadCount': unreadCount,
|
||||
'isTyping': isTyping,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [Conversation].
|
||||
Conversation toEntity() {
|
||||
return Conversation(
|
||||
id: id,
|
||||
participantId: participantId,
|
||||
participantFirstName: participantFirstName,
|
||||
participantLastName: participantLastName,
|
||||
participantProfileImageUrl: participantProfileImageUrl,
|
||||
lastMessage: lastMessage,
|
||||
lastMessageTimestamp: lastMessageTimestamp,
|
||||
unreadCount: unreadCount,
|
||||
isTyping: isTyping,
|
||||
);
|
||||
}
|
||||
|
||||
// Méthodes de parsing
|
||||
static String _parseString(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
static String _parseId(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:afterwork/data/models/user_model.dart';
|
||||
import 'user_model.dart';
|
||||
|
||||
/// Modèle représentant le créateur d'un événement.
|
||||
class CreatorModel extends UserModel {
|
||||
CreatorModel({
|
||||
const CreatorModel({
|
||||
required String id,
|
||||
required String nom,
|
||||
required String prenoms,
|
||||
|
||||
190
lib/data/models/establishment_model.dart
Normal file
190
lib/data/models/establishment_model.dart
Normal file
@@ -0,0 +1,190 @@
|
||||
import '../../domain/entities/establishment.dart';
|
||||
|
||||
/// Modèle de données pour les établissements (Data Transfer Object).
|
||||
class EstablishmentModel {
|
||||
EstablishmentModel({
|
||||
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,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [EstablishmentModel] à partir d'un JSON.
|
||||
factory EstablishmentModel.fromJson(Map<String, dynamic> json) {
|
||||
return EstablishmentModel(
|
||||
id: _parseId(json, 'id', ''),
|
||||
name: _parseString(json, 'name', ''),
|
||||
type: _parseType(json['type'] as String?),
|
||||
address: _parseString(json, 'address', ''),
|
||||
city: _parseString(json, 'city', ''),
|
||||
postalCode: _parseString(json, 'postalCode', ''),
|
||||
description: json['description'] as String?,
|
||||
phoneNumber: json['phoneNumber'] as String?,
|
||||
email: json['email'] as String?,
|
||||
website: json['website'] as String?,
|
||||
imageUrl: json['imageUrl'] as String?,
|
||||
rating: json['rating'] != null ? (json['rating'] as num).toDouble() : null,
|
||||
priceRange: _parsePriceRange(json['priceRange'] as String?),
|
||||
capacity: json['capacity'] as int?,
|
||||
amenities: json['amenities'] != null
|
||||
? List<String>.from(json['amenities'] as List)
|
||||
: [],
|
||||
openingHours: json['openingHours'] as String?,
|
||||
latitude: json['latitude'] != null ? (json['latitude'] as num).toDouble() : null,
|
||||
longitude: json['longitude'] != null ? (json['longitude'] as num).toDouble() : null,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
final PriceRange? priceRange;
|
||||
final int? capacity;
|
||||
final List<String> amenities;
|
||||
final String? openingHours;
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'type': _typeToString(type),
|
||||
'address': address,
|
||||
'city': city,
|
||||
'postalCode': postalCode,
|
||||
if (description != null) 'description': description,
|
||||
if (phoneNumber != null) 'phoneNumber': phoneNumber,
|
||||
if (email != null) 'email': email,
|
||||
if (website != null) 'website': website,
|
||||
if (imageUrl != null) 'imageUrl': imageUrl,
|
||||
if (rating != null) 'rating': rating,
|
||||
if (priceRange != null) 'priceRange': _priceRangeToString(priceRange!),
|
||||
if (capacity != null) 'capacity': capacity,
|
||||
if (amenities.isNotEmpty) 'amenities': amenities,
|
||||
if (openingHours != null) 'openingHours': openingHours,
|
||||
if (latitude != null) 'latitude': latitude,
|
||||
if (longitude != null) 'longitude': longitude,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [Establishment].
|
||||
Establishment toEntity() {
|
||||
return Establishment(
|
||||
id: id,
|
||||
name: name,
|
||||
type: type,
|
||||
address: address,
|
||||
city: city,
|
||||
postalCode: postalCode,
|
||||
description: description,
|
||||
phoneNumber: phoneNumber,
|
||||
email: email,
|
||||
website: website,
|
||||
imageUrl: imageUrl,
|
||||
rating: rating,
|
||||
priceRange: priceRange,
|
||||
capacity: capacity,
|
||||
amenities: amenities,
|
||||
openingHours: openingHours,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
);
|
||||
}
|
||||
|
||||
// Méthodes de parsing
|
||||
static String _parseString(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
static String _parseId(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
static EstablishmentType _parseType(String? type) {
|
||||
if (type == null) return EstablishmentType.other;
|
||||
|
||||
switch (type.toLowerCase()) {
|
||||
case 'bar':
|
||||
return EstablishmentType.bar;
|
||||
case 'restaurant':
|
||||
return EstablishmentType.restaurant;
|
||||
case 'club':
|
||||
return EstablishmentType.club;
|
||||
case 'cafe':
|
||||
case 'café':
|
||||
return EstablishmentType.cafe;
|
||||
case 'lounge':
|
||||
return EstablishmentType.lounge;
|
||||
case 'pub':
|
||||
return EstablishmentType.pub;
|
||||
case 'brewery':
|
||||
case 'brasserie':
|
||||
return EstablishmentType.brewery;
|
||||
case 'winery':
|
||||
case 'cave':
|
||||
return EstablishmentType.winery;
|
||||
default:
|
||||
return EstablishmentType.other;
|
||||
}
|
||||
}
|
||||
|
||||
static PriceRange? _parsePriceRange(String? priceRange) {
|
||||
if (priceRange == null) return null;
|
||||
|
||||
switch (priceRange.toLowerCase()) {
|
||||
case 'cheap':
|
||||
case 'économique':
|
||||
case '€':
|
||||
return PriceRange.cheap;
|
||||
case 'moderate':
|
||||
case 'modéré':
|
||||
case '€€':
|
||||
return PriceRange.moderate;
|
||||
case 'expensive':
|
||||
case 'cher':
|
||||
case '€€€':
|
||||
return PriceRange.expensive;
|
||||
case 'luxury':
|
||||
case 'luxe':
|
||||
case '€€€€':
|
||||
return PriceRange.luxury;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String _typeToString(EstablishmentType type) {
|
||||
return type.toString().split('.').last;
|
||||
}
|
||||
|
||||
static String _priceRangeToString(PriceRange priceRange) {
|
||||
return priceRange.toString().split('.').last;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,44 @@
|
||||
class EventModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String description;
|
||||
final String startDate;
|
||||
final String location;
|
||||
final String category;
|
||||
final String link;
|
||||
final String? imageUrl;
|
||||
final String creatorEmail;
|
||||
final String creatorFirstName; // Prénom du créateur
|
||||
final String creatorLastName; // Nom du créateur
|
||||
final String profileImageUrl;
|
||||
final List<dynamic> participants;
|
||||
String status;
|
||||
final int reactionsCount;
|
||||
final int commentsCount;
|
||||
final int sharesCount;
|
||||
import '../../core/constants/env_config.dart';
|
||||
import '../../core/errors/exceptions.dart';
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../../domain/entities/event.dart';
|
||||
|
||||
/// Modèle de données pour les événements (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine [Event].
|
||||
///
|
||||
/// **Usage:**
|
||||
/// ```dart
|
||||
/// // Depuis JSON
|
||||
/// final event = EventModel.fromJson(jsonData);
|
||||
///
|
||||
/// // Vers JSON
|
||||
/// final json = event.toJson();
|
||||
///
|
||||
/// // Vers entité de domaine
|
||||
/// final entity = event.toEntity();
|
||||
/// ```
|
||||
class EventModel {
|
||||
/// Crée une nouvelle instance de [EventModel].
|
||||
///
|
||||
/// [id] L'identifiant unique de l'événement
|
||||
/// [title] Le titre de l'événement
|
||||
/// [description] La description de l'événement
|
||||
/// [startDate] La date de début (format ISO 8601 string)
|
||||
/// [location] Le lieu de l'événement
|
||||
/// [category] La catégorie de l'événement
|
||||
/// [link] Le lien associé (optionnel)
|
||||
/// [imageUrl] L'URL de l'image (optionnel)
|
||||
/// [creatorEmail] L'email du créateur
|
||||
/// [creatorFirstName] Le prénom du créateur
|
||||
/// [creatorLastName] Le nom du créateur
|
||||
/// [profileImageUrl] L'URL de l'image de profil du créateur
|
||||
/// [participants] La liste des participants (IDs ou objets)
|
||||
/// [status] Le statut de l'événement ('ouvert', 'fermé', 'annulé')
|
||||
/// [reactionsCount] Le nombre de réactions
|
||||
/// [commentsCount] Le nombre de commentaires
|
||||
/// [sharesCount] Le nombre de partages
|
||||
EventModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
@@ -25,7 +47,6 @@ class EventModel {
|
||||
required this.location,
|
||||
required this.category,
|
||||
required this.link,
|
||||
this.imageUrl,
|
||||
required this.creatorEmail,
|
||||
required this.creatorFirstName,
|
||||
required this.creatorLastName,
|
||||
@@ -35,73 +56,211 @@ class EventModel {
|
||||
required this.reactionsCount,
|
||||
required this.commentsCount,
|
||||
required this.sharesCount,
|
||||
this.imageUrl,
|
||||
});
|
||||
|
||||
/// L'identifiant unique de l'événement
|
||||
final String id;
|
||||
|
||||
/// Le titre de l'événement
|
||||
final String title;
|
||||
|
||||
/// La description de l'événement
|
||||
final String description;
|
||||
|
||||
/// La date de début (format ISO 8601 string)
|
||||
final String startDate;
|
||||
|
||||
/// Le lieu de l'événement
|
||||
final String location;
|
||||
|
||||
/// La catégorie de l'événement
|
||||
final String category;
|
||||
|
||||
/// Le lien associé à l'événement
|
||||
final String link;
|
||||
|
||||
/// L'URL de l'image de l'événement (optionnel)
|
||||
final String? imageUrl;
|
||||
|
||||
/// L'email du créateur de l'événement
|
||||
final String creatorEmail;
|
||||
|
||||
/// Le prénom du créateur
|
||||
final String creatorFirstName;
|
||||
|
||||
/// Le nom du créateur
|
||||
final String creatorLastName;
|
||||
|
||||
/// L'URL de l'image de profil du créateur
|
||||
final String profileImageUrl;
|
||||
|
||||
/// La liste des participants (peut contenir des IDs ou des objets)
|
||||
final List<dynamic> participants;
|
||||
|
||||
/// Le statut de l'événement ('ouvert', 'fermé', 'annulé')
|
||||
String status;
|
||||
|
||||
/// Le nombre de réactions
|
||||
final int reactionsCount;
|
||||
|
||||
/// Le nombre de commentaires
|
||||
final int commentsCount;
|
||||
|
||||
/// Le nombre de partages
|
||||
final int sharesCount;
|
||||
|
||||
// ============================================================================
|
||||
// FACTORY METHODS
|
||||
// ============================================================================
|
||||
|
||||
/// Crée un [EventModel] à partir d'un JSON reçu depuis l'API.
|
||||
///
|
||||
/// [json] Les données JSON à parser
|
||||
///
|
||||
/// Returns un [EventModel] avec les données parsées
|
||||
///
|
||||
/// Throws [ValidationException] si les données essentielles sont manquantes
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final json = {
|
||||
/// 'id': '123',
|
||||
/// 'title': 'Concert',
|
||||
/// 'startDate': '2026-01-10T20:00:00Z',
|
||||
/// ...
|
||||
/// };
|
||||
/// final event = EventModel.fromJson(json);
|
||||
/// ```
|
||||
factory EventModel.fromJson(Map<String, dynamic> json) {
|
||||
print('[LOG] Création de l\'EventModel depuis JSON');
|
||||
try {
|
||||
// Validation des champs essentiels
|
||||
if (json['id'] == null || json['id'].toString().isEmpty) {
|
||||
throw ValidationException(
|
||||
'L\'ID de l\'événement est requis',
|
||||
field: 'id',
|
||||
);
|
||||
}
|
||||
|
||||
// Utiliser les valeurs par défaut si une clé est absente
|
||||
final String id = json['id'] ?? 'ID Inconnu';
|
||||
final String title = json['title'] ?? 'Titre Inconnu';
|
||||
final String description = json['description'] ?? 'Description Inconnue';
|
||||
final String startDate = json['startDate'] ?? 'Date de début Inconnue';
|
||||
final String location = json['location'] ?? 'Localisation Inconnue';
|
||||
final String category = json['category'] ?? 'Catégorie Inconnue';
|
||||
final String link = json['link'] ?? 'Lien Inconnu';
|
||||
final String? imageUrl = json['imageUrl'];
|
||||
final String creatorEmail = json['creatorEmail'] ?? 'Email Inconnu';
|
||||
final String creatorFirstName = json['creatorFirstName']; // Ajout du prénom
|
||||
final String creatorLastName = json['creatorLastName']; // Ajout du nom
|
||||
final String profileImageUrl = json['profileImageUrl']; // Ajout du nom
|
||||
final List<dynamic> participants = json['participants'] ?? [];
|
||||
String status = json['status'] ?? 'ouvert';
|
||||
final int reactionsCount = json['reactionsCount'] ?? 0;
|
||||
final int commentsCount = json['commentsCount'] ?? 0;
|
||||
final int sharesCount = json['sharesCount'] ?? 0;
|
||||
if (json['title'] == null || json['title'].toString().isEmpty) {
|
||||
throw ValidationException(
|
||||
'Le titre de l\'événement est requis',
|
||||
field: 'title',
|
||||
);
|
||||
}
|
||||
|
||||
print('[LOG] Champs extraits depuis JSON :');
|
||||
print(' - ID: $id');
|
||||
print(' - Titre: $title');
|
||||
print(' - Description: $description');
|
||||
print(' - Date de début: $startDate');
|
||||
print(' - Localisation: $location');
|
||||
print(' - Catégorie: $category');
|
||||
print(' - Lien: $link');
|
||||
print(' - URL de l\'image: ${imageUrl ?? "Aucune"}');
|
||||
print(' - Email du créateur: $creatorEmail');
|
||||
print(' - Prénom du créateur: $creatorFirstName');
|
||||
print(' - Nom du créateur: $creatorLastName');
|
||||
print(' - Image de profile du créateur: $profileImageUrl');
|
||||
print(' - Participants: ${participants.length} participants');
|
||||
print(' - Statut: $status');
|
||||
print(' - Nombre de réactions: $reactionsCount');
|
||||
print(' - Nombre de commentaires: $commentsCount');
|
||||
print(' - Nombre de partages: $sharesCount');
|
||||
if (json['startDate'] == null || json['startDate'].toString().isEmpty) {
|
||||
throw ValidationException(
|
||||
'La date de début est requise',
|
||||
field: 'startDate',
|
||||
);
|
||||
}
|
||||
|
||||
// Parsing avec valeurs par défaut pour les champs optionnels
|
||||
final model = EventModel(
|
||||
id: json['id'].toString(),
|
||||
title: json['title'].toString(),
|
||||
description: json['description']?.toString() ?? '',
|
||||
startDate: json['startDate'].toString(),
|
||||
location: json['location']?.toString() ?? '',
|
||||
category: json['category']?.toString() ?? 'Autre',
|
||||
link: json['link']?.toString() ?? '',
|
||||
imageUrl: json['imageUrl']?.toString(),
|
||||
creatorEmail: json['creatorEmail']?.toString() ?? '',
|
||||
creatorFirstName: json['creatorFirstName']?.toString() ?? '',
|
||||
creatorLastName: json['creatorLastName']?.toString() ?? '',
|
||||
profileImageUrl: json['profileImageUrl']?.toString() ?? '',
|
||||
participants: json['participants'] is List
|
||||
? json['participants'] as List<dynamic>
|
||||
: [],
|
||||
status: json['status']?.toString() ?? 'ouvert',
|
||||
reactionsCount: _parseInt(json, 'reactionsCount') ?? 0,
|
||||
commentsCount: _parseInt(json, 'commentsCount') ?? 0,
|
||||
sharesCount: _parseInt(json, 'sharesCount') ?? 0,
|
||||
);
|
||||
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
_logEventParsed(model);
|
||||
}
|
||||
|
||||
return model;
|
||||
} catch (e, stackTrace) {
|
||||
if (e is ValidationException) rethrow;
|
||||
AppLogger.e('Erreur lors du parsing JSON', error: e, stackTrace: stackTrace, tag: 'EventModel');
|
||||
throw ValidationException(
|
||||
'Erreur lors du parsing de l\'événement: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse une valeur int depuis le JSON.
|
||||
static int? _parseInt(Map<String, dynamic> json, String key) {
|
||||
final value = json[key];
|
||||
if (value == null) return null;
|
||||
if (value is int) return value;
|
||||
if (value is String) {
|
||||
return int.tryParse(value);
|
||||
}
|
||||
if (value is double) {
|
||||
return value.toInt();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Log les détails d'un événement parsé (uniquement en mode debug).
|
||||
static void _logEventParsed(EventModel event) {
|
||||
AppLogger.d('Événement parsé: ID=${event.id}, Titre=${event.title}, Date=${event.startDate}, Localisation=${event.location}, Statut=${event.status}, Participants=${event.participants.length}', tag: 'EventModel');
|
||||
}
|
||||
|
||||
/// Crée un [EventModel] depuis une entité de domaine [Event].
|
||||
///
|
||||
/// [event] L'entité de domaine à convertir
|
||||
///
|
||||
/// Returns un [EventModel] avec les données de l'entité
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final entity = Event(...);
|
||||
/// final model = EventModel.fromEntity(entity);
|
||||
/// ```
|
||||
factory EventModel.fromEntity(Event event) {
|
||||
return EventModel(
|
||||
id: id,
|
||||
title: title,
|
||||
description: description,
|
||||
startDate: startDate,
|
||||
location: location,
|
||||
category: category,
|
||||
link: link,
|
||||
imageUrl: imageUrl,
|
||||
creatorEmail: creatorEmail,
|
||||
creatorFirstName: creatorFirstName, // Ajout du prénom
|
||||
creatorLastName: creatorLastName, // Ajout du nom
|
||||
profileImageUrl: profileImageUrl,
|
||||
participants: participants,
|
||||
status: status,
|
||||
reactionsCount: reactionsCount,
|
||||
commentsCount: commentsCount,
|
||||
sharesCount: sharesCount,
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
startDate: event.startDate.toIso8601String(),
|
||||
location: event.location,
|
||||
category: event.category,
|
||||
link: event.link ?? '',
|
||||
imageUrl: event.imageUrl,
|
||||
creatorEmail: event.creatorEmail,
|
||||
creatorFirstName: event.creatorFirstName,
|
||||
creatorLastName: event.creatorLastName,
|
||||
profileImageUrl: event.creatorProfileImageUrl,
|
||||
participants: event.participantIds,
|
||||
status: event.status.toApiString(),
|
||||
reactionsCount: event.reactionsCount,
|
||||
commentsCount: event.commentsCount,
|
||||
sharesCount: event.sharesCount,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONVERSION METHODS
|
||||
// ============================================================================
|
||||
|
||||
/// Convertit ce [EventModel] en JSON pour l'envoi vers l'API.
|
||||
///
|
||||
/// Returns une [Map] contenant les données de l'événement
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final event = EventModel(...);
|
||||
/// final json = event.toJson();
|
||||
/// // Envoyer json à l'API
|
||||
/// ```
|
||||
Map<String, dynamic> toJson() {
|
||||
print('[LOG] Conversion de EventModel en JSON');
|
||||
return {
|
||||
final json = <String, dynamic>{
|
||||
'id': id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
@@ -109,16 +268,166 @@ class EventModel {
|
||||
'location': location,
|
||||
'category': category,
|
||||
'link': link,
|
||||
'imageUrl': imageUrl,
|
||||
if (imageUrl != null && imageUrl!.isNotEmpty) 'imageUrl': imageUrl,
|
||||
'creatorEmail': creatorEmail,
|
||||
'creatorFirstName': creatorFirstName, // Ajout du prénom
|
||||
'creatorLastName': creatorLastName, // Ajout du nom
|
||||
'profileImageUrl': profileImageUrl,
|
||||
'creatorFirstName': creatorFirstName,
|
||||
'creatorLastName': creatorLastName,
|
||||
if (profileImageUrl.isNotEmpty) 'profileImageUrl': profileImageUrl,
|
||||
'participants': participants,
|
||||
'status': status,
|
||||
'reactionsCount': reactionsCount,
|
||||
'commentsCount': commentsCount,
|
||||
'sharesCount': sharesCount,
|
||||
};
|
||||
|
||||
AppLogger.d('Conversion en JSON pour l\'événement: $id', tag: 'EventModel');
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [Event].
|
||||
///
|
||||
/// Returns une instance de [Event] avec les mêmes données
|
||||
///
|
||||
/// Throws [ValidationException] si la date ne peut pas être parsée
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final model = EventModel.fromJson(json);
|
||||
/// final entity = model.toEntity();
|
||||
/// ```
|
||||
Event toEntity() {
|
||||
DateTime parsedDate;
|
||||
try {
|
||||
parsedDate = DateTime.parse(startDate);
|
||||
} catch (e) {
|
||||
throw ValidationException(
|
||||
'Format de date invalide: $startDate',
|
||||
field: 'startDate',
|
||||
);
|
||||
}
|
||||
|
||||
// Convertir les participants en liste de strings
|
||||
final participantIds = participants.map((p) {
|
||||
if (p is Map) {
|
||||
return p['id']?.toString() ?? p['userId']?.toString() ?? '';
|
||||
}
|
||||
return p.toString();
|
||||
}).where((id) => id.isNotEmpty).toList();
|
||||
|
||||
return Event(
|
||||
id: id,
|
||||
title: title,
|
||||
description: description,
|
||||
startDate: parsedDate,
|
||||
location: location,
|
||||
category: category,
|
||||
link: link.isEmpty ? null : link,
|
||||
imageUrl: imageUrl,
|
||||
creatorEmail: creatorEmail,
|
||||
creatorFirstName: creatorFirstName,
|
||||
creatorLastName: creatorLastName,
|
||||
creatorProfileImageUrl: profileImageUrl,
|
||||
participantIds: participantIds,
|
||||
status: EventStatus.fromString(status),
|
||||
reactionsCount: reactionsCount,
|
||||
commentsCount: commentsCount,
|
||||
sharesCount: sharesCount,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UTILITY METHODS
|
||||
// ============================================================================
|
||||
|
||||
/// Crée une copie de ce [EventModel] avec des valeurs modifiées.
|
||||
///
|
||||
/// Tous les paramètres sont optionnels. Seuls les paramètres fournis
|
||||
/// seront modifiés dans la nouvelle instance.
|
||||
///
|
||||
/// **Exemple:**
|
||||
/// ```dart
|
||||
/// final updated = event.copyWith(
|
||||
/// title: 'Nouveau titre',
|
||||
/// status: 'fermé',
|
||||
/// );
|
||||
/// ```
|
||||
EventModel copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? description,
|
||||
String? startDate,
|
||||
String? location,
|
||||
String? category,
|
||||
String? link,
|
||||
String? imageUrl,
|
||||
String? creatorEmail,
|
||||
String? creatorFirstName,
|
||||
String? creatorLastName,
|
||||
String? profileImageUrl,
|
||||
List<dynamic>? participants,
|
||||
String? status,
|
||||
int? reactionsCount,
|
||||
int? commentsCount,
|
||||
int? sharesCount,
|
||||
}) {
|
||||
return EventModel(
|
||||
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,
|
||||
profileImageUrl: profileImageUrl ?? this.profileImageUrl,
|
||||
participants: participants ?? this.participants,
|
||||
status: status ?? this.status,
|
||||
reactionsCount: reactionsCount ?? this.reactionsCount,
|
||||
commentsCount: commentsCount ?? this.commentsCount,
|
||||
sharesCount: sharesCount ?? this.sharesCount,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retourne le nombre de participants.
|
||||
///
|
||||
/// Returns le nombre de participants dans la liste
|
||||
int get participantsCount => participants.length;
|
||||
|
||||
/// Vérifie si l'événement est ouvert.
|
||||
///
|
||||
/// Returns `true` si le statut est 'ouvert', `false` sinon
|
||||
bool get isOpen => status.toLowerCase() == 'ouvert';
|
||||
|
||||
/// Vérifie si l'événement est fermé.
|
||||
///
|
||||
/// Returns `true` si le statut est 'fermé', `false` sinon
|
||||
bool get isClosed => status.toLowerCase() == 'fermé';
|
||||
|
||||
/// Vérifie si l'événement est annulé.
|
||||
///
|
||||
/// Returns `true` si le statut est 'annulé', `false` sinon
|
||||
bool get isCancelled => status.toLowerCase() == 'annulé';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'EventModel('
|
||||
'id: $id, '
|
||||
'title: $title, '
|
||||
'startDate: $startDate, '
|
||||
'status: $status'
|
||||
')';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is EventModel && other.id == id;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
|
||||
78
lib/data/models/friend_suggestion_model.dart
Normal file
78
lib/data/models/friend_suggestion_model.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import '../../domain/entities/friend_suggestion.dart';
|
||||
|
||||
/// Modèle de données pour une suggestion d'ami.
|
||||
///
|
||||
/// Cette classe hérite de [FriendSuggestion] et ajoute les fonctionnalités
|
||||
/// de conversion depuis/vers JSON pour la communication avec l'API.
|
||||
class FriendSuggestionModel extends FriendSuggestion {
|
||||
const FriendSuggestionModel({
|
||||
required super.userId,
|
||||
required super.firstName,
|
||||
required super.lastName,
|
||||
required super.email,
|
||||
required super.profileImageUrl,
|
||||
required super.mutualFriendsCount,
|
||||
required super.suggestionReason,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [FriendSuggestionModel] depuis un JSON.
|
||||
///
|
||||
/// Le backend renvoie :
|
||||
/// - userId : UUID de l'utilisateur suggéré
|
||||
/// - prenoms : Prénom(s) de l'utilisateur
|
||||
/// - nom : Nom de famille de l'utilisateur
|
||||
/// - email : Adresse email
|
||||
/// - profileImageUrl : URL de l'image de profil
|
||||
/// - mutualFriendsCount : Nombre d'amis en commun
|
||||
/// - suggestionReason : Raison de la suggestion
|
||||
factory FriendSuggestionModel.fromJson(Map<String, dynamic> json) {
|
||||
return FriendSuggestionModel(
|
||||
userId: json['userId']?.toString() ?? '',
|
||||
firstName: json['prenoms']?.toString() ?? json['firstName']?.toString() ?? '',
|
||||
lastName: json['nom']?.toString() ?? json['lastName']?.toString() ?? '',
|
||||
email: json['email']?.toString() ?? '',
|
||||
profileImageUrl: json['profileImageUrl']?.toString() ?? '',
|
||||
mutualFriendsCount: (json['mutualFriendsCount'] as num?)?.toInt() ?? 0,
|
||||
suggestionReason: json['suggestionReason']?.toString() ?? 'Suggestion',
|
||||
);
|
||||
}
|
||||
|
||||
/// Convertit le modèle en JSON.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'userId': userId,
|
||||
'prenoms': firstName,
|
||||
'nom': lastName,
|
||||
'email': email,
|
||||
'profileImageUrl': profileImageUrl,
|
||||
'mutualFriendsCount': mutualFriendsCount,
|
||||
'suggestionReason': suggestionReason,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit le modèle en entité de domaine.
|
||||
FriendSuggestion toEntity() {
|
||||
return FriendSuggestion(
|
||||
userId: userId,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
email: email,
|
||||
profileImageUrl: profileImageUrl,
|
||||
mutualFriendsCount: mutualFriendsCount,
|
||||
suggestionReason: suggestionReason,
|
||||
);
|
||||
}
|
||||
|
||||
/// Factory pour créer un [FriendSuggestionModel] depuis une entité.
|
||||
factory FriendSuggestionModel.fromEntity(FriendSuggestion entity) {
|
||||
return FriendSuggestionModel(
|
||||
userId: entity.userId,
|
||||
firstName: entity.firstName,
|
||||
lastName: entity.lastName,
|
||||
email: entity.email,
|
||||
profileImageUrl: entity.profileImageUrl,
|
||||
mutualFriendsCount: entity.mutualFriendsCount,
|
||||
suggestionReason: entity.suggestionReason,
|
||||
);
|
||||
}
|
||||
}
|
||||
181
lib/data/models/notification_model.dart
Normal file
181
lib/data/models/notification_model.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
import '../../domain/entities/notification.dart';
|
||||
|
||||
/// Modèle de données pour les notifications (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine Notification.
|
||||
class NotificationModel {
|
||||
NotificationModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.message,
|
||||
required this.type,
|
||||
required this.timestamp,
|
||||
this.isRead = false,
|
||||
this.eventId,
|
||||
this.userId,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [NotificationModel] à partir d'un JSON.
|
||||
factory NotificationModel.fromJson(Map<String, dynamic> json) {
|
||||
return NotificationModel(
|
||||
id: _parseIdRequired(json, 'id', ''),
|
||||
title: _parseString(json, 'title', 'Notification'),
|
||||
message: _parseString(json, 'message', ''),
|
||||
type: _parseNotificationType(json['type'] as String?),
|
||||
timestamp: _parseTimestamp(json['timestamp']),
|
||||
isRead: json['isRead'] as bool? ?? false,
|
||||
eventId: _parseId(json, 'eventId'),
|
||||
userId: _parseId(json, 'userId'),
|
||||
metadata: _parseMetadata(json['metadata']),
|
||||
);
|
||||
}
|
||||
|
||||
/// Crée un [NotificationModel] depuis une entité de domaine [Notification].
|
||||
factory NotificationModel.fromEntity(Notification notification) {
|
||||
return NotificationModel(
|
||||
id: notification.id,
|
||||
title: notification.title,
|
||||
message: notification.message,
|
||||
type: notification.type,
|
||||
timestamp: notification.timestamp,
|
||||
isRead: notification.isRead,
|
||||
eventId: notification.eventId,
|
||||
userId: notification.userId,
|
||||
metadata: notification.metadata,
|
||||
);
|
||||
}
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
final String message;
|
||||
final NotificationType type;
|
||||
final DateTime timestamp;
|
||||
bool isRead;
|
||||
final String? eventId;
|
||||
final String? userId;
|
||||
final Map<String, dynamic>? metadata;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'message': message,
|
||||
'type': type.toString().split('.').last,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'isRead': isRead,
|
||||
if (eventId != null) 'eventId': eventId,
|
||||
if (userId != null) 'userId': userId,
|
||||
if (metadata != null) 'metadata': metadata,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [Notification].
|
||||
Notification toEntity() {
|
||||
return Notification(
|
||||
id: id,
|
||||
title: title,
|
||||
message: message,
|
||||
type: type,
|
||||
timestamp: timestamp,
|
||||
isRead: isRead,
|
||||
eventId: eventId,
|
||||
userId: userId,
|
||||
metadata: metadata,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse une valeur string depuis le JSON avec valeur par défaut.
|
||||
static String _parseString(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
/// Parse le type de notification depuis le JSON.
|
||||
static NotificationType _parseNotificationType(String? type) {
|
||||
if (type == null) return NotificationType.other;
|
||||
|
||||
switch (type.toLowerCase()) {
|
||||
case 'event':
|
||||
case 'événement':
|
||||
return NotificationType.event;
|
||||
case 'friend':
|
||||
case 'ami':
|
||||
return NotificationType.friend;
|
||||
case 'reminder':
|
||||
case 'rappel':
|
||||
return NotificationType.reminder;
|
||||
default:
|
||||
return NotificationType.other;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse un timestamp depuis le JSON.
|
||||
static DateTime _parseTimestamp(dynamic timestamp) {
|
||||
if (timestamp == null) return DateTime.now();
|
||||
|
||||
if (timestamp is String) {
|
||||
try {
|
||||
return DateTime.parse(timestamp);
|
||||
} catch (e) {
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp is int) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
}
|
||||
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
/// Parse un ID (UUID) depuis le JSON.
|
||||
///
|
||||
/// [json] Le JSON à parser
|
||||
/// [key] La clé de l'ID
|
||||
/// [defaultValue] La valeur par défaut si l'ID est null
|
||||
///
|
||||
/// Returns l'ID parsé ou la valeur par défaut
|
||||
static String _parseIdRequired(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/// Parse un ID (UUID) optionnel depuis le JSON.
|
||||
///
|
||||
/// [json] Le JSON à parser
|
||||
/// [key] La clé de l'ID
|
||||
///
|
||||
/// Returns l'ID parsé ou null
|
||||
static String? _parseId(Map<String, dynamic> json, String key) {
|
||||
final value = json[key];
|
||||
if (value == null) return null;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/// Parse les métadonnées depuis le JSON.
|
||||
static Map<String, dynamic>? _parseMetadata(dynamic metadata) {
|
||||
if (metadata == null) return null;
|
||||
if (metadata is Map<String, dynamic>) return metadata;
|
||||
if (metadata is String) {
|
||||
try {
|
||||
// Tenter de parser si c'est une chaîne JSON
|
||||
return {'raw': metadata};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:afterwork/data/models/user_model.dart';
|
||||
import 'user_model.dart';
|
||||
|
||||
/// Modèle représentant un participant à un événement.
|
||||
class ParticipantModel extends UserModel {
|
||||
ParticipantModel({
|
||||
const ParticipantModel({
|
||||
required String id,
|
||||
required String nom,
|
||||
required String prenoms,
|
||||
|
||||
192
lib/data/models/reservation_model.dart
Normal file
192
lib/data/models/reservation_model.dart
Normal file
@@ -0,0 +1,192 @@
|
||||
import '../../domain/entities/reservation.dart';
|
||||
|
||||
/// Modèle de données pour les réservations (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine Reservation.
|
||||
class ReservationModel {
|
||||
ReservationModel({
|
||||
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,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [ReservationModel] à partir d'un JSON.
|
||||
factory ReservationModel.fromJson(Map<String, dynamic> json) {
|
||||
return ReservationModel(
|
||||
id: _parseId(json, 'id', ''),
|
||||
userId: _parseId(json, 'userId', ''),
|
||||
userFullName: _parseString(json, 'userFullName', ''),
|
||||
eventId: _parseId(json, 'eventId', ''),
|
||||
eventTitle: _parseString(json, 'eventTitle', ''),
|
||||
reservationDate: _parseTimestamp(json['reservationDate']),
|
||||
numberOfPeople: _parseInt(json, 'numberOfPeople'),
|
||||
status: _parseStatus(json['status'] as String?),
|
||||
establishmentId: json['establishmentId'] as String?,
|
||||
establishmentName: json['establishmentName'] as String?,
|
||||
notes: json['notes'] as String?,
|
||||
createdAt: json['createdAt'] != null
|
||||
? _parseTimestamp(json['createdAt'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Crée un [ReservationModel] depuis une entité de domaine [Reservation].
|
||||
factory ReservationModel.fromEntity(Reservation reservation) {
|
||||
return ReservationModel(
|
||||
id: reservation.id,
|
||||
userId: reservation.userId,
|
||||
userFullName: reservation.userFullName,
|
||||
eventId: reservation.eventId,
|
||||
eventTitle: reservation.eventTitle,
|
||||
reservationDate: reservation.reservationDate,
|
||||
numberOfPeople: reservation.numberOfPeople,
|
||||
status: reservation.status,
|
||||
establishmentId: reservation.establishmentId,
|
||||
establishmentName: reservation.establishmentName,
|
||||
notes: reservation.notes,
|
||||
createdAt: reservation.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;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'userId': userId,
|
||||
'userFullName': userFullName,
|
||||
'eventId': eventId,
|
||||
'eventTitle': eventTitle,
|
||||
'reservationDate': reservationDate.toIso8601String(),
|
||||
'numberOfPeople': numberOfPeople,
|
||||
'status': _statusToString(status),
|
||||
if (establishmentId != null) 'establishmentId': establishmentId,
|
||||
if (establishmentName != null) 'establishmentName': establishmentName,
|
||||
if (notes != null) 'notes': notes,
|
||||
if (createdAt != null) 'createdAt': createdAt!.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [Reservation].
|
||||
Reservation toEntity() {
|
||||
return Reservation(
|
||||
id: id,
|
||||
userId: userId,
|
||||
userFullName: userFullName,
|
||||
eventId: eventId,
|
||||
eventTitle: eventTitle,
|
||||
reservationDate: reservationDate,
|
||||
numberOfPeople: numberOfPeople,
|
||||
status: status,
|
||||
establishmentId: establishmentId,
|
||||
establishmentName: establishmentName,
|
||||
notes: notes,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse une valeur string depuis le JSON avec valeur par défaut.
|
||||
static String _parseString(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
/// Parse une valeur int depuis le JSON avec valeur par défaut 1.
|
||||
static int _parseInt(Map<String, dynamic> json, String key) {
|
||||
return json[key] as int? ?? 1;
|
||||
}
|
||||
|
||||
/// Parse un timestamp depuis le JSON.
|
||||
static DateTime _parseTimestamp(dynamic timestamp) {
|
||||
if (timestamp == null) return DateTime.now();
|
||||
|
||||
if (timestamp is String) {
|
||||
try {
|
||||
return DateTime.parse(timestamp);
|
||||
} catch (e) {
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp is int) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
}
|
||||
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
/// Parse un ID (UUID) depuis le JSON.
|
||||
static String _parseId(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/// Parse le statut de réservation depuis le JSON.
|
||||
static ReservationStatus _parseStatus(String? status) {
|
||||
if (status == null) return ReservationStatus.pending;
|
||||
|
||||
switch (status.toLowerCase()) {
|
||||
case 'pending':
|
||||
case 'en attente':
|
||||
return ReservationStatus.pending;
|
||||
case 'confirmed':
|
||||
case 'confirmé':
|
||||
case 'confirmée':
|
||||
return ReservationStatus.confirmed;
|
||||
case 'cancelled':
|
||||
case 'annulé':
|
||||
case 'annulée':
|
||||
return ReservationStatus.cancelled;
|
||||
case 'completed':
|
||||
case 'terminé':
|
||||
case 'terminée':
|
||||
return ReservationStatus.completed;
|
||||
default:
|
||||
return ReservationStatus.pending;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convertit le statut en string pour l'API.
|
||||
static String _statusToString(ReservationStatus status) {
|
||||
switch (status) {
|
||||
case ReservationStatus.pending:
|
||||
return 'pending';
|
||||
case ReservationStatus.confirmed:
|
||||
return 'confirmed';
|
||||
case ReservationStatus.cancelled:
|
||||
return 'cancelled';
|
||||
case ReservationStatus.completed:
|
||||
return 'completed';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,153 @@
|
||||
class SocialPost {
|
||||
final String userName;
|
||||
final String userImage;
|
||||
final String postText;
|
||||
final String postImage;
|
||||
final int likes;
|
||||
final int comments;
|
||||
final int shares;
|
||||
final List<String> badges; // Gamification badges
|
||||
final List<String> tags; // Ajout de tags pour personnalisation des posts
|
||||
import '../../domain/entities/social_post.dart';
|
||||
|
||||
SocialPost({
|
||||
required this.userName,
|
||||
required this.userImage,
|
||||
required this.postText,
|
||||
required this.postImage,
|
||||
required this.likes,
|
||||
required this.comments,
|
||||
required this.shares,
|
||||
required this.badges,
|
||||
this.tags = const [],
|
||||
/// Modèle de données pour les posts sociaux (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine SocialPost.
|
||||
class SocialPostModel {
|
||||
SocialPostModel({
|
||||
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,
|
||||
});
|
||||
|
||||
/// Factory pour créer un [SocialPostModel] à partir d'un JSON.
|
||||
factory SocialPostModel.fromJson(Map<String, dynamic> json) {
|
||||
return SocialPostModel(
|
||||
id: _parseId(json, 'id', ''),
|
||||
content: _parseString(json, 'content', ''),
|
||||
userId: _parseId(json, 'userId', ''),
|
||||
userFirstName: _parseString(json, 'userFirstName', ''),
|
||||
userLastName: _parseString(json, 'userLastName', ''),
|
||||
userProfileImageUrl: _parseString(json, 'userProfileImageUrl', ''),
|
||||
timestamp: _parseTimestamp(json['timestamp']),
|
||||
imageUrl: json['imageUrl'] as String?,
|
||||
likesCount: _parseInt(json, 'likesCount'),
|
||||
commentsCount: _parseInt(json, 'commentsCount'),
|
||||
sharesCount: _parseInt(json, 'sharesCount'),
|
||||
isLikedByCurrentUser: json['isLikedByCurrentUser'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Crée un [SocialPostModel] depuis une entité de domaine [SocialPost].
|
||||
factory SocialPostModel.fromEntity(SocialPost post) {
|
||||
return SocialPostModel(
|
||||
id: post.id,
|
||||
content: post.content,
|
||||
userId: post.userId,
|
||||
userFirstName: post.userFirstName,
|
||||
userLastName: post.userLastName,
|
||||
userProfileImageUrl: post.userProfileImageUrl,
|
||||
timestamp: post.timestamp,
|
||||
imageUrl: post.imageUrl,
|
||||
likesCount: post.likesCount,
|
||||
commentsCount: post.commentsCount,
|
||||
sharesCount: post.sharesCount,
|
||||
isLikedByCurrentUser: post.isLikedByCurrentUser,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/// Convertit ce modèle en JSON pour l'envoi vers l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'content': content,
|
||||
'userId': userId,
|
||||
'userFirstName': userFirstName,
|
||||
'userLastName': userLastName,
|
||||
'userProfileImageUrl': userProfileImageUrl,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
if (imageUrl != null) 'imageUrl': imageUrl,
|
||||
'likesCount': likesCount,
|
||||
'commentsCount': commentsCount,
|
||||
'sharesCount': sharesCount,
|
||||
'isLikedByCurrentUser': isLikedByCurrentUser,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit ce modèle vers une entité de domaine [SocialPost].
|
||||
SocialPost toEntity() {
|
||||
return SocialPost(
|
||||
id: id,
|
||||
content: content,
|
||||
userId: userId,
|
||||
userFirstName: userFirstName,
|
||||
userLastName: userLastName,
|
||||
userProfileImageUrl: userProfileImageUrl,
|
||||
timestamp: timestamp,
|
||||
imageUrl: imageUrl,
|
||||
likesCount: likesCount,
|
||||
commentsCount: commentsCount,
|
||||
sharesCount: sharesCount,
|
||||
isLikedByCurrentUser: isLikedByCurrentUser,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse une valeur string depuis le JSON avec valeur par défaut.
|
||||
static String _parseString(
|
||||
Map<String, dynamic> json,
|
||||
String key,
|
||||
String defaultValue,
|
||||
) {
|
||||
return json[key] as String? ?? defaultValue;
|
||||
}
|
||||
|
||||
/// Parse une valeur int depuis le JSON avec valeur par défaut 0.
|
||||
static int _parseInt(Map<String, dynamic> json, String key) {
|
||||
return json[key] as int? ?? 0;
|
||||
}
|
||||
|
||||
/// Parse un timestamp depuis le JSON.
|
||||
static DateTime _parseTimestamp(dynamic timestamp) {
|
||||
if (timestamp == null) return DateTime.now();
|
||||
|
||||
if (timestamp is String) {
|
||||
try {
|
||||
return DateTime.parse(timestamp);
|
||||
} catch (e) {
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp is int) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
}
|
||||
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
/// Parse un ID (UUID) depuis le JSON.
|
||||
///
|
||||
/// [json] Le JSON à parser
|
||||
/// [key] La clé de l'ID
|
||||
/// [defaultValue] La valeur par défaut si l'ID est null
|
||||
///
|
||||
/// Returns l'ID parsé ou la valeur par défaut
|
||||
static String _parseId(Map<String, dynamic> json, String key, String defaultValue) {
|
||||
final value = json[key];
|
||||
if (value == null) return defaultValue;
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
148
lib/data/models/story_model.dart
Normal file
148
lib/data/models/story_model.dart
Normal file
@@ -0,0 +1,148 @@
|
||||
import '../../core/constants/env_config.dart';
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../../domain/entities/story.dart';
|
||||
|
||||
/// Modèle de données pour les stories (Data Transfer Object).
|
||||
///
|
||||
/// Cette classe est responsable de la sérialisation/désérialisation
|
||||
/// avec l'API backend et convertit vers/depuis l'entité de domaine [Story].
|
||||
class StoryModel extends Story {
|
||||
/// Crée une nouvelle instance de [StoryModel].
|
||||
const StoryModel({
|
||||
required super.id,
|
||||
required super.userId,
|
||||
required super.userFirstName,
|
||||
required super.userLastName,
|
||||
required super.userProfileImageUrl,
|
||||
required super.userIsVerified,
|
||||
required super.mediaType,
|
||||
required super.mediaUrl,
|
||||
required super.createdAt,
|
||||
required super.expiresAt,
|
||||
super.thumbnailUrl,
|
||||
super.durationSeconds,
|
||||
super.isActive,
|
||||
super.viewsCount,
|
||||
super.hasViewed,
|
||||
});
|
||||
|
||||
/// Crée un [StoryModel] à partir d'un JSON reçu depuis l'API.
|
||||
factory StoryModel.fromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
return StoryModel(
|
||||
id: json['id']?.toString() ?? '',
|
||||
userId: json['userId']?.toString() ?? '',
|
||||
userFirstName: json['userFirstName']?.toString() ?? '',
|
||||
userLastName: json['userLastName']?.toString() ?? '',
|
||||
userProfileImageUrl: json['userProfileImageUrl']?.toString() ?? '',
|
||||
userIsVerified: json['userIsVerified'] as bool? ?? false,
|
||||
mediaType: _parseMediaType(json['mediaType']),
|
||||
mediaUrl: json['mediaUrl']?.toString() ?? '',
|
||||
thumbnailUrl: json['thumbnailUrl']?.toString(),
|
||||
durationSeconds: json['durationSeconds'] as int?,
|
||||
createdAt: _parseDateTime(json['createdAt']),
|
||||
expiresAt: _parseDateTime(json['expiresAt']),
|
||||
isActive: json['isActive'] as bool? ?? true,
|
||||
viewsCount: json['viewsCount'] as int? ?? 0,
|
||||
hasViewed: json['hasViewed'] as bool? ?? false,
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.e('Erreur lors du parsing JSON', error: e, stackTrace: stackTrace, tag: 'StoryModel');
|
||||
AppLogger.d('JSON reçu: $json', tag: 'StoryModel');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convertit le modèle en JSON pour l'envoi à l'API.
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'userId': userId,
|
||||
'userFirstName': userFirstName,
|
||||
'userLastName': userLastName,
|
||||
'userProfileImageUrl': userProfileImageUrl,
|
||||
'userIsVerified': userIsVerified,
|
||||
'mediaType': _mediaTypeToString(mediaType),
|
||||
'mediaUrl': mediaUrl,
|
||||
'thumbnailUrl': thumbnailUrl,
|
||||
'durationSeconds': durationSeconds,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'expiresAt': expiresAt.toIso8601String(),
|
||||
'isActive': isActive,
|
||||
'viewsCount': viewsCount,
|
||||
'hasViewed': hasViewed,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit le modèle en entité de domaine.
|
||||
Story toEntity() {
|
||||
return Story(
|
||||
id: id,
|
||||
userId: userId,
|
||||
userFirstName: userFirstName,
|
||||
userLastName: userLastName,
|
||||
userProfileImageUrl: userProfileImageUrl,
|
||||
userIsVerified: userIsVerified,
|
||||
mediaType: mediaType,
|
||||
mediaUrl: mediaUrl,
|
||||
thumbnailUrl: thumbnailUrl,
|
||||
durationSeconds: durationSeconds,
|
||||
createdAt: createdAt,
|
||||
expiresAt: expiresAt,
|
||||
isActive: isActive,
|
||||
viewsCount: viewsCount,
|
||||
hasViewed: hasViewed,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse le type de média depuis une string.
|
||||
static StoryMediaType _parseMediaType(dynamic value) {
|
||||
if (value == null) return StoryMediaType.image;
|
||||
final stringValue = value.toString().toUpperCase();
|
||||
switch (stringValue) {
|
||||
case 'IMAGE':
|
||||
return StoryMediaType.image;
|
||||
case 'VIDEO':
|
||||
return StoryMediaType.video;
|
||||
default:
|
||||
AppLogger.w('Type de média inconnu: $value, utilisation de IMAGE par défaut', tag: 'StoryModel');
|
||||
return StoryMediaType.image;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convertit le type de média en string pour l'API.
|
||||
static String _mediaTypeToString(StoryMediaType type) {
|
||||
switch (type) {
|
||||
case StoryMediaType.image:
|
||||
return 'IMAGE';
|
||||
case StoryMediaType.video:
|
||||
return 'VIDEO';
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse une DateTime depuis différents formats possibles.
|
||||
static DateTime _parseDateTime(dynamic value) {
|
||||
if (value == null) return DateTime.now();
|
||||
|
||||
try {
|
||||
// Si c'est déjà une DateTime
|
||||
if (value is DateTime) return value;
|
||||
|
||||
// Si c'est une string ISO 8601
|
||||
if (value is String) {
|
||||
return DateTime.parse(value);
|
||||
}
|
||||
|
||||
// Si c'est un timestamp en millisecondes
|
||||
if (value is int) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(value);
|
||||
}
|
||||
|
||||
AppLogger.w('Format de date non reconnu: $value', tag: 'StoryModel');
|
||||
return DateTime.now();
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.e('Erreur parsing DateTime', error: e, stackTrace: stackTrace, tag: 'StoryModel');
|
||||
return DateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../../core/constants/env_config.dart';
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../../domain/entities/user.dart';
|
||||
|
||||
/// Modèle de données pour les utilisateurs (Data Transfer Object).
|
||||
@@ -37,6 +38,7 @@ class UserModel extends User {
|
||||
required super.email,
|
||||
required super.motDePasse,
|
||||
required super.profileImageUrl,
|
||||
super.isVerified,
|
||||
super.eventsCount,
|
||||
super.friendsCount,
|
||||
super.postsCount,
|
||||
@@ -75,15 +77,14 @@ class UserModel extends User {
|
||||
email: _parseString(json, 'email', ''),
|
||||
motDePasse: _parseString(json, 'motDePasse', ''),
|
||||
profileImageUrl: _parseString(json, 'profileImageUrl', ''),
|
||||
isVerified: json['isVerified'] as bool? ?? false,
|
||||
eventsCount: _parseInt(json, 'eventsCount') ?? 0,
|
||||
friendsCount: _parseInt(json, 'friendsCount') ?? 0,
|
||||
postsCount: _parseInt(json, 'postsCount') ?? 0,
|
||||
visitedPlacesCount: _parseInt(json, 'visitedPlacesCount') ?? 0,
|
||||
);
|
||||
} catch (e) {
|
||||
if (EnvConfig.enableDetailedLogs) {
|
||||
print('[UserModel] Erreur lors du parsing JSON: $e');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
AppLogger.e('Erreur lors du parsing JSON', error: e, stackTrace: stackTrace, tag: 'UserModel');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user