// ===== lib/pages/main_navigation.dart ===== import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:provider/provider.dart'; import 'package:wtpe/pages/session_manager.dart'; import '../main.dart'; import 'home.dart'; import 'dashboard.dart'; import 'login.dart'; import '../widgets/responsive_helper.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:url_launcher/url_launcher.dart'; // Vous devrez ajouter cette dépendance // ===== CONFIGURATION API ===== const String baseUrl = 'https://api.live.wortis.cg/tpe'; const String apiBaseUrl = '$baseUrl/api'; const Map apiHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json', }; // ===== COULEURS GLOBALES ===== class AppColors { static const Color primary = Color(0xFF006699); static const Color secondary = Color(0xFF0088CC); static const Color accent = Color(0xFF006699); static const Color background = Color(0xFFF8FAFC); static const Color surface = Colors.white; static const Color success = Color(0xFF006699); static const Color purple = Color(0xFF006699); } // ===== NAVIGATION ITEM MODEL ===== class NavigationItem { final IconData icon; final IconData activeIcon; final String label; final Color color; NavigationItem({ required this.icon, required this.activeIcon, required this.label, required this.color, }); } // ===== MAIN NAVIGATION PAGE ===== class MainNavigationPage extends StatefulWidget { final int initialIndex; const MainNavigationPage({super.key, this.initialIndex = 0}); @override State createState() => _MainNavigationPageState(); } class _MainNavigationPageState extends State with TickerProviderStateMixin { late int _currentIndex; late PageController _pageController; late AnimationController _animationController; late AnimationController _iconAnimationController; late Animation _scaleAnimation; late List> _iconAnimations; final List _navigationItems = [ NavigationItem( icon: Icons.home_rounded, activeIcon: Icons.home, label: 'Services', color: AppColors.primary, ), NavigationItem( icon: Icons.dashboard_outlined, activeIcon: Icons.dashboard, label: 'Dashboard', color: AppColors.primary, ), NavigationItem( icon: Icons.person_outline_rounded, activeIcon: Icons.person, label: 'Profil', color: AppColors.primary, ), ]; @override void initState() { super.initState(); _currentIndex = widget.initialIndex; _pageController = PageController(initialPage: _currentIndex); _animationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _iconAnimationController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _scaleAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.elasticOut), ); _iconAnimations = _navigationItems.map((item) { return Tween(begin: 1.0, end: 1.2).animate( CurvedAnimation( parent: _iconAnimationController, curve: Curves.elasticOut, ), ); }).toList(); _animationController.forward(); } @override void dispose() { _pageController.dispose(); _animationController.dispose(); _iconAnimationController.dispose(); super.dispose(); } void _onItemTapped(int index) { if (index != _currentIndex) { setState(() { _currentIndex = index; }); _pageController.animateToPage( index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); _iconAnimationController.forward().then((_) { _iconAnimationController.reverse(); }); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: PageView( controller: _pageController, onPageChanged: (index) { setState(() { _currentIndex = index; }); }, children: const [ HomePage(showAppBar: false), DashboardPage(showAppBar: false), ProfilePage(), ], ), bottomNavigationBar: _buildCustomBottomNavigation(), ); } Widget _buildCustomBottomNavigation() { return AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( margin: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( color: AppColors.primary.withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 8), spreadRadius: 0, ), BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(25), child: SizedBox( height: 70, child: Row( children: _navigationItems.asMap().entries.map((entry) { final index = entry.key; final item = entry.value; final isSelected = _currentIndex == index; return Expanded( child: _buildNavItem(item, index, isSelected), ); }).toList(), ), ), ), ), ); }, ); } Widget _buildNavItem(NavigationItem item, int index, bool isSelected) { return GestureDetector( onTap: () => _onItemTapped(index), child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, padding: const EdgeInsets.symmetric(vertical: 6), decoration: BoxDecoration( gradient: isSelected ? LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ item.color.withOpacity(0.1), item.color.withOpacity(0.05), ], ) : null, borderRadius: BorderRadius.circular(20), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ AnimatedBuilder( animation: _iconAnimationController, builder: (context, child) { final scale = isSelected && _iconAnimationController.isAnimating ? _iconAnimations[index].value : 1.0; return Transform.scale( scale: scale, child: Container( width: 36, height: 36, decoration: BoxDecoration( color: isSelected ? item.color.withOpacity(0.15) : Colors.transparent, borderRadius: BorderRadius.circular(10), ), child: Icon( isSelected ? item.activeIcon : item.icon, color: isSelected ? item.color : Colors.grey[600], size: 22, ), ), ); }, ), const SizedBox(height: 2), AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 300), style: TextStyle( fontSize: isSelected ? 11 : 10, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? item.color : Colors.grey[600], ), child: Text(item.label), ), if (isSelected) ...[ const SizedBox(height: 1), AnimatedContainer( duration: const Duration(milliseconds: 300), width: 16, height: 2, decoration: BoxDecoration( color: item.color, borderRadius: BorderRadius.circular(1), ), ), ], ], ), ), ); } } // ===== PROFILE PAGE ===== // ===== PAGE PROFIL REDESIGNÉE ===== // Imports nécessaires (à ajuster selon votre structure) // import 'package:your_app/controllers/auth_controller.dart'; // import 'package:your_app/utils/responsive_helper.dart'; // import 'package:your_app/utils/session_manager.dart'; // import 'package:your_app/utils/app_colors.dart'; // import 'package:your_app/pages/login_page.dart'; // ===== lib/pages/profile.dart AVEC SUPPORT ENTREPRISE COMPLET ===== class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @override State createState() => _ProfilePageState(); } class _ProfilePageState extends State with TickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; // Constants API static const String apiBaseUrl = 'https://api.live.wortis.cg/tpe'; static const Map apiHeaders = { 'Content-Type': 'application/json', }; // NOUVEAU: Couleur entreprise static const Color enterpriseColor = Color(0xFF8B5CF6); @override void initState() { super.initState(); _initializeOrientations(); _setupAnimations(); } void _initializeOrientations() { WidgetsBinding.instance.addPostFrameCallback((_) { ResponsiveHelper.initializeOrientations(context); }); } void _setupAnimations() { _animationController = AnimationController( duration: Duration(milliseconds: 1000), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); _slideAnimation = Tween( begin: Offset(0, 0.3), end: Offset.zero, ).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOutBack), ); _animationController.forward(); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isSmallScreen = context.isSmallPhone; final isMediumScreen = context.isPhone && !context.isSmallPhone; final isTablet = context.isTablet; return Scaffold( backgroundColor: AppColors.background, body: Stack( children: [ // Gradient d'arrière-plan Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ AppColors.primary.withOpacity(0.05), AppColors.background, AppColors.background, ], stops: [0.0, 0.3, 1.0], ), ), ), // Contenu principal FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: CustomScrollView( physics: BouncingScrollPhysics(), slivers: [ SliverToBoxAdapter( child: Padding( padding: context.responsivePadding( phone: EdgeInsets.fromLTRB(12, 40, 12, 120), tablet: EdgeInsets.fromLTRB(20, 60, 20, 120), ), child: Column( children: [ // Header Profil avec support entreprise _buildResponsiveProfileHeader( isSmallScreen, isMediumScreen, isTablet, ), SizedBox(height: isSmallScreen ? 16 : 24), // Badge commission disponible _buildCommissionBadge(), // Section Actions _buildResponsiveActionsSection( isSmallScreen, isMediumScreen, isTablet, ), SizedBox(height: isSmallScreen ? 16 : 24), // Section Paramètres _buildSettingsSection( isSmallScreen, isMediumScreen, isTablet, ), SizedBox(height: isSmallScreen ? 16 : 24), // Bouton déconnexion _buildResponsiveLogoutButton( context, isSmallScreen, isMediumScreen, ), ], ), ), ), ], ), ), ), ], ), ); } // ============= HEADER PROFIL AVEC SUPPORT ENTREPRISE ============= Widget _buildResponsiveProfileHeader( bool isSmallScreen, bool isMediumScreen, bool isTablet, ) { return Consumer( builder: (context, authController, child) { final isEnterprise = authController.isEnterpriseMember; final enterprise = authController.enterprise; return Container( width: double.infinity, padding: EdgeInsets.all( isSmallScreen ? 20 : (isMediumScreen ? 24 : 28), ), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isEnterprise ? [ enterpriseColor, enterpriseColor.withOpacity(0.8), enterpriseColor.withOpacity(0.6), ] : [ AppColors.primary, AppColors.primary.withOpacity(0.8), AppColors.secondary, ], stops: [0.0, 0.6, 1.0], ), borderRadius: BorderRadius.circular(isSmallScreen ? 20 : 24), boxShadow: [ BoxShadow( color: (isEnterprise ? enterpriseColor : AppColors.primary) .withOpacity(0.3), blurRadius: isSmallScreen ? 15 : 25, offset: Offset(0, isSmallScreen ? 8 : 12), spreadRadius: isSmallScreen ? 1 : 2, ), BoxShadow( color: Colors.white.withOpacity(0.1), blurRadius: 5, offset: Offset(-2, -2), ), ], ), child: Column( children: [ // Avatar avec badge entreprise Stack( children: [ Container( width: context.responsiveWidth(phone: 80, tablet: 100), height: context.responsiveHeight(phone: 80, tablet: 100), decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.1), ], ), borderRadius: BorderRadius.circular( isSmallScreen ? 20 : 25, ), border: Border.all( color: Colors.white.withOpacity(0.4), width: isSmallScreen ? 2 : 3, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Icon( isEnterprise ? Icons.business : Icons.person_outline, size: isSmallScreen ? 40 : (isMediumScreen ? 45 : 50), color: Colors.white, ), ), // Badge de statut Positioned( right: 0, bottom: 0, child: Container( width: isSmallScreen ? 24 : 30, height: isSmallScreen ? 24 : 30, decoration: BoxDecoration( color: AppColors.success, borderRadius: BorderRadius.circular( isSmallScreen ? 12 : 15, ), border: Border.all( color: Colors.white, width: isSmallScreen ? 2 : 3, ), boxShadow: [ BoxShadow( color: AppColors.success.withOpacity(0.3), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: Icon( Icons.check, size: isSmallScreen ? 12 : 16, color: Colors.white, ), ), ), ], ), SizedBox(height: isSmallScreen ? 16 : 20), // Badge type de compte Container( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 10 : 12, vertical: isSmallScreen ? 4 : 6, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.25), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isEnterprise ? Icons.business : Icons.person, size: isSmallScreen ? 12 : 14, color: Colors.white.withOpacity(0.9), ), SizedBox(width: isSmallScreen ? 4 : 6), Text( isEnterprise ? 'Compte Entreprise' : 'Agent Individuel', style: TextStyle( color: Colors.white.withOpacity(0.95), fontSize: isSmallScreen ? 10 : 12, fontWeight: FontWeight.w600, letterSpacing: 0.3, ), ), ], ), ), SizedBox(height: isSmallScreen ? 10 : 12), // Nom de l'agent ConstrainedBox( constraints: BoxConstraints(maxWidth: double.infinity), child: FittedBox( fit: BoxFit.scaleDown, child: Text( authController.agentName ?? 'Agent Wortis', style: TextStyle( fontSize: isSmallScreen ? 22 : (isMediumScreen ? 25 : 28), fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 0.5, ), textAlign: TextAlign.center, maxLines: 1, ), ), ), // Nom de l'entreprise si membre if (isEnterprise && enterprise?.nomEntreprise != null) ...[ SizedBox(height: isSmallScreen ? 6 : 8), Container( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 12 : 16, vertical: isSmallScreen ? 6 : 8, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.25), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.apartment, size: isSmallScreen ? 12 : 14, color: Colors.white.withOpacity(0.9), ), SizedBox(width: isSmallScreen ? 6 : 8), Flexible( child: Text( enterprise!.nomEntreprise, style: TextStyle( fontSize: isSmallScreen ? 13 : 15, color: Colors.white.withOpacity(0.95), fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), ), ], SizedBox(height: isSmallScreen ? 8 : 12), // Badge ID Container( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 16 : 20, vertical: isSmallScreen ? 8 : 10, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(25), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: Offset(0, 4), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.badge, size: isSmallScreen ? 14 : 16, color: Colors.white.withOpacity(0.9), ), SizedBox(width: isSmallScreen ? 6 : 8), Flexible( child: Text( 'ID: ${authController.agentId ?? "AGENT001"}', style: TextStyle( fontSize: isSmallScreen ? 14 : 16, color: Colors.white.withOpacity(0.95), fontWeight: FontWeight.w600, letterSpacing: 0.5, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), SizedBox(height: isSmallScreen ? 12 : 16), // Informations entreprise supplémentaires si membre if (isEnterprise && enterprise != null) ...[ Container( padding: EdgeInsets.all(isSmallScreen ? 12 : 14), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.25), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Column( children: [ Text( '${(enterprise as dynamic).nombreMembres ?? enterprise.membresIds.length}', style: TextStyle( fontSize: isSmallScreen ? 16 : 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), SizedBox(height: 2), Text( 'Membres', style: TextStyle( fontSize: isSmallScreen ? 10 : 11, color: Colors.white.withOpacity(0.8), ), ), ], ), Builder( builder: (context) { // Essayer d'accéder au domaine d'activité de manière sûre final domaineActivite = (enterprise as dynamic).domaineActivite; if (domaineActivite != null && domaineActivite.toString().isNotEmpty) { return Row( children: [ SizedBox(width: isSmallScreen ? 16 : 24), Container( width: 1, height: 30, color: Colors.white.withOpacity(0.3), ), SizedBox(width: isSmallScreen ? 16 : 24), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Secteur', style: TextStyle( fontSize: isSmallScreen ? 10 : 11, color: Colors.white.withOpacity(0.8), ), ), SizedBox(height: 2), Text( domaineActivite.toString(), style: TextStyle( fontSize: isSmallScreen ? 12 : 13, fontWeight: FontWeight.w600, color: Colors.white, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ); } return SizedBox.shrink(); }, ), ], ), ), SizedBox(height: isSmallScreen ? 12 : 16), ], // Indicateur de statut Container( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 12 : 16, vertical: isSmallScreen ? 4 : 6, ), decoration: BoxDecoration( color: AppColors.success.withOpacity(0.2), borderRadius: BorderRadius.circular(20), border: Border.all( color: AppColors.success.withOpacity(0.3), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: isSmallScreen ? 6 : 8, height: isSmallScreen ? 6 : 8, decoration: BoxDecoration( color: AppColors.success, borderRadius: BorderRadius.circular( isSmallScreen ? 3 : 4, ), ), ), SizedBox(width: isSmallScreen ? 6 : 8), Text( isEnterprise ? 'Membre Actif' : 'Agent Actif', style: TextStyle( fontSize: isSmallScreen ? 12 : 14, color: Colors.white, fontWeight: FontWeight.w500, ), ), ], ), ), ], ), ); }, ); } // ============= NOTIFICATION BADGE COMMISSION ============= Widget _buildCommissionBadge() { final authController = Provider.of(context, listen: false); return FutureBuilder>( future: _getCommissionBalance(authController.agentId ?? ''), builder: (context, snapshot) { if (!snapshot.hasData || !snapshot.data!['success']) { return SizedBox.shrink(); } final availableCommission = snapshot.data!['commission_disponible'] ?? 0.0; if (availableCommission <= 0) { return SizedBox.shrink(); } return Container( margin: EdgeInsets.symmetric(horizontal: 0, vertical: 8), child: GestureDetector( onTap: () => _showCommissionRechargeDialog( context, availableCommission, snapshot.data!, ), child: Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.success, AppColors.success.withOpacity(0.8), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppColors.success.withOpacity(0.3), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.monetization_on, color: Colors.white, size: 24, ), ), SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${availableCommission.toStringAsFixed(0)} XAF de commission disponible', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), Text( 'Transférez vers votre solde principal', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 13, ), ), ], ), ), Icon(Icons.arrow_forward_ios, color: Colors.white, size: 16), ], ), ), ), ); }, ); } // ============= SECTION ACTIONS ============= Widget _buildResponsiveActionsSection( bool isSmallScreen, bool isMediumScreen, bool isTablet, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(left: 4, bottom: isSmallScreen ? 12 : 16), child: Text( 'Actions Rapides', style: TextStyle( fontSize: isSmallScreen ? 18 : (isMediumScreen ? 19 : 20), fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), _buildResponsiveMenuItem( 'Demander une recharge', 'Augmenter votre solde disponible', Icons.add_circle_outline, AppColors.accent, () => _showRechargeOptionsDialog(context), isPrimary: true, delay: 100, isSmallScreen: isSmallScreen, isMediumScreen: isMediumScreen, ), _buildResponsiveMenuItem( 'Historique des recharges', 'Suivez vos demandes de recharge', Icons.history, AppColors.secondary, () => _showRechargeHistoryDialog(context), delay: 200, isSmallScreen: isSmallScreen, isMediumScreen: isMediumScreen, ), _buildResponsiveMenuItem( 'Historique des commissions', 'Consultez vos transferts de commission', Icons.trending_up, AppColors.success, () => _showCommissionHistoryDialog(context), delay: 250, isSmallScreen: isSmallScreen, isMediumScreen: isMediumScreen, ), _buildResponsiveMenuItem( 'Support', 'Obtenez de l\'aide rapidement', Icons.headset_mic, AppColors.purple, () => _showSupportDialog(context), delay: 300, isSmallScreen: isSmallScreen, isMediumScreen: isMediumScreen, ), ], ); } Widget _buildResponsiveMenuItem( String title, String subtitle, IconData icon, Color color, VoidCallback onTap, { bool isPrimary = false, int delay = 0, bool isSmallScreen = false, bool isMediumScreen = false, }) { return TweenAnimationBuilder( duration: Duration(milliseconds: 600 + delay), tween: Tween(begin: 0.0, end: 1.0), curve: Curves.easeOutBack, builder: (context, animation, child) { final clampedAnimation = animation.clamp(0.0, 1.0); return Transform.translate( offset: Offset(50 * (1 - clampedAnimation), 0), child: Opacity( opacity: clampedAnimation, child: Container( margin: EdgeInsets.only(bottom: isSmallScreen ? 12 : 16), decoration: BoxDecoration( gradient: isPrimary ? LinearGradient( colors: [color, color.withOpacity(0.8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : null, color: isPrimary ? null : AppColors.surface, borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 20), border: Border.all( color: isPrimary ? Colors.transparent : color.withOpacity(0.15), width: 2, ), boxShadow: [ BoxShadow( color: isPrimary ? color.withOpacity(0.3) : Colors.black.withOpacity(0.08), blurRadius: isPrimary ? (isSmallScreen ? 15 : 20) : (isSmallScreen ? 10 : 15), offset: Offset( 0, isPrimary ? (isSmallScreen ? 6 : 8) : (isSmallScreen ? 3 : 4), ), spreadRadius: isPrimary ? (isSmallScreen ? 1 : 2) : 0, ), if (!isPrimary) BoxShadow( color: Colors.white.withOpacity(0.9), blurRadius: 3, offset: Offset(-2, -2), ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 20), child: Padding( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 16 : 20, vertical: isSmallScreen ? 16 : 20, ), child: Row( children: [ Container( width: isSmallScreen ? 48 : (isMediumScreen ? 52 : 56), height: isSmallScreen ? 48 : (isMediumScreen ? 52 : 56), decoration: BoxDecoration( color: isPrimary ? Colors.white.withOpacity(0.2) : color.withOpacity(0.1), borderRadius: BorderRadius.circular( isSmallScreen ? 12 : 16, ), border: Border.all( color: isPrimary ? Colors.white.withOpacity(0.3) : color.withOpacity(0.2), width: 1, ), ), child: Icon( icon, color: isPrimary ? Colors.white : color, size: isSmallScreen ? 24 : (isMediumScreen ? 26 : 28), ), ), SizedBox(width: isSmallScreen ? 12 : 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: isSmallScreen ? 14 : (isMediumScreen ? 15 : 16), fontWeight: FontWeight.w700, color: isPrimary ? Colors.white : Colors.black87, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), SizedBox(height: 4), Text( subtitle, style: TextStyle( fontSize: isSmallScreen ? 11 : (isMediumScreen ? 12 : 13), color: isPrimary ? Colors.white.withOpacity(0.8) : Colors.grey[600], fontWeight: FontWeight.w400, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ), Container( width: isSmallScreen ? 28 : 32, height: isSmallScreen ? 28 : 32, decoration: BoxDecoration( color: isPrimary ? Colors.white.withOpacity(0.2) : color.withOpacity(0.1), borderRadius: BorderRadius.circular( isSmallScreen ? 8 : 10, ), ), child: Icon( Icons.arrow_forward_ios, color: isPrimary ? Colors.white : color, size: isSmallScreen ? 14 : 16, ), ), ], ), ), ), ), ), ), ); }, ); } // ============= BOUTON DÉCONNEXION ============= Widget _buildResponsiveLogoutButton( BuildContext context, bool isSmallScreen, bool isMediumScreen, ) { return TweenAnimationBuilder( duration: Duration(milliseconds: 800), tween: Tween(begin: 0.0, end: 1.0), curve: Curves.easeOutBack, builder: (context, animation, child) { return Transform.scale( scale: animation, child: Container( width: double.infinity, height: isSmallScreen ? 50 : (isMediumScreen ? 53 : 56), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.red[400]!, Colors.red[500]!], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(isSmallScreen ? 14 : 16), boxShadow: [ BoxShadow( color: Colors.red.withOpacity(0.3), blurRadius: isSmallScreen ? 15 : 20, offset: Offset(0, isSmallScreen ? 6 : 8), spreadRadius: isSmallScreen ? 1 : 2, ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _logout(context), borderRadius: BorderRadius.circular(isSmallScreen ? 14 : 16), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.logout, color: Colors.white, size: isSmallScreen ? 18 : 22, ), SizedBox(width: isSmallScreen ? 8 : 12), Text( 'Se Déconnecter', style: TextStyle( fontSize: isSmallScreen ? 14 : 16, fontWeight: FontWeight.w600, color: Colors.white, letterSpacing: 0.5, ), ), ], ), ), ), ), ); }, ); } // ============= APIs COMMISSION ============= Future> _getCommissionBalance(String agentId) async { if (agentId.isEmpty) { return {'success': false, 'commission_disponible': 0.0}; } try { final baseUrl = AuthController.baseUrl ?? apiBaseUrl; final url = '$baseUrl/agent/$agentId/commission/balance'; print('Récupération commission - URL: $url'); final response = await http.get( Uri.parse(url), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ); print('Réponse balance commission - Status: ${response.statusCode}'); print('Réponse balance commission - Body: ${response.body}'); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['success'] == true && data.containsKey('commission')) { return { 'success': true, 'commission_disponible': (data['commission']['commission_disponible'] ?? 0.0).toDouble(), 'commission_totale_gagnee': (data['commission']['commission_totale_gagnee'] ?? 0.0) .toDouble(), 'commission_totale_retiree': (data['commission']['commission_totale_retiree'] ?? 0.0) .toDouble(), 'nombre_transactions': data['commission']['nombre_transactions'] ?? 0, 'derniers_retraits': data['commission']['derniers_retraits'] ?? [], }; } } return {'success': false, 'commission_disponible': 0.0}; } catch (e) { print('Erreur récupération commission: $e'); return {'success': false, 'commission_disponible': 0.0}; } } Future> _getCommissionHistory(String agentId) async { try { final response = await http.get( Uri.parse( '${AuthController.baseUrl}/agent/$agentId/commission/history', ), headers: AuthController.apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return data; } else { return {'success': false, 'message': 'Erreur lors du chargement'}; } } catch (e) { return {'success': false, 'message': 'Erreur de connexion'}; } } // ============= DIALOGS RECHARGE ============= Future _showRechargeOptionsDialog(BuildContext context) async { final authController = Provider.of(context, listen: false); await showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), elevation: 10, child: Container( width: MediaQuery.of(context).size.width * 0.85, constraints: BoxConstraints(maxWidth: 400), child: FutureBuilder>( future: _getCommissionBalance(authController.agentId ?? ''), builder: (context, snapshot) { double availableCommission = 0.0; bool isLoading = !snapshot.hasData; Map commissionData = {}; if (snapshot.hasData && snapshot.data!['success']) { availableCommission = snapshot.data!['commission_disponible'] ?? 0.0; commissionData = snapshot.data!; } return Container( padding: EdgeInsets.all(24), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header avec icône Container( width: 64, height: 64, decoration: BoxDecoration( gradient: LinearGradient( colors: [AppColors.accent, AppColors.primary], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: AppColors.accent.withOpacity(0.3), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Icon( Icons.account_balance_wallet, color: Colors.white, size: 32, ), ), SizedBox(height: 16), Text( 'Recharger mon compte', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, ), ), SizedBox(height: 8), Text( 'Choisissez votre mode de recharge', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), textAlign: TextAlign.center, ), SizedBox(height: 24), // Affichage du solde et commission Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.primary.withOpacity(0.05), AppColors.secondary.withOpacity(0.05), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.primary.withOpacity(0.15), width: 1, ), ), child: Column( children: [ // Solde actuel Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.primary.withOpacity( 0.1, ), borderRadius: BorderRadius.circular(10), ), child: Icon( Icons.account_balance_wallet, color: AppColors.primary, size: 20, ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Solde actuel', style: TextStyle( fontSize: 14, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), SizedBox(height: 2), Text( '${authController.balance.toStringAsFixed(0)} XAF', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), ), ], ), SizedBox(height: 16), Container(height: 1, color: Colors.grey[200]), SizedBox(height: 16), // Commission disponible Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.success.withOpacity( 0.1, ), borderRadius: BorderRadius.circular(10), ), child: Icon( Icons.trending_up, color: AppColors.success, size: 20, ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Commission disponible', style: TextStyle( fontSize: 14, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), SizedBox(height: 2), if (isLoading) Container( width: 80, height: 18, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(4), ), ) else Text( '${availableCommission.toStringAsFixed(0)} XAF', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.success, ), ), ], ), ), ], ), // Statistiques de commission si disponibles if (!isLoading && commissionData.isNotEmpty) ...[ SizedBox(height: 12), Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( children: [ Text( '${commissionData['commission_totale_gagnee']?.toStringAsFixed(0) ?? '0'}', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppColors.success, ), ), Text( 'Gagnée', style: TextStyle( fontSize: 11, color: Colors.grey[600], ), ), ], ), Column( children: [ Text( '${commissionData['commission_totale_retiree']?.toStringAsFixed(0) ?? '0'}', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.orange, ), ), Text( 'Utilisée', style: TextStyle( fontSize: 11, color: Colors.grey[600], ), ), ], ), Column( children: [ Text( '${commissionData['nombre_transactions'] ?? 0}', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), Text( 'Transactions', style: TextStyle( fontSize: 11, color: Colors.grey[600], ), ), ], ), ], ), ), ], ], ), ), SizedBox(height: 24), // Boutons d'action Row( children: [ // Demander une recharge Expanded( child: _buildRechargeOptionButton( context: context, title: 'Demander\nune recharge', subtitle: 'Rechargeur requis', icon: Icons.add_circle_outline, color: AppColors.secondary, onTap: () { Navigator.of(context).pop(); _showRechargeRequestDialog(context); }, ), ), SizedBox(width: 12), // Utiliser la commission Expanded( child: _buildRechargeOptionButton( context: context, title: 'Utiliser la\ncommission', subtitle: 'Transfert instantané', icon: Icons.monetization_on, color: AppColors.success, onTap: !isLoading && availableCommission > 0 ? () { Navigator.of(context).pop(); _showCommissionRechargeDialog( context, availableCommission, commissionData, ); } : null, isDisabled: isLoading || availableCommission <= 0, ), ), ], ), SizedBox(height: 16), TextButton( onPressed: () => Navigator.of(context).pop(), child: Text( 'Fermer', style: TextStyle( color: Colors.grey[600], fontSize: 16, ), ), ), ], ), ), ); }, ), ), ), ); } Widget _buildRechargeOptionButton({ required BuildContext context, required String title, required String subtitle, required IconData icon, required Color color, required VoidCallback? onTap, bool isDisabled = false, }) { return GestureDetector( onTap: isDisabled ? null : onTap, child: AnimatedContainer( duration: Duration(milliseconds: 200), padding: EdgeInsets.all(16), decoration: BoxDecoration( color: isDisabled ? Colors.grey[100] : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDisabled ? Colors.grey[300]! : color.withOpacity(0.3), width: 2, ), boxShadow: isDisabled ? [] : [ BoxShadow( color: color.withOpacity(0.15), blurRadius: 10, offset: Offset(0, 4), ), ], ), child: Column( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: isDisabled ? Colors.grey[300] : color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon( icon, color: isDisabled ? Colors.grey[500] : color, size: 24, ), ), SizedBox(height: 12), Text( title, textAlign: TextAlign.center, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: isDisabled ? Colors.grey[500] : Colors.black87, height: 1.2, ), ), SizedBox(height: 4), Text( subtitle, textAlign: TextAlign.center, style: TextStyle( fontSize: 12, color: isDisabled ? Colors.grey[400] : Colors.grey[600], ), ), ], ), ), ); } // Dialog pour demander une recharge traditionnelle Future _showRechargeRequestDialog(BuildContext context) async { final amountController = TextEditingController(); final pinController = TextEditingController(); final screenSize = MediaQuery.of(context).size; final isSmallScreen = screenSize.height < 600; await showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 24), ), elevation: 10, child: ConstrainedBox( constraints: BoxConstraints( maxWidth: 400, maxHeight: screenSize.height * (isSmallScreen ? 0.9 : 0.8), ), child: Container( width: screenSize.width * 0.85, padding: EdgeInsets.all(isSmallScreen ? 16 : 24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: SingleChildScrollView( physics: BouncingScrollPhysics(), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header Container( width: isSmallScreen ? 48 : 64, height: isSmallScreen ? 48 : 64, decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.secondary, AppColors.accent, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular( isSmallScreen ? 16 : 20, ), boxShadow: [ BoxShadow( color: AppColors.secondary.withOpacity(0.3), blurRadius: isSmallScreen ? 10 : 15, offset: Offset(0, isSmallScreen ? 3 : 5), ), ], ), child: Icon( Icons.add_circle_outline, color: Colors.white, size: isSmallScreen ? 24 : 32, ), ), SizedBox(height: isSmallScreen ? 12 : 16), Text( 'Demander une recharge', style: TextStyle( fontSize: isSmallScreen ? 18 : 22, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), SizedBox(height: isSmallScreen ? 6 : 8), Text( 'Un rechargeur validera votre demande', style: TextStyle( fontSize: isSmallScreen ? 12 : 14, color: Colors.grey[600], ), textAlign: TextAlign.center, ), SizedBox(height: isSmallScreen ? 16 : 20), // Champ montant TextField( controller: amountController, keyboardType: TextInputType.number, style: TextStyle( fontSize: isSmallScreen ? 14 : 16, ), decoration: InputDecoration( labelText: 'Montant à recharger', hintText: 'Ex: 10000', labelStyle: TextStyle( fontSize: isSmallScreen ? 12 : 14, ), hintStyle: TextStyle( fontSize: isSmallScreen ? 12 : 14, ), contentPadding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 12 : 16, vertical: isSmallScreen ? 12 : 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: AppColors.secondary.withOpacity(0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: AppColors.secondary, width: 2, ), ), prefixIcon: Icon( Icons.attach_money, color: AppColors.secondary, size: isSmallScreen ? 20 : 24, ), suffixText: 'XAF', suffixStyle: TextStyle( color: AppColors.secondary, fontWeight: FontWeight.w600, fontSize: isSmallScreen ? 12 : 14, ), ), autofocus: !isSmallScreen, ), SizedBox(height: isSmallScreen ? 12 : 16), // Champ PIN TextField( controller: pinController, keyboardType: TextInputType.number, obscureText: true, style: TextStyle( fontSize: isSmallScreen ? 14 : 16, ), decoration: InputDecoration( labelText: 'Code PIN', hintText: '****', labelStyle: TextStyle( fontSize: isSmallScreen ? 12 : 14, ), contentPadding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 12 : 16, vertical: isSmallScreen ? 12 : 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: AppColors.secondary.withOpacity(0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: AppColors.secondary, width: 2, ), ), prefixIcon: Icon( Icons.lock, color: AppColors.secondary, size: isSmallScreen ? 20 : 24, ), ), ), SizedBox(height: isSmallScreen ? 12 : 16), // Texte d'information Text( 'Votre demande sera envoyée aux rechargeurs pour validation', style: TextStyle( fontSize: isSmallScreen ? 11 : 12, color: Colors.grey[600], fontStyle: FontStyle.italic, ), textAlign: TextAlign.center, ), ], ), ), ), // Boutons fixes en bas SizedBox(height: isSmallScreen ? 16 : 24), Row( children: [ Expanded( child: TextButton( onPressed: () => Navigator.of(context).pop(), style: TextButton.styleFrom( padding: EdgeInsets.symmetric( vertical: isSmallScreen ? 10 : 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( 'Annuler', style: TextStyle( fontSize: isSmallScreen ? 14 : 16, ), ), ), ), SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { final amount = amountController.text.trim(); final pin = pinController.text.trim(); if (amount.isEmpty || pin.isEmpty) { if (mounted) { _showErrorSnackBar( context, 'Veuillez remplir tous les champs', ); } return; } final amountValue = double.tryParse(amount); if (amountValue == null || amountValue <= 0) { if (mounted) { _showErrorSnackBar( context, 'Montant invalide', ); } return; } Navigator.of(context).pop(); _processRechargeRequest(context, amount, pin); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.secondary, foregroundColor: Colors.white, padding: EdgeInsets.symmetric( vertical: isSmallScreen ? 10 : 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 3, ), child: Text( 'Envoyer', style: TextStyle( fontSize: isSmallScreen ? 14 : 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ], ), ), ), ), ); } // Dialog pour utiliser la commission Future _showCommissionRechargeDialog( BuildContext context, double availableCommission, Map commissionData, ) async { final amountController = TextEditingController(); await showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), elevation: 10, child: Container( width: MediaQuery.of(context).size.width * 0.85, constraints: BoxConstraints(maxWidth: 400), padding: EdgeInsets.all(24), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header animé TweenAnimationBuilder( duration: Duration(milliseconds: 600), tween: Tween(begin: 0.0, end: 1.0), builder: (context, value, child) { return Transform.scale( scale: value, child: Container( width: 64, height: 64, decoration: BoxDecoration( gradient: LinearGradient( colors: [AppColors.success, Colors.green[300]!], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: AppColors.success.withOpacity(0.3), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Icon( Icons.monetization_on, color: Colors.white, size: 32, ), ), ); }, ), SizedBox(height: 16), Text( 'Utiliser ma commission', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87, ), ), SizedBox(height: 8), Text( 'Transférez vos commissions vers votre solde principal', style: TextStyle(fontSize: 14, color: Colors.grey[600]), textAlign: TextAlign.center, ), SizedBox(height: 20), // Affichage de la commission disponible Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppColors.success.withOpacity(0.1), AppColors.success.withOpacity(0.05), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: AppColors.success.withOpacity(0.3), width: 1, ), ), child: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.success.withOpacity(0.2), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.account_balance, color: AppColors.success, size: 18, ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Commission disponible', style: TextStyle( fontSize: 13, color: AppColors.success, fontWeight: FontWeight.w600, ), ), SizedBox(height: 2), Text( '${availableCommission.toStringAsFixed(0)} XAF', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.success, ), ), ], ), ), ], ), ), SizedBox(height: 20), // Champ de saisie du montant TextField( controller: amountController, keyboardType: TextInputType.number, decoration: InputDecoration( labelText: 'Montant à transférer', hintText: 'Ex: ${(availableCommission / 2).toStringAsFixed(0)}', border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: AppColors.success.withOpacity(0.3), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: AppColors.success, width: 2, ), ), prefixIcon: Icon( Icons.account_balance_wallet, color: AppColors.success, ), suffixText: 'XAF', suffixStyle: TextStyle( color: AppColors.success, fontWeight: FontWeight.w600, ), ), autofocus: true, ), SizedBox(height: 12), // Boutons montants rapides Row( children: [ Expanded( child: _buildQuickAmountButton( context, '25%', (availableCommission * 0.25).toStringAsFixed(0), amountController, ), ), SizedBox(width: 8), Expanded( child: _buildQuickAmountButton( context, '50%', (availableCommission * 0.5).toStringAsFixed(0), amountController, ), ), SizedBox(width: 8), Expanded( child: _buildQuickAmountButton( context, 'Tout', availableCommission.toStringAsFixed(0), amountController, ), ), ], ), SizedBox(height: 16), Text( 'Ce montant sera instantanément ajouté à votre solde principal', style: TextStyle( fontSize: 12, color: Colors.grey[600], fontStyle: FontStyle.italic, ), textAlign: TextAlign.center, ), SizedBox(height: 24), // Boutons d'action Row( children: [ Expanded( child: TextButton( onPressed: () => Navigator.of(context).pop(), style: TextButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( 'Annuler', style: TextStyle(fontSize: 16), ), ), ), SizedBox(width: 12), Expanded( child: ElevatedButton( onPressed: () { final amount = double.tryParse( amountController.text, ); if (amount != null && amount > 0 && amount <= availableCommission) { Navigator.of(context).pop(); _processCommissionTransfer(context, amount); } else { _showErrorSnackBar( context, 'Montant invalide (max: ${availableCommission.toStringAsFixed(0)} XAF)', ); } }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.success, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 3, ), child: Text( 'Transférer', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ], ), ), ), ), ); } Widget _buildQuickAmountButton( BuildContext context, String label, String amount, TextEditingController controller, ) { return GestureDetector( onTap: () { controller.text = amount; }, child: Container( padding: EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: AppColors.success.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: AppColors.success.withOpacity(0.3), width: 1, ), ), child: Text( label, textAlign: TextAlign.center, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.success, ), ), ), ); } // Dialog historique des commissions Future _showCommissionHistoryDialog(BuildContext context) async { final authController = Provider.of(context, listen: false); showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( width: MediaQuery.of(context).size.width * 0.9, height: MediaQuery.of(context).size.height * 0.8, padding: EdgeInsets.all(24), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Historique des commissions', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), IconButton( onPressed: () => Navigator.pop(context), icon: Icon(Icons.close, color: Colors.grey), ), ], ), SizedBox(height: 16), Expanded( child: FutureBuilder>( future: _getCommissionHistory( authController.agentId ?? '', ), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( AppColors.success, ), ), ); } if (!snapshot.hasData || !snapshot.data!['success']) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.history, size: 64, color: Colors.grey[400], ), SizedBox(height: 16), Text( 'Aucun historique trouvé', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), ], ), ); } final withdrawals = snapshot.data!['historique']['retraits'] as List; return ListView.builder( itemCount: withdrawals.length, itemBuilder: (context, index) { final withdrawal = withdrawals[index]; final date = DateTime.parse( withdrawal['date_creation'], ); return Container( margin: EdgeInsets.only(bottom: 12), padding: EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: AppColors.success.withOpacity(0.2), width: 1, ), ), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: AppColors.success.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.trending_up, color: AppColors.success, size: 24, ), ), SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Transfert de commission', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(height: 4), Text( withdrawal['description'] ?? 'Transfert vers solde', style: TextStyle( fontSize: 13, color: Colors.grey[600], ), ), SizedBox(height: 4), Text( _formatDate(date), style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '+${withdrawal['montant'].toStringAsFixed(0)} XAF', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.success, ), ), SizedBox(height: 4), Container( padding: EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: AppColors.success.withOpacity( 0.1, ), borderRadius: BorderRadius.circular( 8, ), ), child: Text( 'Terminé', style: TextStyle( fontSize: 11, color: AppColors.success, fontWeight: FontWeight.w600, ), ), ), ], ), ], ), ); }, ); }, ), ), ], ), ), ), ); } // ============= TRAITEMENT DES REQUÊTES ============= Future _processRechargeRequest( BuildContext context, String montant, String pin, ) async { if (montant.isEmpty || pin.isEmpty) { _showErrorSnackBar(context, 'Veuillez remplir tous les champs'); return; } final amountValue = double.tryParse(montant); if (amountValue == null || amountValue <= 0) { _showErrorSnackBar(context, 'Montant invalide'); return; } if (pin.length < 4) { _showErrorSnackBar(context, 'PIN doit contenir au moins 4 chiffres'); return; } try { final userId = await SessionManager().getUserId(); if (userId == null) { _showErrorSnackBar(context, 'Session expirée, reconnectez-vous'); return; } await _demandeRecharge(userId, pin, montant, context); } catch (e) { print('Erreur processRechargeRequest : $e'); if (context.mounted) { _showErrorSnackBar(context, 'Une erreur est survenue'); } } } Future _processCommissionTransfer( BuildContext context, double amount, ) async { final authController = Provider.of(context, listen: false); final agentId = authController.agentId; if (agentId == null || agentId.isEmpty) { _showErrorSnackBar(context, 'Erreur: Agent non connecté'); return; } // Dialog de chargement showDialog( context: context, barrierDismissible: false, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Container( padding: EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( AppColors.success, ), strokeWidth: 3, ), SizedBox(height: 16), Text( 'Transfert en cours...', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(height: 8), Text( 'Veuillez patienter', style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), ], ), ), ), ); try { print( 'Envoi transfert commission - Agent ID: $agentId, Montant: $amount', ); var headersList = { 'Accept': '*/*', 'User-Agent': 'FlutterApp (Wortis Agent)', 'Content-Type': 'application/json', }; var url = Uri.parse( 'https://api.live.wortis.cg/tpe/recharge/$agentId/commission', ); var body = { "montant": amount, "description": "Transfert de commission vers solde principal", }; var req = http.Request('POST', url); req.headers.addAll(headersList); req.body = jsonEncode(body); print('URL commission: $url'); print('Headers envoyés: $headersList'); print('Body envoyé: ${jsonEncode(body)}'); var res = await req.send(); final resBody = await res.stream.bytesToString(); // Fermer le dialog de chargement if (Navigator.canPop(context)) { Navigator.of(context).pop(); } print('Réponse commission - Status: ${res.statusCode}'); print('Réponse commission - Body: $resBody'); if (res.statusCode >= 200 && res.statusCode < 300) { final data = jsonDecode(resBody); if (data['success'] == true) { // Rafraîchir le solde await authController.refreshBalance(); // Message de succès if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Container( padding: EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(6), ), child: Icon( Icons.check_circle, color: Colors.white, size: 20, ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'Transfert réussi !', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), Text( '+${amount.toStringAsFixed(0)} XAF ajouté à votre solde', style: TextStyle(fontSize: 12), ), ], ), ), ], ), backgroundColor: AppColors.success, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), margin: EdgeInsets.all(16), duration: Duration(seconds: 4), ), ); setState(() {}); } } else { if (context.mounted) { _showErrorSnackBar( context, data['message'] ?? 'Erreur lors du transfert', ); } } } else { try { final data = jsonDecode(resBody); if (context.mounted) { if (res.statusCode == 404) { _showErrorSnackBar( context, 'Service de commission non disponible', ); } else if (res.statusCode == 401) { _showErrorSnackBar(context, 'Session expirée, reconnectez-vous'); } else { _showErrorSnackBar( context, data['message'] ?? 'Erreur de serveur (${res.statusCode})', ); } } } catch (e) { if (context.mounted) { _showErrorSnackBar( context, 'Erreur (${res.statusCode}): ${res.reasonPhrase}', ); } } } } on http.ClientException catch (e) { if (Navigator.canPop(context)) Navigator.of(context).pop(); print('Erreur réseau commission: $e'); if (context.mounted) { _showErrorSnackBar(context, 'Erreur de connexion réseau'); } } on FormatException catch (e) { if (Navigator.canPop(context)) Navigator.of(context).pop(); print('Erreur format JSON commission: $e'); if (context.mounted) { _showErrorSnackBar(context, 'Erreur de format de réponse'); } } catch (e) { if (Navigator.canPop(context)) Navigator.of(context).pop(); print('Erreur transfert commission: $e'); if (context.mounted) { _showErrorSnackBar( context, 'Erreur lors du transfert: ${e.toString()}', ); } } } // ============= AUTRES MÉTHODES ============= Future> _getTransactionHistory(String userId) async { try { final response = await http.get( Uri.parse('$apiBaseUrl/agent/$userId/transactions'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return { 'success': true, 'transactions': data['transactions'] ?? [], 'total_commission': _calculateTotalCommission( data['transactions'] ?? [], ), }; } else { return {'success': false, 'message': 'Erreur lors du chargement'}; } } catch (e) { return {'success': false, 'message': 'Erreur de connexion'}; } } Future> _getRechargeHistory(String userId) async { try { final response = await http.get( Uri.parse('$apiBaseUrl/recharge/history/agent/$userId'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); return {'success': true, 'recharges': data['recharges'] ?? []}; } else { return {'success': false, 'message': 'Erreur lors du chargement'}; } } catch (e) { return {'success': false, 'message': 'Erreur de connexion'}; } } double _calculateTotalCommission(List transactions) { double totalTransaction = 0; for (var transaction in transactions) { if (transaction['statut'] == 'completed') { totalTransaction += (transaction['montant'] ?? 0.0); } } return totalTransaction * 0.0066; } Future _showRechargeHistoryDialog(BuildContext context) async { try { final userId = await SessionManager().getUserId(); final result = await _getRechargeHistory(userId!); if (!context.mounted) return; if (result['success']) { _showHistoryDialog( context, 'Historique des recharges', result['recharges'], 'recharge', ); } else { _showErrorSnackBar(context, result['message']); } } catch (e) { _showErrorSnackBar(context, 'Erreur lors du chargement'); } } void _showHistoryDialog( BuildContext context, String title, List items, String type, ) { showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( width: MediaQuery.of(context).size.width * 0.9, height: MediaQuery.of(context).size.height * 0.8, padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [AppColors.surface, Colors.grey[50]!], ), borderRadius: BorderRadius.circular(20), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Text( title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), Container( decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(10), ), child: IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close, color: Colors.grey), ), ), ], ), const SizedBox(height: 8), Container( height: 2, decoration: BoxDecoration( gradient: LinearGradient( colors: [AppColors.primary, AppColors.accent], ), borderRadius: BorderRadius.circular(1), ), ), const SizedBox(height: 16), Expanded( child: items.isEmpty ? _buildEmptyState(type) : ListView.builder( physics: BouncingScrollPhysics(), itemCount: items.length, itemBuilder: (context, index) { return AnimatedContainer( duration: Duration( milliseconds: 300 + (index * 100), ), curve: Curves.easeOutBack, child: type == 'transaction' ? _buildEnhancedTransactionItem( items[index], index, ) : _buildEnhancedRechargeHistoryItem( items[index], index, ), ); }, ), ), ], ), ), ), ); } Widget _buildEmptyState(String type) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(20), ), child: Icon( type == 'transaction' ? Icons.receipt_long : Icons.history, size: 40, color: Colors.grey[400], ), ), SizedBox(height: 16), Text( 'Aucun élément dans l\'historique', style: TextStyle( fontSize: 16, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), SizedBox(height: 8), Text( type == 'transaction' ? 'Vos transactions apparaîtront ici' : 'Vos recharges apparaîtront ici', style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ], ), ); } Widget _buildEnhancedTransactionItem( Map transaction, int index, ) { final serviceName = transaction['service_name'] ?? 'Service inconnu'; final montant = (transaction['montant_total_debite'] ?? 0.0).toDouble(); final commission = (transaction['commission_agent'] ?? 0.0).toDouble(); final statut = transaction['status'] ?? 'unknown'; final date = DateTime.tryParse(transaction['created_at'] ?? '') ?? DateTime.now(); return TweenAnimationBuilder( duration: Duration(milliseconds: 600 + (index * 100)), tween: Tween(begin: 0.0, end: 1.0), curve: Curves.easeOutBack, builder: (context, animation, child) { final clampedAnimation = animation.clamp(0.0, 1.0); return Transform.translate( offset: Offset(30 * (1 - clampedAnimation), 0), child: Opacity( opacity: clampedAnimation, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: _getStatusColor(statut).withOpacity(0.2), width: 1, ), boxShadow: [ BoxShadow( color: _getStatusColor(statut).withOpacity(0.1), blurRadius: 10, offset: Offset(0, 4), ), ], ), child: Row( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( gradient: LinearGradient( colors: [ _getStatusColor(statut), _getStatusColor(statut).withOpacity(0.7), ], ), borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: _getStatusColor(statut).withOpacity(0.3), blurRadius: 8, offset: Offset(0, 4), ), ], ), child: Icon( _getServiceIcon(serviceName), color: Colors.white, size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( serviceName, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), SizedBox(height: 4), Row( children: [ Text( '${montant.toStringAsFixed(0)} XAF', style: TextStyle( fontSize: 14, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), Text( ' • ', style: TextStyle(color: Colors.grey[500]), ), Text( 'Commission: ${commission.toStringAsFixed(0)} XAF', style: TextStyle( fontSize: 12, color: AppColors.success, fontWeight: FontWeight.w500, ), ), ], ), SizedBox(height: 4), Text( _formatDate(date), style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: _getStatusColor(statut).withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: _getStatusColor(statut).withOpacity(0.3), ), ), child: Text( _getStatusText(statut), style: TextStyle( fontSize: 11, color: _getStatusColor(statut), fontWeight: FontWeight.w600, ), ), ), ], ), ), ), ); }, ); } Widget _buildEnhancedRechargeHistoryItem( Map recharge, int index, ) { final montant = double.tryParse(recharge['montant'].toString()) ?? 0.0; final status = recharge['status'] ?? ''; final date = DateTime.tryParse(recharge['created_at'] ?? '') ?? DateTime.now(); return TweenAnimationBuilder( duration: Duration(milliseconds: 600 + (index * 100)), tween: Tween(begin: 0.0, end: 1.0), curve: Curves.easeOutBack, builder: (context, animation, child) { final clampedAnimation = animation.clamp(0.0, 1.0); return Transform.translate( offset: Offset(30 * (1 - clampedAnimation), 0), child: Opacity( opacity: clampedAnimation, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: _getStatusColor(status).withOpacity(0.2), width: 1, ), boxShadow: [ BoxShadow( color: _getStatusColor(status).withOpacity(0.1), blurRadius: 10, offset: Offset(0, 4), ), ], ), child: Row( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( gradient: LinearGradient( colors: [ _getStatusColor(status), _getStatusColor(status).withOpacity(0.7), ], ), borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: _getStatusColor(status).withOpacity(0.3), blurRadius: 8, offset: Offset(0, 4), ), ], ), child: Icon( Icons.add_circle, color: Colors.white, size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Recharge de ${montant.toStringAsFixed(0)} XAF', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(height: 8), Text( _formatDate(date), style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: _getStatusColor(status).withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: _getStatusColor(status).withOpacity(0.3), ), ), child: Text( _getStatusText(status), style: TextStyle( fontSize: 11, color: _getStatusColor(status), fontWeight: FontWeight.w600, ), ), ), ], ), ), ), ); }, ); } // ============= UTILITAIRES ============= IconData _getServiceIcon(String serviceName) { if (serviceName.toLowerCase().contains('internet')) { return Icons.wifi; } else if (serviceName.toLowerCase().contains('électricité') || serviceName.toLowerCase().contains('pelisa')) { return Icons.electrical_services; } else if (serviceName.toLowerCase().contains('eau')) { return Icons.water_drop; } else { return Icons.receipt_long; } } String _getStatusText(String status) { switch (status.toLowerCase()) { case 'completed': return 'Terminé'; case 'approved': return 'Approuvé'; case 'pending': return 'En attente'; case 'rejected': return 'Rejeté'; case 'cancelled': return 'Annulé'; default: return status.toUpperCase(); } } Color _getStatusColor(String status) { switch (status.toLowerCase()) { case 'completed': case 'approved': return AppColors.success; case 'pending': return Colors.orange; case 'rejected': case 'cancelled': return Colors.red; default: return Colors.grey; } } String _formatDate(DateTime date) { return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year} à ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}'; } Future _demandeRecharge( String? userId, String pin, String montant, BuildContext context, ) async { if (userId == null) { _showErrorSnackBar(context, 'Erreur: Utilisateur non connecté'); return; } try { print( 'Envoi demande recharge - URL: $apiBaseUrl/recharge/create/agent/$userId', ); print( 'Montant: $montant, PIN: ${pin.isNotEmpty ? "****(${pin.length} chars)" : "vide"}', ); final response = await http.post( Uri.parse('$apiBaseUrl/recharge/create/agent/$userId'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: jsonEncode({'pin': pin, 'montant': montant}), ); print('Réponse recharge - Status: ${response.statusCode}'); print('Réponse recharge - Body: ${response.body}'); if (response.statusCode == 201 || response.statusCode == 200) { final data = jsonDecode(response.body); if (data['success'] == true || data.containsKey('recharge')) { _showSuccessDialog( context, 'Demande de recharge envoyée avec succès', ); } else { _showErrorSnackBar( context, data['message'] ?? 'Erreur lors de la demande', ); } } else { final responseBody = jsonDecode(response.body); final errorMessage = responseBody['message'] ?? 'Erreur inconnue'; if (errorMessage.toLowerCase().contains('pin') || errorMessage.toLowerCase().contains('code') || response.statusCode == 401) { _showErrorSnackBar(context, 'PIN incorrect, veuillez réessayer'); } else if (response.statusCode == 404) { _showErrorSnackBar( context, 'Service non disponible, contactez le support', ); } else { _showErrorSnackBar( context, 'Erreur (${response.statusCode}): $errorMessage', ); } } } on http.ClientException catch (e) { print('Erreur réseau: $e'); _showErrorSnackBar(context, 'Erreur de connexion réseau'); } on FormatException catch (e) { print('Erreur format JSON: $e'); _showErrorSnackBar(context, 'Erreur de format de réponse'); } catch (e) { print('Erreur générale: $e'); _showErrorSnackBar(context, 'Une erreur inattendue s\'est produite'); } } void _showSupportDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), title: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.purple.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.headset_mic, color: AppColors.purple), ), SizedBox(width: 12), Text('Support Technique'), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Besoin d\'aide ? Contactez notre équipe de support :', style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), SizedBox(height: 16), _buildSupportOption( Icons.phone, 'Téléphone', '50 05', AppColors.success, ), SizedBox(height: 12), _buildSupportOption( Icons.email, 'Email', 'support@wortis.cg', AppColors.primary, ), SizedBox(height: 12), _buildSupportOption( Icons.chat, 'WhatsApp', '+242 06 755 0505', Colors.green, ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Fermer'), ), ], ), ); } Widget _buildSupportOption( IconData icon, String title, String value, Color color, ) { return Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), ), child: Row( children: [ Icon(icon, color: color, size: 20), SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 12, color: Colors.grey[700], ), ), Text( value, style: TextStyle( fontSize: 14, color: color, fontWeight: FontWeight.w500, ), ), ], ), ], ), ); } void _showSuccessDialog(BuildContext context, String message) { if (!context.mounted) return; showDialog( context: context, barrierDismissible: true, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( padding: EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColors.success.withOpacity(0.1), Colors.green[50]!, ], ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 60, height: 60, decoration: BoxDecoration( color: AppColors.success, borderRadius: BorderRadius.circular(30), boxShadow: [ BoxShadow( color: AppColors.success.withOpacity(0.3), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Icon(Icons.check, color: Colors.white, size: 32), ), SizedBox(height: 16), Text( 'Succès !', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.success, ), ), SizedBox(height: 8), Text( message, textAlign: TextAlign.center, style: TextStyle(fontSize: 16, color: Colors.grey[700]), ), SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.of(context).pop(), style: ElevatedButton.styleFrom( backgroundColor: AppColors.success, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), padding: EdgeInsets.symmetric(vertical: 12), ), child: Text( 'Parfait !', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ); } void _showErrorSnackBar(BuildContext context, String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.error, color: Colors.white), const SizedBox(width: 8), Expanded(child: Text(message)), ], ), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), ), ); } void _logout(BuildContext context) async { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), title: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(Icons.logout, color: Colors.red), ), SizedBox(width: 12), Text('Déconnexion'), ], ), content: Text( 'Êtes-vous sûr de vouloir vous déconnecter de votre compte agent ?', style: TextStyle(fontSize: 16), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text('Annuler'), ), ElevatedButton( onPressed: () async { Navigator.of(context).pop(); final authController = Provider.of( context, listen: false, ); await authController.logout(); if (context.mounted) { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => const LoginPage()), (route) => false, ); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: Text('Déconnexion'), ), ], ); }, ); } // ============= SECTION PARAMÈTRES ============= Widget _buildSettingsSection( bool isSmallScreen, bool isMediumScreen, bool isTablet, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only(left: 4, bottom: isSmallScreen ? 12 : 16), child: Text( 'Paramètres Kiosque', style: TextStyle( fontSize: isSmallScreen ? 18 : (isMediumScreen ? 19 : 20), fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), Container( padding: EdgeInsets.all(isSmallScreen ? 16 : 20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(isSmallScreen ? 16 : 20), border: Border.all( color: AppColors.primary.withOpacity(0.1), width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: isSmallScreen ? 10 : 15, offset: Offset(0, isSmallScreen ? 3 : 5), ), ], ), child: Column( children: [ _buildSettingTile( icon: Icon(Icons.wifi_find, size: 24.0, color: Colors.blue), title: 'Paramètres WiFi', subtitle: 'Changer de réseau WiFi', color: Colors.blue, onTap: () => _openWifiSettings(), isSmallScreen: isSmallScreen, ), SizedBox(height: isSmallScreen ? 12 : 16), Divider(color: Colors.grey[200], height: 1), SizedBox(height: isSmallScreen ? 12 : 16), _buildSettingTile( icon: Icon( Icons.brightness_6, size: 24.0, color: Colors.orange, ), title: 'Luminosité', subtitle: 'Ajuster la luminosité de l\'écran', color: Colors.orange, onTap: () => _showBrightnessDialog(context), isSmallScreen: isSmallScreen, ), SizedBox(height: isSmallScreen ? 12 : 16), Divider(color: Colors.grey[200], height: 1), SizedBox(height: isSmallScreen ? 12 : 16), _buildSettingTile( icon: FaIcon( FontAwesomeIcons.whatsapp, size: 24.0, color: Colors.green, ), title: 'Ouvrir WhatsApp', subtitle: 'Ouvrir l\'application WhatsApp', color: Colors.green, onTap: () => _openWhatsApp(), isSmallScreen: isSmallScreen, ), ], ), ), ], ); } Widget _buildSettingTile({ required Widget icon, required String title, required String subtitle, required Color color, required VoidCallback onTap, required bool isSmallScreen, }) { return GestureDetector( onTap: onTap, child: Container( padding: EdgeInsets.all(isSmallScreen ? 12 : 16), decoration: BoxDecoration( color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2), width: 1), ), child: Row( children: [ Container( width: isSmallScreen ? 40 : 48, height: isSmallScreen ? 40 : 48, decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.3), width: 1), ), child: Center(child: icon), ), SizedBox(width: isSmallScreen ? 12 : 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: isSmallScreen ? 14 : 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(height: 2), Text( subtitle, style: TextStyle( fontSize: isSmallScreen ? 11 : 13, color: Colors.grey[600], ), ), ], ), ), Icon( Icons.arrow_forward_ios, color: color, size: isSmallScreen ? 14 : 16, ), ], ), ), ); } // ============= MÉTHODES D'ACTION ============= void _openWifiSettings() async { try { const platform = MethodChannel('com.wortis.agent/settings'); await platform.invokeMethod('exitKioskMode'); await platform.invokeMethod('openWifiSettings'); Future.delayed(Duration(seconds: 2), () async { try { await platform.invokeMethod('enableKioskMode'); } catch (e) { print('Erreur réactivation kiosque: $e'); } }); } catch (e) { print('Erreur ouverture WiFi: $e'); _showErrorSnackBar(context, 'Impossible d\'ouvrir les paramètres WiFi'); } } void _showBrightnessDialog(BuildContext context) async { double currentBrightness = 0.5; try { const platform = MethodChannel('com.wortis.agent/settings'); final brightness = await platform.invokeMethod('getSystemBrightness'); currentBrightness = (brightness as double).clamp(0.0, 1.0); } catch (e) { print('Erreur récupération luminosité: $e'); } if (!mounted) return; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setState) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.orange.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.brightness_6, color: Colors.orange), ), SizedBox(width: 12), Text('Réglage Luminosité'), ], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Ajustez la luminosité de l\'écran', style: TextStyle(color: Colors.grey[600]), ), SizedBox(height: 20), Row( children: [ Icon(Icons.brightness_low, color: Colors.grey), Expanded( child: Slider( value: currentBrightness, onChanged: (value) { setState(() { currentBrightness = value; }); _setBrightness(value); }, activeColor: Colors.orange, inactiveColor: Colors.orange.withOpacity(0.3), ), ), Icon(Icons.brightness_high, color: Colors.orange), ], ), SizedBox(height: 8), Text( '${(currentBrightness * 100).round()}%', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.orange, ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Fermer'), ), ], ), ), ); } void _setBrightness(double brightness) async { try { const platform = MethodChannel('com.wortis.agent/settings'); await platform.invokeMethod('setBrightness', {'brightness': brightness}); } catch (e) { print('Erreur réglage luminosité: $e'); if (mounted) { _showErrorSnackBar(context, 'Réglage de luminosité non disponible'); } } } void _openWhatsApp() async { try { const platform = MethodChannel('com.wortis.agent/settings'); await platform.invokeMethod('exitKioskMode'); await platform.invokeMethod('openWhatsApp'); } catch (e) { print('Erreur ouverture WhatsApp: $e'); try { const platform = MethodChannel('com.wortis.agent/settings'); await platform.invokeMethod('enableKioskMode'); } catch (e2) { print('Erreur réactivation kiosque: $e2'); } if (mounted) { _showErrorSnackBar( context, 'WhatsApp non disponible ou erreur d\'ouverture', ); } } } }