/// A BLoC (Business Logic Component) that manages message-related operations for group chats. /// /// This bloc handles all messaging functionality including sending, updating, deleting messages, /// and managing reactions. It provides real-time updates through streams and manages the /// relationship between users and group conversations. /// /// The bloc processes these main events: /// - [LoadMessages]: Loads messages for a group with real-time updates /// - [SendMessage]: Sends a new message to a group chat /// - [DeleteMessage]: Deletes a message from the chat /// - [UpdateMessage]: Updates/edits an existing message /// - [ReactToMessage]: Adds an emoji reaction to a message /// - [RemoveReaction]: Removes a user's reaction from a message /// /// Dependencies: /// - [MessageService]: Service for message operations and business logic /// - [MessageRepository]: Repository for message data operations /// /// Example usage: /// ```dart /// final messageBloc = MessageBloc(); /// /// // Load messages for a group /// messageBloc.add(LoadMessages('groupId123')); /// /// // Send a message /// messageBloc.add(SendMessage( /// groupId: 'groupId123', /// text: 'Hello everyone!', /// senderId: 'userId123', /// senderName: 'John Doe', /// )); /// /// // React to a message /// messageBloc.add(ReactToMessage( /// groupId: 'groupId123', /// messageId: 'msgId456', /// userId: 'userId123', /// reaction: '👍', /// )); /// ``` import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../models/message.dart'; import '../../services/message_service.dart'; import '../../repositories/message_repository.dart'; import 'message_event.dart'; import 'message_state.dart'; /// BLoC that manages message-related operations and real-time chat state. class MessageBloc extends Bloc { /// Service for message operations and business logic final MessageService _messageService; /// Subscription to message stream for real-time updates StreamSubscription>? _messagesSubscription; /// Constructor for MessageBloc. /// /// Initializes the bloc with an optional message service. If no service is provided, /// creates a default MessageService with MessageRepository. Sets up event handlers /// for all message-related operations. /// /// Args: /// [messageService]: Optional service for message operations (auto-created if null) MessageBloc({MessageService? messageService}) : _messageService = messageService ?? MessageService( messageRepository: MessageRepository(), ), super(MessageInitial()) { on(_onLoadMessages); on(_onSendMessage); on(_onDeleteMessage); on(_onUpdateMessage); on(_onReactToMessage); on(_onRemoveReaction); on<_MessagesUpdated>(_onMessagesUpdated); } /// Handles [LoadMessages] events. /// /// Loads messages for a specific group with real-time updates via stream subscription. /// Cancels any existing subscription before creating a new one to prevent memory leaks. /// /// Args: /// [event]: The LoadMessages event containing the group ID /// [emit]: State emitter function Future _onLoadMessages( LoadMessages event, Emitter emit, ) async { emit(MessageLoading()); await _messagesSubscription?.cancel(); _messagesSubscription = _messageService .getMessagesStream(event.groupId) .listen( (messages) { add(_MessagesUpdated(messages: messages, groupId: event.groupId)); }, onError: (error) { add(_MessagesError('Error loading messages: $error')); }, ); } /// Handles [_MessagesUpdated] events. /// /// Processes real-time updates from the message stream, emitting the /// updated message list with the associated group ID. /// /// Args: /// [event]: The _MessagesUpdated event containing messages and group ID /// [emit]: State emitter function void _onMessagesUpdated( _MessagesUpdated event, Emitter emit, ) { emit(MessagesLoaded(messages: event.messages, groupId: event.groupId)); } /// Handles [SendMessage] events. /// /// Sends a new message to a group chat. The stream subscription will /// automatically update the UI with the new message, so no state is emitted here. /// /// Args: /// [event]: The SendMessage event containing message details /// [emit]: State emitter function Future _onSendMessage( SendMessage event, Emitter emit, ) async { try { // Just perform the action, the stream will update await _messageService.sendMessage( groupId: event.groupId, text: event.text, senderId: event.senderId, senderName: event.senderName, ); } catch (e) { emit(MessageError('Error sending message: $e')); } } /// Handles [DeleteMessage] events. /// /// Deletes a message from the group chat. The Firestore stream will /// automatically update the UI, so no state is emitted here unless there's an error. /// /// Args: /// [event]: The DeleteMessage event containing group ID and message ID /// [emit]: State emitter function Future _onDeleteMessage( DeleteMessage event, Emitter emit, ) async { try { // Don't emit state, just perform the action // The Firestore stream will update automatically await _messageService.deleteMessage( groupId: event.groupId, messageId: event.messageId, ); } catch (e) { emit(MessageError('Error deleting message: $e')); } } /// Handles [UpdateMessage] events. /// /// Updates/edits an existing message in the group chat. The Firestore stream will /// automatically update the UI with the edited message, so no state is emitted here. /// /// Args: /// [event]: The UpdateMessage event containing message ID and new text /// [emit]: State emitter function Future _onUpdateMessage( UpdateMessage event, Emitter emit, ) async { try { // Don't emit state, just perform the action // The Firestore stream will update automatically await _messageService.updateMessage( groupId: event.groupId, messageId: event.messageId, newText: event.newText, ); } catch (e) { emit(MessageError('Error updating message: $e')); } } /// Handles [ReactToMessage] events. /// /// Adds an emoji reaction to a message. The Firestore stream will /// automatically update the UI with the new reaction, so no state is emitted here. /// /// Args: /// [event]: The ReactToMessage event containing message ID, user ID, and reaction /// [emit]: State emitter function Future _onReactToMessage( ReactToMessage event, Emitter emit, ) async { try { // Don't emit state, just perform the action // The Firestore stream will update automatically await _messageService.reactToMessage( groupId: event.groupId, messageId: event.messageId, userId: event.userId, reaction: event.reaction, ); } catch (e) { emit(MessageError('Error adding reaction: $e')); } } /// Handles [RemoveReaction] events. /// /// Removes a user's reaction from a message. The Firestore stream will /// automatically update the UI with the removed reaction, so no state is emitted here. /// /// Args: /// [event]: The RemoveReaction event containing message ID and user ID /// [emit]: State emitter function Future _onRemoveReaction( RemoveReaction event, Emitter emit, ) async { try { // Don't emit state, just perform the action // The Firestore stream will update automatically await _messageService.removeReaction( groupId: event.groupId, messageId: event.messageId, userId: event.userId, ); } catch (e) { emit(MessageError('Error removing reaction: $e')); } } /// Cleans up resources when the bloc is closed. /// /// Cancels the message stream subscription to prevent memory leaks /// and ensure proper disposal of resources. @override Future close() { _messagesSubscription?.cancel(); return super.close(); } } /// Private event for handling real-time message updates from streams. /// /// This internal event is used to process updates from the message stream /// subscription and emit appropriate states based on the received data. class _MessagesUpdated extends MessageEvent { /// List of messages received from the stream final List messages; /// Group ID associated with the messages final String groupId; /// Creates a _MessagesUpdated event. /// /// Args: /// [messages]: List of messages from the stream update /// [groupId]: ID of the group these messages belong to const _MessagesUpdated({ required this.messages, required this.groupId, }); @override List get props => [messages, groupId]; } /// Private event for handling message stream errors. /// /// This internal event is used to process errors from the message stream /// subscription and emit appropriate error states. class _MessagesError extends MessageEvent { /// Error message from the stream final String error; /// Creates a _MessagesError event. /// /// Args: /// [error]: Error message from the stream failure const _MessagesError(this.error); @override List get props => [error]; }