// ===== lib/pages/home.dart AVEC SUPPORT ENTREPRISE ===== import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../main.dart'; import '../widgets/balance_widget.dart'; import '../pages/form_service.dart'; import '../widgets/responsive_helper.dart'; class HomePage extends StatefulWidget { final bool showAppBar; const HomePage({super.key, this.showAppBar = true}); @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; late ScrollController _scrollController; final TextEditingController _searchController = TextEditingController(); static const Color primaryColor = Color(0xFF006699); static const Color secondaryColor = Color(0xFF0088CC); static const Color backgroundColor = Color(0xFFF8FAFC); static const Color surfaceColor = Colors.white; static const Color successColor = Color(0xFF10B981); static const Color cardShadowColor = Color(0x08000000); static const Color enterpriseColor = Color( 0xFF006699, ); // Violet pour entreprise @override void initState() { super.initState(); _setupAnimations(); _setupScrollController(); _initializeOrientations(); _loadServices(); } void _initializeOrientations() { WidgetsBinding.instance.addPostFrameCallback((_) { ResponsiveHelper.initializeOrientations(context); }); } void _setupAnimations() { _animationController = AnimationController( duration: Duration(milliseconds: 800), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); _slideAnimation = Tween( begin: Offset(0, 0.05), end: Offset.zero, ).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); _animationController.forward(); } void _setupScrollController() { _scrollController = ScrollController(); } void _loadServices() { WidgetsBinding.instance.addPostFrameCallback((_) { final servicesController = Provider.of( context, listen: false, ); servicesController.loadServices(); }); } @override void dispose() { _animationController.dispose(); _searchController.dispose(); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final screenWidth = MediaQuery.of(context).size.width; final isTablet = screenWidth > 600; return Scaffold( backgroundColor: backgroundColor, body: FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: widget.showAppBar ? _buildWithAppBar(isTablet) : _buildWithSliverAppBar(screenHeight, isTablet), ), ), ); } Widget _buildWithAppBar(bool isTablet) { return Column( children: [ _buildBeautifulAppBar(), Expanded( child: SingleChildScrollView( padding: EdgeInsets.all(isTablet ? 24 : 20), child: Column( children: [ _buildModernSearchBar(), SizedBox(height: 25), _buildStylishCategoryTabs(), SizedBox(height: 30), _buildServicesContent(), SizedBox(height: 20), ], ), ), ), ], ); } Widget _buildWithSliverAppBar(double screenHeight, bool isTablet) { return CustomScrollView( controller: _scrollController, slivers: [ SliverAppBar( expandedHeight: screenHeight * (isTablet ? 0.28 : 0.32), floating: false, pinned: true, elevation: 0, backgroundColor: primaryColor, flexibleSpace: FlexibleSpaceBar( background: _buildBeautifulHeader(), collapseMode: CollapseMode.parallax, ), bottom: PreferredSize( preferredSize: Size.fromHeight(0), child: Container( height: 30, decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30), ), ), ), ), ), SliverToBoxAdapter( child: Container( color: backgroundColor, padding: EdgeInsets.fromLTRB( isTablet ? 24 : 20, 0, isTablet ? 24 : 20, isTablet ? 24 : 20, ), child: Column( children: [ _buildModernSearchBar(), SizedBox(height: 25), _buildStylishCategoryTabs(), SizedBox(height: 30), _buildServicesContent(), SizedBox(height: 120), ], ), ), ), ], ); } // ===== HEADER AMÉLIORÉ AVEC SUPPORT ENTREPRISE ===== Widget _buildBeautifulHeader() { return Consumer( builder: (context, authController, child) { final isEnterprise = authController.isEnterpriseMember; return Container( width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isEnterprise ? [ enterpriseColor, enterpriseColor.withOpacity(0.8), secondaryColor, ] : [ primaryColor, primaryColor.withOpacity(0.8), secondaryColor, ], stops: [0.0, 0.6, 1.0], ), ), child: SafeArea( child: Padding( padding: EdgeInsets.fromLTRB(20, 10, 20, 30), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ // Partie supérieure avec bienvenue et solde Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Badge type de compte Container( padding: EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), 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, color: Colors.white, size: 14, ), SizedBox(width: 6), Text( isEnterprise ? 'Compte Entreprise' : 'TPE Wortis', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), SizedBox(height: 8), // Nom de l'agent Text( authController.agentName ?? "Agent", style: TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), overflow: TextOverflow.ellipsis, ), // Nom de l'entreprise si membre if (isEnterprise && authController.enterprise?.nomEntreprise != null) ...[ SizedBox(height: 4), Row( children: [ Icon( Icons.apartment, color: Colors.white.withOpacity(0.8), size: 14, ), SizedBox(width: 6), Expanded( child: Text( authController .enterprise! .nomEntreprise, style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 14, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ], ), ], ], ), ), // Widget de solde Flexible( child: Container( padding: EdgeInsets.all(3), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: BalanceWidget( showIcon: true, compact: true, textColor: Colors.white, backgroundColor: Colors.transparent, ), ), ), ], ), SizedBox(height: 20), // Statistiques _buildStatsRow(authController), ], ), ), ), ), ); }, ); } // ===== NOUVELLE MÉTHODE: LIGNE DE STATISTIQUES ===== Widget _buildStatsRow(AuthController authController) { return Consumer( builder: (context, servicesController, child) { final activeCount = servicesController.activeServices.length; final totalCount = servicesController.allServices.length; final isEnterprise = authController.isEnterpriseMember; // Version compacte pour petits écrans return LayoutBuilder( builder: (context, constraints) { final isVerySmall = constraints.maxWidth < 350; return Container( padding: EdgeInsets.symmetric( horizontal: isVerySmall ? 8 : 12, vertical: isVerySmall ? 8 : 10, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.2), width: 1, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildCompactStatItem( icon: Icons.verified_user_rounded, value: '$activeCount', isVerySmall: isVerySmall, ), _buildStatDivider(), _buildCompactStatItem( icon: Icons.apps_rounded, value: '$totalCount', isVerySmall: isVerySmall, ), _buildStatDivider(), _buildCompactStatItem( icon: isEnterprise ? Icons.business_center : Icons.category_rounded, value: isEnterprise ? 'PRO' : '${servicesController.sectors.length - 1}', isVerySmall: isVerySmall, ), ], ), ); }, ); }, ); } Widget _buildCompactStatItem({ required IconData icon, required String value, required bool isVerySmall, }) { return Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Colors.white, size: isVerySmall ? 16 : 18), SizedBox(height: isVerySmall ? 2 : 4), Text( value, style: TextStyle( color: Colors.white, fontSize: isVerySmall ? 14 : 16, fontWeight: FontWeight.bold, ), ), ], ); } Widget _buildStatDivider() { return Container( height: 35, // Réduit de 25 à 35 width: 1, color: Colors.white.withOpacity(0.3), margin: EdgeInsets.symmetric(horizontal: 4), // Réduit de 8 à 4 ); } Widget _buildStatItem({ required IconData icon, required String value, required String label, }) { return Padding( padding: EdgeInsets.symmetric(horizontal: 4), // Ajout de padding child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, // AJOUTÉ crossAxisAlignment: CrossAxisAlignment.center, // AJOUTÉ children: [ Icon(icon, color: Colors.white, size: 18), // Réduit de 20 à 18 SizedBox(height: 4), Text( value, style: TextStyle( color: Colors.white, fontSize: 16, // Réduit de 18 à 16 fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, ), SizedBox(height: 2), Text( label, style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 10, // Réduit de 11 à 10 height: 1.2, // Ajusté ), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ); } // ===== APP BAR SIMPLIFIÉE AVEC SUPPORT ENTREPRISE ===== Widget _buildBeautifulAppBar() { return Consumer( builder: (context, authController, child) { final isEnterprise = authController.isEnterpriseMember; return Container( height: 140, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isEnterprise ? [enterpriseColor, secondaryColor] : [primaryColor, secondaryColor], ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(25), bottomRight: Radius.circular(25), ), ), child: SafeArea( child: Padding( padding: EdgeInsets.fromLTRB(20, 20, 20, 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ // Badge type de compte Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isEnterprise ? Icons.business : Icons.person, color: Colors.white, size: 12, ), SizedBox(width: 4), Text( isEnterprise ? 'Entreprise' : 'TPE Wortis', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 10, fontWeight: FontWeight.w600, ), ), ], ), ), SizedBox(height: 5), // Nom agent Text( authController.agentName ?? "Agent", style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), // Nom entreprise si applicable if (isEnterprise && authController.enterprise?.nomEntreprise != null) ...[ SizedBox(height: 2), Text( authController.enterprise!.nomEntreprise, style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 12, ), overflow: TextOverflow.ellipsis, ), ], ], ), ), BalanceWidget( showIcon: true, compact: true, textColor: Colors.white, backgroundColor: Colors.white.withOpacity(0.2), ), ], ), ), ), ); }, ); } Widget _buildModernSearchBar() { return Container( decoration: BoxDecoration( color: surfaceColor, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: cardShadowColor, blurRadius: 20, offset: Offset(0, 8), spreadRadius: 0, ), ], ), child: Consumer( builder: (context, servicesController, child) { return TextField( controller: _searchController, decoration: InputDecoration( hintText: '🔍 Rechercher un service...', hintStyle: TextStyle(color: Colors.grey[400], fontSize: 15), suffixIcon: servicesController.searchQuery.isNotEmpty ? IconButton( icon: Icon(Icons.clear_rounded, color: Colors.grey), onPressed: () { _searchController.clear(); servicesController.clearSearch(); }, ) : IconButton( icon: Icon(Icons.refresh_rounded, color: primaryColor), onPressed: servicesController.isLoading ? null : () => servicesController.refreshServices(), ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 25, vertical: 18, ), ), onChanged: (value) { servicesController.updateSearchQuery(value); }, ); }, ), ); } Widget _buildStylishCategoryTabs() { return Consumer( builder: (context, servicesController, child) { final sectors = servicesController.sectors; final screenWidth = MediaQuery.of(context).size.width; final isTablet = screenWidth > 600; return SizedBox( height: isTablet ? 70 : 55, child: ListView.builder( scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: 5), itemCount: sectors.length, itemBuilder: (context, index) { final sector = sectors[index]; final isSelected = servicesController.selectedSector == sector; final count = sector == 'Tous' ? servicesController.allServices.length : servicesController.servicesCountBySector[sector] ?? 0; return Padding( padding: EdgeInsets.only(right: 15), child: GestureDetector( onTap: () { servicesController.selectSector(sector); }, child: AnimatedContainer( duration: Duration(milliseconds: 200), padding: EdgeInsets.symmetric( horizontal: isTablet ? 30 : 20, vertical: isTablet ? 20 : 15, ), decoration: BoxDecoration( gradient: isSelected ? LinearGradient( colors: [primaryColor, secondaryColor], ) : null, color: isSelected ? null : surfaceColor, borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( color: isSelected ? primaryColor.withOpacity(0.3) : cardShadowColor, blurRadius: isSelected ? 15 : 10, offset: Offset(0, isSelected ? 8 : 4), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Text( sector, style: TextStyle( color: isSelected ? Colors.white : Colors.black87, fontWeight: FontWeight.w600, fontSize: isTablet ? 16 : 14, ), overflow: TextOverflow.ellipsis, ), ), if (count > 0) ...[ SizedBox(width: isTablet ? 10 : 8), Container( padding: EdgeInsets.symmetric( horizontal: isTablet ? 8 : 6, vertical: isTablet ? 3 : 2, ), decoration: BoxDecoration( color: isSelected ? Colors.white.withOpacity(0.25) : primaryColor.withOpacity(0.15), borderRadius: BorderRadius.circular(12), ), child: Text( '$count', style: TextStyle( color: isSelected ? Colors.white : primaryColor, fontSize: isTablet ? 12 : 11, fontWeight: FontWeight.bold, ), ), ), ], ], ), ), ), ); }, ), ); }, ); } Widget _buildServicesContent() { return Consumer( builder: (context, servicesController, child) { if (servicesController.isLoading) { return _buildLoadingState(); } if (servicesController.error != null) { return _buildErrorState(servicesController); } final services = servicesController.filteredServices; if (services.isEmpty) { return _buildEmptyState(); } return _buildBeautifulServicesGrid(services); }, ); } Widget _buildBeautifulServicesGrid(List services) { final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; final isLandscape = screenWidth > screenHeight; int crossAxisCount = 2; double childAspectRatio = 0.75; double spacing = 16; if (screenWidth > 1200) { crossAxisCount = 5; childAspectRatio = 0.85; spacing = 24; } else if (screenWidth > 900 && isLandscape) { crossAxisCount = 4; childAspectRatio = 0.82; spacing = 20; } else if (screenWidth > 800) { crossAxisCount = 3; childAspectRatio = 0.85; spacing = 20; } else if (screenWidth > 600) { crossAxisCount = 3; childAspectRatio = 0.8; spacing = 18; } else if (screenWidth > 400) { crossAxisCount = 2; childAspectRatio = 0.8; spacing = 16; } final sortedServices = [...services]; final servicesController = Provider.of( context, listen: false, ); if (servicesController.selectedSector == 'Tous') { sortedServices.sort((a, b) { if (a.secteurActivite == 'Service Public' && b.secteurActivite != 'Service Public') { return -1; } if (a.secteurActivite != 'Service Public' && b.secteurActivite == 'Service Public') { return 1; } return a.name.compareTo(b.name); }); } return Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: GridView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, crossAxisSpacing: spacing, mainAxisSpacing: spacing, childAspectRatio: childAspectRatio, ), itemCount: sortedServices.length, itemBuilder: (context, index) { final service = sortedServices[index]; return _buildGorgeousServiceCard(service); }, ), ); } // Les autres méthodes restent identiques Widget _buildGorgeousServiceCard(WortisService service) { return LayoutBuilder( builder: (context, constraints) { final cardWidth = constraints.maxWidth; final isTablet = cardWidth > 200; final isVerySmall = cardWidth < 140; final isTiny = cardWidth < 120; // NOUVEAU : pour très petits écrans // Tailles adaptatives final titleSize = isTiny ? 11.0 : (isVerySmall ? 12.0 : (isTablet ? 18.0 : 14.0)); final sectorSize = isTiny ? 8.0 : (isVerySmall ? 9.0 : (isTablet ? 12.0 : 9.0)); final buttonSize = isTiny ? 10.0 : (isVerySmall ? 11.0 : (isTablet ? 14.0 : 11.0)); final statusSize = isTiny ? 8.0 : (isVerySmall ? 9.0 : (isTablet ? 9.0 : 9.0)); final iconSize = isTiny ? 8.0 : (isVerySmall ? 10.0 : (isTablet ? 14.0 : 10.0)); final buttonIconSize = isTiny ? 12.0 : (isVerySmall ? 14.0 : (isTablet ? 15.0 : 16.0)); return Hero( tag: service.name, child: InkWell( borderRadius: BorderRadius.circular(isTablet ? 28 : 25), onTap: () { if (service.status) { Navigator.of(context).push( MaterialPageRoute( builder: (context) => FormService(serviceName: service.name), ), ); } else { _showServiceUnavailableDialog(service); } }, child: Container( decoration: BoxDecoration( color: surfaceColor, borderRadius: BorderRadius.circular(isTablet ? 28 : 25), boxShadow: [ BoxShadow( color: service.status ? service.sectorColor.withOpacity(0.15) : cardShadowColor, blurRadius: 20, offset: const Offset(0, 10), ), ], border: Border.all( color: service.status ? service.sectorColor.withOpacity(0.2) : Colors.grey.withOpacity(0.1), width: 1.5, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // === IMAGE/BANNIÈRE === Flexible( // CHANGÉ de Container à Flexible flex: 3, // AJOUTÉ child: Container( width: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: service.status ? [ service.sectorColor.withOpacity(0.15), service.sectorColor.withOpacity(0.05), ] : [ Colors.grey.withOpacity(0.15), Colors.grey.withOpacity(0.05), ], ), borderRadius: BorderRadius.only( topLeft: Radius.circular(isTablet ? 26 : 23), topRight: Radius.circular(isTablet ? 26 : 23), ), ), child: Stack( fit: StackFit.expand, // AJOUTÉ children: [ ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(isTablet ? 26 : 23), topRight: Radius.circular(isTablet ? 26 : 23), ), child: service.banner != null && service.banner!.isNotEmpty ? Image.network( service.banner!, fit: BoxFit.cover, errorBuilder: ( context, error, stackTrace, ) { return _buildIconFallback( service, isTablet, isVerySmall, ); }, loadingBuilder: ( context, child, loadingProgress, ) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( strokeWidth: 2, color: service.sectorColor, value: loadingProgress .expectedTotalBytes != null ? loadingProgress .cumulativeBytesLoaded / loadingProgress .expectedTotalBytes! : null, ), ); }, ) : _buildIconFallback( service, isTablet, isVerySmall, ), ), // Badge de statut Positioned( top: isTablet ? 12 : (isTiny ? 6 : 8), right: isTablet ? 12 : (isTiny ? 6 : 8), child: Container( padding: EdgeInsets.symmetric( horizontal: isTablet ? 12 : (isTiny ? 4 : (isVerySmall ? 6 : 8)), vertical: isTablet ? 6 : (isTiny ? 2 : (isVerySmall ? 3 : 4)), ), decoration: BoxDecoration( color: (service.status ? successColor : Colors.orange) .withOpacity(0.9), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 4, offset: Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: isTablet ? 8 : (isTiny ? 4 : 6), height: isTablet ? 8 : (isTiny ? 4 : 6), decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), ), if (!isTiny && !isVerySmall) ...[ SizedBox(width: isTablet ? 6 : 4), Text( service.status ? 'Actif' : 'Inactif', style: TextStyle( color: Colors.white, fontSize: statusSize, fontWeight: FontWeight.w600, ), ), ], ], ), ), ), ], ), ), ), // === CONTENU TEXTUEL === Flexible( // CHANGÉ de Expanded à Flexible flex: 4, // AJOUTÉ child: Padding( padding: EdgeInsets.all( isTablet ? 16 : (isTiny ? 8 : (isVerySmall ? 10 : 12)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, // AJOUTÉ children: [ // Nom du service Flexible( // CHANGÉ child: Text( service.name, style: TextStyle( fontSize: titleSize, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.1, // AJOUTÉ ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), SizedBox(height: isTablet ? 8 : (isTiny ? 4 : 6)), // Secteur d'activité (masqué sur très petits écrans) if (!isTiny) Container( padding: EdgeInsets.symmetric( horizontal: isTablet ? 12 : (isVerySmall ? 6 : 8), vertical: isTablet ? 6 : (isVerySmall ? 3 : 4), ), decoration: BoxDecoration( color: service.sectorColor.withOpacity(0.1), borderRadius: BorderRadius.circular(10), border: Border.all( color: service.sectorColor.withOpacity(0.3), width: 0.5, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.business_center_rounded, color: service.sectorColor, size: iconSize, ), SizedBox(width: isTablet ? 6 : 4), Flexible( child: Text( service.secteurActivite, style: TextStyle( fontSize: sectorSize, color: service.sectorColor, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), Spacer(), // AJOUTÉ pour pousser le bouton en bas // Bouton d'action Container( width: double.infinity, padding: EdgeInsets.symmetric( vertical: isTablet ? 12 : (isTiny ? 6 : (isVerySmall ? 8 : 10)), ), decoration: BoxDecoration( gradient: service.status ? LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ service.sectorColor.withOpacity(0.8), service.sectorColor, ], ) : null, color: service.status ? null : Colors.grey.withOpacity(0.15), borderRadius: BorderRadius.circular(12), border: Border.all( color: service.status ? Colors.transparent : Colors.grey.withOpacity(0.2), width: 1, ), boxShadow: service.status ? [ BoxShadow( color: service.sectorColor .withOpacity(0.25), blurRadius: 6, offset: const Offset(0, 3), ), ] : [], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Icon( service.status ? Icons.arrow_forward_rounded : Icons.schedule_rounded, color: service.status ? Colors.white : Colors.grey[600], size: buttonIconSize, ), if (!isTiny) ...[ SizedBox(width: isTablet ? 6 : 4), Flexible( // AJOUTÉ child: Text( service.status ? 'Accéder' : 'Indisponible', style: TextStyle( color: service.status ? Colors.white : Colors.red[800], fontSize: buttonSize, fontWeight: FontWeight.w600, letterSpacing: 0.3, ), overflow: TextOverflow.ellipsis, // AJOUTÉ ), ), ], ], ), ), ], ), ), ), ], ), ), ), ); }, ); } Widget _buildIconFallback( WortisService service, bool isTablet, bool isVerySmall, ) { return Container( width: double.infinity, height: double.infinity, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: service.status ? [ service.sectorColor.withOpacity(0.2), service.sectorColor.withOpacity(0.05), ] : [ Colors.grey.withOpacity(0.2), Colors.grey.withOpacity(0.05), ], ), ), child: Center( child: Icon( service.flutterIcon, color: service.status ? service.sectorColor : Colors.grey, size: isVerySmall ? 32 : (isTablet ? 48 : 40), ), ), ); } Widget _buildLoadingState() { return SizedBox( height: 300, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 60, height: 60, child: CircularProgressIndicator( color: primaryColor, strokeWidth: 4, ), ), SizedBox(height: 20), Text( 'Chargement des services...', style: TextStyle( fontSize: 16, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), ], ), ), ); } Widget _buildErrorState(ServicesController servicesController) { return Container( height: 300, padding: EdgeInsets.all(24), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: Colors.red[400]), SizedBox(height: 16), Text( 'Erreur de chargement', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(height: 8), Text( 'Impossible de charger les services', style: TextStyle(fontSize: 14, color: Colors.grey[600]), textAlign: TextAlign.center, ), SizedBox(height: 24), ElevatedButton.icon( onPressed: () => servicesController.refreshServices(), icon: Icon(Icons.refresh, size: 20), label: Text('Réessayer'), style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), ), ], ), ), ); } Widget _buildEmptyState() { return SizedBox( height: 250, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.search_off, size: 64, color: Colors.grey[400]), SizedBox(height: 16), Text( 'Aucun service trouvé', style: TextStyle( fontSize: 18, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), SizedBox(height: 8), Text( 'Essayez avec d\'autres mots-clés', style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ], ), ), ); } void _showServiceUnavailableDialog(WortisService service) { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), title: Row( children: [ Icon(Icons.warning, color: Colors.orange), SizedBox(width: 8), Expanded( child: Text( 'Service indisponible', style: TextStyle(fontSize: 16), ), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Le service "${service.name}" est actuellement indisponible.', ), SizedBox(height: 12), Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.orange.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Secteur: ${service.secteurActivite}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, ), ), Text( 'Type: ${service.typeService}', style: TextStyle(fontSize: 12), ), ], ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Compris'), ), ElevatedButton( onPressed: () { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon(Icons.notifications_active, color: Colors.white), SizedBox(width: 8), Expanded( child: Text( 'Vous serez notifié quand ${service.name} sera disponible', ), ), ], ), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( 'Me notifier', style: TextStyle(color: Colors.white), ), ), ], ), ); } }