From 1cdb4ec58c07e44f0784d32b964c8865cccf2abe Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 26 Jan 2026 15:27:54 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20Simplifier=20DashboardLayout/AppSca?= =?UTF-8?q?ffold=20(t=C3=A2che=20#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/lib/core/config/navigation_config.dart | 259 +++++++++++++++++ .../presentation/widgets/admin_scaffold.dart | 207 -------------- .../presentation/widgets/app_scaffold.dart | 265 ++---------------- .../widgets/backgrounds/dots_painter.dart | 46 +++ .../backgrounds/gradient_background.dart | 49 ++++ .../widgets/dashboard_layout.dart | 112 ++------ 6 files changed, 395 insertions(+), 543 deletions(-) create mode 100755 app/lib/core/config/navigation_config.dart delete mode 100644 app/lib/presentation/widgets/admin_scaffold.dart mode change 100644 => 100755 app/lib/presentation/widgets/app_scaffold.dart create mode 100755 app/lib/presentation/widgets/backgrounds/dots_painter.dart create mode 100755 app/lib/presentation/widgets/backgrounds/gradient_background.dart diff --git a/app/lib/core/config/navigation_config.dart b/app/lib/core/config/navigation_config.dart new file mode 100755 index 00000000..d26e4316 --- /dev/null +++ b/app/lib/core/config/navigation_config.dart @@ -0,0 +1,259 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:geosector_app/presentation/widgets/badged_navigation_destination.dart'; + +/// Configuration centralisée de la navigation pour toute l'application +/// Gère les destinations, routes et logique de navigation pour admin et user +class NavigationConfig { + // ======================================== + // DESTINATIONS DE NAVIGATION + // ======================================== + + /// Destinations communes à tous les rôles + static const List _commonDestinations = [ + NavigationDestination( + icon: Icon(Icons.dashboard_outlined), + selectedIcon: Icon(Icons.dashboard), + label: 'Tableau de bord', + ), + NavigationDestination( + icon: Icon(Icons.history_outlined), + selectedIcon: Icon(Icons.history), + label: 'Historique', + ), + NavigationDestination( + icon: Icon(Icons.map_outlined), + selectedIcon: Icon(Icons.map), + label: 'Carte', + ), + ]; + + /// Destination Messages avec badge (commune à tous) + static NavigationDestination get _messagesDestination { + return createBadgedNavigationDestination( + icon: const Icon(Icons.chat_outlined), + selectedIcon: const Icon(Icons.chat), + label: 'Messages', + showBadge: true, + ); + } + + /// Destination Terrain (user uniquement) + static const NavigationDestination _fieldModeDestination = NavigationDestination( + icon: Icon(Icons.explore_outlined), + selectedIcon: Icon(Icons.explore), + label: 'Terrain', + ); + + /// Destinations admin desktop uniquement + static const List _adminDesktopDestinations = [ + NavigationDestination( + icon: Icon(Icons.business_outlined), + selectedIcon: Icon(Icons.business), + label: 'Amicale & membres', + ), + NavigationDestination( + icon: Icon(Icons.calendar_today_outlined), + selectedIcon: Icon(Icons.calendar_today), + label: 'Opérations', + ), + NavigationDestination( + icon: Icon(Icons.analytics_outlined), + selectedIcon: Icon(Icons.analytics), + label: 'Connexions', + ), + ]; + + // ======================================== + // GETTERS DE DESTINATIONS + // ======================================== + + /// Obtenir la liste des destinations selon le rôle et le device + static List getDestinations({ + required bool isAdmin, + required bool isMobile, + }) { + final destinations = []; + + // Ajouter les destinations communes + destinations.addAll(_commonDestinations); + + // Ajouter Messages (avec badge) + destinations.add(_messagesDestination); + + if (isAdmin) { + // Admin : ajouter les pages supplémentaires sur desktop uniquement + if (!isMobile) { + destinations.addAll(_adminDesktopDestinations); + } + } else { + // User : ajouter la page Terrain + destinations.add(_fieldModeDestination); + } + + return destinations; + } + + // ======================================== + // NAVIGATION (INDEX → ROUTE) + // ======================================== + + /// Naviguer vers une page selon l'index et le rôle + static void navigateToIndex(BuildContext context, int index, bool isAdmin) { + if (isAdmin) { + _navigateAdminIndex(context, index); + } else { + _navigateUserIndex(context, index); + } + } + + /// Navigation pour les admins + static void _navigateAdminIndex(BuildContext context, int index) { + switch (index) { + case 0: + context.go('/admin'); + break; + case 1: + context.go('/admin/history'); + break; + case 2: + context.go('/admin/map'); + break; + case 3: + context.go('/admin/messages'); + break; + case 4: + context.go('/admin/amicale'); + break; + case 5: + context.go('/admin/operations'); + break; + case 6: + context.go('/admin/connexions'); + break; + default: + context.go('/admin'); + } + } + + /// Navigation pour les utilisateurs standards + static void _navigateUserIndex(BuildContext context, int index) { + switch (index) { + case 0: + context.go('/user/dashboard'); + break; + case 1: + context.go('/user/history'); + break; + case 2: + context.go('/user/map'); + break; + case 3: + context.go('/user/messages'); + break; + case 4: + context.go('/user/field-mode'); + break; + default: + context.go('/user/dashboard'); + } + } + + // ======================================== + // RÉSOLUTION (ROUTE → INDEX) + // ======================================== + + /// Obtenir l'index selon la route actuelle et le rôle + static int getIndexFromRoute(String route, bool isAdmin) { + // Enlever les paramètres de query si présents + final cleanRoute = route.split('?').first; + + if (isAdmin) { + return _getAdminIndexFromRoute(cleanRoute); + } else { + return _getUserIndexFromRoute(cleanRoute); + } + } + + /// Obtenir l'index admin depuis la route + static int _getAdminIndexFromRoute(String route) { + if (route.contains('/admin/history')) return 1; + if (route.contains('/admin/map')) return 2; + if (route.contains('/admin/messages')) return 3; + if (route.contains('/admin/amicale')) return 4; + if (route.contains('/admin/operations')) return 5; + if (route.contains('/admin/connexions')) return 6; + return 0; // Dashboard par défaut + } + + /// Obtenir l'index user depuis la route + static int _getUserIndexFromRoute(String route) { + if (route.contains('/user/history')) return 1; + if (route.contains('/user/map')) return 2; + if (route.contains('/user/messages')) return 3; + if (route.contains('/user/field-mode')) return 4; + return 0; // Dashboard par défaut + } + + // ======================================== + // UTILITAIRES + // ======================================== + + /// Obtenir le nom de la page selon l'index et le rôle + static String getPageNameFromIndex(int index, bool isAdmin) { + if (isAdmin) { + return _getAdminPageName(index); + } else { + return _getUserPageName(index); + } + } + + /// Obtenir le nom de page admin + static String _getAdminPageName(int index) { + switch (index) { + case 0: + return 'dashboard'; + case 1: + return 'history'; + case 2: + return 'map'; + case 3: + return 'messages'; + case 4: + return 'amicale'; + case 5: + return 'operations'; + case 6: + return 'connexions'; + default: + return 'dashboard'; + } + } + + /// Obtenir le nom de page user + static String _getUserPageName(int index) { + switch (index) { + case 0: + return 'dashboard'; + case 1: + return 'history'; + case 2: + return 'map'; + case 3: + return 'messages'; + case 4: + return 'field-mode'; + default: + return 'dashboard'; + } + } + + // ======================================== + // TITRES ET LABELS + // ======================================== + + /// Obtenir le titre du dashboard selon le rôle + static String getDashboardTitle(bool isAdmin) { + return isAdmin ? 'Tableau de bord Administration' : 'GEOSECTOR'; + } +} diff --git a/app/lib/presentation/widgets/admin_scaffold.dart b/app/lib/presentation/widgets/admin_scaffold.dart deleted file mode 100644 index ffadcd94..00000000 --- a/app/lib/presentation/widgets/admin_scaffold.dart +++ /dev/null @@ -1,207 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:geosector_app/presentation/widgets/dashboard_layout.dart'; -import 'package:geosector_app/presentation/widgets/badged_navigation_destination.dart'; -import 'package:geosector_app/app.dart'; -import 'dart:math' as math; - -/// Class pour dessiner les petits points blancs sur le fond -class DotsPainter extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = Colors.white.withOpacity(0.5) - ..style = PaintingStyle.fill; - - final random = math.Random(42); // Seed fixe pour consistance - final numberOfDots = (size.width * size.height) ~/ 1500; - - for (int i = 0; i < numberOfDots; i++) { - final x = random.nextDouble() * size.width; - final y = random.nextDouble() * size.height; - final radius = 1.0 + random.nextDouble() * 2.0; - canvas.drawCircle(Offset(x, y), radius, paint); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} - -/// Scaffold partagé pour toutes les pages d'administration -/// Fournit le fond dégradé et la navigation commune -class AdminScaffold 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; - - const AdminScaffold({ - super.key, - required this.body, - required this.selectedIndex, - required this.pageTitle, - this.onDestinationSelected, - }); - - @override - Widget build(BuildContext context) { - final currentUser = userRepository.getCurrentUser(); - final size = MediaQuery.of(context).size; - final isMobile = size.width <= 900; - - return Stack( - children: [ - // Fond dégradé avec petits points blancs - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.white, Colors.red.shade300], - ), - ), - child: CustomPaint( - painter: DotsPainter(), - child: const SizedBox(width: double.infinity, height: double.infinity), - ), - ), - - // Contenu de la page avec navigation - DashboardLayout( - key: ValueKey('dashboard_layout_$selectedIndex'), - title: 'Tableau de bord Administration', - selectedIndex: selectedIndex, - onDestinationSelected: onDestinationSelected ?? (index) { - // Navigation par défaut si pas de callback personnalisé - AdminNavigationHelper.navigateToIndex(context, index); - }, - destinations: AdminNavigationHelper.getDestinations( - currentUser: currentUser, - isMobile: isMobile, - ), - isAdmin: true, - body: body, - ), - ], - ); - } -} - -/// Helper pour centraliser la logique de navigation admin -class AdminNavigationHelper { - /// Obtenir la liste des destinations de navigation selon le rôle et le device - static List getDestinations({ - required dynamic currentUser, - required bool isMobile, - }) { - final destinations = [ - // Pages de base toujours visibles - const NavigationDestination( - icon: Icon(Icons.dashboard_outlined), - selectedIcon: Icon(Icons.dashboard), - label: 'Tableau de bord', - ), - const NavigationDestination( - icon: Icon(Icons.bar_chart_outlined), - selectedIcon: Icon(Icons.bar_chart), - label: 'Statistiques', - ), - const NavigationDestination( - icon: Icon(Icons.history_outlined), - selectedIcon: Icon(Icons.history), - label: 'Historique', - ), - createBadgedNavigationDestination( - icon: const Icon(Icons.chat_outlined), - selectedIcon: const Icon(Icons.chat), - label: 'Messages', - showBadge: true, - ), - const NavigationDestination( - icon: Icon(Icons.map_outlined), - selectedIcon: Icon(Icons.map), - label: 'Carte', - ), - ]; - - // Ajouter les pages admin (role 2) seulement sur desktop - if (currentUser?.role == 2 && !isMobile) { - destinations.addAll([ - const NavigationDestination( - icon: Icon(Icons.business_outlined), - selectedIcon: Icon(Icons.business), - label: 'Amicale & membres', - ), - const NavigationDestination( - icon: Icon(Icons.calendar_today_outlined), - selectedIcon: Icon(Icons.calendar_today), - label: 'Opérations', - ), - ]); - } - - return destinations; - } - - /// Naviguer vers une page selon l'index - static void navigateToIndex(BuildContext context, int index) { - switch (index) { - case 0: - context.go('/admin'); - break; - case 1: - context.go('/admin/statistics'); - break; - case 2: - context.go('/admin/history'); - break; - case 3: - context.go('/admin/messages'); - break; - case 4: - context.go('/admin/map'); - break; - case 5: - context.go('/admin/amicale'); - break; - case 6: - context.go('/admin/operations'); - break; - default: - context.go('/admin'); - } - } - - /// Obtenir l'index selon la route actuelle - static int getIndexFromRoute(String route) { - if (route.contains('/statistics')) return 1; - if (route.contains('/history')) return 2; - if (route.contains('/messages')) return 3; - if (route.contains('/map')) return 4; - if (route.contains('/amicale')) return 5; - if (route.contains('/operations')) return 6; - return 0; // Dashboard par défaut - } - - /// Obtenir le nom de la page selon l'index - static String getPageNameFromIndex(int index) { - switch (index) { - case 0: return 'dashboard'; - case 1: return 'statistics'; - case 2: return 'history'; - case 3: return 'messages'; - case 4: return 'map'; - case 5: return 'amicale'; - case 6: return 'operations'; - default: return 'dashboard'; - } - } -} \ No newline at end of file diff --git a/app/lib/presentation/widgets/app_scaffold.dart b/app/lib/presentation/widgets/app_scaffold.dart old mode 100644 new mode 100755 index 56d3dfdb..5d54c3c8 --- a/app/lib/presentation/widgets/app_scaffold.dart +++ b/app/lib/presentation/widgets/app_scaffold.dart @@ -1,33 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:geosector_app/presentation/widgets/dashboard_layout.dart'; -import 'package:geosector_app/presentation/widgets/badged_navigation_destination.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'; -import 'dart:math' as math; - -/// Classe pour dessiner les petits points blancs sur le fond -class DotsPainter extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = Colors.white.withOpacity(0.5) - ..style = PaintingStyle.fill; - - final random = math.Random(42); // Seed fixe pour consistance - final numberOfDots = (size.width * size.height) ~/ 1500; - - for (int i = 0; i < numberOfDots; i++) { - final x = random.nextDouble() * size.width; - final y = random.nextDouble() * size.height; - final radius = 1.0 + random.nextDouble() * 2.0; - canvas.drawCircle(Offset(x, y), radius, paint); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} /// Scaffold unifié pour toutes les pages (admin et user) /// Adapte automatiquement son apparence selon le rôle de l'utilisateur @@ -102,43 +78,26 @@ class AppScaffold extends StatelessWidget { } } - // Couleurs de fond selon le rôle - final gradientColors = isAdmin - ? [Colors.white, Colors.red.shade300] // Admin: dégradé rouge - : [Colors.white, Colors.green.shade300]; // User: dégradé vert - - // Titre avec suffixe selon le rôle - final dashboardTitle = isAdmin - ? 'Tableau de bord Administration' - : 'GEOSECTOR'; - return Stack( children: [ // Fond dégradé avec petits points blancs (optionnel) if (showBackground) - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: gradientColors, - ), - ), - child: CustomPaint( - painter: DotsPainter(), - child: const SizedBox(width: double.infinity, height: double.infinity), - ), + GradientBackground( + isAdmin: isAdmin, + showDots: true, ), // Contenu de la page avec navigation DashboardLayout( - key: ValueKey('dashboard_layout_${isAdmin ? 'admin' : 'user'}_$selectedIndex'), - title: dashboardTitle, + key: ValueKey( + 'dashboard_layout_${isAdmin ? 'admin' : 'user'}_$selectedIndex'), + title: NavigationConfig.getDashboardTitle(isAdmin), selectedIndex: selectedIndex, - onDestinationSelected: onDestinationSelected ?? (index) { - NavigationHelper.navigateToIndex(context, index, isAdmin); - }, - destinations: NavigationHelper.getDestinations( + onDestinationSelected: onDestinationSelected ?? + (index) { + NavigationConfig.navigateToIndex(context, index, isAdmin); + }, + destinations: NavigationConfig.getDestinations( isAdmin: isAdmin, isMobile: isMobile, ), @@ -159,27 +118,13 @@ class AppScaffold extends StatelessWidget { }) { final theme = Theme.of(context); - // Utiliser le même fond que pour un utilisateur normal (vert) - final gradientColors = isAdmin - ? [Colors.white, Colors.red.shade300] - : [Colors.white, Colors.green.shade300]; - return Stack( children: [ // Fond dégradé (optionnel) if (showBackground) - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: gradientColors, - ), - ), - child: CustomPaint( - painter: DotsPainter(), - child: const SizedBox(width: double.infinity, height: double.infinity), - ), + GradientBackground( + isAdmin: isAdmin, + showDots: true, ), // Message d'accès restreint @@ -245,182 +190,4 @@ class AppScaffold extends StatelessWidget { ], ); } -} - -/// Helper centralisé pour la navigation -class NavigationHelper { - /// Obtenir la liste des destinations selon le mode d'affichage et le device - static List getDestinations({ - required bool isAdmin, - required bool isMobile, - }) { - final destinations = []; - - // Pages communes à tous les rôles - destinations.addAll([ - const NavigationDestination( - icon: Icon(Icons.dashboard_outlined), - selectedIcon: Icon(Icons.dashboard), - label: 'Tableau de bord', - ), - const NavigationDestination( - icon: Icon(Icons.history_outlined), - selectedIcon: Icon(Icons.history), - label: 'Historique', - ), - const NavigationDestination( - icon: Icon(Icons.map_outlined), - selectedIcon: Icon(Icons.map), - label: 'Carte', - ), - createBadgedNavigationDestination( - icon: const Icon(Icons.chat_outlined), - selectedIcon: const Icon(Icons.chat), - label: 'Messages', - showBadge: true, - ), - ]); - - // Pages spécifiques aux utilisateurs standards - if (!isAdmin) { - destinations.add( - const NavigationDestination( - icon: Icon(Icons.explore_outlined), - selectedIcon: Icon(Icons.explore), - label: 'Terrain', - ), - ); - } - - // Pages spécifiques aux admins (seulement sur desktop) - if (isAdmin && !isMobile) { - destinations.addAll([ - const NavigationDestination( - icon: Icon(Icons.business_outlined), - selectedIcon: Icon(Icons.business), - label: 'Amicale & membres', - ), - const NavigationDestination( - icon: Icon(Icons.calendar_today_outlined), - selectedIcon: Icon(Icons.calendar_today), - label: 'Opérations', - ), - const NavigationDestination( - icon: Icon(Icons.analytics_outlined), - selectedIcon: Icon(Icons.analytics), - label: 'Connexions', - ), - ]); - } - - return destinations; - } - - /// Naviguer vers une page selon l'index et le rôle - static void navigateToIndex(BuildContext context, int index, bool isAdmin) { - if (isAdmin) { - _navigateAdminIndex(context, index); - } else { - _navigateUserIndex(context, index); - } - } - - /// Navigation pour les admins - static void _navigateAdminIndex(BuildContext context, int index) { - switch (index) { - case 0: - context.go('/admin'); - break; - case 1: - context.go('/admin/history'); - break; - case 2: - context.go('/admin/map'); - break; - case 3: - context.go('/admin/messages'); - break; - case 4: - context.go('/admin/amicale'); - break; - case 5: - context.go('/admin/operations'); - break; - case 6: - context.go('/admin/connexions'); - break; - default: - context.go('/admin'); - } - } - - /// Navigation pour les utilisateurs standards - static void _navigateUserIndex(BuildContext context, int index) { - switch (index) { - case 0: - context.go('/user/dashboard'); - break; - case 1: - context.go('/user/history'); - break; - case 2: - context.go('/user/map'); - break; - case 3: - context.go('/user/messages'); - break; - case 4: - context.go('/user/field-mode'); - break; - default: - context.go('/user/dashboard'); - } - } - - /// Obtenir l'index selon la route actuelle et le rôle - static int getIndexFromRoute(String route, bool isAdmin) { - // Enlever les paramètres de query si présents - final cleanRoute = route.split('?').first; - - if (isAdmin) { - if (cleanRoute.contains('/admin/history')) return 1; - if (cleanRoute.contains('/admin/map')) return 2; - if (cleanRoute.contains('/admin/messages')) return 3; - if (cleanRoute.contains('/admin/amicale')) return 4; - if (cleanRoute.contains('/admin/operations')) return 5; - if (cleanRoute.contains('/admin/connexions')) return 6; - return 0; // Dashboard par défaut - } else { - if (cleanRoute.contains('/user/history')) return 1; - if (cleanRoute.contains('/user/map')) return 2; - if (cleanRoute.contains('/user/messages')) return 3; - if (cleanRoute.contains('/user/field-mode')) return 4; - return 0; // Dashboard par défaut - } - } - - /// Obtenir le nom de la page selon l'index et le rôle - static String getPageNameFromIndex(int index, bool isAdmin) { - if (isAdmin) { - switch (index) { - case 0: return 'dashboard'; - case 1: return 'history'; - case 2: return 'map'; - case 3: return 'messages'; - case 4: return 'amicale'; - case 5: return 'operations'; - case 6: return 'connexions'; - default: return 'dashboard'; - } - } else { - switch (index) { - case 0: return 'dashboard'; - case 1: return 'history'; - case 2: return 'map'; - case 3: return 'messages'; - case 4: return 'field-mode'; - default: return 'dashboard'; - } - } - } } \ No newline at end of file diff --git a/app/lib/presentation/widgets/backgrounds/dots_painter.dart b/app/lib/presentation/widgets/backgrounds/dots_painter.dart new file mode 100755 index 00000000..c5f0351d --- /dev/null +++ b/app/lib/presentation/widgets/backgrounds/dots_painter.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'dart:math' as math; + +/// Painter pour dessiner des petits points blancs semi-transparents sur un fond +/// Utilisé pour créer un effet visuel subtil sur les fonds dégradés +class DotsPainter extends CustomPainter { + /// Opacité des points (0.0 à 1.0) + final double opacity; + + /// Seed pour le générateur aléatoire (pour consistance du pattern) + final int seed; + + /// Densité des points (nombre de pixels par point) + /// Plus la valeur est élevée, moins il y a de points + final int density; + + const DotsPainter({ + this.opacity = 0.5, + this.seed = 42, + this.density = 1500, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.white.withOpacity(opacity) + ..style = PaintingStyle.fill; + + final random = math.Random(seed); + final numberOfDots = (size.width * size.height) ~/ density; + + for (int i = 0; i < numberOfDots; i++) { + final x = random.nextDouble() * size.width; + final y = random.nextDouble() * size.height; + final radius = 1.0 + random.nextDouble() * 2.0; + canvas.drawCircle(Offset(x, y), radius, paint); + } + } + + @override + bool shouldRepaint(covariant DotsPainter oldDelegate) { + return oldDelegate.opacity != opacity || + oldDelegate.seed != seed || + oldDelegate.density != density; + } +} diff --git a/app/lib/presentation/widgets/backgrounds/gradient_background.dart b/app/lib/presentation/widgets/backgrounds/gradient_background.dart new file mode 100755 index 00000000..77f18aaa --- /dev/null +++ b/app/lib/presentation/widgets/backgrounds/gradient_background.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:geosector_app/presentation/widgets/backgrounds/dots_painter.dart'; + +/// Widget de fond dégradé avec points décoratifs +/// Utilisé comme fond pour les pages admin et user avec des couleurs différentes +class GradientBackground extends StatelessWidget { + /// Indique si le fond est pour un admin (rouge) ou user (vert) + final bool isAdmin; + + /// Afficher ou non les points décoratifs + final bool showDots; + + /// Opacité des points (0.0 à 1.0) + final double dotsOpacity; + + const GradientBackground({ + super.key, + required this.isAdmin, + this.showDots = true, + this.dotsOpacity = 0.5, + }); + + @override + Widget build(BuildContext context) { + // Couleurs de fond selon le rôle + final gradientColors = isAdmin + ? [Colors.white, Colors.red.shade300] // Admin: dégradé rouge + : [Colors.white, Colors.green.shade300]; // User: dégradé vert + + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: gradientColors, + ), + ), + child: showDots + ? CustomPaint( + painter: DotsPainter(opacity: dotsOpacity), + child: const SizedBox( + width: double.infinity, + height: double.infinity, + ), + ) + : null, + ); + } +} diff --git a/app/lib/presentation/widgets/dashboard_layout.dart b/app/lib/presentation/widgets/dashboard_layout.dart index 7579b7d1..cf732065 100755 --- a/app/lib/presentation/widgets/dashboard_layout.dart +++ b/app/lib/presentation/widgets/dashboard_layout.dart @@ -48,92 +48,30 @@ class DashboardLayout extends StatelessWidget { @override Widget build(BuildContext context) { - try { - debugPrint('Building DashboardLayout'); - - // Vérifier que les destinations ne sont pas vides - if (destinations.isEmpty) { - debugPrint('ERREUR: destinations est vide dans DashboardLayout'); - return const Scaffold( - body: Center( - child: Text('Erreur: Aucune destination de navigation disponible'), - ), - ); - } - - // Vérifier que selectedIndex est valide - if (selectedIndex < 0 || selectedIndex >= destinations.length) { - debugPrint('ERREUR: selectedIndex invalide dans DashboardLayout'); - return Scaffold( - body: Center( - child: - Text('Erreur: Index de navigation invalide ($selectedIndex)'), - ), - ); - } - - // Scaffold avec fond transparent (le fond est géré par AppScaffold) - return Scaffold( - key: ValueKey('dashboard_scaffold_$selectedIndex'), - backgroundColor: Colors.transparent, - appBar: DashboardAppBar( - key: ValueKey('dashboard_appbar_$selectedIndex'), - title: title, - pageTitle: destinations[selectedIndex].label, - isAdmin: isAdmin, - onLogoutPressed: onLogoutPressed, - ), - body: ResponsiveNavigation( - key: ValueKey('responsive_nav_$selectedIndex'), - title: - title, // Même si le titre n'est pas affiché dans la navigation, il est utilisé pour la cohérence - body: body, - selectedIndex: selectedIndex, - onDestinationSelected: onDestinationSelected, - destinations: destinations, - // Ne pas afficher le bouton "Nouveau passage" dans la navigation - showNewPassageButton: false, - onNewPassagePressed: null, - sidebarBottomItems: sidebarBottomItems, - isAdmin: isAdmin, - // Ne pas afficher l'AppBar dans la navigation car nous utilisons DashboardAppBar - showAppBar: false, - ), - ); - } catch (e) { - debugPrint('ERREUR CRITIQUE dans DashboardLayout.build: $e'); - // Afficher une interface de secours en cas d'erreur - return Scaffold( - appBar: AppBar( - title: Text('Erreur - $title'), - backgroundColor: Colors.red, - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error_outline, color: Colors.red, size: 64), - const SizedBox(height: 16), - Text( - 'Une erreur est survenue', - style: TextStyle( - fontSize: AppTheme.r(context, 20), - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text('Détails: $e'), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - Navigator.of(context) - .pushNamedAndRemoveUntil('/', (route) => false); - }, - child: const Text('Retour à l\'accueil'), - ), - ], - ), - ), - ); - } + // Scaffold avec fond transparent (le fond est géré par AppScaffold) + return Scaffold( + key: ValueKey('dashboard_scaffold_$selectedIndex'), + backgroundColor: Colors.transparent, + appBar: DashboardAppBar( + key: ValueKey('dashboard_appbar_$selectedIndex'), + title: title, + pageTitle: destinations[selectedIndex].label, + isAdmin: isAdmin, + onLogoutPressed: onLogoutPressed, + ), + body: ResponsiveNavigation( + key: ValueKey('responsive_nav_$selectedIndex'), + title: title, + body: body, + selectedIndex: selectedIndex, + onDestinationSelected: onDestinationSelected, + destinations: destinations, + showNewPassageButton: false, + onNewPassagePressed: null, + sidebarBottomItems: sidebarBottomItems, + isAdmin: isAdmin, + showAppBar: false, + ), + ); } }