import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:injectable/injectable.dart'; import '../data/datasources/souscription_datasource.dart'; import '../data/models/formule_model.dart'; import '../data/models/souscription_status_model.dart'; import '../../../../core/utils/logger.dart'; // ─────────────────────────────────────────────────────────────── Events ────── abstract class OnboardingEvent extends Equatable { const OnboardingEvent(); @override List get props => []; } /// Démarre le workflow (charge les formules + état courant si souscription existante) class OnboardingStarted extends OnboardingEvent { final String? existingSouscriptionId; final String initialState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION const OnboardingStarted({required this.initialState, this.existingSouscriptionId}); @override List get props => [initialState, existingSouscriptionId]; } /// L'utilisateur a sélectionné une formule et une plage class OnboardingFormuleSelected extends OnboardingEvent { final String codeFormule; final String plage; const OnboardingFormuleSelected({required this.codeFormule, required this.plage}); @override List get props => [codeFormule, plage]; } /// L'utilisateur a sélectionné la période et le type d'organisation class OnboardingPeriodeSelected extends OnboardingEvent { final String typePeriode; final String typeOrganisation; final String organisationId; const OnboardingPeriodeSelected({ required this.typePeriode, required this.typeOrganisation, required this.organisationId, }); @override List get props => [typePeriode, typeOrganisation, organisationId]; } /// Confirme la demande et crée la souscription en base class OnboardingDemandeConfirmee extends OnboardingEvent { const OnboardingDemandeConfirmee(); } /// Initie le paiement Wave class OnboardingPaiementInitie extends OnboardingEvent { const OnboardingPaiementInitie(); } /// L'utilisateur est revenu dans l'app après le paiement Wave class OnboardingRetourDepuisWave extends OnboardingEvent { const OnboardingRetourDepuisWave(); } // ─────────────────────────────────────────────────────────────── States ────── abstract class OnboardingState extends Equatable { const OnboardingState(); @override List get props => []; } class OnboardingInitial extends OnboardingState {} class OnboardingLoading extends OnboardingState {} class OnboardingError extends OnboardingState { final String message; const OnboardingError(this.message); @override List get props => [message]; } /// Étape 1 : choix formule + plage (affiche la grille des formules) class OnboardingStepFormule extends OnboardingState { final List formules; const OnboardingStepFormule(this.formules); @override List get props => [formules]; } /// Étape 2 : choix période + type organisation (formule/plage déjà sélectionnés) class OnboardingStepPeriode extends OnboardingState { final String codeFormule; final String plage; final List formules; const OnboardingStepPeriode({ required this.codeFormule, required this.plage, required this.formules, }); @override List get props => [codeFormule, plage, formules]; } /// Étape 3 : récapitulatif avec montant calculé (avant paiement) class OnboardingStepSummary extends OnboardingState { final SouscriptionStatusModel souscription; const OnboardingStepSummary(this.souscription); @override List get props => [souscription]; } /// Étape 4 : paiement Wave — URL à ouvrir dans le navigateur class OnboardingStepPaiement extends OnboardingState { final SouscriptionStatusModel souscription; final String waveLaunchUrl; const OnboardingStepPaiement({required this.souscription, required this.waveLaunchUrl}); @override List get props => [souscription, waveLaunchUrl]; } /// Étape 5 : en attente de validation SuperAdmin class OnboardingStepAttente extends OnboardingState { final SouscriptionStatusModel? souscription; const OnboardingStepAttente({this.souscription}); @override List get props => [souscription]; } /// Souscription rejetée par le SuperAdmin class OnboardingRejected extends OnboardingState { final String? commentaire; const OnboardingRejected({this.commentaire}); @override List get props => [commentaire]; } // ─────────────────────────────────────────────────────────────── BLoC ──────── @injectable class OnboardingBloc extends Bloc { final SouscriptionDatasource _datasource; // Données accumulées au fil du wizard List _formules = []; String? _codeFormule; String? _plage; String? _typePeriode; String? _typeOrganisation; String? _organisationId; SouscriptionStatusModel? _souscription; OnboardingBloc(this._datasource) : super(OnboardingInitial()) { on(_onStarted); on(_onFormuleSelected); on(_onPeriodeSelected); on(_onDemandeConfirmee); on(_onPaiementInitie); on(_onRetourDepuisWave); } Future _onStarted(OnboardingStarted event, Emitter emit) async { emit(OnboardingLoading()); try { _formules = await _datasource.getFormules(); switch (event.initialState) { case 'NO_SUBSCRIPTION': emit(OnboardingStepFormule(_formules)); case 'AWAITING_PAYMENT': final sosc = await _datasource.getMaSouscription(); if (sosc != null) { _souscription = sosc; emit(OnboardingStepSummary(sosc)); } else { emit(OnboardingStepFormule(_formules)); } case 'PAYMENT_INITIATED': final sosc = await _datasource.getMaSouscription(); if (sosc != null && sosc.waveLaunchUrl != null) { _souscription = sosc; emit(OnboardingStepPaiement( souscription: sosc, waveLaunchUrl: sosc.waveLaunchUrl!, )); } else { emit(OnboardingStepAttente(souscription: sosc)); } case 'AWAITING_VALIDATION': final sosc = await _datasource.getMaSouscription(); _souscription = sosc; emit(OnboardingStepAttente(souscription: sosc)); case 'REJECTED': final sosc = await _datasource.getMaSouscription(); emit(OnboardingRejected(commentaire: sosc?.statutValidation)); default: emit(OnboardingStepFormule(_formules)); } } catch (e) { AppLogger.error('OnboardingBloc._onStarted: $e'); emit(const OnboardingError('Impossible de charger les formules. Vérifiez votre connexion.')); } } void _onFormuleSelected(OnboardingFormuleSelected event, Emitter emit) { _codeFormule = event.codeFormule; _plage = event.plage; emit(OnboardingStepPeriode( codeFormule: event.codeFormule, plage: event.plage, formules: _formules, )); } void _onPeriodeSelected(OnboardingPeriodeSelected event, Emitter emit) { _typePeriode = event.typePeriode; _typeOrganisation = event.typeOrganisation; _organisationId = event.organisationId; } Future _onDemandeConfirmee( OnboardingDemandeConfirmee event, Emitter emit) async { if (_codeFormule == null || _plage == null || _typePeriode == null || _typeOrganisation == null || _organisationId == null) { emit(const OnboardingError('Données manquantes. Recommencez depuis le début.')); return; } emit(OnboardingLoading()); try { final sosc = await _datasource.creerDemande( typeFormule: _codeFormule!, plageMembres: _plage!, typePeriode: _typePeriode!, typeOrganisation: _typeOrganisation!, organisationId: _organisationId!, ); if (sosc != null) { _souscription = sosc; emit(OnboardingStepSummary(sosc)); } else { emit(const OnboardingError('Erreur lors de la création de la demande.')); } } catch (e) { emit(OnboardingError('Erreur: $e')); } } Future _onPaiementInitie( OnboardingPaiementInitie event, Emitter emit) async { final souscId = _souscription?.souscriptionId; if (souscId == null) { emit(const OnboardingError('Souscription introuvable.')); return; } emit(OnboardingLoading()); try { final updated = await _datasource.initierPaiement(souscId); if (updated?.waveLaunchUrl != null) { _souscription = updated; emit(OnboardingStepPaiement( souscription: updated!, waveLaunchUrl: updated.waveLaunchUrl!, )); } else { emit(const OnboardingError('Impossible d\'initier le paiement Wave.')); } } catch (e) { emit(OnboardingError('Erreur paiement: $e')); } } Future _onRetourDepuisWave( OnboardingRetourDepuisWave event, Emitter emit) async { emit(OnboardingLoading()); try { final souscId = _souscription?.souscriptionId; if (souscId != null) { await _datasource.confirmerPaiement(souscId); } final sosc = await _datasource.getMaSouscription(); _souscription = sosc; emit(OnboardingStepAttente(souscription: sosc)); } catch (e) { // En cas d'erreur, on affiche quand même l'écran d'attente emit(OnboardingStepAttente(souscription: _souscription)); } } }