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,16 +1,18 @@
|
||||
/// Datasource distant pour la communication (API)
|
||||
/// Datasource distant pour la messagerie v4 — /api/messagerie/*
|
||||
library messaging_remote_datasource;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../../core/config/environment.dart';
|
||||
import '../../../../core/error/exceptions.dart';
|
||||
import '../../../authentication/data/datasources/keycloak_auth_service.dart';
|
||||
import '../models/message_model.dart';
|
||||
import '../models/conversation_model.dart';
|
||||
import '../../domain/entities/message.dart';
|
||||
import '../models/message_model.dart';
|
||||
import '../models/contact_policy_model.dart';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
@lazySingleton
|
||||
class MessagingRemoteDatasource {
|
||||
@@ -22,7 +24,7 @@ class MessagingRemoteDatasource {
|
||||
required this.authService,
|
||||
});
|
||||
|
||||
/// Headers HTTP avec authentification — rafraîchit le token si expiré (fix IC-03)
|
||||
/// Headers HTTP avec authentification — rafraîchit le token si expiré
|
||||
Future<Map<String, String>> _getHeaders() async {
|
||||
final token = await authService.getValidAccessToken();
|
||||
return {
|
||||
@@ -32,290 +34,271 @@ class MessagingRemoteDatasource {
|
||||
};
|
||||
}
|
||||
|
||||
// === CONVERSATIONS ===
|
||||
String get _base => '${AppConfig.apiBaseUrl}/api/messagerie';
|
||||
|
||||
Future<List<ConversationModel>> getConversations({
|
||||
String? organizationId,
|
||||
bool includeArchived = false,
|
||||
}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/api/conversations')
|
||||
.replace(queryParameters: {
|
||||
if (organizationId != null) 'organisationId': organizationId,
|
||||
'includeArchived': includeArchived.toString(),
|
||||
});
|
||||
// ── Conversations ─────────────────────────────────────────────────────────
|
||||
|
||||
final response = await client.get(uri, headers: await _getHeaders());
|
||||
Future<List<ConversationSummaryModel>> getMesConversations() async {
|
||||
final response = await client.get(
|
||||
Uri.parse('$_base/conversations'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 200) {
|
||||
final List<dynamic> jsonList = json.decode(response.body);
|
||||
return jsonList
|
||||
.map((json) => ConversationModel.fromJson(json))
|
||||
final list = json.decode(response.body) as List<dynamic>;
|
||||
return list
|
||||
.map((j) => ConversationSummaryModel.fromJson(j as Map<String, dynamic>))
|
||||
.toList();
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors de la récupération des conversations');
|
||||
}
|
||||
throw ServerException('Erreur récupération conversations (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<ConversationModel> getConversation(String id) async {
|
||||
final response = await client.get(
|
||||
Uri.parse('$_base/conversations/$id'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 200) {
|
||||
return ConversationModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
if (response.statusCode == 404) throw NotFoundException('Conversation non trouvée');
|
||||
throw ServerException('Erreur récupération conversation (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<ConversationModel> demarrerConversationDirecte({
|
||||
required String destinataireId,
|
||||
required String organisationId,
|
||||
String? premierMessage,
|
||||
}) async {
|
||||
final body = json.encode({
|
||||
'destinataireId': destinataireId,
|
||||
'organisationId': organisationId,
|
||||
if (premierMessage != null) 'premierMessage': premierMessage,
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
Uri.parse('$_base/conversations/directe'),
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
return ConversationModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Erreur démarrage conversation directe (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<ConversationModel> demarrerConversationRole({
|
||||
required String roleCible,
|
||||
required String organisationId,
|
||||
String? premierMessage,
|
||||
}) async {
|
||||
final body = json.encode({
|
||||
'roleCible': roleCible,
|
||||
'organisationId': organisationId,
|
||||
if (premierMessage != null) 'premierMessage': premierMessage,
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
Uri.parse('$_base/conversations/role'),
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
return ConversationModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Erreur démarrage conversation rôle (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<void> archiverConversation(String id) async {
|
||||
final response = await client.delete(
|
||||
Uri.parse('$_base/conversations/$id'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw ServerException('Erreur archivage conversation (${response.statusCode})');
|
||||
}
|
||||
}
|
||||
|
||||
Future<ConversationModel> getConversationById(String conversationId) async {
|
||||
final uri = Uri.parse(
|
||||
'${AppConfig.apiBaseUrl}/api/conversations/$conversationId');
|
||||
// ── Messages ──────────────────────────────────────────────────────────────
|
||||
|
||||
Future<MessageModel> envoyerMessage(
|
||||
String conversationId, {
|
||||
required String typeMessage,
|
||||
String? contenu,
|
||||
String? urlFichier,
|
||||
int? dureeAudio,
|
||||
String? messageParentId,
|
||||
}) async {
|
||||
final body = json.encode({
|
||||
'typeMessage': typeMessage,
|
||||
if (contenu != null) 'contenu': contenu,
|
||||
if (urlFichier != null) 'urlFichier': urlFichier,
|
||||
if (dureeAudio != null) 'dureeAudio': dureeAudio,
|
||||
if (messageParentId != null) 'messageParentId': messageParentId,
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
Uri.parse('$_base/conversations/$conversationId/messages'),
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
return MessageModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Erreur envoi message (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<List<MessageModel>> getMessages(String conversationId, {int page = 0}) async {
|
||||
final uri = Uri.parse('$_base/conversations/$conversationId/messages')
|
||||
.replace(queryParameters: {'page': page.toString()});
|
||||
|
||||
final response = await client.get(uri, headers: await _getHeaders());
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 200) {
|
||||
return ConversationModel.fromJson(json.decode(response.body));
|
||||
} else if (response.statusCode == 404) {
|
||||
throw NotFoundException('Conversation non trouvée');
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors de la récupération de la conversation');
|
||||
final list = json.decode(response.body) as List<dynamic>;
|
||||
return list
|
||||
.map((j) => MessageModel.fromJson(j as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
throw ServerException('Erreur récupération messages (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<ConversationModel> createConversation({
|
||||
required String name,
|
||||
required List<String> participantIds,
|
||||
String? organizationId,
|
||||
String? description,
|
||||
}) async {
|
||||
final uri =
|
||||
Uri.parse('${AppConfig.apiBaseUrl}/api/conversations');
|
||||
|
||||
final body = json.encode({
|
||||
'name': name,
|
||||
'participantIds': participantIds,
|
||||
'type': 'GROUP', // Default to GROUP for multi-participant conversations
|
||||
if (organizationId != null) 'organisationId': organizationId,
|
||||
if (description != null) 'description': description,
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
uri,
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
return ConversationModel.fromJson(json.decode(response.body));
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors de la création de la conversation');
|
||||
}
|
||||
}
|
||||
|
||||
// === MESSAGES ===
|
||||
|
||||
Future<List<MessageModel>> getMessages({
|
||||
required String conversationId,
|
||||
int? limit,
|
||||
String? beforeMessageId,
|
||||
}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/api/messages')
|
||||
.replace(queryParameters: {
|
||||
'conversationId': conversationId,
|
||||
if (limit != null) 'limit': limit.toString(),
|
||||
// beforeMessageId not supported by backend yet, omit
|
||||
});
|
||||
|
||||
final response = await client.get(uri, headers: await _getHeaders());
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final List<dynamic> jsonList = json.decode(response.body);
|
||||
return jsonList.map((json) => MessageModel.fromJson(json)).toList();
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors de la récupération des messages');
|
||||
}
|
||||
}
|
||||
|
||||
Future<MessageModel> sendMessage({
|
||||
required String conversationId,
|
||||
required String content,
|
||||
List<String>? attachments,
|
||||
MessagePriority priority = MessagePriority.normal,
|
||||
}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/api/messages');
|
||||
|
||||
final body = json.encode({
|
||||
'conversationId': conversationId,
|
||||
'content': content,
|
||||
if (attachments != null) 'attachments': attachments,
|
||||
'priority': priority.name.toUpperCase(),
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
uri,
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
return MessageModel.fromJson(json.decode(response.body));
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors de l\'envoi du message');
|
||||
}
|
||||
}
|
||||
|
||||
Future<MessageModel> sendBroadcast({
|
||||
required String organizationId,
|
||||
required String subject,
|
||||
required String content,
|
||||
MessagePriority priority = MessagePriority.normal,
|
||||
List<String>? attachments,
|
||||
}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/api/messaging/broadcast');
|
||||
|
||||
final body = json.encode({
|
||||
'organizationId': organizationId,
|
||||
'subject': subject,
|
||||
'content': content,
|
||||
'priority': priority.name,
|
||||
if (attachments != null) 'attachments': attachments,
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
uri,
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (response.statusCode == 201 || response.statusCode == 200) {
|
||||
return MessageModel.fromJson(json.decode(response.body));
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else if (response.statusCode == 403) {
|
||||
throw ForbiddenException('Permission insuffisante pour envoyer un broadcast');
|
||||
} else {
|
||||
throw ServerException('Erreur lors de l\'envoi du broadcast');
|
||||
}
|
||||
}
|
||||
|
||||
// === CONVERSATION ACTIONS ===
|
||||
|
||||
Future<void> archiveConversation(String conversationId, {bool archive = true}) async {
|
||||
final uri = Uri.parse(
|
||||
'${AppConfig.apiBaseUrl}/api/conversations/$conversationId/archive')
|
||||
.replace(queryParameters: {'archive': archive.toString()});
|
||||
|
||||
final response = await client.put(uri, headers: await _getHeaders());
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors de l\'archivage de la conversation');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markConversationAsRead(String conversationId) async {
|
||||
final uri = Uri.parse(
|
||||
'${AppConfig.apiBaseUrl}/api/conversations/$conversationId/mark-read');
|
||||
|
||||
final response = await client.put(uri, headers: await _getHeaders());
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors du marquage de la conversation comme lue');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleMuteConversation(String conversationId) async {
|
||||
final uri = Uri.parse(
|
||||
'${AppConfig.apiBaseUrl}/api/conversations/$conversationId/toggle-mute');
|
||||
|
||||
final response = await client.put(uri, headers: await _getHeaders());
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors du toggle mute de la conversation');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> togglePinConversation(String conversationId) async {
|
||||
final uri = Uri.parse(
|
||||
'${AppConfig.apiBaseUrl}/api/conversations/$conversationId/toggle-pin');
|
||||
|
||||
final response = await client.put(uri, headers: await _getHeaders());
|
||||
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else {
|
||||
throw ServerException('Erreur lors du toggle pin de la conversation');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === MESSAGE ACTIONS ===
|
||||
|
||||
Future<MessageModel> editMessage({
|
||||
required String messageId,
|
||||
required String newContent,
|
||||
}) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/api/messages/$messageId');
|
||||
|
||||
final body = json.encode({'content': newContent});
|
||||
|
||||
Future<void> marquerLu(String conversationId) async {
|
||||
final response = await client.put(
|
||||
uri,
|
||||
Uri.parse('$_base/conversations/$conversationId/lire'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw ServerException('Erreur marquage lu (${response.statusCode})');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> supprimerMessage(String conversationId, String messageId) async {
|
||||
final response = await client.delete(
|
||||
Uri.parse('$_base/conversations/$conversationId/messages/$messageId'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw ServerException('Erreur suppression message (${response.statusCode})');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Blocages ──────────────────────────────────────────────────────────────
|
||||
|
||||
Future<void> bloquerMembre({
|
||||
required String membreABloquerId,
|
||||
String? organisationId,
|
||||
String? raison,
|
||||
}) async {
|
||||
final body = json.encode({
|
||||
'membreABloquerId': membreABloquerId,
|
||||
if (organisationId != null) 'organisationId': organisationId,
|
||||
if (raison != null) 'raison': raison,
|
||||
});
|
||||
|
||||
final response = await client.post(
|
||||
Uri.parse('$_base/blocages'),
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return MessageModel.fromJson(json.decode(response.body));
|
||||
} else if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else if (response.statusCode == 404) {
|
||||
throw NotFoundException('Message non trouvé');
|
||||
} else {
|
||||
throw ServerException('Erreur lors de l\'édition du message');
|
||||
_checkAuth(response);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw ServerException('Erreur blocage membre (${response.statusCode})');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMessage(String messageId) async {
|
||||
final uri = Uri.parse('${AppConfig.apiBaseUrl}/api/messages/$messageId');
|
||||
Future<void> debloquerMembre(String membreId, {String? organisationId}) async {
|
||||
final uri = Uri.parse('$_base/blocages/$membreId').replace(
|
||||
queryParameters: {
|
||||
if (organisationId != null) 'organisationId': organisationId,
|
||||
},
|
||||
);
|
||||
|
||||
final response = await client.delete(uri, headers: await _getHeaders());
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
if (response.statusCode == 401) {
|
||||
throw UnauthorizedException();
|
||||
} else if (response.statusCode == 404) {
|
||||
throw NotFoundException('Message non trouvé');
|
||||
} else {
|
||||
throw ServerException('Erreur lors de la suppression du message');
|
||||
}
|
||||
throw ServerException('Erreur déblocage membre (${response.statusCode})');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> markMessageAsRead(String messageId) async {
|
||||
// Backend has no per-message read endpoint — use markConversationAsRead
|
||||
if (AppConfig.enableLogging) {
|
||||
debugPrint('[Messaging] markMessageAsRead ignored (no per-message endpoint), messageId=$messageId');
|
||||
Future<List<Map<String, dynamic>>> getMesBlocages() async {
|
||||
final response = await client.get(
|
||||
Uri.parse('$_base/blocages'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 200) {
|
||||
final list = json.decode(response.body) as List<dynamic>;
|
||||
return list.map((j) => j as Map<String, dynamic>).toList();
|
||||
}
|
||||
throw ServerException('Erreur récupération blocages (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<int> getUnreadCount({String? organizationId}) async {
|
||||
try {
|
||||
final conversations = await getConversations(organizationId: organizationId);
|
||||
return conversations.fold<int>(0, (sum, c) => sum + c.unreadCount);
|
||||
} catch (_) {
|
||||
return 0;
|
||||
// ── Politique de communication ────────────────────────────────────────────
|
||||
|
||||
Future<ContactPolicyModel> getPolitique(String organisationId) async {
|
||||
final response = await client.get(
|
||||
Uri.parse('$_base/politique/$organisationId'),
|
||||
headers: await _getHeaders(),
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 200) {
|
||||
return ContactPolicyModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Erreur récupération politique (${response.statusCode})');
|
||||
}
|
||||
|
||||
Future<ContactPolicyModel> mettreAJourPolitique(
|
||||
String organisationId, {
|
||||
required String typePolitique,
|
||||
required bool autoriserMembreVersMembre,
|
||||
required bool autoriserMembreVersRole,
|
||||
required bool autoriserNotesVocales,
|
||||
}) async {
|
||||
final body = json.encode({
|
||||
'typePolitique': typePolitique,
|
||||
'autoriserMembreVersMembre': autoriserMembreVersMembre,
|
||||
'autoriserMembreVersRole': autoriserMembreVersRole,
|
||||
'autoriserNotesVocales': autoriserNotesVocales,
|
||||
});
|
||||
|
||||
final response = await client.put(
|
||||
Uri.parse('$_base/politique/$organisationId'),
|
||||
headers: await _getHeaders(),
|
||||
body: body,
|
||||
);
|
||||
|
||||
_checkAuth(response);
|
||||
if (response.statusCode == 200) {
|
||||
return ContactPolicyModel.fromJson(json.decode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Erreur mise à jour politique (${response.statusCode})');
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
void _checkAuth(http.Response response) {
|
||||
if (response.statusCode == 401) throw UnauthorizedException();
|
||||
if (response.statusCode == 403) throw ForbiddenException('Permission insuffisante');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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é.
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user