351 lines
11 KiB
Dart
351 lines
11 KiB
Dart
/// 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],
|
|
/// ));
|
|
/// ```
|
|
library;
|
|
|
|
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<GroupEvent, GroupState> {
|
|
/// 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<LoadGroupsByUserId>(_onLoadGroupsByUserId);
|
|
on<_GroupsUpdated>(_onGroupsUpdated);
|
|
on<LoadGroupsByTrip>(_onLoadGroupsByTrip);
|
|
on<CreateGroup>(_onCreateGroup);
|
|
on<CreateGroupWithMembers>(_onCreateGroupWithMembers);
|
|
on<AddMemberToGroup>(_onAddMemberToGroup);
|
|
on<RemoveMemberFromGroup>(_onRemoveMemberFromGroup);
|
|
on<UpdateGroup>(_onUpdateGroup);
|
|
on<DeleteGroup>(_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<void> _onLoadGroupsByUserId(
|
|
LoadGroupsByUserId event,
|
|
Emitter<GroupState> 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(
|
|
'GroupBloc',
|
|
'Error loading groups: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de charger les groupes'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onGroupsUpdated(
|
|
_GroupsUpdated event,
|
|
Emitter<GroupState> 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<void> _onLoadGroupsByTrip(
|
|
LoadGroupsByTrip event,
|
|
Emitter<GroupState> emit,
|
|
) async {
|
|
try {
|
|
emit(GroupLoading());
|
|
final group = await _repository.getGroupByTripId(event.tripId);
|
|
if (group != null) {
|
|
emit(GroupsLoaded([group]));
|
|
} else {
|
|
emit(const GroupsLoaded([]));
|
|
}
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error loading group by trip: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de charger le groupe du voyage'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onCreateGroup(
|
|
CreateGroup event,
|
|
Emitter<GroupState> 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, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error creating group: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de créer le groupe'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onCreateGroupWithMembers(
|
|
CreateGroupWithMembers event,
|
|
Emitter<GroupState> emit,
|
|
) async {
|
|
try {
|
|
emit(GroupLoading());
|
|
final groupId = await _repository.createGroupWithMembers(
|
|
group: event.group,
|
|
members: event.members,
|
|
);
|
|
emit(GroupCreated(groupId: groupId));
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error creating group with members: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de créer le groupe'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onAddMemberToGroup(
|
|
AddMemberToGroup event,
|
|
Emitter<GroupState> emit,
|
|
) async {
|
|
try {
|
|
await _repository.addMember(event.groupId, event.member);
|
|
emit(const GroupOperationSuccess('Member added'));
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error adding member: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible d\'ajouter le membre'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onRemoveMemberFromGroup(
|
|
RemoveMemberFromGroup event,
|
|
Emitter<GroupState> emit,
|
|
) async {
|
|
try {
|
|
await _repository.removeMember(event.groupId, event.userId);
|
|
emit(const GroupOperationSuccess('Member removed'));
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error removing member: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de supprimer le membre'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onUpdateGroup(
|
|
UpdateGroup event,
|
|
Emitter<GroupState> emit,
|
|
) async {
|
|
try {
|
|
await _repository.updateGroup(event.groupId, event.group);
|
|
emit(const GroupOperationSuccess('Group updated'));
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error updating group: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de mettre à jour le groupe'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> _onDeleteGroup(
|
|
DeleteGroup event,
|
|
Emitter<GroupState> emit,
|
|
) async {
|
|
try {
|
|
await _repository.deleteGroup(event.tripId);
|
|
emit(const GroupOperationSuccess('Group deleted'));
|
|
} catch (e, stackTrace) {
|
|
_errorService.logError(
|
|
'GroupBloc',
|
|
'Error deleting group: $e',
|
|
stackTrace,
|
|
);
|
|
emit(const GroupError('Impossible de supprimer le groupe'));
|
|
}
|
|
}
|
|
|
|
/// 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<void> 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<Group> 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<Object?> get props => [groups, error];
|
|
}
|