import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/presentation/widgets/help_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({ super.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, }); @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), ) : null, body: isDesktop ? _buildDesktopLayout() : _buildMobileLayout(), bottomNavigationBar: (isDesktop) ? null : _buildBottomNavigationBar(), ); } /// Construction du layout pour les écrans de bureau (web) Widget _buildDesktopLayout() { return Row( children: [ _buildSidebar(), Expanded( child: Container( color: Colors .transparent, // Fond transparent pour voir l'AdminBackground child: widget.body, ), ), ], ); } /// Construction du layout pour les écrans mobiles Widget _buildMobileLayout() { return Container( color: Colors.transparent, // Fond transparent pour voir l'AdminBackground child: widget.body, ); } /// Construction de la barre de navigation inférieure pour mobile Widget _buildBottomNavigationBar() { final theme = Theme.of(context); // Définir les couleurs selon le rôle (admin = rouge, user = vert) final Color selectedColor = widget.isAdmin ? Colors.red : Colors.green; return Theme( data: theme.copyWith( colorScheme: theme.colorScheme.copyWith( onSecondaryContainer: selectedColor, // Couleur de l'icône sélectionnée secondaryContainer: selectedColor.withOpacity(0.15), // Couleur de fond de l'indicateur ), ), child: NavigationBar( selectedIndex: widget.selectedIndex, onDestinationSelected: widget.onDestinationSelected, backgroundColor: theme.colorScheme.surface, elevation: 8, labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, 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 (widgets personnalisés) if (widget.sidebarBottomItems != null && !_isSidebarMinimized) ...widget.sidebarBottomItems!, // Options administrateur - uniquement pour les administrateurs et en version web if (widget.isAdmin && MediaQuery.of(context).size.width > 900) ...[], // Option "Aide" - toujours visible en bas _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); }, ), const SizedBox(height: 16), ], ), ), ); } /// 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).icon : null; // Définir les couleurs selon le rôle (admin = rouge, user = vert) final Color selectedColor = widget.isAdmin ? Colors.red : Colors.green; final Color unselectedColor = theme.colorScheme.onSurface.withOpacity(0.6); // 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 ? selectedColor.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: iconData != null ? Icon( iconData, color: isSelected ? selectedColor : unselectedColor, size: 24, ) : icon, ), ), ), ); } else { // Version normale avec texte et icône return ListTile( leading: iconData != null ? Icon( iconData, color: isSelected ? selectedColor : unselectedColor, ) : icon, title: Text( displayTitle, style: TextStyle( color: isSelected ? selectedColor : theme.colorScheme.onSurface, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), tileColor: isSelected ? selectedColor.withOpacity(0.1) : null, onTap: () { widget.onDestinationSelected(index); }, ); } } } /// 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, required this.onTap, required this.isSidebarMinimized, this.subtitle, this.trailing, }); @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, ); } } }