302 lines
9.6 KiB
Dart
302 lines
9.6 KiB
Dart
/// 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: '👍',
|
|
/// ));
|
|
/// ```
|
|
library;
|
|
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<MessageEvent, MessageState> {
|
|
/// Service for message operations and business logic
|
|
final MessageService _messageService;
|
|
|
|
/// Subscription to message stream for real-time updates
|
|
StreamSubscription<List<Message>>? _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<LoadMessages>(_onLoadMessages);
|
|
on<SendMessage>(_onSendMessage);
|
|
on<DeleteMessage>(_onDeleteMessage);
|
|
on<UpdateMessage>(_onUpdateMessage);
|
|
on<ReactToMessage>(_onReactToMessage);
|
|
on<RemoveReaction>(_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<void> _onLoadMessages(
|
|
LoadMessages event,
|
|
Emitter<MessageState> 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<MessageState> 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<void> _onSendMessage(
|
|
SendMessage event,
|
|
Emitter<MessageState> 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<void> _onDeleteMessage(
|
|
DeleteMessage event,
|
|
Emitter<MessageState> 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<void> _onUpdateMessage(
|
|
UpdateMessage event,
|
|
Emitter<MessageState> 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<void> _onReactToMessage(
|
|
ReactToMessage event,
|
|
Emitter<MessageState> 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<void> _onRemoveReaction(
|
|
RemoveReaction event,
|
|
Emitter<MessageState> 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<void> 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<Message> 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<Object?> 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<Object?> get props => [error];
|
|
}
|