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:
@@ -1,127 +1,120 @@
|
||||
/// Entité métier Conversation
|
||||
/// Entités métier Conversation v4
|
||||
///
|
||||
/// Représente une conversation (fil de messages) dans UnionFlow
|
||||
/// Correspond aux DTOs backend : ConversationSummaryResponse et ConversationResponse
|
||||
library conversation;
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'message.dart';
|
||||
|
||||
/// Type de conversation
|
||||
enum ConversationType {
|
||||
/// Conversation individuelle (1-1)
|
||||
individual,
|
||||
// ── Résumé de conversation (liste) ───────────────────────────────────────────
|
||||
|
||||
/// Conversation de groupe
|
||||
group,
|
||||
|
||||
/// Canal broadcast (lecture seule pour la plupart)
|
||||
broadcast,
|
||||
|
||||
/// Canal d'annonces organisation
|
||||
announcement,
|
||||
}
|
||||
|
||||
/// Entité Conversation
|
||||
class Conversation extends Equatable {
|
||||
/// Résumé d'une conversation pour l'affichage en liste
|
||||
class ConversationSummary extends Equatable {
|
||||
final String id;
|
||||
final String name;
|
||||
final String? description;
|
||||
final ConversationType type;
|
||||
final List<String> participantIds;
|
||||
final String? organizationId;
|
||||
final Message? lastMessage;
|
||||
final int unreadCount;
|
||||
final bool isMuted;
|
||||
final bool isPinned;
|
||||
final bool isArchived;
|
||||
final DateTime createdAt;
|
||||
final DateTime? updatedAt;
|
||||
final String? avatarUrl;
|
||||
final Map<String, dynamic>? metadata;
|
||||
final String typeConversation; // DIRECTE | ROLE_CANAL | GROUPE
|
||||
final String titre;
|
||||
final String statut; // ACTIVE | ARCHIVEE
|
||||
final String? dernierMessageApercu;
|
||||
final String? dernierMessageType;
|
||||
final DateTime? dernierMessageAt;
|
||||
final int nonLus;
|
||||
final String? organisationId;
|
||||
|
||||
const Conversation({
|
||||
const ConversationSummary({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.description,
|
||||
required this.type,
|
||||
required this.participantIds,
|
||||
this.organizationId,
|
||||
this.lastMessage,
|
||||
this.unreadCount = 0,
|
||||
this.isMuted = false,
|
||||
this.isPinned = false,
|
||||
this.isArchived = false,
|
||||
required this.createdAt,
|
||||
this.updatedAt,
|
||||
this.avatarUrl,
|
||||
this.metadata,
|
||||
required this.typeConversation,
|
||||
required this.titre,
|
||||
required this.statut,
|
||||
this.dernierMessageApercu,
|
||||
this.dernierMessageType,
|
||||
this.dernierMessageAt,
|
||||
this.nonLus = 0,
|
||||
this.organisationId,
|
||||
});
|
||||
|
||||
/// Vérifie si la conversation a des messages non lus
|
||||
bool get hasUnread => unreadCount > 0;
|
||||
|
||||
/// Vérifie si c'est une conversation individuelle
|
||||
bool get isIndividual => type == ConversationType.individual;
|
||||
|
||||
/// Vérifie si c'est un broadcast
|
||||
bool get isBroadcast => type == ConversationType.broadcast;
|
||||
|
||||
/// Nombre de participants
|
||||
int get participantCount => participantIds.length;
|
||||
|
||||
/// Copie avec modifications
|
||||
Conversation copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? description,
|
||||
ConversationType? type,
|
||||
List<String>? participantIds,
|
||||
String? organizationId,
|
||||
Message? lastMessage,
|
||||
int? unreadCount,
|
||||
bool? isMuted,
|
||||
bool? isPinned,
|
||||
bool? isArchived,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? avatarUrl,
|
||||
Map<String, dynamic>? metadata,
|
||||
}) {
|
||||
return Conversation(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
type: type ?? this.type,
|
||||
participantIds: participantIds ?? this.participantIds,
|
||||
organizationId: organizationId ?? this.organizationId,
|
||||
lastMessage: lastMessage ?? this.lastMessage,
|
||||
unreadCount: unreadCount ?? this.unreadCount,
|
||||
isMuted: isMuted ?? this.isMuted,
|
||||
isPinned: isPinned ?? this.isPinned,
|
||||
isArchived: isArchived ?? this.isArchived,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
avatarUrl: avatarUrl ?? this.avatarUrl,
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
}
|
||||
bool get hasUnread => nonLus > 0;
|
||||
bool get isDirecte => typeConversation == 'DIRECTE';
|
||||
bool get isRoleCanal => typeConversation == 'ROLE_CANAL';
|
||||
bool get isGroupe => typeConversation == 'GROUPE';
|
||||
bool get isActive => statut == 'ACTIVE';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
participantIds,
|
||||
organizationId,
|
||||
lastMessage,
|
||||
unreadCount,
|
||||
isMuted,
|
||||
isPinned,
|
||||
isArchived,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
avatarUrl,
|
||||
metadata,
|
||||
id, typeConversation, titre, statut,
|
||||
dernierMessageApercu, dernierMessageType, dernierMessageAt,
|
||||
nonLus, organisationId,
|
||||
];
|
||||
}
|
||||
|
||||
// ── Participant ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// Participant dans une conversation
|
||||
class ConversationParticipant extends Equatable {
|
||||
final String membreId;
|
||||
final String? prenom;
|
||||
final String? nom;
|
||||
final String? roleDansConversation;
|
||||
final DateTime? luJusqua;
|
||||
|
||||
const ConversationParticipant({
|
||||
required this.membreId,
|
||||
this.prenom,
|
||||
this.nom,
|
||||
this.roleDansConversation,
|
||||
this.luJusqua,
|
||||
});
|
||||
|
||||
String get nomComplet {
|
||||
if (prenom != null && nom != null) return '$prenom $nom';
|
||||
if (prenom != null) return prenom!;
|
||||
if (nom != null) return nom!;
|
||||
return membreId;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [membreId, prenom, nom, roleDansConversation, luJusqua];
|
||||
}
|
||||
|
||||
// ── Conversation complète (détail) ───────────────────────────────────────────
|
||||
|
||||
/// Conversation complète avec participants et messages
|
||||
class Conversation extends Equatable {
|
||||
final String id;
|
||||
final String typeConversation;
|
||||
final String titre;
|
||||
final String statut;
|
||||
final String? organisationId;
|
||||
final String? organisationNom;
|
||||
final DateTime? dateCreation;
|
||||
final int nombreMessages;
|
||||
final List<ConversationParticipant> participants;
|
||||
final List<Message> messages;
|
||||
final int nonLus;
|
||||
final String? roleCible;
|
||||
|
||||
const Conversation({
|
||||
required this.id,
|
||||
required this.typeConversation,
|
||||
required this.titre,
|
||||
required this.statut,
|
||||
this.organisationId,
|
||||
this.organisationNom,
|
||||
this.dateCreation,
|
||||
this.nombreMessages = 0,
|
||||
this.participants = const [],
|
||||
this.messages = const [],
|
||||
this.nonLus = 0,
|
||||
this.roleCible,
|
||||
});
|
||||
|
||||
bool get isDirecte => typeConversation == 'DIRECTE';
|
||||
bool get isRoleCanal => typeConversation == 'ROLE_CANAL';
|
||||
bool get isGroupe => typeConversation == 'GROUPE';
|
||||
bool get isActive => statut == 'ACTIVE';
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id, typeConversation, titre, statut, organisationId, organisationNom,
|
||||
dateCreation, nombreMessages, participants, messages, nonLus, roleCible,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user