feat: Implement activity management feature with Firestore integration
- Added AddActivityBottomSheet for adding custom activities to trips. - Created Activity model to represent tourist activities. - Developed ActivityRepository for managing activities in Firestore. - Integrated ActivityPlacesService for searching activities via Google Places API. - Updated ShowTripDetailsContent to navigate to activities page. - Enhanced main.dart to include ActivityBloc and necessary repositories.
This commit is contained in:
409
lib/blocs/activity/activity_bloc.dart
Normal file
409
lib/blocs/activity/activity_bloc.dart
Normal file
@@ -0,0 +1,409 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../models/activity.dart';
|
||||
import '../../repositories/activity_repository.dart';
|
||||
import '../../services/activity_places_service.dart';
|
||||
import '../../services/error_service.dart';
|
||||
import 'activity_event.dart';
|
||||
import 'activity_state.dart';
|
||||
|
||||
/// BLoC for managing activity-related state and operations
|
||||
class ActivityBloc extends Bloc<ActivityEvent, ActivityState> {
|
||||
final ActivityRepository _repository;
|
||||
final ActivityPlacesService _placesService;
|
||||
final ErrorService _errorService;
|
||||
|
||||
ActivityBloc({
|
||||
required ActivityRepository repository,
|
||||
required ActivityPlacesService placesService,
|
||||
required ErrorService errorService,
|
||||
}) : _repository = repository,
|
||||
_placesService = placesService,
|
||||
_errorService = errorService,
|
||||
super(const ActivityInitial()) {
|
||||
|
||||
on<LoadActivities>(_onLoadActivities);
|
||||
on<SearchActivities>(_onSearchActivities);
|
||||
on<SearchActivitiesByText>(_onSearchActivitiesByText);
|
||||
on<AddActivity>(_onAddActivity);
|
||||
on<AddActivitiesBatch>(_onAddActivitiesBatch);
|
||||
on<VoteForActivity>(_onVoteForActivity);
|
||||
on<DeleteActivity>(_onDeleteActivity);
|
||||
on<FilterActivities>(_onFilterActivities);
|
||||
on<RefreshActivities>(_onRefreshActivities);
|
||||
on<ClearSearchResults>(_onClearSearchResults);
|
||||
on<UpdateActivity>(_onUpdateActivity);
|
||||
on<ToggleActivityFavorite>(_onToggleActivityFavorite);
|
||||
}
|
||||
|
||||
/// Handles loading activities for a trip
|
||||
Future<void> _onLoadActivities(
|
||||
LoadActivities event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const ActivityLoading());
|
||||
|
||||
final activities = await _repository.getActivitiesByTrip(event.tripId);
|
||||
|
||||
emit(ActivityLoaded(
|
||||
activities: activities,
|
||||
filteredActivities: activities,
|
||||
));
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur chargement activités: $e');
|
||||
emit(const ActivityError('Impossible de charger les activités'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles searching activities using Google Places API
|
||||
Future<void> _onSearchActivities(
|
||||
SearchActivities event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const ActivitySearching());
|
||||
|
||||
final searchResults = await _placesService.searchActivities(
|
||||
destination: event.destination,
|
||||
tripId: event.tripId,
|
||||
category: event.category,
|
||||
);
|
||||
|
||||
emit(ActivitySearchResults(
|
||||
searchResults: searchResults,
|
||||
query: event.category?.displayName ?? 'Toutes les activités',
|
||||
));
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur recherche activités: $e');
|
||||
emit(const ActivityError('Impossible de rechercher les activités'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles text-based activity search
|
||||
Future<void> _onSearchActivitiesByText(
|
||||
SearchActivitiesByText event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(const ActivitySearching());
|
||||
|
||||
final searchResults = await _placesService.searchActivitiesByText(
|
||||
query: event.query,
|
||||
destination: event.destination,
|
||||
tripId: event.tripId,
|
||||
);
|
||||
|
||||
emit(ActivitySearchResults(
|
||||
searchResults: searchResults,
|
||||
query: event.query,
|
||||
));
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur recherche textuelle: $e');
|
||||
emit(const ActivityError('Impossible de rechercher les activités'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles adding a single activity
|
||||
Future<void> _onAddActivity(
|
||||
AddActivity event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
// Check if activity already exists
|
||||
if (event.activity.placeId != null) {
|
||||
final existing = await _repository.findExistingActivity(
|
||||
event.activity.tripId,
|
||||
event.activity.placeId!,
|
||||
);
|
||||
|
||||
if (existing != null) {
|
||||
emit(const ActivityError('Cette activité a déjà été ajoutée'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final activityId = await _repository.addActivity(event.activity);
|
||||
|
||||
if (activityId != null) {
|
||||
emit(ActivityAdded(
|
||||
activity: event.activity.copyWith(id: activityId),
|
||||
message: 'Activité ajoutée avec succès',
|
||||
));
|
||||
// Reload activities
|
||||
add(LoadActivities(event.activity.tripId));
|
||||
} else {
|
||||
emit(const ActivityError('Impossible d\'ajouter l\'activité'));
|
||||
}
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur ajout activité: $e');
|
||||
emit(const ActivityError('Impossible d\'ajouter l\'activité'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles adding multiple activities in batch
|
||||
Future<void> _onAddActivitiesBatch(
|
||||
AddActivitiesBatch event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
// Filter out existing activities
|
||||
final filteredActivities = <Activity>[];
|
||||
|
||||
emit(ActivityBatchAdding(
|
||||
activitiesToAdd: event.activities,
|
||||
progress: 0,
|
||||
total: event.activities.length,
|
||||
));
|
||||
|
||||
for (int i = 0; i < event.activities.length; i++) {
|
||||
final activity = event.activities[i];
|
||||
|
||||
if (activity.placeId != null) {
|
||||
final existing = await _repository.findExistingActivity(
|
||||
activity.tripId,
|
||||
activity.placeId!,
|
||||
);
|
||||
|
||||
if (existing == null) {
|
||||
filteredActivities.add(activity);
|
||||
}
|
||||
} else {
|
||||
filteredActivities.add(activity);
|
||||
}
|
||||
|
||||
// Update progress
|
||||
emit(ActivityBatchAdding(
|
||||
activitiesToAdd: event.activities,
|
||||
progress: i + 1,
|
||||
total: event.activities.length,
|
||||
));
|
||||
}
|
||||
|
||||
if (filteredActivities.isEmpty) {
|
||||
emit(const ActivityError('Toutes les activités ont déjà été ajoutées'));
|
||||
return;
|
||||
}
|
||||
|
||||
final addedIds = await _repository.addActivitiesBatch(filteredActivities);
|
||||
|
||||
if (addedIds.isNotEmpty) {
|
||||
emit(ActivityOperationSuccess(
|
||||
'${addedIds.length} activité(s) ajoutée(s) avec succès',
|
||||
operationType: 'batch_add',
|
||||
));
|
||||
// Reload activities
|
||||
add(LoadActivities(event.activities.first.tripId));
|
||||
} else {
|
||||
emit(const ActivityError('Impossible d\'ajouter les activités'));
|
||||
}
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur ajout en lot: $e');
|
||||
emit(const ActivityError('Impossible d\'ajouter les activités'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles voting for an activity
|
||||
Future<void> _onVoteForActivity(
|
||||
VoteForActivity event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
// Show voting state
|
||||
if (state is ActivityLoaded) {
|
||||
final currentState = state as ActivityLoaded;
|
||||
emit(ActivityVoting(
|
||||
activityId: event.activityId,
|
||||
activities: currentState.activities,
|
||||
));
|
||||
}
|
||||
|
||||
final success = await _repository.voteForActivity(
|
||||
event.activityId,
|
||||
event.userId,
|
||||
event.vote,
|
||||
);
|
||||
|
||||
if (success) {
|
||||
emit(ActivityVoteRecorded(
|
||||
activityId: event.activityId,
|
||||
vote: event.vote,
|
||||
userId: event.userId,
|
||||
));
|
||||
|
||||
// Reload activities to reflect the new vote
|
||||
if (state is ActivityLoaded) {
|
||||
final currentState = state as ActivityLoaded;
|
||||
final activities = await _repository.getActivitiesByTrip(
|
||||
currentState.activities.first.tripId,
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
activities: activities,
|
||||
filteredActivities: _applyFilters(
|
||||
activities,
|
||||
currentState.activeFilter,
|
||||
currentState.minRating,
|
||||
currentState.showVotedOnly,
|
||||
event.userId,
|
||||
),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
emit(const ActivityError('Impossible d\'enregistrer le vote'));
|
||||
}
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur vote: $e');
|
||||
emit(const ActivityError('Impossible d\'enregistrer le vote'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles deleting an activity
|
||||
Future<void> _onDeleteActivity(
|
||||
DeleteActivity event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
final success = await _repository.deleteActivity(event.activityId);
|
||||
|
||||
if (success) {
|
||||
emit(ActivityDeleted(
|
||||
activityId: event.activityId,
|
||||
message: 'Activité supprimée avec succès',
|
||||
));
|
||||
|
||||
// Reload if we're on the activity list
|
||||
if (state is ActivityLoaded) {
|
||||
final currentState = state as ActivityLoaded;
|
||||
if (currentState.activities.isNotEmpty) {
|
||||
add(LoadActivities(currentState.activities.first.tripId));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit(const ActivityError('Impossible de supprimer l\'activité'));
|
||||
}
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur suppression: $e');
|
||||
emit(const ActivityError('Impossible de supprimer l\'activité'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles filtering activities
|
||||
Future<void> _onFilterActivities(
|
||||
FilterActivities event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
if (state is ActivityLoaded) {
|
||||
final currentState = state as ActivityLoaded;
|
||||
|
||||
final filteredActivities = _applyFilters(
|
||||
currentState.activities,
|
||||
event.category,
|
||||
event.minRating,
|
||||
event.showVotedOnly ?? false,
|
||||
'', // UserId would be needed for showVotedOnly filter
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
filteredActivities: filteredActivities,
|
||||
activeFilter: event.category,
|
||||
minRating: event.minRating,
|
||||
showVotedOnly: event.showVotedOnly ?? false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles refreshing activities
|
||||
Future<void> _onRefreshActivities(
|
||||
RefreshActivities event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
add(LoadActivities(event.tripId));
|
||||
}
|
||||
|
||||
/// Handles clearing search results
|
||||
Future<void> _onClearSearchResults(
|
||||
ClearSearchResults event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
if (state is ActivitySearchResults) {
|
||||
emit(const ActivityInitial());
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles updating an activity
|
||||
Future<void> _onUpdateActivity(
|
||||
UpdateActivity event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
if (state is ActivityLoaded) {
|
||||
final currentState = state as ActivityLoaded;
|
||||
emit(ActivityUpdating(
|
||||
activityId: event.activity.id,
|
||||
activities: currentState.activities,
|
||||
));
|
||||
}
|
||||
|
||||
final success = await _repository.updateActivity(event.activity);
|
||||
|
||||
if (success) {
|
||||
emit(const ActivityOperationSuccess(
|
||||
'Activité mise à jour avec succès',
|
||||
operationType: 'update',
|
||||
));
|
||||
|
||||
// Reload activities
|
||||
add(LoadActivities(event.activity.tripId));
|
||||
} else {
|
||||
emit(const ActivityError('Impossible de mettre à jour l\'activité'));
|
||||
}
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur mise à jour: $e');
|
||||
emit(const ActivityError('Impossible de mettre à jour l\'activité'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles toggling activity favorite status
|
||||
Future<void> _onToggleActivityFavorite(
|
||||
ToggleActivityFavorite event,
|
||||
Emitter<ActivityState> emit,
|
||||
) async {
|
||||
try {
|
||||
// This would require extending the Activity model to include favorites
|
||||
// For now, we'll use the voting system as a favorite system
|
||||
add(VoteForActivity(
|
||||
activityId: event.activityId,
|
||||
userId: event.userId,
|
||||
vote: 1,
|
||||
));
|
||||
} catch (e) {
|
||||
_errorService.logError('activity_bloc', 'Erreur favori: $e');
|
||||
emit(const ActivityError('Impossible de modifier les favoris'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies filters to the activities list
|
||||
List<Activity> _applyFilters(
|
||||
List<Activity> activities,
|
||||
String? category,
|
||||
double? minRating,
|
||||
bool showVotedOnly,
|
||||
String userId,
|
||||
) {
|
||||
var filtered = activities;
|
||||
|
||||
if (category != null) {
|
||||
filtered = filtered.where((a) => a.category == category).toList();
|
||||
}
|
||||
|
||||
if (minRating != null) {
|
||||
filtered = filtered.where((a) => (a.rating ?? 0) >= minRating).toList();
|
||||
}
|
||||
|
||||
if (showVotedOnly && userId.isNotEmpty) {
|
||||
filtered = filtered.where((a) => a.hasUserVoted(userId)).toList();
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
153
lib/blocs/activity/activity_event.dart
Normal file
153
lib/blocs/activity/activity_event.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../models/activity.dart';
|
||||
|
||||
/// Base class for all activity-related events
|
||||
abstract class ActivityEvent extends Equatable {
|
||||
const ActivityEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Event to load activities for a specific trip
|
||||
class LoadActivities extends ActivityEvent {
|
||||
final String tripId;
|
||||
|
||||
const LoadActivities(this.tripId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [tripId];
|
||||
}
|
||||
|
||||
/// Event to search activities using Google Places API
|
||||
class SearchActivities extends ActivityEvent {
|
||||
final String tripId;
|
||||
final String destination;
|
||||
final ActivityCategory? category;
|
||||
|
||||
const SearchActivities({
|
||||
required this.tripId,
|
||||
required this.destination,
|
||||
this.category,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [tripId, destination, category];
|
||||
}
|
||||
|
||||
/// Event to search activities by text query
|
||||
class SearchActivitiesByText extends ActivityEvent {
|
||||
final String tripId;
|
||||
final String destination;
|
||||
final String query;
|
||||
|
||||
const SearchActivitiesByText({
|
||||
required this.tripId,
|
||||
required this.destination,
|
||||
required this.query,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [tripId, destination, query];
|
||||
}
|
||||
|
||||
/// Event to add a single activity to the trip
|
||||
class AddActivity extends ActivityEvent {
|
||||
final Activity activity;
|
||||
|
||||
const AddActivity(this.activity);
|
||||
|
||||
@override
|
||||
List<Object> get props => [activity];
|
||||
}
|
||||
|
||||
/// Event to add multiple activities at once
|
||||
class AddActivitiesBatch extends ActivityEvent {
|
||||
final List<Activity> activities;
|
||||
|
||||
const AddActivitiesBatch(this.activities);
|
||||
|
||||
@override
|
||||
List<Object> get props => [activities];
|
||||
}
|
||||
|
||||
/// Event to vote for an activity
|
||||
class VoteForActivity extends ActivityEvent {
|
||||
final String activityId;
|
||||
final String userId;
|
||||
final int vote; // 1 for positive, -1 for negative, 0 to remove vote
|
||||
|
||||
const VoteForActivity({
|
||||
required this.activityId,
|
||||
required this.userId,
|
||||
required this.vote,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId, userId, vote];
|
||||
}
|
||||
|
||||
/// Event to delete an activity
|
||||
class DeleteActivity extends ActivityEvent {
|
||||
final String activityId;
|
||||
|
||||
const DeleteActivity(this.activityId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId];
|
||||
}
|
||||
|
||||
/// Event to filter activities
|
||||
class FilterActivities extends ActivityEvent {
|
||||
final String? category;
|
||||
final double? minRating;
|
||||
final bool? showVotedOnly;
|
||||
|
||||
const FilterActivities({
|
||||
this.category,
|
||||
this.minRating,
|
||||
this.showVotedOnly,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [category, minRating, showVotedOnly];
|
||||
}
|
||||
|
||||
/// Event to refresh activities
|
||||
class RefreshActivities extends ActivityEvent {
|
||||
final String tripId;
|
||||
|
||||
const RefreshActivities(this.tripId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [tripId];
|
||||
}
|
||||
|
||||
/// Event to clear search results
|
||||
class ClearSearchResults extends ActivityEvent {
|
||||
const ClearSearchResults();
|
||||
}
|
||||
|
||||
/// Event to update activity details
|
||||
class UpdateActivity extends ActivityEvent {
|
||||
final Activity activity;
|
||||
|
||||
const UpdateActivity(this.activity);
|
||||
|
||||
@override
|
||||
List<Object> get props => [activity];
|
||||
}
|
||||
|
||||
/// Event to toggle favorite status
|
||||
class ToggleActivityFavorite extends ActivityEvent {
|
||||
final String activityId;
|
||||
final String userId;
|
||||
|
||||
const ToggleActivityFavorite({
|
||||
required this.activityId,
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId, userId];
|
||||
}
|
||||
240
lib/blocs/activity/activity_state.dart
Normal file
240
lib/blocs/activity/activity_state.dart
Normal file
@@ -0,0 +1,240 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import '../../models/activity.dart';
|
||||
|
||||
/// Base class for all activity-related states
|
||||
abstract class ActivityState extends Equatable {
|
||||
const ActivityState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
/// Initial state when no activities have been loaded
|
||||
class ActivityInitial extends ActivityState {
|
||||
const ActivityInitial();
|
||||
}
|
||||
|
||||
/// State when activities are being loaded
|
||||
class ActivityLoading extends ActivityState {
|
||||
const ActivityLoading();
|
||||
}
|
||||
|
||||
/// State when activities are being searched
|
||||
class ActivitySearching extends ActivityState {
|
||||
const ActivitySearching();
|
||||
}
|
||||
|
||||
/// State when activities have been loaded successfully
|
||||
class ActivityLoaded extends ActivityState {
|
||||
final List<Activity> activities;
|
||||
final List<Activity> filteredActivities;
|
||||
final String? activeFilter;
|
||||
final double? minRating;
|
||||
final bool showVotedOnly;
|
||||
|
||||
const ActivityLoaded({
|
||||
required this.activities,
|
||||
required this.filteredActivities,
|
||||
this.activeFilter,
|
||||
this.minRating,
|
||||
this.showVotedOnly = false,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
activities,
|
||||
filteredActivities,
|
||||
activeFilter,
|
||||
minRating,
|
||||
showVotedOnly,
|
||||
];
|
||||
|
||||
/// Creates a copy of the current state with optional modifications
|
||||
ActivityLoaded copyWith({
|
||||
List<Activity>? activities,
|
||||
List<Activity>? filteredActivities,
|
||||
String? activeFilter,
|
||||
double? minRating,
|
||||
bool? showVotedOnly,
|
||||
}) {
|
||||
return ActivityLoaded(
|
||||
activities: activities ?? this.activities,
|
||||
filteredActivities: filteredActivities ?? this.filteredActivities,
|
||||
activeFilter: activeFilter ?? this.activeFilter,
|
||||
minRating: minRating ?? this.minRating,
|
||||
showVotedOnly: showVotedOnly ?? this.showVotedOnly,
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets activities by category
|
||||
List<Activity> getActivitiesByCategory(String category) {
|
||||
return activities.where((activity) => activity.category == category).toList();
|
||||
}
|
||||
|
||||
/// Gets top rated activities
|
||||
List<Activity> getTopRatedActivities({int limit = 10}) {
|
||||
final sorted = List<Activity>.from(activities);
|
||||
sorted.sort((a, b) {
|
||||
final aScore = a.totalVotes;
|
||||
final bScore = b.totalVotes;
|
||||
|
||||
if (aScore != bScore) {
|
||||
return bScore.compareTo(aScore);
|
||||
}
|
||||
|
||||
return (b.rating ?? 0).compareTo(a.rating ?? 0);
|
||||
});
|
||||
|
||||
return sorted.take(limit).toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// State when search results are available
|
||||
class ActivitySearchResults extends ActivityState {
|
||||
final List<Activity> searchResults;
|
||||
final String query;
|
||||
final bool isLoading;
|
||||
|
||||
const ActivitySearchResults({
|
||||
required this.searchResults,
|
||||
required this.query,
|
||||
this.isLoading = false,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [searchResults, query, isLoading];
|
||||
|
||||
/// Creates a copy with optional modifications
|
||||
ActivitySearchResults copyWith({
|
||||
List<Activity>? searchResults,
|
||||
String? query,
|
||||
bool? isLoading,
|
||||
}) {
|
||||
return ActivitySearchResults(
|
||||
searchResults: searchResults ?? this.searchResults,
|
||||
query: query ?? this.query,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// State when an operation has completed successfully
|
||||
class ActivityOperationSuccess extends ActivityState {
|
||||
final String message;
|
||||
final String? operationType;
|
||||
|
||||
const ActivityOperationSuccess(
|
||||
this.message, {
|
||||
this.operationType,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message, operationType];
|
||||
}
|
||||
|
||||
/// State when an error has occurred
|
||||
class ActivityError extends ActivityState {
|
||||
final String message;
|
||||
final String? errorCode;
|
||||
final dynamic error;
|
||||
|
||||
const ActivityError(
|
||||
this.message, {
|
||||
this.errorCode,
|
||||
this.error,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message, errorCode, error];
|
||||
}
|
||||
|
||||
/// State when voting is in progress
|
||||
class ActivityVoting extends ActivityState {
|
||||
final String activityId;
|
||||
final List<Activity> activities;
|
||||
|
||||
const ActivityVoting({
|
||||
required this.activityId,
|
||||
required this.activities,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId, activities];
|
||||
}
|
||||
|
||||
/// State when activity is being updated
|
||||
class ActivityUpdating extends ActivityState {
|
||||
final String activityId;
|
||||
final List<Activity> activities;
|
||||
|
||||
const ActivityUpdating({
|
||||
required this.activityId,
|
||||
required this.activities,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId, activities];
|
||||
}
|
||||
|
||||
/// State when activities are being added in batch
|
||||
class ActivityBatchAdding extends ActivityState {
|
||||
final List<Activity> activitiesToAdd;
|
||||
final int progress;
|
||||
final int total;
|
||||
|
||||
const ActivityBatchAdding({
|
||||
required this.activitiesToAdd,
|
||||
required this.progress,
|
||||
required this.total,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activitiesToAdd, progress, total];
|
||||
|
||||
/// Gets the progress as a percentage
|
||||
double get progressPercentage => total > 0 ? progress / total : 0.0;
|
||||
}
|
||||
|
||||
/// State when an activity has been successfully added
|
||||
class ActivityAdded extends ActivityState {
|
||||
final Activity activity;
|
||||
final String message;
|
||||
|
||||
const ActivityAdded({
|
||||
required this.activity,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activity, message];
|
||||
}
|
||||
|
||||
/// State when an activity has been successfully deleted
|
||||
class ActivityDeleted extends ActivityState {
|
||||
final String activityId;
|
||||
final String message;
|
||||
|
||||
const ActivityDeleted({
|
||||
required this.activityId,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId, message];
|
||||
}
|
||||
|
||||
/// State when vote has been successfully recorded
|
||||
class ActivityVoteRecorded extends ActivityState {
|
||||
final String activityId;
|
||||
final int vote;
|
||||
final String userId;
|
||||
|
||||
const ActivityVoteRecorded({
|
||||
required this.activityId,
|
||||
required this.vote,
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [activityId, vote, userId];
|
||||
}
|
||||
Reference in New Issue
Block a user