/// A BLoC (Business Logic Component) that manages group-related operations. /// /// This bloc handles all group operations including creation, updates, member management, /// and loading groups for users or trips. It provides real-time updates through streams /// and manages the relationship between users, groups, and trips. /// /// The bloc processes these main events: /// - [LoadGroupsByUserId]: Loads all groups for a specific user with real-time updates /// - [LoadGroupsByTrip]: Loads the group associated with a specific trip /// - [CreateGroup]: Creates a new group without members /// - [CreateGroupWithMembers]: Creates a new group with initial members /// - [AddMemberToGroup]: Adds a member to an existing group /// - [RemoveMemberFromGroup]: Removes a member from a group /// - [UpdateGroup]: Updates group information /// - [DeleteGroup]: Deletes a group /// /// Dependencies: /// - [GroupRepository]: Repository for group data operations /// - [ErrorService]: Service for error logging and handling /// /// Example usage: /// ```dart /// final groupBloc = GroupBloc(groupRepository); /// /// // Load groups for a user /// groupBloc.add(LoadGroupsByUserId('userId123')); /// /// // Create a new group with members /// groupBloc.add(CreateGroupWithMembers( /// group: newGroup, /// members: [member1, member2], /// )); /// ``` import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/services/error_service.dart'; import 'group_event.dart'; import 'group_state.dart'; import '../../repositories/group_repository.dart'; import '../../models/group.dart'; /// BLoC that manages group-related operations and state. class GroupBloc extends Bloc { /// Repository for group data operations final GroupRepository _repository; /// Subscription to group stream for real-time updates StreamSubscription? _groupsSubscription; /// Service for error handling and logging final _errorService = ErrorService(); /// Constructor for GroupBloc. /// /// Initializes the bloc with the group repository and sets up event handlers /// for all group-related operations. /// /// Args: /// [_repository]: Repository for group data operations GroupBloc(this._repository) : super(GroupInitial()) { on(_onLoadGroupsByUserId); on<_GroupsUpdated>(_onGroupsUpdated); on(_onLoadGroupsByTrip); on(_onCreateGroup); on(_onCreateGroupWithMembers); on(_onAddMemberToGroup); on(_onRemoveMemberFromGroup); on(_onUpdateGroup); on(_onDeleteGroup); } /// Handles [LoadGroupsByUserId] events. /// /// Loads all groups for a specific user with real-time updates via stream subscription. /// Cancels any existing subscription before creating a new one to prevent memory leaks. /// /// Args: /// [event]: The LoadGroupsByUserId event containing the user ID /// [emit]: State emitter function Future _onLoadGroupsByUserId( LoadGroupsByUserId event, Emitter emit, ) async { try { emit(GroupLoading()); await _groupsSubscription?.cancel(); _groupsSubscription = _repository.getGroupsByUserId(event.userId).listen( (groups) { add(_GroupsUpdated(groups)); }, onError: (error) { add(_GroupsUpdated([], error: error.toString())); }, ); } catch (e, stackTrace) { _errorService.logError(e.toString(), stackTrace); emit(GroupError(e.toString())); } } /// Handles [_GroupsUpdated] events. /// /// Processes real-time updates from the group stream, either emitting /// the updated group list or an error state if the stream encountered an error. /// /// Args: /// [event]: The _GroupsUpdated event containing groups or error information /// [emit]: State emitter function Future _onGroupsUpdated( _GroupsUpdated event, Emitter emit, ) async { if (event.error != null) { _errorService.logError(event.error!, StackTrace.current); emit(GroupError(event.error!)); } else { emit(GroupsLoaded(event.groups)); } } /// Handles [LoadGroupsByTrip] events. /// /// Loads the group associated with a specific trip. Since each trip typically /// has one primary group, this returns a single group or an empty list. /// /// Args: /// [event]: The LoadGroupsByTrip event containing the trip ID /// [emit]: State emitter function Future _onLoadGroupsByTrip( LoadGroupsByTrip event, Emitter emit, ) async { try { emit(GroupLoading()); final group = await _repository.getGroupByTripId(event.tripId); if (group != null) { emit(GroupsLoaded([group])); } else { emit(const GroupsLoaded([])); } } catch (e) { emit(GroupError(e.toString())); } } /// Handles [CreateGroup] events. /// /// Creates a new group without any initial members. The group creator /// can add members later using AddMemberToGroup events. /// /// Args: /// [event]: The CreateGroup event containing the group data /// [emit]: State emitter function Future _onCreateGroup( CreateGroup event, Emitter emit, ) async { try { emit(GroupLoading()); final groupId = await _repository.createGroupWithMembers( group: event.group, members: [], ); emit(GroupCreated(groupId: groupId)); emit(const GroupOperationSuccess('Group created successfully')); } catch (e) { emit(GroupError('Error during creation: $e')); } } /// Handles [CreateGroupWithMembers] events. /// /// Creates a new group with an initial set of members. This is useful /// for setting up complete groups in one operation, such as when /// planning a trip with known participants. /// /// Args: /// [event]: The CreateGroupWithMembers event containing group data and member list /// [emit]: State emitter function Future _onCreateGroupWithMembers( CreateGroupWithMembers event, Emitter emit, ) async { try { emit(GroupLoading()); final groupId = await _repository.createGroupWithMembers( group: event.group, members: event.members, ); emit(GroupCreated(groupId: groupId)); } catch (e) { emit(GroupError('Error during creation: $e')); } } /// Handles [AddMemberToGroup] events. /// /// Adds a new member to an existing group. The member will be able to /// participate in group expenses and access group features. /// /// Args: /// [event]: The AddMemberToGroup event containing group ID and member data /// [emit]: State emitter function Future _onAddMemberToGroup( AddMemberToGroup event, Emitter emit, ) async { try { await _repository.addMember(event.groupId, event.member); emit(const GroupOperationSuccess('Member added')); } catch (e) { emit(GroupError('Error during addition: $e')); } } /// Handles [RemoveMemberFromGroup] events. /// /// Removes a member from a group. This will affect expense calculations /// and the member will no longer have access to group features. /// /// Args: /// [event]: The RemoveMemberFromGroup event containing group ID and user ID /// [emit]: State emitter function Future _onRemoveMemberFromGroup( RemoveMemberFromGroup event, Emitter emit, ) async { try { await _repository.removeMember(event.groupId, event.userId); emit(const GroupOperationSuccess('Member removed')); } catch (e) { emit(GroupError('Error during removal: $e')); } } /// Handles [UpdateGroup] events. /// /// Updates group information such as name, description, or settings. /// Member lists are managed through separate add/remove member events. /// /// Args: /// [event]: The UpdateGroup event containing group ID and updated group data /// [emit]: State emitter function Future _onUpdateGroup( UpdateGroup event, Emitter emit, ) async { try { await _repository.updateGroup(event.groupId, event.group); emit(const GroupOperationSuccess('Group updated')); } catch (e) { emit(GroupError('Error during update: $e')); } } /// Handles [DeleteGroup] events. /// /// Permanently deletes a group and all associated data. This action /// cannot be undone and will affect all group members and expenses. /// /// Args: /// [event]: The DeleteGroup event containing the trip ID to delete /// [emit]: State emitter function Future _onDeleteGroup( DeleteGroup event, Emitter emit, ) async { try { await _repository.deleteGroup(event.tripId); emit(const GroupOperationSuccess('Group deleted')); } catch (e) { emit(GroupError('Error during deletion: $e')); } } /// Cleans up resources when the bloc is closed. /// /// Cancels the group stream subscription to prevent memory leaks /// and ensure proper disposal of resources. @override Future close() { _groupsSubscription?.cancel(); return super.close(); } } /// Private event for handling real-time group updates from streams. /// /// This internal event is used to process updates from the group stream /// subscription and emit appropriate states based on the received data. class _GroupsUpdated extends GroupEvent { /// List of groups received from the stream final List groups; /// Error message if the stream encountered an error final String? error; /// Creates a _GroupsUpdated event. /// /// Args: /// [groups]: List of groups from the stream update /// [error]: Optional error message if stream failed const _GroupsUpdated(this.groups, {this.error}); @override List get props => [groups, error]; }