import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:geosector_app/core/theme/app_theme.dart'; import 'package:geosector_app/app.dart'; import 'package:geosector_app/core/services/app_info_service.dart'; import 'package:geosector_app/core/services/current_amicale_service.dart'; import 'package:geosector_app/presentation/widgets/connectivity_indicator.dart'; import 'package:geosector_app/presentation/widgets/user_form_dialog.dart'; import 'package:geosector_app/core/utils/api_exception.dart'; import 'package:geosector_app/presentation/widgets/loading_spin_overlay.dart'; import 'package:geosector_app/presentation/widgets/result_dialog.dart'; import 'package:go_router/go_router.dart'; /// AppBar personnalisée pour les tableaux de bord class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget { /// Le titre principal de l'AppBar (généralement le nom de l'application) final String title; /// Le titre de la page actuelle (optionnel) final String? pageTitle; /// Indique si l'utilisateur est un administrateur final bool isAdmin; /// Callback appelé lorsque le bouton de déconnexion est pressé final VoidCallback? onLogoutPressed; const DashboardAppBar({ super.key, required this.title, this.pageTitle, this.isAdmin = false, this.onLogoutPressed, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); // Vérifier si le logo de l'amicale est présent pour ajuster la largeur du leading final amicale = CurrentAmicaleService.instance.currentAmicale; final hasAmicaleLogo = amicale?.logoBase64 != null && amicale!.logoBase64!.isNotEmpty; return Column( mainAxisSize: MainAxisSize.min, children: [ AppBar( title: _buildTitle(context), backgroundColor: theme.colorScheme.primary, foregroundColor: theme.colorScheme.onPrimary, elevation: 4, leading: _buildLogo(), // Ajuster la largeur du leading si le logo de l'amicale est présent leadingWidth: hasAmicaleLogo ? 110 : 56, // 56 par défaut, 110 pour 2 logos + espacement actions: _buildActions(context), ), // Bordure colorée selon le rôle Container( height: 3, color: isAdmin ? Colors.red : Colors.green, ), ], ); } /// Construction du logo dans l'AppBar Widget _buildLogo() { final amicale = CurrentAmicaleService.instance.currentAmicale; final logoBase64 = amicale?.logoBase64; return Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( 'assets/images/logo-geosector-1024.png', width: 40, height: 40, ), // Afficher le logo de l'amicale s'il est disponible if (logoBase64 != null && logoBase64.isNotEmpty) ...[ const SizedBox(width: 8), _buildAmicaleLogo(logoBase64), ], ], ), ); } /// Construction du logo de l'amicale à partir du Base64 Widget _buildAmicaleLogo(String logoBase64) { try { // Le logoBase64 peut être au format "data:image/png;base64,..." ou juste la chaîne base64 String base64String = logoBase64; if (logoBase64.contains('base64,')) { base64String = logoBase64.split('base64,').last; } final decodedBytes = base64Decode(base64String); return Container( width: 40, height: 40, decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), color: Colors.white, ), child: ClipRRect( borderRadius: BorderRadius.circular(4), child: Image.memory( decodedBytes, width: 40, height: 40, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { // En cas d'erreur de décodage, ne rien afficher debugPrint('Erreur lors du chargement du logo amicale: $error'); return const SizedBox.shrink(); }, ), ), ); } catch (e) { // En cas d'erreur, ne rien afficher debugPrint('Erreur lors du décodage du logo amicale: $e'); return const SizedBox.shrink(); } } /// Construction des actions de l'AppBar List _buildActions(BuildContext context) { final theme = Theme.of(context); final List actions = []; // Ajouter l'indicateur de connectivité actions.add( const Padding( padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0), child: ConnectivityIndicator( showErrorMessage: false, showConnectionType: true, ), ), ); actions.add(const SizedBox(width: 8)); // Ajouter la version de l'application actions.add( Text( "v${AppInfoService.version}", style: TextStyle( fontSize: AppTheme.r(context, 12), color: Colors.white70, ), ), ); actions.add(const SizedBox(width: 8)); // Ajouter le sélecteur de thème avec confirmation (désactivé temporairement) // TODO: Réactiver quand le thème sombre sera corrigé // actions.add( // _buildThemeSwitcherWithConfirmation(context), // ); // // actions.add(const SizedBox(width: 8)); // Ajouter le bouton "Mon compte" actions.add( IconButton( icon: const Icon(Icons.person), tooltip: 'Mon compte', onPressed: () { // Afficher la boîte de dialogue UserForm avec l'utilisateur actuel final user = userRepository.currentUser; if (user != null) { showDialog( context: context, builder: (context) => UserFormDialog( title: 'Mon compte', user: user, readOnly: false, showRoleSelector: false, onSubmit: (updatedUser, {String? password}) async { // Afficher le loading final overlay = LoadingSpinOverlayUtils.show( context: context, message: 'Mise à jour du profil...', ); try { // Sauvegarder les modifications de l'utilisateur // Note: password est ignoré ici car l'utilisateur normal ne peut pas changer son mot de passe await userRepository.updateUser(updatedUser); // Masquer le loading LoadingSpinOverlayUtils.hideSpecific(overlay); if (context.mounted) { // Afficher le résultat de succès await ResultDialog.show( context: context, success: true, message: 'Profil mis à jour', ); if (context.mounted) { Navigator.of(context).pop(); } } } catch (e) { debugPrint('❌ Erreur mise à jour de votre profil: $e'); // Masquer le loading LoadingSpinOverlayUtils.hideSpecific(overlay); if (context.mounted) { // Afficher l'erreur await ResultDialog.show( context: context, success: false, message: ApiException.fromError(e).message, ); } } }, ), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Erreur: Utilisateur non trouvé'), backgroundColor: theme.colorScheme.error, ), ); } }, ), ); actions.add(const SizedBox(width: 8)); // Ajouter le bouton de déconnexion actions.add( IconButton( icon: const Icon(Icons.logout), tooltip: 'Déconnexion', style: IconButton.styleFrom( foregroundColor: Colors.red, ), onPressed: onLogoutPressed ?? () { showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Déconnexion'), content: const Text('Voulez-vous vraiment vous déconnecter ?'), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Annuler'), ), TextButton( onPressed: () async { // Fermer la dialog d'abord Navigator.of(dialogContext).pop(); // Utiliser le context original de l'AppBar pour la navigation final success = await userRepository.logout(context); // Vérification supplémentaire et navigation forcée si nécessaire if (success && context.mounted) { // Attendre un court instant pour que les changements d'état se propagent await Future.delayed( const Duration(milliseconds: 100)); // Navigation vers splash avec paramètres pour redirection automatique if (context.mounted) { final loginType = isAdmin ? 'admin' : 'user'; context.go('/?action=login&type=$loginType'); } } }, child: const Text('Déconnexion'), ), ], ), ); }, ), ); actions.add(const SizedBox(width: 8)); // Espacement à droite return actions; } /// Construction du titre de l'AppBar Widget _buildTitle(BuildContext context) { // Titre vide pour économiser de l'espace sur mobile return const Text(''); } @override Size get preferredSize => const Size.fromHeight(kToolbarHeight + 3); // +3 pour la bordure }