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:
dahoud
2026-04-15 20:26:35 +00:00
parent 07b8488714
commit 45dcd2171e
23 changed files with 2096 additions and 1588 deletions

View File

@@ -1,416 +1,252 @@
/// Implémentation du repository de messagerie
/// Implémentation du repository de messagerie v4
library messaging_repository_impl;
import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/network/network_info.dart';
import '../../domain/entities/conversation.dart';
import '../../domain/entities/message.dart';
import '../../domain/entities/message_template.dart';
import '../../domain/entities/contact_policy.dart';
import '../../domain/repositories/messaging_repository.dart';
import '../datasources/messaging_remote_datasource.dart';
@LazySingleton(as: MessagingRepository)
class MessagingRepositoryImpl implements MessagingRepository {
final MessagingRemoteDatasource remoteDatasource;
final NetworkInfo networkInfo;
MessagingRepositoryImpl({
required this.remoteDatasource,
required this.networkInfo,
});
MessagingRepositoryImpl({required this.remoteDatasource});
@override
Future<Either<Failure, List<Conversation>>> getConversations({
String? organizationId,
bool includeArchived = false,
}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<List<ConversationSummary>> getMesConversations() async {
try {
final conversations = await remoteDatasource.getConversations(
organizationId: organizationId,
includeArchived: includeArchived,
);
return Right(conversations);
return await remoteDatasource.getMesConversations();
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, Conversation>> getConversationById(
String conversationId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<Conversation> getConversation(String conversationId) async {
try {
final conversation =
await remoteDatasource.getConversationById(conversationId);
return Right(conversation);
return await remoteDatasource.getConversation(conversationId);
} on NotFoundException {
return Left(NotFoundFailure('Conversation non trouvée'));
throw Exception('Conversation non trouvée');
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, Conversation>> createConversation({
required String name,
required List<String> participantIds,
String? organizationId,
String? description,
Future<Conversation> demarrerConversationDirecte({
required String destinataireId,
required String organisationId,
String? premierMessage,
}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
final conversation = await remoteDatasource.createConversation(
name: name,
participantIds: participantIds,
organizationId: organizationId,
description: description,
return await remoteDatasource.demarrerConversationDirecte(
destinataireId: destinataireId,
organisationId: organisationId,
premierMessage: premierMessage,
);
return Right(conversation);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, List<Message>>> getMessages({
required String conversationId,
int? limit,
String? beforeMessageId,
Future<Conversation> demarrerConversationRole({
required String roleCible,
required String organisationId,
String? premierMessage,
}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
final messages = await remoteDatasource.getMessages(
conversationId: conversationId,
limit: limit,
beforeMessageId: beforeMessageId,
return await remoteDatasource.demarrerConversationRole(
roleCible: roleCible,
organisationId: organisationId,
premierMessage: premierMessage,
);
return Right(messages);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, Message>> sendMessage({
required String conversationId,
required String content,
List<String>? attachments,
MessagePriority priority = MessagePriority.normal,
}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<void> archiverConversation(String conversationId) async {
try {
final message = await remoteDatasource.sendMessage(
conversationId: conversationId,
content: content,
attachments: attachments,
priority: priority,
);
return Right(message);
await remoteDatasource.archiverConversation(conversationId);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, Message>> sendBroadcast({
required String organizationId,
required String subject,
required String content,
MessagePriority priority = MessagePriority.normal,
List<String>? attachments,
Future<Message> envoyerMessage(
String conversationId, {
required String typeMessage,
String? contenu,
String? urlFichier,
int? dureeAudio,
String? messageParentId,
}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
final message = await remoteDatasource.sendBroadcast(
organizationId: organizationId,
subject: subject,
content: content,
priority: priority,
attachments: attachments,
return await remoteDatasource.envoyerMessage(
conversationId,
typeMessage: typeMessage,
contenu: contenu,
urlFichier: urlFichier,
dureeAudio: dureeAudio,
messageParentId: messageParentId,
);
return Right(message);
} on UnauthorizedException {
throw Exception('Session expirée — veuillez vous reconnecter');
} on ForbiddenException catch (e) {
return Left(ForbiddenFailure(e.message));
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception(e.message);
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, void>> markMessageAsRead(String messageId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<List<Message>> getMessages(String conversationId, {int page = 0}) async {
try {
await remoteDatasource.markMessageAsRead(messageId);
return const Right(null);
return await remoteDatasource.getMessages(conversationId, page: page);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, int>> getUnreadCount({String? organizationId}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<void> marquerLu(String conversationId) async {
try {
final count =
await remoteDatasource.getUnreadCount(organizationId: organizationId);
return Right(count);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
await remoteDatasource.marquerLu(conversationId);
} catch (_) {
// Non-bloquant — ignorer les erreurs de marquage
}
}
// === CONVERSATION ACTIONS ===
@override
Future<Either<Failure, void>> archiveConversation(String conversationId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<void> supprimerMessage(String conversationId, String messageId) async {
try {
await remoteDatasource.archiveConversation(conversationId);
return const Right(null);
await remoteDatasource.supprimerMessage(conversationId, messageId);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, Message>> sendTargetedMessage({
required String organizationId,
required List<String> targetRoles,
required String subject,
required String content,
MessagePriority priority = MessagePriority.normal,
Future<void> bloquerMembre({
required String membreABloquerId,
String? organisationId,
String? raison,
}) async {
// TODO: Backend needs specific endpoint for targeted messages
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, void>> markConversationAsRead(String conversationId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
await remoteDatasource.markConversationAsRead(conversationId);
return const Right(null);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
}
}
@override
Future<Either<Failure, void>> toggleMuteConversation(String conversationId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
await remoteDatasource.toggleMuteConversation(conversationId);
return const Right(null);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
}
}
@override
Future<Either<Failure, void>> togglePinConversation(String conversationId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
await remoteDatasource.togglePinConversation(conversationId);
return const Right(null);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
}
}
// === MESSAGE ACTIONS ===
@override
Future<Either<Failure, Message>> editMessage({
required String messageId,
required String newContent,
}) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
try {
final message = await remoteDatasource.editMessage(
messageId: messageId,
newContent: newContent,
await remoteDatasource.bloquerMembre(
membreABloquerId: membreABloquerId,
organisationId: organisationId,
raison: raison,
);
return Right(message);
} on NotFoundException {
return Left(NotFoundFailure('Message non trouvé'));
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, void>> deleteMessage(String messageId) async {
if (!await networkInfo.isConnected) {
return Left(NetworkFailure('Pas de connexion Internet'));
}
Future<void> debloquerMembre(String membreId, {String? organisationId}) async {
try {
await remoteDatasource.deleteMessage(messageId);
return const Right(null);
} on NotFoundException {
return Left(NotFoundFailure('Message non trouvé'));
await remoteDatasource.debloquerMembre(membreId, organisationId: organisationId);
} on UnauthorizedException {
return Left(UnauthorizedFailure('Session expirée'));
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
return Left(ServerFailure(e.message));
throw Exception(e.message);
} catch (e) {
return Left(UnexpectedFailure('Erreur inattendue: $e'));
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<Either<Failure, List<MessageTemplate>>> getTemplates({
String? organizationId,
TemplateCategory? category,
Future<List<Map<String, dynamic>>> getMesBlocages() async {
try {
return await remoteDatasource.getMesBlocages();
} on UnauthorizedException {
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
throw Exception(e.message);
} catch (e) {
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<ContactPolicy> getPolitique(String organisationId) async {
try {
return await remoteDatasource.getPolitique(organisationId);
} on UnauthorizedException {
throw Exception('Session expirée — veuillez vous reconnecter');
} on ServerException catch (e) {
throw Exception(e.message);
} catch (e) {
throw Exception('Erreur inattendue: $e');
}
}
@override
Future<ContactPolicy> mettreAJourPolitique(
String organisationId, {
required String typePolitique,
required bool autoriserMembreVersMembre,
required bool autoriserMembreVersRole,
required bool autoriserNotesVocales,
}) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, MessageTemplate>> getTemplateById(String templateId) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, MessageTemplate>> createTemplate({
required String name,
required String description,
required TemplateCategory category,
required String subject,
required String body,
List<Map<String, dynamic>>? variables,
String? organizationId,
}) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, MessageTemplate>> updateTemplate({
required String templateId,
String? name,
String? description,
String? subject,
String? body,
bool? isActive,
}) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, void>> deleteTemplate(String templateId) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, Message>> sendFromTemplate({
required String templateId,
required Map<String, String> variables,
required List<String> recipientIds,
}) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
}
@override
Future<Either<Failure, Map<String, dynamic>>> getMessagingStats({
required String organizationId,
DateTime? startDate,
DateTime? endDate,
}) async {
return Left(NotImplementedFailure('Fonctionnalité en cours de développement'));
try {
return await remoteDatasource.mettreAJourPolitique(
organisationId,
typePolitique: typePolitique,
autoriserMembreVersMembre: autoriserMembreVersMembre,
autoriserMembreVersRole: autoriserMembreVersRole,
autoriserNotesVocales: autoriserNotesVocales,
);
} on UnauthorizedException {
throw Exception('Session expirée — veuillez vous reconnecter');
} on ForbiddenException catch (e) {
throw Exception(e.message);
} on ServerException catch (e) {
throw Exception(e.message);
} catch (e) {
throw Exception('Erreur inattendue: $e');
}
}
}