Auth: - profile_repository.dart: /api/auth/change-password → /api/membres/auth/change-password Multi-org (Phase 3): - OrgSelectorPage, OrgSwitcherBloc, OrgSwitcherEntry - org_context_service.dart: headers X-Active-Organisation-Id + X-Active-Role Navigation: - MorePage: navigation conditionnelle par typeOrganisation - Suppression adaptive_navigation (remplacé par main_navigation_layout) Auth AppAuth: - keycloak_webview_auth_service: fixes AppAuth Android - AuthBloc: gestion REAUTH_REQUIS + premierLoginComplet Onboarding: - Nouveaux états: payment_method_page, onboarding_shared_widgets - SouscriptionStatusModel mis à jour StatutValidationSouscription Android: - build.gradle: ProGuard/R8, network_security_config - Gradle wrapper mis à jour
459 lines
14 KiB
Dart
459 lines
14 KiB
Dart
/// BLoC pour la gestion des événements
|
|
library evenements_bloc;
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:injectable/injectable.dart';
|
|
import 'evenements_event.dart';
|
|
import 'evenements_state.dart';
|
|
import '../domain/usecases/get_events.dart';
|
|
import '../domain/usecases/get_event_by_id.dart';
|
|
import '../domain/usecases/create_event.dart' as uc;
|
|
import '../domain/usecases/update_event.dart' as uc;
|
|
import '../domain/usecases/delete_event.dart' as uc;
|
|
import '../domain/usecases/register_for_event.dart';
|
|
import '../domain/usecases/cancel_registration.dart';
|
|
import '../domain/usecases/get_event_participants.dart';
|
|
import '../domain/repositories/evenement_repository.dart';
|
|
|
|
/// BLoC pour la gestion des événements (Clean Architecture)
|
|
@injectable
|
|
class EvenementsBloc extends Bloc<EvenementsEvent, EvenementsState> {
|
|
final GetEvents _getEvents;
|
|
final GetEventById _getEventById;
|
|
final uc.CreateEvent _createEvent;
|
|
final uc.UpdateEvent _updateEvent;
|
|
final uc.DeleteEvent _deleteEvent;
|
|
final RegisterForEvent _registerForEvent;
|
|
final CancelRegistration _cancelRegistration;
|
|
final GetEventParticipants _getEventParticipants;
|
|
final IEvenementRepository _repository; // Pour méthodes non-couvertes par use cases
|
|
|
|
EvenementsBloc(
|
|
this._getEvents,
|
|
this._getEventById,
|
|
this._createEvent,
|
|
this._updateEvent,
|
|
this._deleteEvent,
|
|
this._registerForEvent,
|
|
this._cancelRegistration,
|
|
this._getEventParticipants,
|
|
this._repository,
|
|
) : super(const EvenementsInitial()) {
|
|
on<LoadEvenements>(_onLoadEvenements);
|
|
on<LoadEvenementById>(_onLoadEvenementById);
|
|
on<CreateEvenement>(_onCreateEvenement);
|
|
on<UpdateEvenement>(_onUpdateEvenement);
|
|
on<DeleteEvenement>(_onDeleteEvenement);
|
|
on<LoadEvenementsAVenir>(_onLoadEvenementsAVenir);
|
|
on<LoadEvenementsEnCours>(_onLoadEvenementsEnCours);
|
|
on<LoadEvenementsPasses>(_onLoadEvenementsPasses);
|
|
on<InscrireEvenement>(_onInscrireEvenement);
|
|
on<DesinscrireEvenement>(_onDesinscrireEvenement);
|
|
on<LoadParticipants>(_onLoadParticipants);
|
|
on<LoadEvenementsStats>(_onLoadEvenementsStats);
|
|
}
|
|
|
|
/// Charge la liste des événements
|
|
Future<void> _onLoadEvenements(
|
|
LoadEvenements event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
if (event.refresh && state is EvenementsLoaded) {
|
|
final currentState = state as EvenementsLoaded;
|
|
emit(EvenementsRefreshing(currentState.evenements));
|
|
} else {
|
|
emit(const EvenementsLoading());
|
|
}
|
|
|
|
final result = await _getEvents(
|
|
page: event.page,
|
|
size: event.size,
|
|
recherche: event.recherche,
|
|
);
|
|
|
|
emit(EvenementsLoaded(
|
|
evenements: result.evenements,
|
|
total: result.total,
|
|
page: result.page,
|
|
size: result.size,
|
|
totalPages: result.totalPages,
|
|
));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement des événements. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Charge un événement par ID
|
|
Future<void> _onLoadEvenementById(
|
|
LoadEvenementById event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final evenement = await _getEventById(event.id);
|
|
|
|
if (evenement != null) {
|
|
emit(EvenementDetailLoaded(evenement));
|
|
} else {
|
|
emit(const EvenementsError(
|
|
message: 'Événement non trouvé',
|
|
code: '404',
|
|
));
|
|
}
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement de l\'événement. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Crée un nouvel événement
|
|
Future<void> _onCreateEvenement(
|
|
CreateEvenement event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final evenement = await _createEvent(event.evenement);
|
|
|
|
emit(EvenementCreated(evenement));
|
|
} on DioException catch (e) {
|
|
if (e.type == DioExceptionType.cancel) return;
|
|
if (e.response?.statusCode == 400) {
|
|
final errors = _extractValidationErrors(e.response?.data);
|
|
emit(EvenementsValidationError(
|
|
message: 'Erreur de validation',
|
|
validationErrors: errors,
|
|
code: '400',
|
|
));
|
|
} else {
|
|
_emitDioError(e, emit);
|
|
}
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors de la création de l\'événement. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Met à jour un événement
|
|
Future<void> _onUpdateEvenement(
|
|
UpdateEvenement event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final evenement = await _updateEvent(event.id, event.evenement);
|
|
|
|
emit(EvenementUpdated(evenement));
|
|
} on DioException catch (e) {
|
|
if (e.type == DioExceptionType.cancel) return;
|
|
if (e.response?.statusCode == 400) {
|
|
final errors = _extractValidationErrors(e.response?.data);
|
|
emit(EvenementsValidationError(
|
|
message: 'Erreur de validation',
|
|
validationErrors: errors,
|
|
code: '400',
|
|
));
|
|
} else {
|
|
_emitDioError(e, emit);
|
|
}
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors de la mise à jour de l\'événement. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Supprime un événement
|
|
Future<void> _onDeleteEvenement(
|
|
DeleteEvenement event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
await _deleteEvent(event.id);
|
|
|
|
emit(EvenementDeleted(event.id));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors de la suppression de l\'événement. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Charge les événements à venir
|
|
Future<void> _onLoadEvenementsAVenir(
|
|
LoadEvenementsAVenir event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final result = await _repository.getEvenementsAVenir(
|
|
page: event.page,
|
|
size: event.size,
|
|
);
|
|
|
|
emit(EvenementsLoaded(
|
|
evenements: result.evenements,
|
|
total: result.total,
|
|
page: result.page,
|
|
size: result.size,
|
|
totalPages: result.totalPages,
|
|
));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement des événements à venir. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Charge les événements en cours
|
|
Future<void> _onLoadEvenementsEnCours(
|
|
LoadEvenementsEnCours event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final result = await _repository.getEvenementsEnCours(
|
|
page: event.page,
|
|
size: event.size,
|
|
);
|
|
|
|
emit(EvenementsLoaded(
|
|
evenements: result.evenements,
|
|
total: result.total,
|
|
page: result.page,
|
|
size: result.size,
|
|
totalPages: result.totalPages,
|
|
));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement des événements en cours. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Charge les événements passés
|
|
Future<void> _onLoadEvenementsPasses(
|
|
LoadEvenementsPasses event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final result = await _repository.getEvenementsPasses(
|
|
page: event.page,
|
|
size: event.size,
|
|
);
|
|
|
|
emit(EvenementsLoaded(
|
|
evenements: result.evenements,
|
|
total: result.total,
|
|
page: result.page,
|
|
size: result.size,
|
|
totalPages: result.totalPages,
|
|
));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement des événements passés. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// S'inscrire à un événement
|
|
Future<void> _onInscrireEvenement(
|
|
InscrireEvenement event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
await _registerForEvent(event.evenementId);
|
|
|
|
emit(EvenementInscrit(event.evenementId));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors de l\'inscription à l\'événement. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Se désinscrire d'un événement
|
|
Future<void> _onDesinscrireEvenement(
|
|
DesinscrireEvenement event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
await _cancelRegistration(event.evenementId);
|
|
|
|
emit(EvenementDesinscrit(event.evenementId));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors de la désinscription de l\'événement. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Charge les participants
|
|
Future<void> _onLoadParticipants(
|
|
LoadParticipants event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final participants = await _getEventParticipants(event.evenementId);
|
|
|
|
emit(ParticipantsLoaded(
|
|
evenementId: event.evenementId,
|
|
participants: participants,
|
|
));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement des participants. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Charge les statistiques
|
|
Future<void> _onLoadEvenementsStats(
|
|
LoadEvenementsStats event,
|
|
Emitter<EvenementsState> emit,
|
|
) async {
|
|
try {
|
|
emit(const EvenementsLoading());
|
|
|
|
final stats = await _repository.getEvenementsStats();
|
|
|
|
emit(EvenementsStatsLoaded(stats));
|
|
} on DioException catch (e) {
|
|
_emitDioError(e, emit);
|
|
} catch (e) {
|
|
if (e is DioException && e.type == DioExceptionType.cancel) return;
|
|
emit(EvenementsError(
|
|
message: 'Erreur lors du chargement des statistiques. Veuillez réessayer.',
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Extrait les erreurs de validation
|
|
Map<String, String> _extractValidationErrors(dynamic data) {
|
|
final errors = <String, String>{};
|
|
if (data is Map<String, dynamic> && data.containsKey('errors')) {
|
|
final errorsData = data['errors'];
|
|
if (errorsData is Map<String, dynamic>) {
|
|
errorsData.forEach((key, value) {
|
|
errors[key] = value.toString();
|
|
});
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/// Génère un message d'erreur réseau approprié
|
|
String _getNetworkErrorMessage(DioException e) {
|
|
switch (e.type) {
|
|
case DioExceptionType.connectionTimeout:
|
|
return 'Délai de connexion dépassé. Vérifiez votre connexion internet.';
|
|
case DioExceptionType.sendTimeout:
|
|
return 'Délai d\'envoi dépassé. Vérifiez votre connexion internet.';
|
|
case DioExceptionType.receiveTimeout:
|
|
return 'Délai de réception dépassé. Vérifiez votre connexion internet.';
|
|
case DioExceptionType.badResponse:
|
|
final statusCode = e.response?.statusCode;
|
|
if (statusCode == 401) {
|
|
return 'Non autorisé. Veuillez vous reconnecter.';
|
|
} else if (statusCode == 403) {
|
|
return 'Accès refusé. Vous n\'avez pas les permissions nécessaires.';
|
|
} else if (statusCode == 404) {
|
|
return 'Ressource non trouvée.';
|
|
} else if (statusCode == 409) {
|
|
return 'Conflit. Cette ressource existe déjà.';
|
|
} else if (statusCode != null && statusCode >= 500) {
|
|
return 'Erreur serveur. Veuillez réessayer plus tard.';
|
|
}
|
|
return 'Erreur lors de la communication avec le serveur.';
|
|
case DioExceptionType.cancel:
|
|
return 'Requête annulée.';
|
|
case DioExceptionType.unknown:
|
|
return 'Erreur de connexion. Vérifiez votre connexion internet.';
|
|
default:
|
|
return 'Erreur réseau inattendue.';
|
|
}
|
|
}
|
|
|
|
/// Émet le bon état selon le type d'erreur :
|
|
/// 401/403 → EvenementsError (autorisation), autres → EvenementsNetworkError (réseau).
|
|
void _emitDioError(DioException e, Emitter<EvenementsState> emit) {
|
|
if (e.type == DioExceptionType.cancel) return;
|
|
final statusCode = e.response?.statusCode;
|
|
if (statusCode == 401 || statusCode == 403) {
|
|
emit(EvenementsError(
|
|
message: _getNetworkErrorMessage(e),
|
|
error: e,
|
|
));
|
|
} else {
|
|
emit(EvenementsNetworkError(
|
|
message: _getNetworkErrorMessage(e),
|
|
code: statusCode?.toString(),
|
|
error: e,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|