import 'package:flutter/material.dart'; import '../../theme/app_theme.dart'; /// Layout de page unifié pour toutes les features de l'application /// /// Fournit une structure cohérente avec : /// - AppBar standardisée avec actions personnalisables /// - Body avec padding et scroll automatique /// - FloatingActionButton optionnel /// - Gestion des états de chargement et d'erreur class UnifiedPageLayout extends StatelessWidget { /// Titre de la page affiché dans l'AppBar final String title; /// Sous-titre optionnel affiché sous le titre final String? subtitle; /// Icône principale de la page final IconData? icon; /// Couleur de l'icône (par défaut : primaryColor) final Color? iconColor; /// Actions personnalisées dans l'AppBar final List? actions; /// Contenu principal de la page final Widget body; /// FloatingActionButton optionnel final Widget? floatingActionButton; /// Position du FloatingActionButton final FloatingActionButtonLocation? floatingActionButtonLocation; /// Indique si la page est en cours de chargement final bool isLoading; /// Message d'erreur à afficher final String? errorMessage; /// Callback pour rafraîchir la page final VoidCallback? onRefresh; /// Padding personnalisé pour le body (par défaut : 16.0) final EdgeInsetsGeometry? padding; /// Indique si le body doit être scrollable (par défaut : true) final bool scrollable; /// Couleur de fond personnalisée final Color? backgroundColor; /// Indique si l'AppBar doit être affichée (par défaut : true) final bool showAppBar; const UnifiedPageLayout({ super.key, required this.title, required this.body, this.subtitle, this.icon, this.iconColor, this.actions, this.floatingActionButton, this.floatingActionButtonLocation, this.isLoading = false, this.errorMessage, this.onRefresh, this.padding, this.scrollable = true, this.backgroundColor, this.showAppBar = true, }); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: backgroundColor ?? AppTheme.backgroundLight, appBar: showAppBar ? _buildAppBar(context) : null, body: _buildBody(context), floatingActionButton: floatingActionButton, floatingActionButtonLocation: floatingActionButtonLocation, ); } PreferredSizeWidget _buildAppBar(BuildContext context) { return AppBar( backgroundColor: Colors.white, elevation: 0, scrolledUnderElevation: 1, surfaceTintColor: Colors.white, title: Row( children: [ if (icon != null) ...[ Icon( icon, color: iconColor ?? AppTheme.primaryColor, size: 24, ), const SizedBox(width: 12), ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), if (subtitle != null) Text( subtitle!, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w400, color: AppTheme.textSecondary, ), ), ], ), ), ], ), actions: actions, ); } Widget _buildBody(BuildContext context) { Widget content = body; // Gestion des états d'erreur if (errorMessage != null) { content = _buildErrorState(context); } // Gestion de l'état de chargement else if (isLoading) { content = _buildLoadingState(); } // Application du padding if (padding != null || (padding == null && scrollable)) { content = Padding( padding: padding ?? const EdgeInsets.all(16.0), child: content, ); } // Gestion du scroll if (scrollable && errorMessage == null && !isLoading) { if (onRefresh != null) { content = RefreshIndicator( onRefresh: () async => onRefresh!(), child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: content, ), ); } else { content = SingleChildScrollView(child: content); } } return SafeArea(child: content); } Widget _buildLoadingState() { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(AppTheme.primaryColor), ), SizedBox(height: 16), Text( 'Chargement...', style: TextStyle( color: AppTheme.textSecondary, fontSize: 16, ), ), ], ), ); } Widget _buildErrorState(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 64, color: AppTheme.errorColor, ), const SizedBox(height: 16), Text( 'Une erreur est survenue', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: AppTheme.textPrimary, ), ), const SizedBox(height: 8), Text( errorMessage!, textAlign: TextAlign.center, style: const TextStyle( fontSize: 14, color: AppTheme.textSecondary, ), ), const SizedBox(height: 24), if (onRefresh != null) ElevatedButton.icon( onPressed: onRefresh, icon: const Icon(Icons.refresh), label: const Text('Réessayer'), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.primaryColor, foregroundColor: Colors.white, ), ), ], ), ), ); } }