feat(communication): module messagerie unifié + contact policies + blocages
Aligné avec le backend MessagingResource : - Nouveau module communication (conversations, messages, participants) - Respect des ContactPolicy (qui peut parler à qui par rôle) - Gestion MemberBlock (blocages individuels) - UI : conversations list, conversation detail, broadcast, tiles - BLoC : MessagingBloc avec events (envoyer, démarrer conversation rôle, etc.)
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
/// Modèle de données ContactPolicy v4 avec désérialisation JSON
|
||||
library contact_policy_model;
|
||||
|
||||
import '../../domain/entities/contact_policy.dart';
|
||||
|
||||
/// Modèle ContactPolicy v4
|
||||
class ContactPolicyModel extends ContactPolicy {
|
||||
const ContactPolicyModel({
|
||||
super.id,
|
||||
super.organisationId,
|
||||
required super.typePolitique,
|
||||
super.autoriserMembreVersMembre,
|
||||
super.autoriserMembreVersRole,
|
||||
super.autoriserNotesVocales,
|
||||
});
|
||||
|
||||
factory ContactPolicyModel.fromJson(Map<String, dynamic> json) {
|
||||
return ContactPolicyModel(
|
||||
id: json['id']?.toString(),
|
||||
organisationId: json['organisationId']?.toString(),
|
||||
typePolitique: json['typePolitique']?.toString() ?? 'OUVERT',
|
||||
autoriserMembreVersMembre: json['autoriserMembreVersMembre'] == true,
|
||||
autoriserMembreVersRole: json['autoriserMembreVersRole'] == true,
|
||||
autoriserNotesVocales: json['autoriserNotesVocales'] == true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +1,110 @@
|
||||
/// Model de données Conversation avec sérialisation JSON
|
||||
/// Modèles de données Conversation v4 avec désérialisation JSON
|
||||
library conversation_model;
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import '../../domain/entities/conversation.dart';
|
||||
import '../../domain/entities/message.dart';
|
||||
import 'message_model.dart';
|
||||
|
||||
part 'conversation_model.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class ConversationModel extends Conversation {
|
||||
@JsonKey(
|
||||
fromJson: _messageFromJson,
|
||||
toJson: _messageToJson,
|
||||
)
|
||||
@override
|
||||
final Message? lastMessage;
|
||||
|
||||
const ConversationModel({
|
||||
/// Modèle de résumé de conversation (liste)
|
||||
class ConversationSummaryModel extends ConversationSummary {
|
||||
const ConversationSummaryModel({
|
||||
required super.id,
|
||||
required super.name,
|
||||
super.description,
|
||||
required super.type,
|
||||
required super.participantIds,
|
||||
super.organizationId,
|
||||
this.lastMessage,
|
||||
super.unreadCount,
|
||||
super.isMuted,
|
||||
super.isPinned,
|
||||
super.isArchived,
|
||||
required super.createdAt,
|
||||
super.updatedAt,
|
||||
super.avatarUrl,
|
||||
super.metadata,
|
||||
}) : super(lastMessage: lastMessage);
|
||||
required super.typeConversation,
|
||||
required super.titre,
|
||||
required super.statut,
|
||||
super.dernierMessageApercu,
|
||||
super.dernierMessageType,
|
||||
super.dernierMessageAt,
|
||||
super.nonLus,
|
||||
super.organisationId,
|
||||
});
|
||||
|
||||
static Message? _messageFromJson(Map<String, dynamic>? json) =>
|
||||
json == null ? null : MessageModel.fromJson(json);
|
||||
|
||||
static Map<String, dynamic>? _messageToJson(Message? message) =>
|
||||
message == null ? null : MessageModel.fromEntity(message).toJson();
|
||||
|
||||
factory ConversationModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$ConversationModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ConversationModelToJson(this);
|
||||
|
||||
factory ConversationModel.fromEntity(Conversation conversation) {
|
||||
return ConversationModel(
|
||||
id: conversation.id,
|
||||
name: conversation.name,
|
||||
description: conversation.description,
|
||||
type: conversation.type,
|
||||
participantIds: conversation.participantIds,
|
||||
organizationId: conversation.organizationId,
|
||||
lastMessage: conversation.lastMessage,
|
||||
unreadCount: conversation.unreadCount,
|
||||
isMuted: conversation.isMuted,
|
||||
isPinned: conversation.isPinned,
|
||||
isArchived: conversation.isArchived,
|
||||
createdAt: conversation.createdAt,
|
||||
updatedAt: conversation.updatedAt,
|
||||
avatarUrl: conversation.avatarUrl,
|
||||
metadata: conversation.metadata,
|
||||
factory ConversationSummaryModel.fromJson(Map<String, dynamic> json) {
|
||||
return ConversationSummaryModel(
|
||||
id: json['id']?.toString() ?? '',
|
||||
typeConversation: json['typeConversation']?.toString() ?? 'DIRECTE',
|
||||
titre: json['titre']?.toString() ?? '',
|
||||
statut: json['statut']?.toString() ?? 'ACTIVE',
|
||||
dernierMessageApercu: json['dernierMessageApercu']?.toString(),
|
||||
dernierMessageType: json['dernierMessageType']?.toString(),
|
||||
dernierMessageAt: json['dernierMessageAt'] != null
|
||||
? DateTime.tryParse(json['dernierMessageAt'].toString())
|
||||
: null,
|
||||
nonLus: _parseInt(json['nonLus']),
|
||||
organisationId: json['organisationId']?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
Conversation toEntity() => this;
|
||||
}
|
||||
|
||||
/// Modèle de participant
|
||||
class ConversationParticipantModel extends ConversationParticipant {
|
||||
const ConversationParticipantModel({
|
||||
required super.membreId,
|
||||
super.prenom,
|
||||
super.nom,
|
||||
super.roleDansConversation,
|
||||
super.luJusqua,
|
||||
});
|
||||
|
||||
factory ConversationParticipantModel.fromJson(Map<String, dynamic> json) {
|
||||
return ConversationParticipantModel(
|
||||
membreId: json['membreId']?.toString() ?? '',
|
||||
prenom: json['prenom']?.toString(),
|
||||
nom: json['nom']?.toString(),
|
||||
roleDansConversation: json['roleDansConversation']?.toString(),
|
||||
luJusqua: json['luJusqua'] != null
|
||||
? DateTime.tryParse(json['luJusqua'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Modèle de conversation complète (détail)
|
||||
class ConversationModel extends Conversation {
|
||||
const ConversationModel({
|
||||
required super.id,
|
||||
required super.typeConversation,
|
||||
required super.titre,
|
||||
required super.statut,
|
||||
super.organisationId,
|
||||
super.organisationNom,
|
||||
super.dateCreation,
|
||||
super.nombreMessages,
|
||||
super.participants,
|
||||
super.messages,
|
||||
super.nonLus,
|
||||
super.roleCible,
|
||||
});
|
||||
|
||||
factory ConversationModel.fromJson(Map<String, dynamic> json) {
|
||||
final participantsJson = json['participants'] as List<dynamic>? ?? [];
|
||||
final messagesJson = json['messages'] as List<dynamic>? ?? [];
|
||||
|
||||
return ConversationModel(
|
||||
id: json['id']?.toString() ?? '',
|
||||
typeConversation: json['typeConversation']?.toString() ?? 'DIRECTE',
|
||||
titre: json['titre']?.toString() ?? '',
|
||||
statut: json['statut']?.toString() ?? 'ACTIVE',
|
||||
organisationId: json['organisationId']?.toString(),
|
||||
organisationNom: json['organisationNom']?.toString(),
|
||||
dateCreation: json['dateCreation'] != null
|
||||
? DateTime.tryParse(json['dateCreation'].toString())
|
||||
: null,
|
||||
nombreMessages: _parseInt(json['nombreMessages']),
|
||||
participants: participantsJson
|
||||
.map((p) => ConversationParticipantModel.fromJson(p as Map<String, dynamic>))
|
||||
.toList(),
|
||||
messages: messagesJson
|
||||
.map((m) => MessageModel.fromJson(m as Map<String, dynamic>))
|
||||
.toList(),
|
||||
nonLus: _parseInt(json['nonLus']),
|
||||
roleCible: json['roleCible']?.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
int _parseInt(dynamic value) {
|
||||
if (value == null) return 0;
|
||||
if (value is int) return value;
|
||||
if (value is double) return value.toInt();
|
||||
return int.tryParse(value.toString()) ?? 0;
|
||||
}
|
||||
|
||||
@@ -1,57 +1,2 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'conversation_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ConversationModel _$ConversationModelFromJson(Map<String, dynamic> json) =>
|
||||
ConversationModel(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String?,
|
||||
type: $enumDecode(_$ConversationTypeEnumMap, json['type']),
|
||||
participantIds: (json['participantIds'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
organizationId: json['organizationId'] as String?,
|
||||
lastMessage: ConversationModel._messageFromJson(
|
||||
json['lastMessage'] as Map<String, dynamic>?),
|
||||
unreadCount: (json['unreadCount'] as num?)?.toInt() ?? 0,
|
||||
isMuted: json['isMuted'] as bool? ?? false,
|
||||
isPinned: json['isPinned'] as bool? ?? false,
|
||||
isArchived: json['isArchived'] as bool? ?? false,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: json['updatedAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['updatedAt'] as String),
|
||||
avatarUrl: json['avatarUrl'] as String?,
|
||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ConversationModelToJson(ConversationModel instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'description': instance.description,
|
||||
'type': _$ConversationTypeEnumMap[instance.type]!,
|
||||
'participantIds': instance.participantIds,
|
||||
'organizationId': instance.organizationId,
|
||||
'unreadCount': instance.unreadCount,
|
||||
'isMuted': instance.isMuted,
|
||||
'isPinned': instance.isPinned,
|
||||
'isArchived': instance.isArchived,
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
'updatedAt': instance.updatedAt?.toIso8601String(),
|
||||
'avatarUrl': instance.avatarUrl,
|
||||
'metadata': instance.metadata,
|
||||
'lastMessage': ConversationModel._messageToJson(instance.lastMessage),
|
||||
};
|
||||
|
||||
const _$ConversationTypeEnumMap = {
|
||||
ConversationType.individual: 'individual',
|
||||
ConversationType.group: 'group',
|
||||
ConversationType.broadcast: 'broadcast',
|
||||
ConversationType.announcement: 'announcement',
|
||||
};
|
||||
// Modèles v4 : désérialisation manuelle, code generation non utilisé.
|
||||
|
||||
@@ -1,83 +1,48 @@
|
||||
/// Model de données Message avec sérialisation JSON
|
||||
/// Modèle de données Message v4 avec désérialisation JSON
|
||||
library message_model;
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import '../../domain/entities/message.dart';
|
||||
|
||||
part 'message_model.g.dart';
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
/// Modèle Message v4
|
||||
class MessageModel extends Message {
|
||||
const MessageModel({
|
||||
required super.id,
|
||||
required super.conversationId,
|
||||
required super.senderId,
|
||||
required super.senderName,
|
||||
super.senderAvatar,
|
||||
required super.content,
|
||||
required super.type,
|
||||
required super.status,
|
||||
super.priority,
|
||||
required super.recipientIds,
|
||||
super.recipientRoles,
|
||||
super.organizationId,
|
||||
required super.createdAt,
|
||||
super.readAt,
|
||||
super.metadata,
|
||||
super.attachments,
|
||||
super.isEdited,
|
||||
super.editedAt,
|
||||
super.isDeleted,
|
||||
required super.typeMessage,
|
||||
super.contenu,
|
||||
super.urlFichier,
|
||||
super.dureeAudio,
|
||||
super.supprime,
|
||||
super.expediteurId,
|
||||
super.expediteurNom,
|
||||
super.expediteurPrenom,
|
||||
super.messageParentId,
|
||||
super.messageParentApercu,
|
||||
super.dateEnvoi,
|
||||
});
|
||||
|
||||
factory MessageModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$MessageModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$MessageModelToJson(this);
|
||||
|
||||
factory MessageModel.fromEntity(Message message) {
|
||||
factory MessageModel.fromJson(Map<String, dynamic> json) {
|
||||
return MessageModel(
|
||||
id: message.id,
|
||||
conversationId: message.conversationId,
|
||||
senderId: message.senderId,
|
||||
senderName: message.senderName,
|
||||
senderAvatar: message.senderAvatar,
|
||||
content: message.content,
|
||||
type: message.type,
|
||||
status: message.status,
|
||||
priority: message.priority,
|
||||
recipientIds: message.recipientIds,
|
||||
recipientRoles: message.recipientRoles,
|
||||
organizationId: message.organizationId,
|
||||
createdAt: message.createdAt,
|
||||
readAt: message.readAt,
|
||||
metadata: message.metadata,
|
||||
attachments: message.attachments,
|
||||
isEdited: message.isEdited,
|
||||
editedAt: message.editedAt,
|
||||
isDeleted: message.isDeleted,
|
||||
id: json['id']?.toString() ?? '',
|
||||
typeMessage: json['typeMessage']?.toString() ?? 'TEXTE',
|
||||
contenu: json['contenu']?.toString(),
|
||||
urlFichier: json['urlFichier']?.toString(),
|
||||
dureeAudio: _parseInt(json['dureeAudio']),
|
||||
supprime: json['supprime'] == true,
|
||||
expediteurId: json['expediteurId']?.toString(),
|
||||
expediteurNom: json['expediteurNom']?.toString(),
|
||||
expediteurPrenom: json['expediteurPrenom']?.toString(),
|
||||
messageParentId: json['messageParentId']?.toString(),
|
||||
messageParentApercu: json['messageParentApercu']?.toString(),
|
||||
dateEnvoi: json['dateEnvoi'] != null
|
||||
? DateTime.tryParse(json['dateEnvoi'].toString())
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Message toEntity() => Message(
|
||||
id: id,
|
||||
conversationId: conversationId,
|
||||
senderId: senderId,
|
||||
senderName: senderName,
|
||||
senderAvatar: senderAvatar,
|
||||
content: content,
|
||||
type: type,
|
||||
status: status,
|
||||
priority: priority,
|
||||
recipientIds: recipientIds,
|
||||
recipientRoles: recipientRoles,
|
||||
organizationId: organizationId,
|
||||
createdAt: createdAt,
|
||||
readAt: readAt,
|
||||
metadata: metadata,
|
||||
attachments: attachments,
|
||||
isEdited: isEdited,
|
||||
editedAt: editedAt,
|
||||
isDeleted: isDeleted,
|
||||
);
|
||||
}
|
||||
|
||||
int? _parseInt(dynamic value) {
|
||||
if (value == null) return null;
|
||||
if (value is int) return value;
|
||||
if (value is double) return value.toInt();
|
||||
return int.tryParse(value.toString());
|
||||
}
|
||||
|
||||
@@ -1,84 +1,2 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'message_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
MessageModel _$MessageModelFromJson(Map<String, dynamic> json) => MessageModel(
|
||||
id: json['id'] as String,
|
||||
conversationId: json['conversationId'] as String,
|
||||
senderId: json['senderId'] as String,
|
||||
senderName: json['senderName'] as String,
|
||||
senderAvatar: json['senderAvatar'] as String?,
|
||||
content: json['content'] as String,
|
||||
type: $enumDecode(_$MessageTypeEnumMap, json['type']),
|
||||
status: $enumDecode(_$MessageStatusEnumMap, json['status']),
|
||||
priority:
|
||||
$enumDecodeNullable(_$MessagePriorityEnumMap, json['priority']) ??
|
||||
MessagePriority.normal,
|
||||
recipientIds: (json['recipientIds'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
recipientRoles: (json['recipientRoles'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
organizationId: json['organizationId'] as String?,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
readAt: json['readAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['readAt'] as String),
|
||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
||||
attachments: (json['attachments'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
isEdited: json['isEdited'] as bool? ?? false,
|
||||
editedAt: json['editedAt'] == null
|
||||
? null
|
||||
: DateTime.parse(json['editedAt'] as String),
|
||||
isDeleted: json['isDeleted'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MessageModelToJson(MessageModel instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'conversationId': instance.conversationId,
|
||||
'senderId': instance.senderId,
|
||||
'senderName': instance.senderName,
|
||||
'senderAvatar': instance.senderAvatar,
|
||||
'content': instance.content,
|
||||
'type': _$MessageTypeEnumMap[instance.type]!,
|
||||
'status': _$MessageStatusEnumMap[instance.status]!,
|
||||
'priority': _$MessagePriorityEnumMap[instance.priority]!,
|
||||
'recipientIds': instance.recipientIds,
|
||||
'recipientRoles': instance.recipientRoles,
|
||||
'organizationId': instance.organizationId,
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
'readAt': instance.readAt?.toIso8601String(),
|
||||
'metadata': instance.metadata,
|
||||
'attachments': instance.attachments,
|
||||
'isEdited': instance.isEdited,
|
||||
'editedAt': instance.editedAt?.toIso8601String(),
|
||||
'isDeleted': instance.isDeleted,
|
||||
};
|
||||
|
||||
const _$MessageTypeEnumMap = {
|
||||
MessageType.individual: 'individual',
|
||||
MessageType.broadcast: 'broadcast',
|
||||
MessageType.targeted: 'targeted',
|
||||
MessageType.system: 'system',
|
||||
};
|
||||
|
||||
const _$MessageStatusEnumMap = {
|
||||
MessageStatus.sent: 'sent',
|
||||
MessageStatus.delivered: 'delivered',
|
||||
MessageStatus.read: 'read',
|
||||
MessageStatus.failed: 'failed',
|
||||
};
|
||||
|
||||
const _$MessagePriorityEnumMap = {
|
||||
MessagePriority.normal: 'normal',
|
||||
MessagePriority.high: 'high',
|
||||
MessagePriority.urgent: 'urgent',
|
||||
};
|
||||
// Modèles v4 : désérialisation manuelle, code generation non utilisé.
|
||||
|
||||
Reference in New Issue
Block a user