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.)
203 lines
6.3 KiB
Dart
203 lines
6.3 KiB
Dart
/// BLoC de gestion de la messagerie v4
|
|
library messaging_bloc;
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
|
|
import '../../../../core/utils/logger.dart';
|
|
import '../../../../core/websocket/websocket_service.dart';
|
|
import '../../domain/repositories/messaging_repository.dart';
|
|
import 'messaging_event.dart';
|
|
import 'messaging_state.dart';
|
|
|
|
@injectable
|
|
class MessagingBloc extends Bloc<MessagingEvent, MessagingState> {
|
|
final MessagingRepository _repository;
|
|
final WebSocketService _webSocketService;
|
|
|
|
StreamSubscription<WebSocketEvent>? _wsSubscription;
|
|
String? _currentConversationId;
|
|
|
|
MessagingBloc({
|
|
required MessagingRepository repository,
|
|
required WebSocketService webSocketService,
|
|
}) : _repository = repository,
|
|
_webSocketService = webSocketService,
|
|
super(MessagingInitial()) {
|
|
on<LoadMesConversations>(_onLoadMesConversations);
|
|
on<OpenConversation>(_onOpenConversation);
|
|
on<DemarrerConversationDirecte>(_onDemarrerConversationDirecte);
|
|
on<DemarrerConversationRole>(_onDemarrerConversationRole);
|
|
on<ArchiverConversation>(_onArchiverConversation);
|
|
on<EnvoyerMessageTexte>(_onEnvoyerMessageTexte);
|
|
on<LoadMessages>(_onLoadMessages);
|
|
on<MarquerLu>(_onMarquerLu);
|
|
on<SupprimerMessage>(_onSupprimerMessage);
|
|
on<NouveauMessageWebSocket>(_onNouveauMessageWebSocket);
|
|
|
|
_listenWebSocket();
|
|
}
|
|
|
|
void _listenWebSocket() {
|
|
_wsSubscription = _webSocketService.eventStream.listen((event) {
|
|
if (event is ChatMessageEvent && event.conversationId != null) {
|
|
AppLogger.info('MessagingBloc: NOUVEAU_MESSAGE WS pour conv ${event.conversationId}');
|
|
add(NouveauMessageWebSocket(event.conversationId!));
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _onLoadMesConversations(
|
|
LoadMesConversations event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
emit(MessagingLoading());
|
|
try {
|
|
final conversations = await _repository.getMesConversations();
|
|
emit(MesConversationsLoaded(conversations));
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onOpenConversation(
|
|
OpenConversation event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
emit(MessagingLoading());
|
|
try {
|
|
_currentConversationId = event.conversationId;
|
|
final conversation = await _repository.getConversation(event.conversationId);
|
|
emit(ConversationOuverte(conversation));
|
|
// Marquer comme lu en arrière-plan (non-bloquant)
|
|
_repository.marquerLu(event.conversationId);
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onDemarrerConversationDirecte(
|
|
DemarrerConversationDirecte event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
emit(MessagingLoading());
|
|
try {
|
|
final conversation = await _repository.demarrerConversationDirecte(
|
|
destinataireId: event.destinataireId,
|
|
organisationId: event.organisationId,
|
|
premierMessage: event.premierMessage,
|
|
);
|
|
emit(ConversationCreee(conversation));
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onDemarrerConversationRole(
|
|
DemarrerConversationRole event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
emit(MessagingLoading());
|
|
try {
|
|
final conversation = await _repository.demarrerConversationRole(
|
|
roleCible: event.roleCible,
|
|
organisationId: event.organisationId,
|
|
premierMessage: event.premierMessage,
|
|
);
|
|
emit(ConversationCreee(conversation));
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onArchiverConversation(
|
|
ArchiverConversation event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
try {
|
|
await _repository.archiverConversation(event.conversationId);
|
|
emit(const MessagingActionOk('archiver'));
|
|
add(const LoadMesConversations());
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onEnvoyerMessageTexte(
|
|
EnvoyerMessageTexte event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
try {
|
|
final message = await _repository.envoyerMessage(
|
|
event.conversationId,
|
|
typeMessage: 'TEXTE',
|
|
contenu: event.contenu,
|
|
messageParentId: event.messageParentId,
|
|
);
|
|
emit(MessageEnvoye(message: message, conversationId: event.conversationId));
|
|
// Recharger les messages après envoi
|
|
add(LoadMessages(conversationId: event.conversationId));
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onLoadMessages(
|
|
LoadMessages event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
try {
|
|
final messages = await _repository.getMessages(event.conversationId, page: event.page);
|
|
emit(MessagesLoaded(
|
|
conversationId: event.conversationId,
|
|
messages: messages,
|
|
hasMore: messages.length >= 20,
|
|
));
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onMarquerLu(
|
|
MarquerLu event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
await _repository.marquerLu(event.conversationId);
|
|
}
|
|
|
|
Future<void> _onSupprimerMessage(
|
|
SupprimerMessage event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
try {
|
|
await _repository.supprimerMessage(event.conversationId, event.messageId);
|
|
emit(const MessagingActionOk('supprimer-message'));
|
|
add(LoadMessages(conversationId: event.conversationId));
|
|
} catch (e) {
|
|
emit(MessagingError(e.toString().replaceFirst('Exception: ', '')));
|
|
}
|
|
}
|
|
|
|
Future<void> _onNouveauMessageWebSocket(
|
|
NouveauMessageWebSocket event,
|
|
Emitter<MessagingState> emit,
|
|
) async {
|
|
// Si la conversation est actuellement ouverte, recharger les messages
|
|
if (_currentConversationId == event.conversationId) {
|
|
add(LoadMessages(conversationId: event.conversationId));
|
|
} else {
|
|
// Sinon, rafraîchir la liste pour mettre à jour le badge non-lus
|
|
add(const LoadMesConversations());
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> close() {
|
|
_wsSubscription?.cancel();
|
|
_currentConversationId = null;
|
|
return super.close();
|
|
}
|
|
}
|