import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../bloc/onboarding_bloc.dart'; import '../../data/models/formule_model.dart'; import '../../../../shared/design_system/tokens/app_colors.dart'; import '../../../../shared/design_system/tokens/unionflow_colors.dart'; import 'onboarding_shared_widgets.dart'; /// Étape 1 — Choix de la taille de l'organisation et du niveau de formule class PlanSelectionPage extends StatefulWidget { final List formules; const PlanSelectionPage({super.key, required this.formules}); @override State createState() => _PlanSelectionPageState(); } class _PlanSelectionPageState extends State { String? _selectedPlage; String? _selectedFormule; final _scrollController = ScrollController(); static const _plages = [ _Plage('PETITE', 'Petite', '1 – 100', Icons.group_outlined, 'Associations naissantes'), _Plage('MOYENNE', 'Moyenne', '101 – 500', Icons.groups_outlined, 'Organisations établies'), _Plage('GRANDE', 'Grande', '501 – 2 000', Icons.corporate_fare_outlined, 'Grandes structures'), _Plage('TRES_GRANDE', 'Très grande', '2 000+', Icons.account_balance_outlined, 'Fédérations & réseaux'), ]; static const _formuleColors = { 'BASIC': UnionFlowColors.unionGreen, 'STANDARD': UnionFlowColors.gold, 'PREMIUM': UnionFlowColors.indigo, }; static const _formuleIcons = { 'BASIC': Icons.star_border_rounded, 'STANDARD': Icons.star_half_rounded, 'PREMIUM': Icons.star_rounded, }; static const _formuleFeatures = { 'BASIC': ['Gestion des membres', 'Cotisations de base', 'Rapports mensuels', 'Support email'], 'STANDARD': ['Tout Basic +', 'Événements & solidarité', 'Communication interne', 'Tableaux de bord avancés', 'Support prioritaire'], 'PREMIUM': ['Tout Standard +', 'Multi-organisations', 'Analytics temps réel', 'API ouverte', 'Support dédié 24/7'], }; List get _filteredFormules => widget.formules .where((f) => _selectedPlage == null || f.plage == _selectedPlage) .toList() ..sort((a, b) => a.ordreAffichage.compareTo(b.ordreAffichage)); bool get _canProceed => _selectedPlage != null && _selectedFormule != null; @override void dispose() { _scrollController.dispose(); super.dispose(); } void _onPlageSelected(String code) { setState(() { _selectedPlage = code; _selectedFormule = null; }); // Scroll vers la section formules après la frame courante WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 400), curve: Curves.easeOutCubic, ); } }); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: Column( children: [ OnboardingStepHeader( step: 1, total: 3, title: 'Choisissez votre formule', subtitle: 'Sélectionnez la taille de votre organisation\npuis le niveau d\'abonnement adapté.', ), Expanded( child: SingleChildScrollView( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(20, 20, 20, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Section 1 : Taille ──────────────────────────────── OnboardingSectionTitle( icon: Icons.people_alt_outlined, title: 'Taille de votre organisation', badge: _selectedPlage != null ? 'Sélectionné ✓' : null, ), const SizedBox(height: 12), // Grille 2×2 pour scanner rapidement GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 1.45, children: _plages.map((p) => _PlageCard( plage: p, selected: _selectedPlage == p.code, isDark: isDark, onTap: () => _onPlageSelected(p.code), )).toList(), ), // ── Section 2 : Formule (apparaît en fondu) ────────── AnimatedSwitcher( duration: const Duration(milliseconds: 350), transitionBuilder: (child, anim) => FadeTransition( opacity: anim, child: SlideTransition( position: Tween( begin: const Offset(0, 0.08), end: Offset.zero, ).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut)), child: child, ), ), child: _selectedPlage == null ? const SizedBox.shrink() : Column( key: ValueKey(_selectedPlage), crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 28), OnboardingSectionTitle( icon: Icons.workspace_premium_outlined, title: 'Niveau d\'abonnement', badge: _selectedFormule != null ? 'Sélectionné ✓' : null, ), const SizedBox(height: 4), Text( 'Modifiable à tout moment depuis vos paramètres.', style: TextStyle( fontSize: 11, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ), const SizedBox(height: 14), ..._filteredFormules.map((f) => _FormuleCard( formule: f, color: _formuleColors[f.code] ?? UnionFlowColors.unionGreen, icon: _formuleIcons[f.code] ?? Icons.star_border_rounded, features: _formuleFeatures[f.code] ?? [], selected: _selectedFormule == f.code, isPopular: f.code == 'STANDARD', isDark: isDark, onTap: () => setState(() => _selectedFormule = f.code), )), ], ), ), ], ), ), ), ], ), bottomNavigationBar: OnboardingBottomBar( enabled: _canProceed, label: 'Choisir la période →', hint: _canProceed ? null : 'Sélectionnez une taille et une formule pour continuer', onPressed: () => context.read().add( OnboardingFormuleSelected( codeFormule: _selectedFormule!, plage: _selectedPlage!, ), ), ), ); } } // ─── Carte de taille (grille 2×2) ──────────────────────────────────────────── class _Plage { final String code, label, sublabel, description; final IconData icon; const _Plage(this.code, this.label, this.sublabel, this.icon, this.description); } class _PlageCard extends StatelessWidget { final _Plage plage; final bool selected; final bool isDark; final VoidCallback onTap; const _PlageCard({ required this.plage, required this.selected, required this.isDark, required this.onTap, }); @override Widget build(BuildContext context) { final bgColor = isDark ? AppColors.surfaceDark : AppColors.surface; final borderColor = isDark ? AppColors.borderDark : AppColors.border; final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary; final textSecondary= isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 180), decoration: BoxDecoration( color: selected ? UnionFlowColors.unionGreen.withOpacity(isDark ? 0.15 : 0.06) : bgColor, border: Border.all( color: selected ? UnionFlowColors.unionGreen : borderColor, width: selected ? 2 : 1, ), borderRadius: BorderRadius.circular(14), boxShadow: selected ? UnionFlowColors.greenGlowShadow : null, ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: selected ? UnionFlowColors.unionGreen : UnionFlowColors.unionGreen.withOpacity(0.1), borderRadius: BorderRadius.circular(9), ), child: Icon( plage.icon, color: selected ? Colors.white : UnionFlowColors.unionGreen, size: 19, ), ), Icon( selected ? Icons.check_circle_rounded : Icons.radio_button_unchecked, color: selected ? UnionFlowColors.unionGreen : borderColor, size: 18, ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( plage.label, style: TextStyle( fontWeight: FontWeight.w700, fontSize: 14, color: selected ? UnionFlowColors.unionGreen : textPrimary, ), ), const SizedBox(height: 1), Text( '${plage.sublabel} membres', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: selected ? UnionFlowColors.unionGreen.withOpacity(0.8) : textSecondary, ), ), Text( plage.description, style: TextStyle( fontSize: 9, color: textSecondary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ], ), ), ); } } // ─── Carte de formule ───────────────────────────────────────────────────────── class _FormuleCard extends StatelessWidget { final FormuleModel formule; final Color color; final IconData icon; final List features; final bool selected; final bool isPopular; final bool isDark; final VoidCallback onTap; const _FormuleCard({ required this.formule, required this.color, required this.icon, required this.features, required this.selected, required this.isPopular, required this.isDark, required this.onTap, }); @override Widget build(BuildContext context) { final bgCard = isDark ? AppColors.surfaceDark : AppColors.surface; final borderColor = isDark ? AppColors.borderDark : AppColors.border; final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary; final textSecondary= isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.only(bottom: 14), decoration: BoxDecoration( color: bgCard, border: Border.all( color: selected ? color : borderColor, width: selected ? 2.5 : 1, ), borderRadius: BorderRadius.circular(16), boxShadow: selected ? [BoxShadow(color: color.withOpacity(isDark ? 0.25 : 0.18), blurRadius: 20, offset: const Offset(0, 8))] : isDark ? null : UnionFlowColors.softShadow, ), child: Column( children: [ // ── Header coloré ───────────────────────────────── Container( padding: const EdgeInsets.fromLTRB(16, 14, 16, 14), decoration: BoxDecoration( color: selected ? color : color.withOpacity(isDark ? 0.15 : 0.06), borderRadius: const BorderRadius.vertical(top: Radius.circular(14)), ), child: Row( children: [ Icon(icon, color: selected ? Colors.white : color, size: 24), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Wrap permet au badge de passer à la ligne sur écrans étroits Wrap( spacing: 8, runSpacing: 4, crossAxisAlignment: WrapCrossAlignment.center, children: [ Text( formule.libelle, style: TextStyle( color: selected ? Colors.white : color, fontWeight: FontWeight.w800, fontSize: 16, ), ), if (isPopular) Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: selected ? Colors.white.withOpacity(0.25) : color.withOpacity(0.15), borderRadius: BorderRadius.circular(10), ), child: Text( '⭐ POPULAIRE', style: TextStyle( fontSize: 8, fontWeight: FontWeight.w800, color: selected ? Colors.white : color, letterSpacing: 0.3, ), ), ), ], ), if (formule.description != null) Text( formule.description!, style: TextStyle( color: selected ? Colors.white70 : textSecondary, fontSize: 12, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), // Prix Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( _formatPrix(formule.prixMensuel), style: TextStyle( color: selected ? Colors.white : color, fontWeight: FontWeight.w900, fontSize: 20, ), ), Text( 'FCFA/mois', style: TextStyle( color: selected ? Colors.white70 : textSecondary, fontSize: 10, ), ), if (formule.prixAnnuel != null && formule.prixAnnuel! > 0) ...[ const SizedBox(height: 2), Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), decoration: BoxDecoration( color: selected ? Colors.white.withOpacity(0.2) : AppColors.success.withOpacity(0.12), borderRadius: BorderRadius.circular(6), ), child: Text( '−${_annualSavingPct(formule)}% /an', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: selected ? Colors.white : AppColors.success, ), ), ), ], ], ), ], ), ), // ── Features ────────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), child: Column( children: features.map((f) => Padding( padding: const EdgeInsets.only(bottom: 7), child: Row( children: [ Icon(Icons.check_circle_outline_rounded, size: 16, color: color), const SizedBox(width: 8), Expanded( child: Text( f, style: TextStyle(fontSize: 13, color: textPrimary), ), ), ], ), )).toList(), ), ), // ── Sélection indicator ──────────────────────────── if (selected) Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: color.withOpacity(isDark ? 0.2 : 0.08), borderRadius: const BorderRadius.vertical(bottom: Radius.circular(14)), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.check_circle_rounded, size: 14, color: color), const SizedBox(width: 6), Text( 'Formule sélectionnée', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: color, ), ), ], ), ), ], ), ), ); } String _formatPrix(double prix) { if (prix >= 1000000) return '${(prix / 1000000).toStringAsFixed(1)} M'; if (prix >= 1000) return '${(prix / 1000).toStringAsFixed(0)} k'; return prix.toStringAsFixed(0); } String _annualSavingPct(FormuleModel f) { if (f.prixAnnuel == null || f.prixAnnuel! <= 0 || f.prixMensuel <= 0) return '0'; final monthly12 = f.prixMensuel * 12; final saving = ((monthly12 - f.prixAnnuel!) / monthly12 * 100).round(); return saving.toString(); } }