import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../bloc/onboarding_bloc.dart'; import '../../data/models/souscription_status_model.dart'; import '../../../../shared/design_system/tokens/unionflow_colors.dart'; import '../../../../core/config/environment.dart'; /// Étape 4 — Lancement du paiement Wave + attente du retour class WavePaymentPage extends StatefulWidget { final SouscriptionStatusModel souscription; final String waveLaunchUrl; const WavePaymentPage({ super.key, required this.souscription, required this.waveLaunchUrl, }); @override State createState() => _WavePaymentPageState(); } class _WavePaymentPageState extends State with WidgetsBindingObserver { bool _paymentLaunched = false; bool _appResumed = false; bool _simulating = false; /// En dev/mock, la session Wave ne peut pas s'ouvrir — on simule directement. bool get _isMock => widget.waveLaunchUrl.contains('mock') || widget.waveLaunchUrl.contains('localhost') || !AppConfig.isProd; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed && _paymentLaunched && !_appResumed) { _appResumed = true; context.read().add(const OnboardingRetourDepuisWave()); } } Future _lancerOuSimuler() async { if (_isMock) { // Mode dev/mock : simuler le paiement directement setState(() => _simulating = true); await Future.delayed(const Duration(milliseconds: 800)); if (mounted) { context.read().add(const OnboardingRetourDepuisWave()); } return; } // Mode prod : ouvrir Wave final uri = Uri.parse(widget.waveLaunchUrl); if (await canLaunchUrl(uri)) { setState(() => _paymentLaunched = true); await launchUrl(uri, mode: LaunchMode.externalApplication); } else { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Impossible d\'ouvrir Wave. Vérifiez que l\'application est installée.'), backgroundColor: UnionFlowColors.error, behavior: SnackBarBehavior.floating, ), ); } } } @override Widget build(BuildContext context) { final montant = widget.souscription.montantTotal ?? 0; const waveBlue = Color(0xFF00B9F1); return Scaffold( backgroundColor: UnionFlowColors.background, body: SafeArea( child: Padding( padding: const EdgeInsets.all(24), child: Column( children: [ Row( children: [ if (!_paymentLaunched && !_simulating) IconButton( onPressed: () => Navigator.of(context).maybePop(), icon: const Icon(Icons.arrow_back_rounded), color: UnionFlowColors.textSecondary, ), const Spacer(), if (_isMock) Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: UnionFlowColors.warningPale, borderRadius: BorderRadius.circular(20), border: Border.all( color: UnionFlowColors.warning.withOpacity(0.4)), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.science_rounded, size: 13, color: UnionFlowColors.warning), SizedBox(width: 4), Text( 'Mode dev', style: TextStyle( color: UnionFlowColors.warning, fontSize: 11, fontWeight: FontWeight.w700, ), ), ], ), ) else Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 4), decoration: BoxDecoration( color: waveBlue.withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: const Text( 'Wave Mobile Money', style: TextStyle( color: waveBlue, fontSize: 12, fontWeight: FontWeight.w700, ), ), ), ], ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_simulating) ...[ // Animation de simulation Container( width: 110, height: 110, decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF00B9F1), Color(0xFF0096C7)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(28), boxShadow: [ BoxShadow( color: waveBlue.withOpacity(0.35), blurRadius: 24, offset: const Offset(0, 10), ), ], ), child: const Center( child: SizedBox( width: 48, height: 48, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 3, ), ), ), ), const SizedBox(height: 28), const Text( 'Simulation du paiement…', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary, ), ), const SizedBox(height: 8), const Text( 'Confirmation en cours', style: TextStyle( color: UnionFlowColors.textSecondary, fontSize: 14), ), ] else ...[ // Logo Wave Container( width: 110, height: 110, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(28), border: Border.all(color: UnionFlowColors.border), boxShadow: [ BoxShadow( color: waveBlue.withOpacity(0.2), blurRadius: 24, offset: const Offset(0, 10), ), ], ), padding: const EdgeInsets.all(16), child: Image.asset( 'assets/images/payment_methods/wave/logo.png', fit: BoxFit.contain, errorBuilder: (_, __, ___) => const Icon( Icons.waves_rounded, color: waveBlue, size: 52, ), ), ), const SizedBox(height: 28), Text( _paymentLaunched ? 'Paiement en cours…' : _isMock ? 'Simuler le paiement' : 'Prêt à payer', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w800, color: UnionFlowColors.textPrimary, ), ), const SizedBox(height: 8), // Montant RichText( text: TextSpan( children: [ TextSpan( text: '${_formatPrix(montant)} ', style: const TextStyle( fontSize: 32, fontWeight: FontWeight.w900, color: waveBlue, letterSpacing: -0.5, ), ), const TextSpan( text: 'FCFA', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: UnionFlowColors.textSecondary, ), ), ], ), ), if (widget.souscription.organisationNom != null) ...[ const SizedBox(height: 4), Text( widget.souscription.organisationNom!, style: const TextStyle( color: UnionFlowColors.textSecondary, fontSize: 13), ), ], const SizedBox(height: 32), if (!_paymentLaunched) ...[ if (_isMock) Container( margin: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: UnionFlowColors.warningPale, borderRadius: BorderRadius.circular(10), border: Border.all( color: UnionFlowColors.warning.withOpacity(0.3)), ), child: const Row( children: [ Icon(Icons.science_outlined, color: UnionFlowColors.warning, size: 16), SizedBox(width: 8), Expanded( child: Text( 'Environnement de développement — le paiement sera simulé automatiquement.', style: TextStyle( fontSize: 12, color: UnionFlowColors.warning, height: 1.4), ), ), ], ), ), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _lancerOuSimuler, icon: Icon(_isMock ? Icons.play_circle_rounded : Icons.open_in_new_rounded), label: Text( _isMock ? 'Simuler le paiement Wave' : 'Ouvrir Wave', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700), ), style: ElevatedButton.styleFrom( backgroundColor: waveBlue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), shadowColor: waveBlue.withOpacity(0.4), elevation: 3, ), ), ), ] else ...[ // Paiement lancé en prod Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: UnionFlowColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: UnionFlowColors.softShadow, ), child: Column( children: [ const SizedBox( width: 40, height: 40, child: CircularProgressIndicator( color: waveBlue, strokeWidth: 3, ), ), const SizedBox(height: 16), const Text( 'Paiement en cours dans Wave', style: TextStyle( fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary, ), ), const SizedBox(height: 6), const Text( 'Revenez dans l\'app une fois\nvotre paiement confirmé.', textAlign: TextAlign.center, style: TextStyle( color: UnionFlowColors.textSecondary, fontSize: 13, height: 1.4), ), ], ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () => context .read() .add(const OnboardingRetourDepuisWave()), icon: const Icon( Icons.check_circle_outline_rounded), label: const Text( 'J\'ai effectué le paiement', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700), ), style: ElevatedButton.styleFrom( backgroundColor: UnionFlowColors.unionGreen, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), ), ), ), const SizedBox(height: 10), TextButton.icon( onPressed: _lancerOuSimuler, icon: const Icon(Icons.refresh_rounded, size: 18), label: const Text('Rouvrir Wave'), style: TextButton.styleFrom( foregroundColor: waveBlue), ), ], ], ], ), ), ], ), ), ), ); } String _formatPrix(double prix) { if (prix >= 1000000) return '${(prix / 1000000).toStringAsFixed(1)} M'; final s = prix.toStringAsFixed(0); if (s.length > 3) { return '${s.substring(0, s.length - 3)} ${s.substring(s.length - 3)}'; } return s; } }