import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:flutter/material.dart'; import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:go_router/go_router.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/presentation/widgets/help_dialog.dart'; import 'package:geosector_app/presentation/widgets/profile_dialog.dart'; /// Widget qui fournit une navigation responsive pour l'application. /// Affiche une sidebar en mode desktop et une bottomBar en mode mobile. class ResponsiveNavigation extends StatefulWidget { /// Le contenu principal à afficher final Widget body; /// Le titre de la page final String title; /// L'index de la page sélectionnée final int selectedIndex; /// Callback appelé lorsqu'un élément de navigation est sélectionné final Function(int) onDestinationSelected; /// Liste des destinations de navigation final List destinations; /// Actions supplémentaires à afficher dans l'AppBar final List? additionalActions; /// Indique si le bouton "Nouveau passage" doit être affiché final bool showNewPassageButton; /// Callback appelé lorsque le bouton "Nouveau passage" est pressé final VoidCallback? onNewPassagePressed; /// Clé de la boîte Hive pour sauvegarder les paramètres final String settingsBoxKey; /// Clé pour sauvegarder l'état de la sidebar final String sidebarStateKey; /// Widgets à afficher en bas de la sidebar final List? sidebarBottomItems; /// Indique si l'utilisateur est un administrateur final bool isAdmin; /// Indique si l'AppBar doit être affiché final bool showAppBar; const ResponsiveNavigation({ Key? key, required this.body, required this.title, required this.selectedIndex, required this.onDestinationSelected, required this.destinations, this.additionalActions, this.showNewPassageButton = true, this.onNewPassagePressed, this.settingsBoxKey = AppKeys.settingsBoxName, this.sidebarStateKey = 'isSidebarMinimized', this.sidebarBottomItems, this.isAdmin = false, this.showAppBar = true, }) : super(key: key); @override State createState() => _ResponsiveNavigationState(); } class _ResponsiveNavigationState extends State { /// État de la barre latérale (minimisée ou non) bool _isSidebarMinimized = false; /// Référence à la boîte Hive pour les paramètres late Box _settingsBox; @override void initState() { super.initState(); _initSettings(); } /// Initialiser la boîte de paramètres et charger les préférences Future _initSettings() async { try { // Ouvrir la boîte de paramètres si elle n'est pas déjà ouverte if (!Hive.isBoxOpen(widget.settingsBoxKey)) { _settingsBox = await Hive.openBox(widget.settingsBoxKey); } else { _settingsBox = Hive.box(widget.settingsBoxKey); } // Charger l'état de la barre latérale final sidebarState = _settingsBox.get(widget.sidebarStateKey); if (sidebarState != null && sidebarState is bool) { setState(() { _isSidebarMinimized = sidebarState; }); } } catch (e) { debugPrint('Erreur lors du chargement des paramètres: $e'); } } /// Sauvegarder l'état de la barre latérale void _saveSettings() { try { // Sauvegarder l'état de la barre latérale _settingsBox.put(widget.sidebarStateKey, _isSidebarMinimized); } catch (e) { debugPrint('Erreur lors de la sauvegarde des paramètres: $e'); } } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final isDesktop = size.width > 900; return Scaffold( appBar: widget.showAppBar ? AppBar( title: Text(widget.title), actions: _buildAppBarActions(context), ) : null, body: isDesktop ? _buildDesktopLayout() : _buildMobileLayout(), bottomNavigationBar: (isDesktop) ? null : _buildBottomNavigationBar(), ); } /// Construction du layout pour les écrans de bureau (web) Widget _buildDesktopLayout() { // Utiliser une couleur de fond différente selon le type d'utilisateur final backgroundColor = widget.isAdmin ? const Color(0xFFFFEBEE) // Fond rouge clair pour l'interface admin : const Color( 0xFFE8F5E9); // Fond vert clair pour l'interface utilisateur return Row( children: [ _buildSidebar(), Expanded( child: Container( color: backgroundColor, child: widget.body, ), ), ], ); } /// Construction du layout pour les écrans mobiles Widget _buildMobileLayout() { // Utiliser une couleur de fond différente selon le type d'utilisateur final backgroundColor = widget.isAdmin ? const Color(0xFFFFEBEE) // Fond rouge clair pour l'interface admin : const Color( 0xFFE8F5E9); // Fond vert clair pour l'interface utilisateur return Container( color: backgroundColor, child: widget.body, ); } /// Construction des actions de l'AppBar List _buildAppBarActions(BuildContext context) { List actions = []; // Ajouter les actions supplémentaires si elles existent if (widget.additionalActions != null && widget.additionalActions!.isNotEmpty) { actions.addAll(widget.additionalActions!); } else if (widget.showNewPassageButton && widget.selectedIndex == 0) { // Ajouter le bouton "Nouveau passage" en haut à droite pour la page d'accueil actions.add( TextButton.icon( icon: const Icon(Icons.add_location_alt, color: Colors.white), label: const Text('Nouveau passage', style: TextStyle(color: Colors.white)), onPressed: widget.onNewPassagePressed ?? () { // Fonction par défaut si onNewPassagePressed n'est pas fourni _showPassageForm(context); }, style: TextButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.secondary, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), ), ); actions.add(const SizedBox(width: 16)); // Espacement à droite } return actions; } /// Construction de la barre de navigation inférieure pour mobile Widget _buildBottomNavigationBar() { final theme = Theme.of(context); return NavigationBar( selectedIndex: widget.selectedIndex, onDestinationSelected: widget.onDestinationSelected, backgroundColor: theme.colorScheme.surface, elevation: 8, destinations: widget.destinations, ); } /// Obtenir le nom complet de l'utilisateur (prénom + nom) String _getFullUserName(BuildContext context) { // Utiliser l'instance globale définie dans app.dart final user = userRepository.currentUser; if (user == null) return 'Utilisateur'; String fullName = ''; // Ajouter le prénom si disponible if (user.firstName != null && user.firstName!.isNotEmpty) { fullName += user.firstName!; } // Ajouter le nom if (user.name != null && user.name!.isNotEmpty) { // Ajouter un espace si le prénom est déjà présent if (fullName.isNotEmpty) { fullName += ' '; } fullName += user.name!; } // Si aucun nom n'a été trouvé, utiliser 'Utilisateur' par défaut return fullName.isEmpty ? 'Utilisateur' : fullName; } /// Obtenir les initiales du prénom et du nom de l'utilisateur String _getUserInitials(BuildContext context) { // Utiliser l'instance globale définie dans app.dart final user = userRepository.currentUser; if (user == null) return 'U'; String initials = ''; // Ajouter l'initiale du prénom si disponible if (user.firstName != null && user.firstName!.isNotEmpty) { initials += user.firstName!.substring(0, 1).toUpperCase(); } // Ajouter l'initiale du nom if (user.name != null && user.name!.isNotEmpty) { initials += user.name!.substring(0, 1).toUpperCase(); } // Si aucune initiale n'a été trouvée, utiliser 'U' par défaut return initials.isEmpty ? 'U' : initials; } /// Afficher le sectName entre parenthèses s'il existe Widget _buildSectNameText(BuildContext context) { final theme = Theme.of(context); // Utiliser l'instance globale définie dans app.dart final user = userRepository.currentUser; // Si l'utilisateur n'a pas de sectName ou s'il est vide, retourner un widget vide if (user == null || user.sectName == null || user.sectName!.isEmpty) { return const SizedBox.shrink(); } // Sinon, afficher le sectName entre parenthèses return Text( '(${user.sectName})', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ); } /// Construction de la barre latérale pour la version web Widget _buildSidebar() { final theme = Theme.of(context); return Card( margin: EdgeInsets.zero, elevation: 4, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.zero, ), child: Container( width: _isSidebarMinimized ? 70 : 250, color: theme.colorScheme.surface, child: Column( children: [ // Bouton pour minimiser/maximiser la barre latérale Align( alignment: _isSidebarMinimized ? Alignment.center : Alignment.centerRight, child: Padding( padding: EdgeInsets.only(top: 8, right: _isSidebarMinimized ? 0 : 8), child: IconButton( icon: Icon(_isSidebarMinimized ? Icons.chevron_right : Icons.chevron_left), onPressed: () { setState(() { _isSidebarMinimized = !_isSidebarMinimized; _saveSettings(); // Sauvegarder l'état de la barre latérale }); }, tooltip: _isSidebarMinimized ? 'Développer' : 'Réduire', ), ), ), const SizedBox(height: 8), if (!_isSidebarMinimized) CircleAvatar( radius: 40, backgroundColor: theme.colorScheme.primary, child: Text( _getUserInitials(context), style: TextStyle( fontSize: 28, color: theme.colorScheme.onPrimary, ), ), ), const SizedBox(height: 8), if (!_isSidebarMinimized) ...[ Text( _getFullUserName(context), style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), // Afficher le sectName entre parenthèses s'il existe _buildSectNameText(context), Text( userRepository.currentUser?.email ?? '', style: theme.textTheme.bodySmall, ), const SizedBox(height: 24), ] else const SizedBox(height: 8), const Divider(), // Éléments de navigation for (int i = 0; i < widget.destinations.length; i++) _buildNavItem( i, widget.destinations[i].label, widget.destinations[i].icon), const Spacer(), const Divider(), // Éléments du bas de la sidebar if (widget.sidebarBottomItems != null && !_isSidebarMinimized) ...widget.sidebarBottomItems!, // Éléments par défaut du bas de la sidebar if (!_isSidebarMinimized) Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text( 'Paramètres', style: theme.textTheme.titleSmall?.copyWith( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), ), _SettingsItem( icon: Icons.person, title: 'Mon compte', subtitle: null, isSidebarMinimized: _isSidebarMinimized, onTap: () { // Afficher la boîte de dialogue de profil avec l'ID de l'utilisateur actuel // Utiliser l'instance globale définie dans app.dart final user = userRepository.currentUser; if (user != null && user.id != null) { // Convertir l'ID en chaîne de caractères si nécessaire ProfileDialog.show(context, user.id!.toString()); } else { // Afficher un message d'erreur si l'utilisateur n'est pas trouvé ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Erreur: Utilisateur non trouvé'), backgroundColor: Theme.of(context).colorScheme.error, ), ); } }, ), if (widget.isAdmin && userRepository.currentUser?.role == 2) _SettingsItem( icon: Icons.people, title: 'Amicale & membres', isSidebarMinimized: _isSidebarMinimized, onTap: () { // Navigation vers le tableau de bord admin avec sélection de l'onglet "Amicale et membres" context.go('/admin'); // Sélectionner l'onglet "Amicale et membres" (index 5) // Nous devons sauvegarder cet index dans les paramètres pour que le tableau de bord // puisse le récupérer et sélectionner le bon onglet final settingsBox = Hive.box(AppKeys.settingsBoxName); settingsBox.put('adminSelectedPageIndex', 5); }, ), const SizedBox(height: 16), _SettingsItem( icon: Icons.help_outline, title: 'Aide', isSidebarMinimized: _isSidebarMinimized, onTap: () { // Afficher la boîte de dialogue d'aide avec le titre de la page courante HelpDialog.show(context, widget.title); }, ), ], ), ), ); } /// Construction d'un élément de navigation pour la barre latérale Widget _buildNavItem(int index, String title, Widget icon) { final theme = Theme.of(context); final isSelected = widget.selectedIndex == index; final IconData? iconData = (icon is Icon) ? (icon as Icon).icon : null; // Remplacer certains titres si l'interface est de type "user" String displayTitle = title; if (!widget.isAdmin) { if (title == "Accueil") { displayTitle = "Tableau de bord"; } else if (title == "Stats") { displayTitle = "Statistiques"; } } if (_isSidebarMinimized) { // Version minimisée - afficher uniquement l'icône return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Tooltip( message: displayTitle, child: InkWell( onTap: () { widget.onDestinationSelected(index); }, child: Container( width: 50, height: 50, decoration: BoxDecoration( color: isSelected ? theme.colorScheme.primary.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: iconData != null ? Icon( iconData, color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurface.withOpacity(0.6), size: 24, ) : icon, ), ), ), ); } else { // Version normale avec texte et icône return ListTile( leading: iconData != null ? Icon( iconData, color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurface.withOpacity(0.6), ) : icon, title: Text( displayTitle, style: TextStyle( color: isSelected ? theme.colorScheme.primary : theme.colorScheme.onSurface, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), tileColor: isSelected ? theme.colorScheme.primary.withOpacity(0.1) : null, onTap: () { widget.onDestinationSelected(index); }, ); } } /// Affiche le formulaire de passage void _showPassageForm(BuildContext context) { final theme = Theme.of(context); showDialog( context: context, builder: (context) => AlertDialog( title: Text( 'Nouveau passage', style: TextStyle( color: theme.colorScheme.primary, fontWeight: FontWeight.bold, ), ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( decoration: InputDecoration( labelText: 'Adresse', prefixIcon: const Icon(Icons.location_on), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), ), const SizedBox(height: 16), DropdownButtonFormField( decoration: InputDecoration( labelText: 'Type de passage', prefixIcon: const Icon(Icons.category), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), items: const [ DropdownMenuItem( value: 1, child: Text('Effectué'), ), DropdownMenuItem( value: 2, child: Text('À finaliser'), ), DropdownMenuItem( value: 3, child: Text('Refusé'), ), DropdownMenuItem( value: 4, child: Text('Don'), ), DropdownMenuItem( value: 5, child: Text('Lot'), ), DropdownMenuItem( value: 6, child: Text('Maison vide'), ), ], onChanged: (value) {}, ), const SizedBox(height: 16), TextField( decoration: InputDecoration( labelText: 'Commentaire', prefixIcon: const Icon(Icons.comment), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), maxLines: 3, ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text( 'Annuler', style: TextStyle( color: theme.colorScheme.error, ), ), ), ElevatedButton( onPressed: () { // Enregistrer le passage Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Passage enregistré avec succès'), backgroundColor: theme.colorScheme.primary, ), ); }, style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: theme.colorScheme.onPrimary, ), child: const Text('Enregistrer'), ), ], ), ); } } /// Widget pour les éléments de paramètres class _SettingsItem extends StatelessWidget { final IconData icon; final String title; final String? subtitle; final Widget? trailing; final VoidCallback onTap; final bool isSidebarMinimized; const _SettingsItem({ required this.icon, required this.title, this.subtitle, this.trailing, required this.onTap, required this.isSidebarMinimized, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); if (isSidebarMinimized) { // Version minimisée - afficher uniquement l'icône return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Tooltip( message: title, child: InkWell( onTap: onTap, child: Container( width: 50, height: 50, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: Icon( icon, color: theme.colorScheme.primary, size: 24, ), ), ), ), ); } else { // Version normale avec texte et icône return ListTile( leading: Icon( icon, color: theme.colorScheme.primary, ), title: Text(title), subtitle: subtitle != null ? Text(subtitle!) : null, trailing: trailing, onTap: onTap, ); } } }