import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/models/cotisation_model.dart'; import '../../../../core/models/payment_model.dart'; import '../../../../shared/theme/app_theme.dart'; import '../../../../shared/widgets/buttons/buttons.dart'; import '../../../../shared/widgets/buttons/primary_button.dart'; import '../bloc/cotisations_bloc.dart'; import '../bloc/cotisations_event.dart'; import '../bloc/cotisations_state.dart'; import '../widgets/payment_method_selector.dart'; import '../widgets/payment_form_widget.dart'; import '../widgets/wave_payment_widget.dart'; import '../widgets/cotisation_timeline_widget.dart'; /// Page de détail d'une cotisation class CotisationDetailPage extends StatefulWidget { final CotisationModel cotisation; const CotisationDetailPage({ super.key, required this.cotisation, }); @override State createState() => _CotisationDetailPageState(); } class _CotisationDetailPageState extends State with TickerProviderStateMixin { late final CotisationsBloc _cotisationsBloc; late final TabController _tabController; late final AnimationController _animationController; late final Animation _fadeAnimation; @override void initState() { super.initState(); _cotisationsBloc = getIt(); _tabController = TabController(length: 3, vsync: this); _animationController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); _animationController.forward(); } @override void dispose() { _tabController.dispose(); _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _cotisationsBloc, child: Scaffold( backgroundColor: AppTheme.backgroundLight, body: BlocListener( listener: (context, state) { if (state is PaymentSuccess) { _showPaymentSuccessDialog(state); } else if (state is PaymentFailure) { _showPaymentErrorDialog(state); } else if (state is PaymentInProgress) { _showPaymentProgressDialog(state); } }, child: FadeTransition( opacity: _fadeAnimation, child: CustomScrollView( slivers: [ _buildAppBar(), SliverToBoxAdapter( child: Column( children: [ _buildStatusCard(), const SizedBox(height: 16), _buildTabSection(), ], ), ), ], ), ), ), bottomNavigationBar: _buildBottomActions(), ), ); } Widget _buildAppBar() { return SliverAppBar( expandedHeight: 200, pinned: true, backgroundColor: _getStatusColor(), flexibleSpace: FlexibleSpaceBar( title: Text( widget.cotisation.typeCotisation, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), background: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ _getStatusColor(), _getStatusColor().withOpacity(0.8), ], ), ), child: Stack( children: [ Positioned( right: -50, top: -50, child: Container( width: 200, height: 200, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withOpacity(0.1), ), ), ), Positioned( right: 20, bottom: 20, child: Icon( _getStatusIcon(), size: 80, color: Colors.white.withOpacity(0.3), ), ), ], ), ), ), actions: [ IconButton( icon: const Icon(Icons.share, color: Colors.white), onPressed: _shareReceipt, ), PopupMenuButton( icon: const Icon(Icons.more_vert, color: Colors.white), onSelected: _handleMenuAction, itemBuilder: (context) => [ const PopupMenuItem( value: 'export', child: Row( children: [ Icon(Icons.download), SizedBox(width: 8), Text('Exporter'), ], ), ), const PopupMenuItem( value: 'print', child: Row( children: [ Icon(Icons.print), SizedBox(width: 8), Text('Imprimer'), ], ), ), const PopupMenuItem( value: 'history', child: Row( children: [ Icon(Icons.history), SizedBox(width: 8), Text('Historique'), ], ), ), ], ), ], ); } Widget _buildStatusCard() { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 20, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Montant à payer', style: TextStyle( fontSize: 14, color: AppTheme.textSecondary, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( '${widget.cotisation.montantDu.toStringAsFixed(0)} XOF', style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _getStatusColor().withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _getStatusIcon(), size: 16, color: _getStatusColor(), ), const SizedBox(width: 4), Text( widget.cotisation.statut, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _getStatusColor(), ), ), ], ), ), ], ), const SizedBox(height: 20), _buildInfoRow('Membre', widget.cotisation.nomMembre ?? 'N/A'), _buildInfoRow('Période', _formatPeriode()), _buildInfoRow('Échéance', _formatDate(widget.cotisation.dateEcheance)), if (widget.cotisation.montantPaye > 0) _buildInfoRow('Montant payé', '${widget.cotisation.montantPaye.toStringAsFixed(0)} XOF'), if (widget.cotisation.isEnRetard) _buildInfoRow('Retard', '${widget.cotisation.joursRetard} jours', isWarning: true), ], ), ); } Widget _buildInfoRow(String label, String value, {bool isWarning = false}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: 14, color: AppTheme.textSecondary, ), ), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: isWarning ? AppTheme.warningColor : AppTheme.textPrimary, ), ), ], ), ); } Widget _buildTabSection() { return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 20, offset: const Offset(0, 4), ), ], ), child: Column( children: [ TabBar( controller: _tabController, labelColor: AppTheme.primaryColor, unselectedLabelColor: AppTheme.textSecondary, indicatorColor: AppTheme.primaryColor, tabs: const [ Tab(text: 'Détails', icon: Icon(Icons.info_outline)), Tab(text: 'Paiement', icon: Icon(Icons.payment)), Tab(text: 'Historique', icon: Icon(Icons.history)), ], ), SizedBox( height: 400, child: TabBarView( controller: _tabController, children: [ _buildDetailsTab(), _buildPaymentTab(), _buildHistoryTab(), ], ), ), ], ), ); } Widget _buildDetailsTab() { return Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDetailSection('Informations générales', [ _buildDetailItem('Type', widget.cotisation.typeCotisation), _buildDetailItem('Référence', widget.cotisation.numeroReference), _buildDetailItem('Date création', _formatDate(widget.cotisation.dateCreation)), _buildDetailItem('Statut', widget.cotisation.statut), ]), const SizedBox(height: 20), _buildDetailSection('Montants', [ _buildDetailItem('Montant dû', '${widget.cotisation.montantDu.toStringAsFixed(0)} XOF'), _buildDetailItem('Montant payé', '${widget.cotisation.montantPaye.toStringAsFixed(0)} XOF'), _buildDetailItem('Reste à payer', '${(widget.cotisation.montantDu - widget.cotisation.montantPaye).toStringAsFixed(0)} XOF'), ]), if (widget.cotisation.description?.isNotEmpty == true) ...[ const SizedBox(height: 20), _buildDetailSection('Description', [ Text( widget.cotisation.description!, style: const TextStyle( fontSize: 14, color: AppTheme.textSecondary, ), ), ]), ], ], ), ); } Widget _buildPaymentTab() { if (widget.cotisation.isEntierementPayee) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.check_circle, size: 64, color: AppTheme.successColor, ), SizedBox(height: 16), Text( 'Cotisation entièrement payée', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppTheme.successColor, ), ), ], ), ); } return BlocBuilder( builder: (context, state) { if (state is PaymentInProgress) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Traitement du paiement en cours...'), ], ), ); } return Column( children: [ // Widget Wave Money en priorité WavePaymentWidget( cotisation: widget.cotisation, showFullInterface: true, onPaymentInitiated: () { // Feedback visuel lors de l'initiation du paiement ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Redirection vers Wave Money...'), backgroundColor: Color(0xFF00D4FF), duration: Duration(seconds: 2), ), ); }, ), const SizedBox(height: 16), // Séparateur avec texte Row( children: [ const Expanded(child: Divider()), Container( padding: const EdgeInsets.symmetric(horizontal: 16), child: const Text( 'Ou choisir une autre méthode', style: TextStyle( color: AppTheme.textSecondary, fontSize: 12, ), ), ), const Expanded(child: Divider()), ], ), const SizedBox(height: 16), // Formulaire de paiement classique PaymentFormWidget( cotisation: widget.cotisation, onPaymentInitiated: (paymentData) { _cotisationsBloc.add(InitiatePayment( cotisationId: widget.cotisation.id, montant: paymentData['montant'], methodePaiement: paymentData['methodePaiement'], numeroTelephone: paymentData['numeroTelephone'], nomPayeur: paymentData['nomPayeur'], emailPayeur: paymentData['emailPayeur'], )); }, ), ], ); }, ); } Widget _buildHistoryTab() { return CotisationTimelineWidget(cotisation: widget.cotisation); } Widget _buildDetailSection(String title, List children) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), const SizedBox(height: 12), ...children, ], ); } Widget _buildDetailItem(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: const TextStyle( fontSize: 14, color: AppTheme.textSecondary, ), ), Text( value, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppTheme.textPrimary, ), ), ], ), ); } Widget _buildBottomActions() { if (widget.cotisation.isEntierementPayee) { return Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, offset: Offset(0, -2), ), ], ), child: PrimaryButton( text: 'Télécharger le reçu', icon: Icons.download, onPressed: _downloadReceipt, ), ); } return Container( padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 10, offset: Offset(0, -2), ), ], ), child: Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: _scheduleReminder, icon: const Icon(Icons.notifications), label: const Text('Rappel'), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), const SizedBox(width: 12), Expanded( flex: 2, child: PrimaryButton( text: 'Payer maintenant', icon: Icons.payment, onPressed: () { _tabController.animateTo(1); // Aller à l'onglet paiement }, ), ), ], ), ); } // Méthodes utilitaires Color _getStatusColor() { switch (widget.cotisation.statut.toLowerCase()) { case 'payee': return AppTheme.successColor; case 'en_retard': return AppTheme.errorColor; case 'en_attente': return AppTheme.warningColor; default: return AppTheme.primaryColor; } } IconData _getStatusIcon() { switch (widget.cotisation.statut.toLowerCase()) { case 'payee': return Icons.check_circle; case 'en_retard': return Icons.warning; case 'en_attente': return Icons.schedule; default: return Icons.payment; } } String _formatDate(DateTime date) { return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}'; } String _formatPeriode() { return '${widget.cotisation.mois}/${widget.cotisation.annee}'; } // Actions void _shareReceipt() { // TODO: Implémenter le partage ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Partage - En cours de développement')), ); } void _handleMenuAction(String action) { switch (action) { case 'export': _exportReceipt(); break; case 'print': _printReceipt(); break; case 'history': _showFullHistory(); break; } } void _exportReceipt() { _cotisationsBloc.add(ExportCotisations('pdf', cotisations: [widget.cotisation])); } void _printReceipt() { // TODO: Implémenter l'impression ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Impression - En cours de développement')), ); } void _showFullHistory() { // TODO: Naviguer vers l'historique complet ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Historique complet - En cours de développement')), ); } void _downloadReceipt() { _exportReceipt(); } void _scheduleReminder() { _cotisationsBloc.add(ScheduleNotifications([widget.cotisation])); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Rappel programmé avec succès'), backgroundColor: AppTheme.successColor, ), ); } // Dialogs void _showPaymentSuccessDialog(PaymentSuccess state) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Row( children: [ Icon(Icons.check_circle, color: AppTheme.successColor), SizedBox(width: 8), Text('Paiement réussi'), ], ), content: Text('Votre paiement de ${state.payment.montant.toStringAsFixed(0)} XOF a été confirmé.'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); Navigator.of(context).pop(); // Retour à la liste }, child: const Text('OK'), ), ], ), ); } void _showPaymentErrorDialog(PaymentFailure state) { showDialog( context: context, builder: (context) => AlertDialog( title: const Row( children: [ Icon(Icons.error, color: AppTheme.errorColor), SizedBox(width: 8), Text('Échec du paiement'), ], ), content: Text(state.errorMessage), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('OK'), ), ], ), ); } void _showPaymentProgressDialog(PaymentInProgress state) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator(), const SizedBox(height: 16), Text('Traitement du paiement de ${state.montant.toStringAsFixed(0)} XOF...'), const SizedBox(height: 8), Text('Méthode: ${state.methodePaiement}'), ], ), ), ); } }