482 lines
15 KiB
Dart
482 lines
15 KiB
Dart
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
|
import 'package:flutter/material.dart';
|
|
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/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<NavigationDestination> destinations;
|
|
|
|
/// Actions supplémentaires à afficher dans l'AppBar
|
|
final List<Widget>? 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<Widget>? 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<ResponsiveNavigation> createState() => _ResponsiveNavigationState();
|
|
}
|
|
|
|
class _ResponsiveNavigationState extends State<ResponsiveNavigation> {
|
|
/// É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<void> _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);
|
|
|
|
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 (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 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);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
);
|
|
}
|
|
}
|
|
}
|