Files
geo/app/lib/presentation/widgets/app_scaffold.dart
Pierre 1cdb4ec58c refactor: Simplifier DashboardLayout/AppScaffold (tâche #74)
Centralisation et simplification de l'architecture de navigation :

CRÉATIONS :
- navigation_config.dart : Configuration centralisée de la navigation
  * Toutes les destinations (admin/user)
  * Logique de navigation (index → route)
  * Résolution inverse (route → index)
  * Titres et utilitaires

- backgrounds/dots_painter.dart : Painter de points décoratifs
  * Extrait depuis AppScaffold et AdminScaffold
  * Paramétrable (opacité, densité, seed)
  * Réutilisable

- backgrounds/gradient_background.dart : Fond dégradé
  * Gère les couleurs admin (rouge) / user (vert)
  * Option pour afficher/masquer les points
  * Widget indépendant

SIMPLIFICATIONS :
- app_scaffold.dart : 426 → 192 lignes (-55%)
  * Utilise NavigationConfig au lieu de NavigationHelper
  * Utilise GradientBackground au lieu de code dupliqué
  * Suppression de DotsPainter local

- dashboard_layout.dart : 140 → 77 lignes (-45%)
  * Suppression validations excessives (try/catch, vérifications)
  * Code épuré et plus lisible

SUPPRESSIONS :
- admin_scaffold.dart : Supprimé (207 lignes)
  * Obsolète depuis unification avec AppScaffold
  * Code dupliqué avec AppScaffold
  * AdminNavigationHelper fusionné dans NavigationConfig

RÉSULTATS :
- Avant : 773 lignes (AppScaffold + AdminScaffold + DashboardLayout)
- Après : 623 lignes (tout inclus)
- Réduction nette : -150 lignes (-19%)
- Architecture plus claire et maintenable
- Aucune duplication de code
- Navigation centralisée en un seul endroit

Résout tâche #74 du PLANNING-2026-Q1.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 15:27:54 +01:00

193 lines
6.3 KiB
Dart
Executable File

import 'package:flutter/material.dart';
import 'package:geosector_app/presentation/widgets/dashboard_layout.dart';
import 'package:geosector_app/presentation/widgets/backgrounds/gradient_background.dart';
import 'package:geosector_app/core/config/navigation_config.dart';
import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:geosector_app/app.dart';
/// Scaffold unifié pour toutes les pages (admin et user)
/// Adapte automatiquement son apparence selon le rôle de l'utilisateur
class AppScaffold extends StatelessWidget {
/// Le contenu de la page
final Widget body;
/// L'index de navigation sélectionné
final int selectedIndex;
/// Le titre de la page
final String pageTitle;
/// Callback optionnel pour gérer la navigation personnalisée
final Function(int)? onDestinationSelected;
/// Forcer le mode admin (optionnel, sinon détecte automatiquement)
final bool? forceAdmin;
/// Afficher ou non le fond dégradé avec points (économise des ressources si désactivé)
final bool showBackground;
const AppScaffold({
super.key,
required this.body,
required this.selectedIndex,
required this.pageTitle,
this.onDestinationSelected,
this.forceAdmin,
this.showBackground = true,
});
@override
Widget build(BuildContext context) {
final currentUser = userRepository.getCurrentUser();
final size = MediaQuery.of(context).size;
final isMobile = size.width <= 900;
// Déterminer si l'utilisateur est admin (prend en compte le mode d'affichage)
final userRole = currentUser?.role ?? 1;
final isAdmin = forceAdmin ?? CurrentUserService.instance.shouldShowAdminUI;
debugPrint('🎨 AppScaffold: isAdmin=$isAdmin, displayMode=${CurrentUserService.instance.displayMode}, userRole=$userRole');
// Pour les utilisateurs standards, vérifier les conditions d'accès
if (!isAdmin) {
final hasOperation = userRepository.getCurrentOperation() != null;
final hasSectors = userRepository.getUserSectors().isNotEmpty;
// Si pas d'opération, afficher le message approprié
if (!hasOperation) {
return _buildRestrictedAccess(
context: context,
icon: Icons.warning_outlined,
title: 'Aucune opération assignée',
message: 'Vous n\'avez pas encore été affecté à une opération. '
'Veuillez contacter votre administrateur pour obtenir un accès.',
isAdmin: false,
);
}
// Si pas de secteur, afficher le message approprié
if (!hasSectors) {
return _buildRestrictedAccess(
context: context,
icon: Icons.map_outlined,
title: 'Aucun secteur assigné',
message: 'Vous n\'êtes affecté sur aucun secteur. '
'Contactez votre administrateur pour qu\'il vous en affecte au moins un.',
isAdmin: false,
);
}
}
return Stack(
children: [
// Fond dégradé avec petits points blancs (optionnel)
if (showBackground)
GradientBackground(
isAdmin: isAdmin,
showDots: true,
),
// Contenu de la page avec navigation
DashboardLayout(
key: ValueKey(
'dashboard_layout_${isAdmin ? 'admin' : 'user'}_$selectedIndex'),
title: NavigationConfig.getDashboardTitle(isAdmin),
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected ??
(index) {
NavigationConfig.navigateToIndex(context, index, isAdmin);
},
destinations: NavigationConfig.getDestinations(
isAdmin: isAdmin,
isMobile: isMobile,
),
isAdmin: isAdmin,
body: body,
),
],
);
}
/// Construit l'écran d'accès restreint
Widget _buildRestrictedAccess({
required BuildContext context,
required IconData icon,
required String title,
required String message,
required bool isAdmin,
}) {
final theme = Theme.of(context);
return Stack(
children: [
// Fond dégradé (optionnel)
if (showBackground)
GradientBackground(
isAdmin: isAdmin,
showDots: true,
),
// Message d'accès restreint
DashboardLayout(
title: 'GEOSECTOR',
selectedIndex: 0,
onDestinationSelected: (index) {
// Ne rien faire car l'utilisateur ne peut pas naviguer
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.warning_outlined),
selectedIcon: Icon(Icons.warning),
label: 'Accès restreint',
),
],
isAdmin: false,
body: Center(
child: Container(
padding: const EdgeInsets.all(24),
constraints: const BoxConstraints(maxWidth: 500),
decoration: BoxDecoration(
color: theme.colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 80,
color: theme.colorScheme.error,
),
const SizedBox(height: 24),
Text(
title,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
message,
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
),
),
),
),
],
);
}
}