diff --git a/lib/features/about/presentation/pages/about_page.dart b/lib/features/about/presentation/pages/about_page.dart index e9c818d..7686420 100644 --- a/lib/features/about/presentation/pages/about_page.dart +++ b/lib/features/about/presentation/pages/about_page.dart @@ -6,6 +6,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/widgets/core_card.dart'; import '../../../../shared/widgets/info_badge.dart'; +import '../../../../shared/widgets/powered_by_lions_dev.dart'; /// Page À propos - UnionFlow Mobile @@ -40,7 +41,8 @@ class _AboutPageState extends State { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: UFAppBar( - title: 'À PROPOS', + title: 'À propos', + moduleGradient: ModuleColors.systemeGradient, actions: [ IconButton( icon: const Icon(Icons.share_outlined, size: 20), @@ -48,35 +50,42 @@ class _AboutPageState extends State { ), ], ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header harmonisé - _buildHeader(), - const SizedBox(height: 8), + body: SafeArea( + top: false, + child: SingleChildScrollView( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header harmonisé + _buildHeader(), + const SizedBox(height: 8), - // Informations de l'application - _buildAppInfoSection(), - const SizedBox(height: 8), + // Informations de l'application + _buildAppInfoSection(), + const SizedBox(height: 8), - // Équipe de développement - _buildTeamSection(), - const SizedBox(height: 8), + // Équipe de développement + _buildTeamSection(), + const SizedBox(height: 8), - // Fonctionnalités - _buildFeaturesSection(), - const SizedBox(height: 8), + // Fonctionnalités + _buildFeaturesSection(), + const SizedBox(height: 8), - // Liens utiles - _buildLinksSection(), - const SizedBox(height: 8), + // Liens utiles + _buildLinksSection(), + const SizedBox(height: 8), - // Support et contact - _buildSupportSection(), - const SizedBox(height: 80), - ], + // Support et contact + _buildSupportSection(), + const SizedBox(height: 16), + + // Branding « Powered by Lions Dev » (logo adaptatif dark/light) + const Center(child: PoweredByLionsDev()), + const SizedBox(height: 80), + ], + ), ), ), ); @@ -84,18 +93,19 @@ class _AboutPageState extends State { /// Header épuré Widget _buildHeader() { + final isDark = Theme.of(context).brightness == Brightness.dark; return Center( child: Column( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: AppColors.primaryGreen.withOpacity(0.1), + color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.account_balance, - color: AppColors.primaryGreen, + color: AppColors.primary, size: 32, ), ), @@ -112,8 +122,8 @@ class _AboutPageState extends State { if (_packageInfo != null) InfoBadge( text: 'VERSION ${_packageInfo!.version}', - backgroundColor: AppColors.lightSurface, - textColor: AppColors.textSecondaryLight, + backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface, + textColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ], ), @@ -149,7 +159,7 @@ class _AboutPageState extends State { children: [ Text( label, - style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight), + style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), ), Flexible( child: Text( @@ -175,16 +185,17 @@ class _AboutPageState extends State { ), const SizedBox(height: 8), _buildTeamMember( - 'UnionFlow Team', - 'Architecture & Dev', + 'Lions Dev', + 'Intégrateur de solutions digitales innovantes — lions.dev', Icons.code, - AppColors.primaryGreen, + AppColors.primary, + onTap: () => _launchUrl('https://www.lions.dev'), ), _buildTeamMember( - 'Design System', - 'UI / UX Focus', - Icons.design_services, - AppColors.brandGreenLight, + 'UnionFlow', + 'Mouvement d\'entraide & solidarité', + Icons.account_balance, + AppColors.primaryLight, ), ], ), @@ -192,8 +203,8 @@ class _AboutPageState extends State { } /// Membre de l'équipe - Widget _buildTeamMember(String name, String role, IconData icon, Color color) { - return Padding( + Widget _buildTeamMember(String name, String role, IconData icon, Color color, {VoidCallback? onTap}) { + final content = Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ @@ -215,9 +226,17 @@ class _AboutPageState extends State { ], ), ), + if (onTap != null) + Icon(Icons.open_in_new, color: color, size: 14), ], ), ); + if (onTap == null) return content; + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: content, + ); } /// Section fonctionnalités @@ -231,8 +250,8 @@ class _AboutPageState extends State { style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), ), const SizedBox(height: 8), - _buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primaryGreen), - _buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.brandGreenLight), + _buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primary), + _buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.primaryLight), _buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success), _buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning), ], @@ -284,6 +303,7 @@ class _AboutPageState extends State { /// Élément de lien Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) { + final isDark = Theme.of(context).brightness == Brightness.dark; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(4), @@ -291,7 +311,7 @@ class _AboutPageState extends State { padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ - Icon(icon, color: AppColors.primaryGreen, size: 16), + Icon(icon, color: AppColors.primary, size: 16), const SizedBox(width: 12), Expanded( child: Column( @@ -302,7 +322,7 @@ class _AboutPageState extends State { ], ), ), - const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14), + Icon(Icons.chevron_right, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 14), ], ), ), @@ -327,7 +347,7 @@ class _AboutPageState extends State { child: Column( children: [ Text('© 2024 UNIONFLOW', style: AppTypography.badgeText), - Text('Fait avec ❤️ pour les syndicats', style: AppTypography.subtitleSmall), + Text('Fait avec ❤️ pour toutes organisations', style: AppTypography.subtitleSmall), ], ), ), @@ -338,6 +358,7 @@ class _AboutPageState extends State { /// Élément de support Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) { + final isDark = Theme.of(context).brightness == Brightness.dark; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(4), @@ -356,7 +377,7 @@ class _AboutPageState extends State { ], ), ), - const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14), + Icon(Icons.chevron_right, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 14), ], ), ), @@ -398,8 +419,8 @@ class _AboutPageState extends State { _launchUrl('mailto:support@unionflow.com?subject=Rapport de bug - UnionFlow Mobile'); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Envoyer un email'), ), @@ -429,8 +450,8 @@ class _AboutPageState extends State { _launchUrl('mailto:support@unionflow.com?subject=Suggestion d\'amélioration - UnionFlow Mobile'); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Envoyer une suggestion'), ), @@ -460,8 +481,8 @@ class _AboutPageState extends State { _launchStoreForRating(); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Évaluer maintenant'), ), diff --git a/lib/features/authentication/presentation/pages/login_page.dart b/lib/features/authentication/presentation/pages/login_page.dart index d2ce21d..07b9459 100644 --- a/lib/features/authentication/presentation/pages/login_page.dart +++ b/lib/features/authentication/presentation/pages/login_page.dart @@ -8,9 +8,17 @@ import 'package:url_launcher/url_launcher.dart'; import '../bloc/auth_bloc.dart'; import '../../../../core/config/environment.dart'; +import '../../../../shared/design_system/unionflow_design_system.dart'; +import '../../../../shared/widgets/powered_by_lions_dev.dart'; -/// UnionFlow — Écran de connexion premium -/// Gradient forêt + glassmorphism + animations + biométrie + remember me +// ── Couleurs signature ──────────────────────────────────────────────────────── +const _kGradTop = Color(0xFF1D4ED8); +const _kGradMid = Color(0xFF2563EB); +const _kGradBot = Color(0xFF7616E8); +const _kPrimaryBlue = Color(0xFF2563EB); + +/// UnionFlow — Écran de connexion. +/// Gradient signature (#1D4ED8 → #2563EB → #7616E8) + glassmorphism. class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -19,43 +27,39 @@ class LoginPage extends StatefulWidget { } class _LoginPageState extends State with TickerProviderStateMixin { - late final AnimationController _fadeController; - late final AnimationController _slideController; + late final AnimationController _fadeCtrl; + late final AnimationController _slideCtrl; late final Animation _fadeAnim; late final Animation _slideAnim; bool _biometricAvailable = false; - final _localAuth = LocalAuthentication(); - static const _gradTop = Color(0xFF1B5E20); - static const _gradMid = Color(0xFF2E7D32); - static const _gradBot = Color(0xFF388E3C); - static const _primaryGreen = Color(0xFF2E7D32); - @override void initState() { super.initState(); - _fadeController = AnimationController(vsync: this, duration: const Duration(milliseconds: 900)); - _slideController = AnimationController(vsync: this, duration: const Duration(milliseconds: 750)); - _fadeAnim = CurvedAnimation(parent: _fadeController, curve: Curves.easeOut); + _fadeCtrl = AnimationController( + vsync: this, duration: const Duration(milliseconds: 900)); + _slideCtrl = AnimationController( + vsync: this, duration: const Duration(milliseconds: 750)); + _fadeAnim = CurvedAnimation(parent: _fadeCtrl, curve: Curves.easeOut); _slideAnim = Tween(begin: const Offset(0, 0.12), end: Offset.zero) - .animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic)); - _fadeController.forward(); - _slideController.forward(); + .animate(CurvedAnimation(parent: _slideCtrl, curve: Curves.easeOutCubic)); + _fadeCtrl.forward(); + _slideCtrl.forward(); _checkBiometrics(); } @override void dispose() { - _fadeController.dispose(); - _slideController.dispose(); + _fadeCtrl.dispose(); + _slideCtrl.dispose(); super.dispose(); } Future _checkBiometrics() async { try { - final canCheck = await _localAuth.canCheckBiometrics; + final canCheck = await _localAuth.canCheckBiometrics; final supported = await _localAuth.isDeviceSupported(); if (mounted) setState(() => _biometricAvailable = canCheck && supported); } catch (_) {} @@ -67,9 +71,7 @@ class _LoginPageState extends State with TickerProviderStateMixin { localizedReason: 'Authentifiez-vous pour accéder à UnionFlow', options: const AuthenticationOptions(stickyAuth: true, biometricOnly: false), ); - if (ok && mounted) { - context.read().add(const AuthStatusChecked()); - } + if (ok && mounted) context.read().add(const AuthStatusChecked()); } catch (_) {} } @@ -81,54 +83,41 @@ class _LoginPageState extends State with TickerProviderStateMixin { '&response_type=code&scope=openid&kc_action=reset_credentials', ); try { - if (await canLaunchUrl(url)) await launchUrl(url, mode: LaunchMode.externalApplication); + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } } catch (_) {} } - void _onLogin() { - context.read().add(const AuthLoginRequested()); + void _onAuthStateChanged(BuildContext context, AuthState state) { + if (state is AuthError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + backgroundColor: AppColors.error, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ); + } } @override Widget build(BuildContext context) { return Scaffold( body: BlocConsumer( - listener: (context, state) { - if (state is AuthError) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.message), - backgroundColor: const Color(0xFFB71C1C), - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ); - } - }, + listener: _onAuthStateChanged, builder: (context, state) { final isLoading = state is AuthLoading; return Stack( children: [ - // Gradient background - Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [_gradTop, _gradMid, _gradBot], - stops: [0.0, 0.55, 1.0], - ), - ), - ), - - // Subtle hexagon pattern overlay + const _GradientBackground(), const Positioned.fill(child: _HexPatternOverlay()), - - // Content SafeArea( child: Center( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24), + padding: const EdgeInsets.symmetric( + horizontal: 28, vertical: 24), child: FadeTransition( opacity: _fadeAnim, child: SlideTransition( @@ -136,9 +125,20 @@ class _LoginPageState extends State with TickerProviderStateMixin { child: Column( mainAxisSize: MainAxisSize.min, children: [ - _buildLogoSection(), + const _LoginLogoSection(), const SizedBox(height: 16), - _buildGlassCard(isLoading), + _LoginGlassCard( + isLoading: isLoading, + onLogin: () => context + .read() + .add(const AuthLoginRequested()), + onForgotPassword: _openForgotPassword, + biometricAvailable: _biometricAvailable, + onBiometric: _authenticateBiometric, + ), + const SizedBox(height: 24), + // Branding « Powered by Lions Dev » — fond toujours dark + const PoweredByLionsDev(forceBrightness: Brightness.dark), ], ), ), @@ -152,13 +152,43 @@ class _LoginPageState extends State with TickerProviderStateMixin { ), ); } +} - Widget _buildLogoSection() { +// ───────────────────────────────────────────────────────────────────────────── +// Widgets extraits +// ───────────────────────────────────────────────────────────────────────────── + +class _GradientBackground extends StatelessWidget { + const _GradientBackground(); + + @override + Widget build(BuildContext context) { + return const DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [_kGradTop, _kGradMid, _kGradBot], + stops: [0.0, 0.55, 1.0], + ), + ), + child: SizedBox.expand(), + ); + } +} + +class _LoginLogoSection extends StatelessWidget { + const _LoginLogoSection(); + + @override + Widget build(BuildContext context) { return Column( children: [ - CustomPaint( - size: const Size(48, 48), - painter: _HexLogoMark(), + Image.asset( + 'assets/images/unionflow-logo.png', + width: 80, + height: 80, + fit: BoxFit.contain, ), const SizedBox(height: 10), Text( @@ -175,7 +205,6 @@ class _LoginPageState extends State with TickerProviderStateMixin { 'Gérez votre organisation avec sérénité', style: GoogleFonts.roboto( fontSize: 13, - fontWeight: FontWeight.w400, color: Colors.white.withOpacity(0.78), letterSpacing: 0.2, ), @@ -184,19 +213,35 @@ class _LoginPageState extends State with TickerProviderStateMixin { ], ); } +} - Widget _buildGlassCard(bool isLoading) { +class _LoginGlassCard extends StatelessWidget { + const _LoginGlassCard({ + required this.isLoading, + required this.onLogin, + required this.onForgotPassword, + required this.biometricAvailable, + required this.onBiometric, + }); + + final bool isLoading; + final VoidCallback onLogin; + final VoidCallback onForgotPassword; + final bool biometricAvailable; + final VoidCallback onBiometric; + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.11), borderRadius: BorderRadius.circular(16), border: Border.all( - color: Colors.white.withOpacity(0.22), - width: 1.5, - ), + color: Colors.white.withOpacity(0.22), width: 1.5), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.18), + color: AppColors.shadowStrong, blurRadius: 40, offset: const Offset(0, 10), ), @@ -225,12 +270,10 @@ class _LoginPageState extends State with TickerProviderStateMixin { ), ), const SizedBox(height: 12), - - // Forgot password Align( alignment: Alignment.centerRight, child: TextButton( - onPressed: _openForgotPassword, + onPressed: onForgotPassword, style: TextButton.styleFrom( padding: EdgeInsets.zero, minimumSize: Size.zero, @@ -249,43 +292,47 @@ class _LoginPageState extends State with TickerProviderStateMixin { ), ), const SizedBox(height: 16), - - // Login button - isLoading - ? const Center(child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5)) - : ElevatedButton( - onPressed: _onLogin, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: _primaryGreen, - padding: const EdgeInsets.symmetric(vertical: 10), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - elevation: 0, - ), - child: Text( - 'Se connecter', - style: GoogleFonts.roboto( - fontSize: 15.5, - fontWeight: FontWeight.w700, - color: _primaryGreen, - letterSpacing: 0.2, - ), - ), + if (isLoading) + const Center( + child: CircularProgressIndicator( + color: Colors.white, strokeWidth: 2.5), + ) + else + ElevatedButton( + onPressed: onLogin, + style: ElevatedButton.styleFrom( + backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface, + foregroundColor: _kPrimaryBlue, + padding: const EdgeInsets.symmetric(vertical: 10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8)), + elevation: 0, + ), + child: Text( + 'Se connecter', + style: GoogleFonts.roboto( + fontSize: 15.5, + fontWeight: FontWeight.w700, + color: _kPrimaryBlue, + letterSpacing: 0.2, ), - - // Biometric - if (_biometricAvailable) ...[ + ), + ), + if (biometricAvailable) ...[ const SizedBox(height: 14), Center( child: TextButton.icon( - onPressed: _authenticateBiometric, - icon: const Icon(Icons.fingerprint_rounded, color: Colors.white60, size: 22), + onPressed: onBiometric, + icon: const Icon(Icons.fingerprint_rounded, + color: Colors.white60, size: 22), label: Text( 'Connexion biométrique', - style: GoogleFonts.roboto(fontSize: 12.5, color: Colors.white60), + style: GoogleFonts.roboto( + fontSize: 12.5, color: Colors.white60), ), style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 6), ), ), ), @@ -300,14 +347,12 @@ class _LoginPageState extends State with TickerProviderStateMixin { // Painters // ───────────────────────────────────────────────────────────────────────────── -/// Motif hexagonal en overlay sur le dégradé (opacité 4%) class _HexPatternOverlay extends StatelessWidget { const _HexPatternOverlay(); @override - Widget build(BuildContext context) { - return CustomPaint(painter: _HexPatternPainter()); - } + Widget build(BuildContext context) => + CustomPaint(painter: _HexPatternPainter()); } class _HexPatternPainter extends CustomPainter { @@ -318,14 +363,17 @@ class _HexPatternPainter extends CustomPainter { ..style = PaintingStyle.stroke ..strokeWidth = 0.8; - const r = 22.0; + const r = 22.0; const hSpace = r * 1.75; const vSpace = r * 1.52; for (double row = -1; row * vSpace < size.height + vSpace; row++) { final offset = (row % 2 == 0) ? 0.0 : hSpace / 2; - for (double col = -1; col * hSpace - offset < size.width + hSpace; col++) { - _hexagon(canvas, paint, Offset(col * hSpace + offset, row * vSpace), r); + for (double col = -1; + col * hSpace - offset < size.width + hSpace; + col++) { + _hexagon(canvas, paint, + Offset(col * hSpace + offset, row * vSpace), r); } } } @@ -334,8 +382,9 @@ class _HexPatternPainter extends CustomPainter { final path = Path(); for (int i = 0; i < 6; i++) { final a = (i * 60 - 30) * math.pi / 180; - final p = Offset(center.dx + r * math.cos(a), center.dy + r * math.sin(a)); - if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy); + final p = Offset( + center.dx + r * math.cos(a), center.dy + r * math.sin(a)); + i == 0 ? path.moveTo(p.dx, p.dy) : path.lineTo(p.dx, p.dy); } path.close(); canvas.drawPath(path, paint); @@ -344,57 +393,3 @@ class _HexPatternPainter extends CustomPainter { @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } - -/// Logo hexagonal avec initiales "UF" -class _HexLogoMark extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - final cx = size.width / 2; - final cy = size.height / 2; - final r = size.width / 2 - 2; - - // Fond hexagonal blanc semi-transparent - final bgPaint = Paint() - ..color = Colors.white.withOpacity(0.18) - ..style = PaintingStyle.fill; - final borderPaint = Paint() - ..color = Colors.white.withOpacity(0.6) - ..style = PaintingStyle.stroke - ..strokeWidth = 1.8; - - final path = Path(); - for (int i = 0; i < 6; i++) { - final a = (i * 60 - 30) * math.pi / 180; - final p = Offset(cx + r * math.cos(a), cy + r * math.sin(a)); - if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy); - } - path.close(); - canvas.drawPath(path, bgPaint); - canvas.drawPath(path, borderPaint); - - // Lignes stylisées "UF" dessinées (plus propre qu'un TextPainter dans un painter) - final linePaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.stroke - ..strokeWidth = 2.8 - ..strokeCap = StrokeCap.round - ..strokeJoin = StrokeJoin.round; - - // Lettre U - final uPath = Path() - ..moveTo(cx - 13, cy - 10) - ..lineTo(cx - 13, cy + 5) - ..quadraticBezierTo(cx - 13, cy + 12, cx - 7, cy + 12) - ..quadraticBezierTo(cx - 1, cy + 12, cx - 1, cy + 5) - ..lineTo(cx - 1, cy - 10); - canvas.drawPath(uPath, linePaint); - - // Lettre F - canvas.drawLine(Offset(cx + 3, cy - 10), Offset(cx + 3, cy + 12), linePaint); - canvas.drawLine(Offset(cx + 3, cy - 10), Offset(cx + 13, cy - 10), linePaint); - canvas.drawLine(Offset(cx + 3, cy + 1), Offset(cx + 11, cy + 1), linePaint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => false; -} diff --git a/lib/features/communication/presentation/widgets/conversation_tile.dart b/lib/features/communication/presentation/widgets/conversation_tile.dart index a56c663..6a82105 100644 --- a/lib/features/communication/presentation/widgets/conversation_tile.dart +++ b/lib/features/communication/presentation/widgets/conversation_tile.dart @@ -1,13 +1,14 @@ -/// Widget tuile de conversation +/// Widget tuile de conversation v4 library conversation_tile; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; + import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../domain/entities/conversation.dart'; class ConversationTile extends StatelessWidget { - final Conversation conversation; + final ConversationSummary conversation; final VoidCallback onTap; const ConversationTile({ @@ -31,41 +32,55 @@ class ConversationTile extends StatelessWidget { } } + IconData _typeIcon() { + switch (conversation.typeConversation) { + case 'DIRECTE': + return Icons.person_outline; + case 'ROLE_CANAL': + return Icons.account_circle_outlined; + case 'GROUPE': + return Icons.group_outlined; + default: + return Icons.chat_bubble_outline; + } + } + + String _apercuMessage() { + if (conversation.dernierMessageApercu == null) return 'Aucun message'; + final type = conversation.dernierMessageType ?? 'TEXTE'; + if (type == 'VOCAL') return '🎙️ Note vocale'; + if (type == 'IMAGE') return '📷 Image'; + return conversation.dernierMessageApercu!; + } + @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), child: Container( padding: const EdgeInsets.all(SpacingTokens.md), decoration: BoxDecoration( - color: ColorTokens.surface, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), border: Border.all( color: conversation.hasUnread - ? AppColors.primaryGreen.withOpacity(0.3) + ? AppColors.primary.withOpacity(0.3) : ColorTokens.outline, ), ), child: Row( children: [ - // Avatar - CircleAvatar( - radius: 24, - backgroundColor: AppColors.primaryGreen.withOpacity(0.1), - backgroundImage: conversation.avatarUrl != null - ? NetworkImage(conversation.avatarUrl!) - : null, - child: conversation.avatarUrl == null - ? Text( - conversation.name.isNotEmpty - ? conversation.name[0].toUpperCase() - : '?', - style: AppTypography.actionText.copyWith( - color: AppColors.primaryGreen, - ), - ) - : null, + // Avatar type + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular), + ), + child: Icon(_typeIcon(), color: AppColors.primary, size: 24), ), const SizedBox(width: SpacingTokens.md), @@ -79,7 +94,7 @@ class ConversationTile extends StatelessWidget { children: [ Expanded( child: Text( - conversation.name, + conversation.titre, style: AppTypography.actionText.copyWith( fontWeight: conversation.hasUnread ? FontWeight.bold @@ -89,29 +104,27 @@ class ConversationTile extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - if (conversation.lastMessage != null) + if (conversation.dernierMessageAt != null) Text( - _formatDate(conversation.lastMessage!.createdAt), + _formatDate(conversation.dernierMessageAt!), style: AppTypography.subtitleSmall.copyWith( - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ), ], ), - if (conversation.lastMessage != null) ...[ - const SizedBox(height: 4), - Text( - conversation.lastMessage!.content, - style: AppTypography.bodyTextSmall.copyWith( - color: AppColors.textSecondaryLight, - fontWeight: conversation.hasUnread - ? FontWeight.w600 - : FontWeight.normal, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + const SizedBox(height: 4), + Text( + _apercuMessage(), + style: AppTypography.bodyTextSmall.copyWith( + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, + fontWeight: conversation.hasUnread + ? FontWeight.w600 + : FontWeight.normal, ), - ], + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), ], ), ), @@ -120,16 +133,13 @@ class ConversationTile extends StatelessWidget { if (conversation.hasUnread) ...[ const SizedBox(width: SpacingTokens.sm), Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: AppColors.primaryGreen, + color: AppColors.primary, borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular), ), child: Text( - '${conversation.unreadCount}', + '${conversation.nonLus}', style: AppTypography.badgeText.copyWith( color: Colors.white, fontWeight: FontWeight.bold, @@ -137,27 +147,6 @@ class ConversationTile extends StatelessWidget { ), ), ], - - // Icônes statut - if (conversation.isPinned || conversation.isMuted) ...[ - const SizedBox(width: SpacingTokens.sm), - Column( - children: [ - if (conversation.isPinned) - Icon( - Icons.push_pin, - size: 16, - color: AppColors.textSecondaryLight, - ), - if (conversation.isMuted) - Icon( - Icons.volume_off, - size: 16, - color: AppColors.textSecondaryLight, - ), - ], - ), - ], ], ), ), diff --git a/lib/features/dashboard/presentation/widgets/connected/connected_recent_activities.dart b/lib/features/dashboard/presentation/widgets/connected/connected_recent_activities.dart index b0a79fd..6b9a7bd 100644 --- a/lib/features/dashboard/presentation/widgets/connected/connected_recent_activities.dart +++ b/lib/features/dashboard/presentation/widgets/connected/connected_recent_activities.dart @@ -33,16 +33,16 @@ class ConnectedRecentActivities extends StatelessWidget { BlocBuilder( builder: (context, state) { if (state is DashboardLoading) { - return _buildLoadingList(); + return _buildLoadingList(context); } else if (state is DashboardLoaded || state is DashboardRefreshing) { - final data = state is DashboardLoaded - ? state.dashboardData + final data = state is DashboardLoaded + ? state.dashboardData : (state as DashboardRefreshing).dashboardData; return _buildActivitiesList(context, data.recentActivities); } else if (state is DashboardError) { return _buildErrorState(state.message); } - return _buildEmptyState(); + return _buildEmptyState(context); }, ), ], @@ -55,7 +55,7 @@ class ConnectedRecentActivities extends StatelessWidget { children: [ const Icon( Icons.history, - color: AppColors.primaryGreen, + color: AppColors.primary, size: 18, ), const SizedBox(width: 8), @@ -71,7 +71,7 @@ class ConnectedRecentActivities extends StatelessWidget { child: Text( 'TOUT VOIR', style: AppTypography.badgeText.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, ), ), @@ -82,7 +82,7 @@ class ConnectedRecentActivities extends StatelessWidget { Widget _buildActivitiesList(BuildContext context, List activities) { if (activities.isEmpty) { - return _buildEmptyState(); + return _buildEmptyState(context); } final displayActivities = activities.take(maxItems).toList(); @@ -142,7 +142,7 @@ class ConnectedRecentActivities extends StatelessWidget { activity.userName, style: AppTypography.subtitleSmall.copyWith( fontWeight: FontWeight.w600, - color: AppColors.primaryGreen, + color: AppColors.primary, fontSize: 9, ), ), @@ -157,11 +157,11 @@ class ConnectedRecentActivities extends StatelessWidget { ), // Action button si disponible if (activity.hasAction) - const Icon( + Icon( Icons.chevron_right, size: 14, - color: AppColors.textSecondaryLight, - ), + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), ], ), ), @@ -189,25 +189,27 @@ class ConnectedRecentActivities extends StatelessWidget { } } - Widget _buildLoadingList() { + Widget _buildLoadingList(BuildContext context) { return Column( children: List.generate(3, (index) => Column( children: [ - _buildLoadingItem(), + _buildLoadingItem(context), if (index < 2) const SizedBox(height: 12), ], )), ); } - Widget _buildLoadingItem() { + Widget _buildLoadingItem(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final borderColor = isDark ? AppColors.borderDark : AppColors.border; return Row( children: [ Container( width: 28, height: 28, decoration: BoxDecoration( - color: AppColors.lightBorder, + color: borderColor, borderRadius: BorderRadius.circular(14), ), ), @@ -220,7 +222,7 @@ class ConnectedRecentActivities extends StatelessWidget { height: 16, width: double.infinity, decoration: BoxDecoration( - color: AppColors.lightBorder, + color: borderColor, borderRadius: BorderRadius.circular(4), ), ), @@ -229,7 +231,7 @@ class ConnectedRecentActivities extends StatelessWidget { height: 12, width: 200, decoration: BoxDecoration( - color: AppColors.lightBorder.withOpacity(0.5), + color: borderColor.withOpacity(0.5), borderRadius: BorderRadius.circular(4), ), ), @@ -238,7 +240,7 @@ class ConnectedRecentActivities extends StatelessWidget { height: 12, width: 120, decoration: BoxDecoration( - color: AppColors.lightBorder.withOpacity(0.5), + color: borderColor.withOpacity(0.5), borderRadius: BorderRadius.circular(4), ), ), @@ -261,11 +263,12 @@ class ConnectedRecentActivities extends StatelessWidget { ); } - Widget _buildEmptyState() { + Widget _buildEmptyState(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Center( child: Column( children: [ - const Icon(Icons.history, color: AppColors.textSecondaryLight, size: 32), + Icon(Icons.history, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 32), const SizedBox(height: 8), const Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall), Text('Les activités apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)), @@ -291,20 +294,20 @@ class ConnectedRecentActivities extends StatelessWidget { } } - Color _getActivityColor(String type) { + Color _getActivityColor(String type, {bool isDark = false}) { switch (type.toLowerCase()) { case 'member': return AppColors.success; case 'event': return AppColors.info; case 'contribution': - return AppColors.brandGreen; + return AppColors.primaryDark; case 'organization': - return AppColors.primaryGreen; + return AppColors.primary; case 'system': return AppColors.warning; default: - return AppColors.textSecondaryLight; + return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; } } } diff --git a/lib/features/dashboard/presentation/widgets/connected/connected_upcoming_events.dart b/lib/features/dashboard/presentation/widgets/connected/connected_upcoming_events.dart index dc5867b..da3ca00 100644 --- a/lib/features/dashboard/presentation/widgets/connected/connected_upcoming_events.dart +++ b/lib/features/dashboard/presentation/widgets/connected/connected_upcoming_events.dart @@ -30,14 +30,14 @@ class ConnectedUpcomingEvents extends StatelessWidget { if (state is DashboardLoading) { return _buildLoadingList(); } else if (state is DashboardLoaded || state is DashboardRefreshing) { - final data = state is DashboardLoaded - ? state.dashboardData + final data = state is DashboardLoaded + ? state.dashboardData : (state as DashboardRefreshing).dashboardData; return _buildEventsList(context, data.upcomingEvents); } else if (state is DashboardError) { return _buildErrorState(state.message); } - return _buildEmptyState(); + return _buildEmptyState(context); }, ), ], @@ -50,7 +50,7 @@ class ConnectedUpcomingEvents extends StatelessWidget { children: [ const Icon( Icons.event_outlined, - color: AppColors.primaryGreen, + color: AppColors.primary, size: 18, ), const SizedBox(width: 8), @@ -66,7 +66,7 @@ class ConnectedUpcomingEvents extends StatelessWidget { child: Text( 'TOUT VOIR', style: AppTypography.badgeText.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, ), ), @@ -77,7 +77,7 @@ class ConnectedUpcomingEvents extends StatelessWidget { Widget _buildEventsList(BuildContext context, List events) { if (events.isEmpty) { - return _buildEmptyState(); + return _buildEmptyState(context); } final displayEvents = events.take(maxItems).toList(); @@ -99,7 +99,7 @@ class ConnectedUpcomingEvents extends StatelessWidget { } Widget _buildEventCard(BuildContext context, UpcomingEventEntity event) { - final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primaryGreen); + final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primary); return CoreCard( backgroundColor: Theme.of(context).cardColor, @@ -140,7 +140,7 @@ class ConnectedUpcomingEvents extends StatelessWidget { ), Row( children: [ - const Icon(Icons.location_on_outlined, size: 10, color: AppColors.textSecondaryLight), + Icon(Icons.location_on_outlined, size: 10, color: Theme.of(context).colorScheme.onSurfaceVariant), const SizedBox(width: 4), Expanded( child: Text( @@ -188,7 +188,7 @@ class ConnectedUpcomingEvents extends StatelessWidget { child: LinearProgressIndicator( value: event.fillPercentage, minHeight: 4, - backgroundColor: AppColors.lightBorder, + backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.borderDark : AppColors.border, valueColor: AlwaysStoppedAnimation( event.isFull ? AppColors.error : (event.isAlmostFull ? AppColors.warning : AppColors.success), ), @@ -230,11 +230,12 @@ class ConnectedUpcomingEvents extends StatelessWidget { ); } - Widget _buildEmptyState() { + Widget _buildEmptyState(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Center( child: Column( children: [ - const Icon(Icons.event_outlined, color: AppColors.textSecondaryLight, size: 32), + Icon(Icons.event_outlined, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 32), const SizedBox(height: 8), const Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall), Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)), diff --git a/lib/features/dashboard/presentation/widgets/notifications/dashboard_notifications_widget.dart b/lib/features/dashboard/presentation/widgets/notifications/dashboard_notifications_widget.dart index a0444be..dee5ed3 100644 --- a/lib/features/dashboard/presentation/widgets/notifications/dashboard_notifications_widget.dart +++ b/lib/features/dashboard/presentation/widgets/notifications/dashboard_notifications_widget.dart @@ -37,7 +37,7 @@ class DashboardNotificationsWidget extends StatelessWidget { } else if (state is DashboardError) { return _buildErrorNotifications(); } - return _buildEmptyNotifications(); + return _buildEmptyNotifications(context); }, ), ], @@ -49,7 +49,7 @@ class DashboardNotificationsWidget extends StatelessWidget { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppColors.primaryGreen.withOpacity(0.05), + color: AppColors.primary.withOpacity(0.05), borderRadius: const BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), @@ -60,7 +60,7 @@ class DashboardNotificationsWidget extends StatelessWidget { Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( - color: AppColors.primaryGreen, + color: AppColors.primary, borderRadius: BorderRadius.circular(4), ), child: const Icon( @@ -74,7 +74,7 @@ class DashboardNotificationsWidget extends StatelessWidget { child: Text( 'NOTIFICATIONS', style: AppTypography.subtitleSmall.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, letterSpacing: 1.1, ), @@ -119,25 +119,26 @@ class DashboardNotificationsWidget extends StatelessWidget { Widget _buildNotifications(BuildContext context, DashboardEntity data) { final notifications = _generateNotifications(context, data); - + if (notifications.isEmpty) { - return _buildEmptyNotifications(); + return _buildEmptyNotifications(context); } return Column( children: notifications.take(maxNotifications).map((notification) { - return _buildNotificationItem(notification); + return _buildNotificationItem(context, notification); }).toList(), ); } - Widget _buildNotificationItem(DashboardNotification notification) { + Widget _buildNotificationItem(BuildContext context, DashboardNotification notification) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: AppColors.lightBorder, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1, ), ), @@ -199,7 +200,7 @@ class DashboardNotificationsWidget extends StatelessWidget { Text( notification.message, style: AppTypography.subtitleSmall.copyWith( - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, fontSize: 10, ), ), @@ -209,7 +210,7 @@ class DashboardNotificationsWidget extends StatelessWidget { Text( notification.timeAgo, style: AppTypography.subtitleSmall.copyWith( - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, fontSize: 9, ), ), @@ -220,7 +221,7 @@ class DashboardNotificationsWidget extends StatelessWidget { child: Text( notification.actionLabel!, style: AppTypography.badgeText.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 9, ), @@ -268,15 +269,16 @@ class DashboardNotificationsWidget extends StatelessWidget { ); } - Widget _buildEmptyNotifications() { + Widget _buildEmptyNotifications(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Container( padding: const EdgeInsets.all(24), child: Center( child: Column( children: [ - const Icon( + Icon( Icons.notifications_none_outlined, - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 24, ), const SizedBox(height: 8), @@ -359,7 +361,7 @@ class DashboardNotificationsWidget extends StatelessWidget { title: 'Nouvelles activités', message: '${data.recentActivitiesCount} activités récentes', icon: Icons.fiber_new_outlined, - color: AppColors.brandGreen, + color: AppColors.primaryDark, timeAgo: '15min', isUrgent: false, actionLabel: 'Voir', diff --git a/lib/features/epargne/presentation/pages/epargne_page.dart b/lib/features/epargne/presentation/pages/epargne_page.dart index 8bcf291..ec189b4 100644 --- a/lib/features/epargne/presentation/pages/epargne_page.dart +++ b/lib/features/epargne/presentation/pages/epargne_page.dart @@ -173,28 +173,28 @@ class _EpargnePageState extends State { 'VUE D\'ENSEMBLE', style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold), ), - const Icon(Icons.account_balance_wallet, color: AppColors.primaryGreen, size: 24), + const Icon(Icons.account_balance_wallet, color: AppColors.primary, size: 24), ], ), const SizedBox(height: SpacingTokens.md), Text( '${total.toStringAsFixed(0)} XOF', - style: AppTypography.headerSmall.copyWith(fontSize: 24, color: AppColors.primaryGreen), + style: AppTypography.headerSmall.copyWith(fontSize: 24, color: AppColors.primary), ), const SizedBox(height: SpacingTokens.xs), Text( 'Solde disponible total • ${_comptes.length} compte${_comptes.length > 1 ? 's' : ''}', - style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight), + style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), ), if (_comptes.any((c) => c.soldeBloque > 0)) ...[ const SizedBox(height: SpacingTokens.xs), Row( children: [ - Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700), + Icon(Icons.shield_outlined, size: 12, color: AppColors.warning), const SizedBox(width: 4), Text( 'Certains fonds sont sous surveillance LCB-FT', - style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: Colors.amber.shade700), + style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.warning), ), ], ), @@ -234,12 +234,17 @@ class _EpargnePageState extends State { if (typeLibelle != null) Text( typeLibelle, - style: AppTypography.subtitleSmall.copyWith(color: AppColors.textSecondaryLight), + style: AppTypography.subtitleSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), ), if (dateStr != null) Text( dateStr, - style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight), + style: AppTypography.bodyTextSmall.copyWith( + fontSize: 10, + color: Theme.of(context).brightness == Brightness.dark + ? AppColors.textSecondaryDark + : AppColors.textSecondary, + ), ), ], ), @@ -252,16 +257,16 @@ class _EpargnePageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( - color: Colors.amber.withOpacity(0.15), + color: AppColors.warningContainer, borderRadius: BorderRadius.circular(6), - border: Border.all(color: Colors.amber.shade700, width: 1), + border: Border.all(color: AppColors.warning, width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700), + Icon(Icons.shield_outlined, size: 12, color: AppColors.warning), const SizedBox(width: 3), - Text('LCB-FT', style: TextStyle(fontSize: 9, fontWeight: FontWeight.bold, color: Colors.amber.shade700)), + Text('LCB-FT', style: TextStyle(fontSize: 9, fontWeight: FontWeight.bold, color: AppColors.warning)), ], ), ), @@ -270,7 +275,7 @@ class _EpargnePageState extends State { if (c.statut != null) InfoBadge( text: c.statut!, - backgroundColor: c.statut == 'ACTIF' ? AppColors.success : AppColors.textSecondaryLight, + backgroundColor: c.statut == 'ACTIF' ? AppColors.success : AppColors.textSecondary, ), ], ), @@ -284,7 +289,7 @@ class _EpargnePageState extends State { Text('SOLDE ACTUEL', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)), Text( '${c.soldeActuel.toStringAsFixed(0)} XOF', - style: AppTypography.headerSmall.copyWith(fontSize: 14, color: AppColors.primaryGreen), + style: AppTypography.headerSmall.copyWith(fontSize: 14, color: AppColors.primary), ), ], ), @@ -305,7 +310,7 @@ class _EpargnePageState extends State { const SizedBox(height: SpacingTokens.sm), Text( c.description!, - style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondaryLight), + style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondary), maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -321,8 +326,8 @@ class _EpargnePageState extends State { onPressed: () => _openDepot(c), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), - backgroundColor: AppColors.primaryGreen.withOpacity(0.1), - foregroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary.withOpacity(0.1), + foregroundColor: AppColors.primary, ), child: const Text('Dépôt', style: TextStyle(fontSize: 12)), ), @@ -333,8 +338,8 @@ class _EpargnePageState extends State { onPressed: soldeDispo > 0 ? () => _openRetrait(c) : null, style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), - backgroundColor: AppColors.primaryGreen.withOpacity(0.1), - foregroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary.withOpacity(0.1), + foregroundColor: AppColors.primary, ), child: const Text('Retrait', style: TextStyle(fontSize: 12)), ), @@ -346,8 +351,8 @@ class _EpargnePageState extends State { onPressed: soldeDispo > 0 ? () => _openTransfert(c) : null, style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), - backgroundColor: AppColors.primaryGreen.withOpacity(0.1), - foregroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary.withOpacity(0.1), + foregroundColor: AppColors.primary, ), child: const Text('Transférer', style: TextStyle(fontSize: 12)), ), @@ -365,7 +370,9 @@ class _EpargnePageState extends State { label: const Text('Détail', style: TextStyle(fontSize: 12)), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), - foregroundColor: AppColors.textPrimaryLight, + foregroundColor: Theme.of(context).brightness == Brightness.dark + ? AppColors.textPrimaryDark + : AppColors.textPrimary, ), ), ), @@ -377,7 +384,9 @@ class _EpargnePageState extends State { label: const Text('Historique', style: TextStyle(fontSize: 12)), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), - foregroundColor: AppColors.textPrimaryLight, + foregroundColor: Theme.of(context).brightness == Brightness.dark + ? AppColors.textPrimaryDark + : AppColors.textPrimary, ), ), ), @@ -424,17 +433,17 @@ class _EpargnePageState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.savings_outlined, size: 64, color: AppColors.textSecondaryLight), + Icon(Icons.savings_outlined, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant), const SizedBox(height: SpacingTokens.lg), Text( 'Aucun compte épargne', - style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight), + style: AppTypography.actionText.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), textAlign: TextAlign.center, ), const SizedBox(height: SpacingTokens.sm), Text( 'Votre organisation peut ouvrir un compte épargne pour vous. Contactez-la pour en bénéficier.', - style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight), + style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), textAlign: TextAlign.center, ), ], @@ -459,19 +468,18 @@ class _EpargnePageState extends State { Widget build(BuildContext context) { final showFab = _canCreateCompte(context); return Scaffold( - backgroundColor: AppColors.background, - appBar: const UFAppBar( - title: 'COMPTES ÉPARGNE', - backgroundColor: AppColors.surface, - foregroundColor: AppColors.textPrimaryLight, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: UFAppBar( + title: 'Comptes Épargne', + moduleGradient: ModuleColors.epargneGradient, ), body: _buildBodyContent(), floatingActionButton: showFab ? FloatingActionButton( onPressed: _openCreerCompte, tooltip: 'Créer un compte épargne pour un membre', - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, child: const Icon(Icons.add), ) : null, diff --git a/lib/features/finance_workflow/presentation/pages/budgets_list_page.dart b/lib/features/finance_workflow/presentation/pages/budgets_list_page.dart index f1a5958..9fe91e5 100644 --- a/lib/features/finance_workflow/presentation/pages/budgets_list_page.dart +++ b/lib/features/finance_workflow/presentation/pages/budgets_list_page.dart @@ -38,9 +38,10 @@ class _BudgetsListView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: ColorTokens.background, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: UFAppBar( - title: 'BUDGETS', + title: 'Budgets', + moduleGradient: ModuleColors.financeWorkflowGradient, automaticallyImplyLeading: true, actions: [ IconButton( @@ -57,7 +58,9 @@ class _BudgetsListView extends StatelessWidget { ), ], ), - body: BlocConsumer( + body: SafeArea( + top: false, + child: BlocConsumer( listener: (context, state) { if (state is BudgetCreated) { ScaffoldMessenger.of(context).showSnackBar( @@ -98,13 +101,14 @@ class _BudgetsListView extends StatelessWidget { return const SizedBox.shrink(); }, ), + ), floatingActionButton: FloatingActionButton.extended( onPressed: () { SnackbarHelper.showNotImplemented(context, 'Création de budget'); }, icon: const Icon(Icons.add), label: const Text('Nouveau budget'), - backgroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary, ), ); } @@ -143,7 +147,7 @@ class _BudgetsListView extends StatelessWidget { Widget _buildFilterChips(BuildContext context, BudgetsLoaded state) { return Container( padding: const EdgeInsets.all(SpacingTokens.md), - color: AppColors.lightBackground, + color: Theme.of(context).scaffoldBackgroundColor, child: Wrap( spacing: SpacingTokens.sm, runSpacing: SpacingTokens.sm, @@ -178,28 +182,36 @@ class _BudgetsListView extends StatelessWidget { } Widget _buildEmptyState(String message) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.account_balance_wallet_outlined, - size: 48, - color: AppColors.textSecondaryLight.withOpacity(0.5), + return Builder( + builder: (context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.account_balance_wallet_outlined, + size: 48, + color: textSecondary.withOpacity(0.5), + ), + const SizedBox(height: SpacingTokens.lg), + Text( + message, + style: AppTypography.headerSmall.copyWith( + color: textSecondary, + ), + ), + ], ), - const SizedBox(height: SpacingTokens.lg), - Text( - message, - style: AppTypography.headerSmall.copyWith( - color: AppColors.textSecondaryLight, - ), - ), - ], - ), + ); + }, ); } Widget _buildErrorState(BuildContext context, String message) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -213,7 +225,7 @@ class _BudgetsListView extends StatelessWidget { Text( message, style: AppTypography.headerSmall.copyWith( - color: AppColors.textSecondaryLight, + color: textSecondary, ), textAlign: TextAlign.center, ), @@ -274,14 +286,14 @@ class _BudgetCard extends StatelessWidget { } } - Color _getStatusColor(BudgetStatus status) { + Color _getStatusColor(BudgetStatus status, {bool isDark = false}) { switch (status) { case BudgetStatus.draft: - return AppColors.textSecondaryLight; + return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; case BudgetStatus.active: - return AppColors.brandGreen; + return AppColors.primaryDark; case BudgetStatus.closed: - return AppColors.textSecondaryLight; + return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; case BudgetStatus.cancelled: return AppColors.error; } @@ -302,17 +314,20 @@ class _BudgetCard extends StatelessWidget { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; final currencyFormat = NumberFormat.currency(symbol: budget.currency); return Container( padding: const EdgeInsets.all(SpacingTokens.md), decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), - border: Border.all(color: AppColors.lightBorder), + border: Border.all( + color: isDark ? AppColors.borderDark : AppColors.border, + ), boxShadow: const [ BoxShadow( - color: Color(0x0A000000), + color: AppColors.shadow, blurRadius: 8, offset: Offset(0, 2), ), @@ -335,13 +350,13 @@ class _BudgetCard extends StatelessWidget { vertical: 4, ), decoration: BoxDecoration( - color: _getStatusColor(budget.status).withOpacity(0.1), + color: _getStatusColor(budget.status, isDark: isDark).withOpacity(0.1), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), ), child: Text( _getStatusLabel(budget.status), style: AppTypography.badgeText.copyWith( - color: _getStatusColor(budget.status), + color: _getStatusColor(budget.status, isDark: isDark), fontWeight: FontWeight.bold, ), ), @@ -368,7 +383,7 @@ class _BudgetCard extends StatelessWidget { Text( currencyFormat.format(budget.totalPlanned), style: AppTypography.headerSmall.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, ), ), @@ -389,7 +404,7 @@ class _BudgetCard extends StatelessWidget { style: AppTypography.headerSmall.copyWith( color: budget.isOverBudget ? AppColors.error - : AppColors.brandGreen, + : AppColors.primaryDark, fontWeight: FontWeight.bold, ), ), @@ -401,10 +416,10 @@ class _BudgetCard extends StatelessWidget { const SizedBox(height: SpacingTokens.sm), LinearProgressIndicator( value: budget.realizationRate / 100, - backgroundColor: AppColors.lightBorder, + backgroundColor: isDark ? AppColors.borderDark : AppColors.border, color: budget.isOverBudget ? AppColors.error - : AppColors.brandGreen, + : AppColors.primaryDark, ), const SizedBox(height: SpacingTokens.xs), Text( diff --git a/lib/features/finance_workflow/presentation/pages/pending_approvals_page.dart b/lib/features/finance_workflow/presentation/pages/pending_approvals_page.dart index b7ade4d..418661e 100644 --- a/lib/features/finance_workflow/presentation/pages/pending_approvals_page.dart +++ b/lib/features/finance_workflow/presentation/pages/pending_approvals_page.dart @@ -39,9 +39,10 @@ class _PendingApprovalsView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: ColorTokens.background, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: UFAppBar( - title: 'APPROBATIONS EN ATTENTE', + title: 'Approbations en attente', + moduleGradient: ModuleColors.financeWorkflowGradient, automaticallyImplyLeading: true, actions: [ IconButton( @@ -155,6 +156,7 @@ class _PendingApprovalsView extends StatelessWidget { } Widget _buildErrorState(BuildContext context, String message) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -168,7 +170,7 @@ class _PendingApprovalsView extends StatelessWidget { Text( message, style: AppTypography.headerSmall.copyWith( - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), textAlign: TextAlign.center, ), @@ -188,31 +190,37 @@ class _PendingApprovalsView extends StatelessWidget { } Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.check_circle_outline, - size: 48, - color: AppColors.success.withOpacity(0.5), + return Builder( + builder: (context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.check_circle_outline, + size: 48, + color: AppColors.success.withOpacity(0.5), + ), + const SizedBox(height: SpacingTokens.lg), + Text( + 'Aucune approbation en attente', + style: AppTypography.headerSmall.copyWith( + color: textSecondary, + ), + ), + const SizedBox(height: SpacingTokens.sm), + Text( + 'Toutes les transactions sont approuvées', + style: AppTypography.bodyTextSmall.copyWith( + color: textSecondary, + ), + ), + ], ), - const SizedBox(height: SpacingTokens.lg), - Text( - 'Aucune approbation en attente', - style: AppTypography.headerSmall.copyWith( - color: AppColors.textSecondaryLight, - ), - ), - const SizedBox(height: SpacingTokens.sm), - Text( - 'Toutes les transactions sont approuvées', - style: AppTypography.bodyTextSmall.copyWith( - color: AppColors.textSecondaryLight, - ), - ), - ], - ), + ); + }, ); } @@ -248,12 +256,12 @@ class _ApprovalCard extends StatelessWidget { } } - Color _getLevelColor(ApprovalLevel level) { + Color _getLevelColor(ApprovalLevel level, {bool isDark = false}) { switch (level) { case ApprovalLevel.none: - return AppColors.textSecondaryLight; + return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; case ApprovalLevel.level1: - return AppColors.brandGreen; + return AppColors.primaryDark; case ApprovalLevel.level2: return AppColors.warning; case ApprovalLevel.level3: @@ -263,17 +271,18 @@ class _ApprovalCard extends StatelessWidget { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; final currencyFormat = NumberFormat.currency(symbol: approval.currency); return Container( padding: const EdgeInsets.all(SpacingTokens.md), decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), - border: Border.all(color: AppColors.lightBorder), + border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border), boxShadow: const [ BoxShadow( - color: Color(0x0A000000), + color: AppColors.shadow, blurRadius: 8, offset: Offset(0, 2), ), @@ -296,13 +305,13 @@ class _ApprovalCard extends StatelessWidget { vertical: 4, ), decoration: BoxDecoration( - color: _getLevelColor(approval.requiredLevel).withOpacity(0.1), + color: _getLevelColor(approval.requiredLevel, isDark: isDark).withOpacity(0.1), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), ), child: Text( 'Niveau ${approval.requiredApprovals}', style: AppTypography.badgeText.copyWith( - color: _getLevelColor(approval.requiredLevel), + color: _getLevelColor(approval.requiredLevel, isDark: isDark), fontWeight: FontWeight.bold, ), ), @@ -313,7 +322,7 @@ class _ApprovalCard extends StatelessWidget { Text( currencyFormat.format(approval.amount), style: AppTypography.headerSmall.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, ), ), @@ -323,7 +332,7 @@ class _ApprovalCard extends StatelessWidget { Icon( Icons.person_outline, size: 16, - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), const SizedBox(width: 4), Expanded( @@ -340,7 +349,7 @@ class _ApprovalCard extends StatelessWidget { Icon( Icons.access_time, size: 16, - color: AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), const SizedBox(width: 4), Text( diff --git a/lib/features/finance_workflow/presentation/widgets/approve_dialog.dart b/lib/features/finance_workflow/presentation/widgets/approve_dialog.dart index 492623f..b7f5cce 100644 --- a/lib/features/finance_workflow/presentation/widgets/approve_dialog.dart +++ b/lib/features/finance_workflow/presentation/widgets/approve_dialog.dart @@ -53,6 +53,7 @@ class _ApproveDialogState extends State { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency); return AlertDialog( @@ -72,9 +73,9 @@ class _ApproveDialogState extends State { Container( padding: const EdgeInsets.all(SpacingTokens.md), decoration: BoxDecoration( - color: AppColors.lightBackground, + color: isDark ? AppColors.surfaceDark : AppColors.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), - border: Border.all(color: AppColors.lightBorder), + border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -88,7 +89,7 @@ class _ApproveDialogState extends State { 'Montant', currencyFormat.format(widget.approval.amount), valueStyle: AppTypography.actionText.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, ), ), @@ -145,7 +146,7 @@ class _ApproveDialogState extends State { label: const Text('Approuver'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.success, - foregroundColor: Colors.white, + foregroundColor: AppColors.onPrimary, ), ), ], diff --git a/lib/features/finance_workflow/presentation/widgets/reject_dialog.dart b/lib/features/finance_workflow/presentation/widgets/reject_dialog.dart index ab1af4d..559c381 100644 --- a/lib/features/finance_workflow/presentation/widgets/reject_dialog.dart +++ b/lib/features/finance_workflow/presentation/widgets/reject_dialog.dart @@ -53,6 +53,7 @@ class _RejectDialogState extends State { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency); return AlertDialog( @@ -75,9 +76,9 @@ class _RejectDialogState extends State { Container( padding: const EdgeInsets.all(SpacingTokens.md), decoration: BoxDecoration( - color: AppColors.lightBackground, + color: isDark ? AppColors.surfaceDark : AppColors.surface, borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), - border: Border.all(color: AppColors.lightBorder), + border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -91,7 +92,7 @@ class _RejectDialogState extends State { 'Montant', currencyFormat.format(widget.approval.amount), valueStyle: AppTypography.actionText.copyWith( - color: AppColors.primaryGreen, + color: AppColors.primary, fontWeight: FontWeight.bold, ), ), @@ -141,7 +142,7 @@ class _RejectDialogState extends State { label: const Text('Rejeter'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.error, - foregroundColor: Colors.white, + foregroundColor: AppColors.onError, ), ), ], diff --git a/lib/features/help/presentation/pages/help_support_page.dart b/lib/features/help/presentation/pages/help_support_page.dart index 45c412c..0a9f3d8 100644 --- a/lib/features/help/presentation/pages/help_support_page.dart +++ b/lib/features/help/presentation/pages/help_support_page.dart @@ -39,40 +39,46 @@ class _HelpSupportPageState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - appBar: const UFAppBar(title: 'AIDE & SUPPORT'), - body: SingleChildScrollView( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Header harmonisé - _buildHeader(), - const SizedBox(height: 8), + appBar: UFAppBar( + title: 'Aide & Support', + moduleGradient: ModuleColors.supportGradient, + ), + body: SafeArea( + top: false, + child: SingleChildScrollView( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header harmonisé + _buildHeader(), + const SizedBox(height: 8), - // Barre de recherche - _buildSearchSection(), - const SizedBox(height: 8), + // Barre de recherche + _buildSearchSection(), + const SizedBox(height: 8), - // Actions rapides - _buildQuickActionsSection(), - const SizedBox(height: 8), + // Actions rapides + _buildQuickActionsSection(), + const SizedBox(height: 8), - // Catégories FAQ - _buildCategoriesSection(), - const SizedBox(height: 8), + // Catégories FAQ + _buildCategoriesSection(), + const SizedBox(height: 8), - // FAQ - _buildFAQSection(), - const SizedBox(height: 8), + // FAQ + _buildFAQSection(), + const SizedBox(height: 8), - // Guides et tutoriels - _buildGuidesSection(), - const SizedBox(height: 8), + // Guides et tutoriels + _buildGuidesSection(), + const SizedBox(height: 8), - // Contact support - _buildContactSection(), - const SizedBox(height: 80), - ], + // Contact support + _buildContactSection(), + const SizedBox(height: 80), + ], + ), ), ), ); @@ -86,12 +92,12 @@ class _HelpSupportPageState extends State { Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: AppColors.primaryGreen.withOpacity(0.1), + color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.help_outline, - color: AppColors.primaryGreen, + color: AppColors.primary, size: 32, ), ), @@ -113,13 +119,17 @@ class _HelpSupportPageState extends State { /// Section de recherche Widget _buildSearchSection() { + final isDark = Theme.of(context).brightness == Brightness.dark; + final bgSearch = isDark ? AppColors.surfaceVariantDark : AppColors.surface; + final borderSearch = isDark ? AppColors.borderDark : AppColors.border; + final iconColor = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; return CoreCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - const Icon(Icons.search, color: AppColors.primaryGreen, size: 18), + const Icon(Icons.search, color: AppColors.primary, size: 18), const SizedBox(width: 8), Text( 'RECHERCHER DANS L\'AIDE', @@ -130,9 +140,9 @@ class _HelpSupportPageState extends State { const SizedBox(height: 12), Container( decoration: BoxDecoration( - color: AppColors.lightSurface, + color: bgSearch, borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColors.lightBorder), + border: Border.all(color: borderSearch), ), child: TextField( controller: _searchController, @@ -141,14 +151,14 @@ class _HelpSupportPageState extends State { decoration: InputDecoration( hintText: 'Une question, un mot-clé...', hintStyle: AppTypography.subtitleSmall, - prefixIcon: const Icon(Icons.search, color: AppColors.textSecondaryLight, size: 18), + prefixIcon: Icon(Icons.search, color: iconColor, size: 18), suffixIcon: _searchQuery.isNotEmpty ? IconButton( onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, - icon: const Icon(Icons.clear, color: AppColors.textSecondaryLight, size: 18), + icon: Icon(Icons.clear, color: iconColor, size: 18), ) : null, border: InputBorder.none, @@ -180,7 +190,7 @@ class _HelpSupportPageState extends State { 'CHAT', 'Support Direct', Icons.chat_bubble_outline, - AppColors.primaryGreen, + AppColors.primary, () => _startLiveChat(), ), ), @@ -286,16 +296,16 @@ class _HelpSupportPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent, + color: isSelected ? AppColors.primary.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(4), border: Border.all( - color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder, + color: isSelected ? AppColors.primary : AppColors.border, ), ), child: Text( label.toUpperCase(), style: AppTypography.badgeText.copyWith( - color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight, + color: isSelected ? AppColors.primary : AppColors.textSecondary, fontSize: 9, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), @@ -338,11 +348,13 @@ class _HelpSupportPageState extends State { ), leading: Icon( faq['icon'] as IconData, - color: AppColors.primaryGreen, + color: AppColors.primary, size: 18, ), - iconColor: AppColors.primaryGreen, - collapsedIconColor: AppColors.textSecondaryLight, + iconColor: AppColors.primary, + collapsedIconColor: Theme.of(context).brightness == Brightness.dark + ? AppColors.textSecondaryDark + : AppColors.textSecondary, shape: const RoundedRectangleBorder(side: BorderSide.none), children: [ Padding( @@ -370,7 +382,7 @@ class _HelpSupportPageState extends State { ), ), _buildGuideItem('Introduction', 'Démarrer avec UnionFlow', Icons.play_circle_outline, AppColors.success, () => _openGuide('getting-started')), - _buildGuideItem('Membres', 'Gérer vos adhérents', Icons.people_outline, AppColors.primaryGreen, () => _openGuide('members')), + _buildGuideItem('Membres', 'Gérer vos adhérents', Icons.people_outline, AppColors.primary, () => _openGuide('members')), _buildGuideItem('Organisations', 'Structures & Syndicats', Icons.business_outlined, AppColors.info, () => _openGuide('organizations')), ], ); @@ -394,7 +406,13 @@ class _HelpSupportPageState extends State { ], ), ), - const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 16), + Icon( + Icons.chevron_right, + color: Theme.of(context).brightness == Brightness.dark + ? AppColors.textSecondaryDark + : AppColors.textSecondary, + size: 16, + ), ], ), ); @@ -402,8 +420,9 @@ class _HelpSupportPageState extends State { /// Section contact Widget _buildContactSection() { + final isDark = Theme.of(context).brightness == Brightness.dark; return CoreCard( - backgroundColor: AppColors.primaryGreen, // Correction: color -> backgroundColor + backgroundColor: AppColors.primary, // Correction: color -> backgroundColor child: Column( children: [ const Icon(Icons.headset_mic_outlined, color: Colors.white, size: 32), @@ -426,8 +445,8 @@ class _HelpSupportPageState extends State { child: UFPrimaryButton( label: 'EMAIL', // Correction: text -> label onPressed: () => _contactByEmail(), - backgroundColor: Colors.white, - textColor: AppColors.primaryGreen, + backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface, + textColor: AppColors.primary, ), ), const SizedBox(width: 12), @@ -436,7 +455,7 @@ class _HelpSupportPageState extends State { label: 'CHAT', // Correction: text -> label onPressed: () => _startLiveChat(), backgroundColor: Colors.white.withOpacity(0.2), - textColor: Colors.white, + textColor: AppColors.onPrimary, ), ), ], @@ -516,8 +535,8 @@ class _HelpSupportPageState extends State { _contactByEmail(); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Envoyer un email'), ), @@ -548,7 +567,7 @@ class _HelpSupportPageState extends State { }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.error, - foregroundColor: Colors.white, + foregroundColor: AppColors.onError, ), child: const Text('Signaler'), ), @@ -578,8 +597,8 @@ class _HelpSupportPageState extends State { _launchUrl('mailto:support@unionflow.com?subject=Demande de fonctionnalité - UnionFlow Mobile'); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Envoyer'), ), @@ -613,8 +632,8 @@ class _HelpSupportPageState extends State { _launchUrl('https://docs.unionflow.com/$guideId'); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Voir en ligne'), ), @@ -644,8 +663,8 @@ class _HelpSupportPageState extends State { _contactByEmail(); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Contacter le support'), ), diff --git a/lib/features/organizations/presentation/pages/create_organization_page.dart b/lib/features/organizations/presentation/pages/create_organization_page.dart index 5eecea5..67cfc57 100644 --- a/lib/features/organizations/presentation/pages/create_organization_page.dart +++ b/lib/features/organizations/presentation/pages/create_organization_page.dart @@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart'; import '../../bloc/org_types_bloc.dart'; import '../../domain/entities/type_reference_entity.dart'; import '../../../../shared/design_system/tokens/app_colors.dart'; +import '../../../../shared/design_system/tokens/module_colors.dart'; +import '../../../../shared/design_system/components/uf_app_bar.dart'; import '../../../../core/di/injection_container.dart'; const List _devises = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES']; @@ -106,12 +108,10 @@ class _CreateOrganizationPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: AppColors.lightBackground, - appBar: AppBar( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, - title: const Text('Nouvelle Organisation'), - elevation: 0, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: UFAppBar( + title: 'Nouvelle Organisation', + moduleGradient: ModuleColors.organisationsGradient, actions: [ TextButton( onPressed: _isFormValid() ? _saveOrganisation : null, @@ -119,7 +119,9 @@ class _CreateOrganizationPageState extends State { ), ], ), - body: BlocListener( + body: SafeArea( + top: false, + child: BlocListener( listener: (context, state) { if (state is OrganizationCreated) { ScaffoldMessenger.of(context).showSnackBar( @@ -128,7 +130,7 @@ class _CreateOrganizationPageState extends State { Navigator.of(context).pop(true); } else if (state is OrganizationsError) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(state.message), backgroundColor: Colors.red), + SnackBar(content: Text(state.message), backgroundColor: AppColors.error), ); } }, @@ -161,20 +163,21 @@ class _CreateOrganizationPageState extends State { ), ), ), + ), ); } Widget _buildSection(String title, IconData icon, List children) { return Container( padding: const EdgeInsets.all(12), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Icon(icon, size: 16, color: AppColors.primaryGreen), + Icon(icon, size: 16, color: AppColors.primary), const SizedBox(width: 6), - Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)), + Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primary)), ]), const SizedBox(height: 10), ...children, @@ -237,9 +240,18 @@ class _CreateOrganizationPageState extends State { onTap: () => _pickDateFondation(context), child: InputDecorator( decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)), - child: Text( - _dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date', - style: TextStyle(color: _dateFondation != null ? AppColors.textPrimaryLight : AppColors.textSecondaryLight), + child: Builder( + builder: (ctx) { + final isDark = Theme.of(ctx).brightness == Brightness.dark; + return Text( + _dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date', + style: TextStyle( + color: _dateFondation != null + ? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary) + : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary), + ), + ); + }, ), ), ), @@ -355,7 +367,7 @@ class _CreateOrganizationPageState extends State { subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)), value: _organisationPublique, onChanged: (v) => setState(() => _organisationPublique = v), - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), SwitchListTile( dense: true, @@ -364,7 +376,7 @@ class _CreateOrganizationPageState extends State { subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)), value: _accepteNouveauxMembres, onChanged: (v) => setState(() => _accepteNouveauxMembres = v), - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), ]; @@ -392,7 +404,7 @@ class _CreateOrganizationPageState extends State { title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)), value: _cotisationObligatoire, onChanged: (v) => setState(() => _cotisationObligatoire = v), - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), if (_cotisationObligatoire) ...[ const SizedBox(height: 8), @@ -451,8 +463,8 @@ class _CreateOrganizationPageState extends State { icon: const Icon(Icons.save), label: const Text('Créer l\'organisation'), style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, padding: const EdgeInsets.symmetric(vertical: 10), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), @@ -466,7 +478,7 @@ class _CreateOrganizationPageState extends State { icon: const Icon(Icons.cancel), label: const Text('Annuler'), style: OutlinedButton.styleFrom( - foregroundColor: AppColors.textSecondaryLight, + foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary, padding: const EdgeInsets.symmetric(vertical: 10), ), ), diff --git a/lib/features/organizations/presentation/pages/edit_organization_page.dart b/lib/features/organizations/presentation/pages/edit_organization_page.dart index 377c4d4..6534073 100644 --- a/lib/features/organizations/presentation/pages/edit_organization_page.dart +++ b/lib/features/organizations/presentation/pages/edit_organization_page.dart @@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart'; import '../../bloc/org_types_bloc.dart'; import '../../domain/entities/type_reference_entity.dart'; import '../../../../shared/design_system/tokens/app_colors.dart'; +import '../../../../shared/design_system/tokens/module_colors.dart'; +import '../../../../shared/design_system/components/uf_app_bar.dart'; import '../../../../core/di/injection_container.dart'; const List _devisesEdit = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES']; @@ -185,12 +187,10 @@ class _EditOrganizationPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: AppColors.lightBackground, - appBar: AppBar( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, - title: const Text('Modifier Organisation'), - elevation: 0, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: UFAppBar( + title: 'Modifier Organisation', + moduleGradient: ModuleColors.organisationsGradient, actions: [ TextButton( onPressed: _hasChanges() ? _saveChanges : null, @@ -212,7 +212,7 @@ class _EditOrganizationPageState extends State { Navigator.of(context).pop(true); } else if (state is OrganizationsError) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(state.message), backgroundColor: Colors.red), + SnackBar(content: Text(state.message), backgroundColor: AppColors.error), ); } }, @@ -254,14 +254,14 @@ class _EditOrganizationPageState extends State { Widget _buildSection(String title, IconData icon, List children) { return Container( padding: const EdgeInsets.all(12), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ - Icon(icon, size: 16, color: AppColors.primaryGreen), + Icon(icon, size: 16, color: AppColors.primary), const SizedBox(width: 6), - Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)), + Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primary)), ]), const SizedBox(height: 10), ...children, @@ -326,9 +326,18 @@ class _EditOrganizationPageState extends State { onTap: () => _pickDateFondation(context), child: InputDecorator( decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)), - child: Text( - _dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date', - style: TextStyle(color: _dateFondation != null ? AppColors.textPrimaryLight : AppColors.textSecondaryLight), + child: Builder( + builder: (ctx) { + final isDark = Theme.of(ctx).brightness == Brightness.dark; + return Text( + _dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date', + style: TextStyle( + color: _dateFondation != null + ? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary) + : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary), + ), + ); + }, ), ), ), @@ -452,7 +461,7 @@ class _EditOrganizationPageState extends State { subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)), value: _organisationPublique, onChanged: (v) => setState(() => _organisationPublique = v), - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), SwitchListTile( dense: true, @@ -461,7 +470,7 @@ class _EditOrganizationPageState extends State { subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)), value: _accepteNouveauxMembres, onChanged: (v) => setState(() => _accepteNouveauxMembres = v), - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), ]; @@ -490,7 +499,7 @@ class _EditOrganizationPageState extends State { title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)), value: _cotisationObligatoire, onChanged: (v) => setState(() => _cotisationObligatoire = v), - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), if (_cotisationObligatoire) ...[ const SizedBox(height: 8), @@ -559,12 +568,15 @@ class _EditOrganizationPageState extends State { ]; Widget _buildReadOnlyRow(IconData icon, String label, String value) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; + final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary; return Row(children: [ - Icon(icon, size: 18, color: AppColors.textSecondaryLight), + Icon(icon, size: 18, color: textSecondary), const SizedBox(width: 10), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight)), - Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, fontWeight: FontWeight.w600)), + Text(label, style: TextStyle(fontSize: 11, color: textSecondary)), + Text(value, style: TextStyle(fontSize: 13, color: textPrimary, fontWeight: FontWeight.w600)), ])), ]); } @@ -578,8 +590,8 @@ class _EditOrganizationPageState extends State { icon: const Icon(Icons.save), label: const Text('Enregistrer les modifications'), style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, padding: const EdgeInsets.symmetric(vertical: 10), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), @@ -592,7 +604,7 @@ class _EditOrganizationPageState extends State { onPressed: _showDiscardDialog, icon: const Icon(Icons.cancel), label: const Text('Annuler'), - style: OutlinedButton.styleFrom(foregroundColor: AppColors.textSecondaryLight, padding: const EdgeInsets.symmetric(vertical: 10)), + style: OutlinedButton.styleFrom(foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary, padding: const EdgeInsets.symmetric(vertical: 10)), ), ), ]); @@ -681,8 +693,8 @@ class _EditOrganizationPageState extends State { TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Continuer l\'édition')), ElevatedButton( onPressed: () { Navigator.of(ctx).pop(); Navigator.of(context).pop(); }, - style: ElevatedButton.styleFrom(backgroundColor: Colors.red), - child: const Text('Abandonner', style: TextStyle(color: Colors.white)), + style: ElevatedButton.styleFrom(backgroundColor: AppColors.error), + child: const Text('Abandonner', style: TextStyle(color: AppColors.onPrimary)), ), ], ), diff --git a/lib/features/settings/presentation/pages/system_settings_page.dart b/lib/features/settings/presentation/pages/system_settings_page.dart index 45741d7..3aa2060 100644 --- a/lib/features/settings/presentation/pages/system_settings_page.dart +++ b/lib/features/settings/presentation/pages/system_settings_page.dart @@ -8,19 +8,34 @@ import '../../data/models/system_metrics_model.dart'; import '../bloc/system_settings_bloc.dart'; import '../bloc/system_settings_event.dart'; import '../bloc/system_settings_state.dart'; +import '../../../backup/presentation/pages/backup_page.dart'; /// Page Paramètres Système - UnionFlow Mobile -/// -/// Page complète de gestion des paramètres système avec configuration globale, -/// maintenance, monitoring, sécurité et administration avancée. -class SystemSettingsPage extends StatefulWidget { +/// +/// Le BlocProvider est créé ici (StatelessWidget) afin que this.context +/// dans _SystemSettingsViewState soit toujours sous le provider. +class SystemSettingsPage extends StatelessWidget { const SystemSettingsPage({super.key}); @override - State createState() => _SystemSettingsPageState(); + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => sl() + ..add(LoadSystemConfig()) + ..add(LoadSystemMetrics()), + child: const _SystemSettingsView(), + ); + } } -class _SystemSettingsPageState extends State +class _SystemSettingsView extends StatefulWidget { + const _SystemSettingsView(); + + @override + State<_SystemSettingsView> createState() => _SystemSettingsViewState(); +} + +class _SystemSettingsViewState extends State<_SystemSettingsView> with TickerProviderStateMixin { late TabController _tabController; @@ -32,17 +47,19 @@ class _SystemSettingsPageState extends State bool _debugMode = false; bool _analyticsEnabled = true; bool _crashReportingEnabled = true; - bool _autoBackupEnabled = true; bool _sslEnforced = true; bool _apiLoggingEnabled = false; bool _performanceMonitoring = true; + bool _alertCpuHigh = true; + bool _alertLowMemory = true; + bool _alertDiskFull = true; + bool _alertFailedConnections = false; + String _selectedLogLevel = 'INFO'; - String _selectedBackupFrequency = 'Quotidien'; String _selectedCacheStrategy = 'Intelligent'; final List _logLevels = ['DEBUG', 'INFO', 'WARN', 'ERROR']; - final List _backupFrequencies = ['Temps réel', 'Horaire', 'Quotidien', 'Hebdomadaire']; final List _cacheStrategies = ['Agressif', 'Intelligent', 'Conservateur', 'Désactivé']; @override @@ -65,38 +82,44 @@ class _SystemSettingsPageState extends State // Accès réservé aux super administrateurs (configuration système globale) if (authState is! AuthAuthenticated || authState.effectiveRole != UserRole.superAdmin) { return Scaffold( - backgroundColor: AppColors.lightBackground, - appBar: AppBar( - title: const Text('Paramètres Système'), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), - tooltip: 'Retour', - ), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: UFAppBar( + title: 'Paramètres Système', + moduleGradient: ModuleColors.parametresGradient, ), body: Center( child: Padding( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.all(SpacingTokens.xxxl), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.lock_outline, size: 64, color: AppColors.textSecondaryLight.withOpacity(0.5)), - const SizedBox(height: 16), + Icon( + Icons.lock_outline, + size: 64, + color: (Theme.of(context).brightness == Brightness.dark + ? AppColors.textSecondaryDark + : AppColors.textSecondary).withOpacity(0.5), + ), + const SizedBox(height: SpacingTokens.xl), Text( 'Accès réservé', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, - color: AppColors.textPrimaryLight, + color: Theme.of(context).brightness == Brightness.dark + ? AppColors.textPrimaryDark + : AppColors.textPrimary, ), textAlign: TextAlign.center, ), - const SizedBox(height: 8), + const SizedBox(height: SpacingTokens.md), Text( 'Les paramètres système sont réservés aux administrateurs plateforme.', style: TextStyle( fontSize: 14, - color: AppColors.textSecondaryLight, + color: Theme.of(context).brightness == Brightness.dark + ? AppColors.textSecondaryDark + : AppColors.textSecondary, ), textAlign: TextAlign.center, ), @@ -107,16 +130,27 @@ class _SystemSettingsPageState extends State ); } - return BlocProvider( - create: (_) => sl() - ..add(LoadSystemConfig()) - ..add(LoadSystemMetrics()), - child: BlocConsumer( + return BlocConsumer( listener: (context, state) { if (state is SystemMetricsLoaded) { setState(() { _metrics = state.metrics; }); + } else if (state is SystemConfigLoaded) { + setState(() { + _maintenanceMode = state.config.maintenanceMode ?? false; + _analyticsEnabled = state.config.metricsCollectionEnabled ?? true; + _crashReportingEnabled = state.config.performanceOptimizationEnabled ?? true; + _sslEnforced = state.config.twoFactorAuthEnabled ?? true; + _apiLoggingEnabled = state.config.detailedLoggingEnabled ?? false; + _performanceMonitoring = state.config.realTimeMonitoringEnabled ?? true; + _alertCpuHigh = state.config.cpuHighAlertEnabled ?? true; + _alertLowMemory = state.config.memoryLowAlertEnabled ?? true; + _alertDiskFull = state.config.criticalErrorAlertEnabled ?? true; + _alertFailedConnections = state.config.connectionFailureAlertEnabled ?? false; + if (state.config.logLevel != null) _selectedLogLevel = state.config.logLevel!; + // Backup config retiré — géré dans BackupPage + }); } else if (state is SystemSettingsSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -137,226 +171,153 @@ class _SystemSettingsPageState extends State }, builder: (context, state) { return Scaffold( - backgroundColor: AppColors.lightBackground, - body: Column( - children: [ - // Header harmonisé - _buildHeader(), - - // Onglets - _buildTabBar(), - - // Contenu des onglets - Expanded( - child: TabBarView( - controller: _tabController, - children: [ - _buildGeneralTab(), - _buildSecurityTab(), - _buildPerformanceTab(), - _buildMaintenanceTab(), - _buildMonitoringTab(), - ], - ), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: UFAppBar( + title: 'Paramètres Système', + moduleGradient: ModuleColors.parametresGradient, + automaticallyImplyLeading: true, + actions: [ + IconButton( + icon: const Icon(Icons.monitor_heart, color: AppColors.onGradient), + onPressed: () => _showSystemStatus(), + tooltip: 'État du système', + ), + IconButton( + icon: const Icon(Icons.download, color: AppColors.onGradient), + onPressed: () => _exportSystemConfig(), + tooltip: 'Exporter la configuration', ), ], ), - ); - }, - ), - ); - }, - ); - } - - /// Header harmonisé avec indicateurs système - Widget _buildHeader() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: SpacingTokens.sm, vertical: SpacingTokens.xs), - padding: const EdgeInsets.all(SpacingTokens.md), - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [AppColors.brandGreen, AppColors.primaryGreen], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(SpacingTokens.xl), - boxShadow: [ - BoxShadow( - color: AppColors.primaryGreen.withOpacity(0.3), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - ), - child: Column( - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.settings, - color: Colors.white, - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Paramètres Système', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - Text( - 'Configuration globale et administration', - style: TextStyle( - fontSize: 14, - color: Colors.white.withOpacity(0.8), - ), - ), - ], - ), - ), - Row( + body: SafeArea( + top: false, + child: Column( children: [ - Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), - child: IconButton( - onPressed: () => _showSystemStatus(), - icon: const Icon( - Icons.monitor_heart, - color: Colors.white, - ), - tooltip: 'État du système', - ), - ), - const SizedBox(width: 8), - Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), - child: IconButton( - onPressed: () => _exportSystemConfig(), - icon: const Icon( - Icons.download, - color: Colors.white, - ), - tooltip: 'Exporter la configuration', + _buildSystemIndicators(), + _buildTabBar(), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + _buildGeneralTab(), + _buildSecurityTab(), + _buildPerformanceTab(), + _buildMaintenanceTab(), + _buildMonitoringTab(), + ], ), ), ], ), - ], - ), - const SizedBox(height: 8), + ), + ); + }, + ); + }, + ); + } - // Indicateurs système - Row( - children: [ - Expanded( - child: _buildSystemIndicator( - 'Statut', - _metrics?.systemStatus ?? (_maintenanceMode ? 'MAINTENANCE' : 'OPERATIONAL'), - _maintenanceMode ? Icons.build : Icons.check_circle, - _maintenanceMode ? Colors.orange : Colors.green, - ), - ), - const SizedBox(width: 12), - Expanded( - child: _buildSystemIndicator( - 'Charge CPU', - _metrics != null ? '${_metrics!.cpuUsagePercent?.toStringAsFixed(0) ?? '0'}%' : '-', - Icons.memory, - Colors.blue, - ), - ), - const SizedBox(width: 12), - Expanded( - child: _buildSystemIndicator( - 'Utilisateurs', - _metrics?.activeUsersCount?.toString() ?? '-', - Icons.people, - Colors.purple, - ), - ), - ], + /// Bande d'indicateurs système (sous l'AppBar) + Widget _buildSystemIndicators() { + final isDark = Theme.of(context).brightness == Brightness.dark; + return Container( + margin: const EdgeInsets.fromLTRB(SpacingTokens.lg, SpacingTokens.md, SpacingTokens.lg, 0), + padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.md, vertical: SpacingTokens.md), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), + border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border), + boxShadow: const [BoxShadow(color: AppColors.shadow, blurRadius: 8, offset: Offset(0, 2))], + ), + child: Row( + children: [ + Expanded( + child: _buildSystemIndicator( + 'Statut', + _metrics?.systemStatus ?? (_maintenanceMode ? 'MAINTENANCE' : 'OPERATIONAL'), + _maintenanceMode ? Icons.build : Icons.check_circle, + _maintenanceMode ? AppColors.warning : AppColors.success, + ), + ), + Container(width: 1, height: 40, color: isDark ? AppColors.borderDark : AppColors.border), + Expanded( + child: _buildSystemIndicator( + 'Charge CPU', + _metrics != null ? '${_metrics!.cpuUsagePercent?.toStringAsFixed(0) ?? '0'}%' : '-', + Icons.memory, + AppColors.info, + ), + ), + Container(width: 1, height: 40, color: isDark ? AppColors.borderDark : AppColors.border), + Expanded( + child: _buildSystemIndicator( + 'Utilisateurs', + _metrics?.activeUsersCount?.toString() ?? '-', + Icons.people, + AppColors.accent, + ), ), ], ), ); } - /// Indicateur système + /// Indicateur système — conçu pour la bande de KPIs dans le body Widget _buildSystemIndicator(String label, String value, IconData icon, Color color) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - Icon( - icon, - color: Colors.white, - size: 20, + final isDark = Theme.of(context).brightness == Brightness.dark; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(SpacingTokens.md), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + shape: BoxShape.circle, ), - const SizedBox(height: 4), - Text( - value, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + child: Icon(icon, color: color, size: 18), + ), + const SizedBox(height: SpacingTokens.sm), + Text( + value, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.bold, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), - Text( - label, - style: TextStyle( - fontSize: 10, - color: Colors.white.withOpacity(0.8), - ), + ), + Text( + label, + style: TextStyle( + fontSize: 10, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), - ], - ), + ), + ], ); } /// Barre d'onglets Widget _buildTabBar() { return Container( - margin: const EdgeInsets.symmetric(horizontal: 12), + margin: const EdgeInsets.fromLTRB(SpacingTokens.lg, SpacingTokens.md, SpacingTokens.lg, 0), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), + boxShadow: const [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: AppColors.shadow, blurRadius: 10, - offset: const Offset(0, 2), + offset: Offset(0, 2), ), ], ), child: TabBar( controller: _tabController, - labelColor: AppColors.primaryGreen, - unselectedLabelColor: AppColors.textSecondaryLight, - indicatorColor: AppColors.primaryGreen, + labelColor: AppColors.primary, + unselectedLabelColor: Theme.of(context).brightness == Brightness.dark + ? AppColors.textSecondaryDark + : AppColors.textSecondary, + indicatorColor: AppColors.primary, indicatorWeight: 3, indicatorSize: TabBarIndicatorSize.tab, labelStyle: const TextStyle( @@ -396,10 +357,10 @@ class _SystemSettingsPageState extends State /// Onglet général Widget _buildGeneralTab() { return SingleChildScrollView( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( children: [ - const SizedBox(height: 8), + const SizedBox(height: SpacingTokens.xl), // Configuration de base _buildSettingsSection( @@ -413,6 +374,7 @@ class _SystemSettingsPageState extends State _maintenanceMode, (value) { setState(() => _maintenanceMode = value); + context.read().add(UpdateSystemConfig({'maintenanceMode': value})); _showMaintenanceModeDialog(value); }, isWarning: true, @@ -423,6 +385,7 @@ class _SystemSettingsPageState extends State _debugMode, (value) { setState(() => _debugMode = value); + context.read().add(UpdateSystemConfig({'detailedLoggingEnabled': value})); _showSuccessSnackBar('Mode debug ${value ? 'activé' : 'désactivé'}'); }, ), @@ -436,7 +399,7 @@ class _SystemSettingsPageState extends State ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Gestion des données _buildSettingsSection( @@ -464,13 +427,13 @@ class _SystemSettingsPageState extends State 'Optimiser la base de données', 'Réorganiser et compacter la base de données', Icons.tune, - AppColors.primaryGreen, + AppColors.primary, () => _optimizeDatabase(), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Configuration réseau _buildSettingsSection( @@ -500,10 +463,10 @@ class _SystemSettingsPageState extends State /// Onglet sécurité Widget _buildSecurityTab() { return SingleChildScrollView( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( children: [ - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Sécurité réseau _buildSettingsSection( @@ -517,6 +480,7 @@ class _SystemSettingsPageState extends State _sslEnforced, (value) { setState(() => _sslEnforced = value); + context.read().add(UpdateSystemConfig({'twoFactorAuthEnabled': value})); _showSuccessSnackBar('SSL ${value ? 'obligatoire' : 'optionnel'}'); }, ), @@ -526,6 +490,7 @@ class _SystemSettingsPageState extends State _apiLoggingEnabled, (value) { setState(() => _apiLoggingEnabled = value); + context.read().add(UpdateSystemConfig({'detailedLoggingEnabled': value})); _showSuccessSnackBar('Logs API ${value ? 'activés' : 'désactivés'}'); }, ), @@ -539,7 +504,7 @@ class _SystemSettingsPageState extends State ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Authentification _buildSettingsSection( @@ -559,20 +524,20 @@ class _SystemSettingsPageState extends State 'Forcer la déconnexion globale', 'Déconnecter tous les utilisateurs', Icons.logout, - Colors.red, + AppColors.error, () => _forceGlobalLogout(), ), _buildActionSetting( 'Réinitialiser les sessions', 'Nettoyer les sessions expirées', Icons.refresh, - AppColors.primaryGreen, + AppColors.primary, () => _resetSessions(), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Audit et conformité _buildSettingsSection( @@ -584,7 +549,7 @@ class _SystemSettingsPageState extends State 'Générer rapport d\'audit', 'Créer un rapport complet des activités', Icons.assessment, - AppColors.primaryGreen, + AppColors.primary, () => _generateAuditReport(), ), _buildActionSetting( @@ -613,10 +578,10 @@ class _SystemSettingsPageState extends State /// Onglet performance Widget _buildPerformanceTab() { return SingleChildScrollView( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( children: [ - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Monitoring système _buildSettingsSection( @@ -630,6 +595,7 @@ class _SystemSettingsPageState extends State _performanceMonitoring, (value) { setState(() => _performanceMonitoring = value); + context.read().add(UpdateSystemConfig({'realTimeMonitoringEnabled': value})); _showSuccessSnackBar('Monitoring ${value ? 'activé' : 'désactivé'}'); }, ), @@ -639,6 +605,7 @@ class _SystemSettingsPageState extends State _crashReportingEnabled, (value) { setState(() => _crashReportingEnabled = value); + context.read().add(UpdateSystemConfig({'performanceOptimizationEnabled': value})); _showSuccessSnackBar('Rapports de crash ${value ? 'activés' : 'désactivés'}'); }, ), @@ -648,48 +615,17 @@ class _SystemSettingsPageState extends State _analyticsEnabled, (value) { setState(() => _analyticsEnabled = value); + context.read().add(UpdateSystemConfig({'metricsCollectionEnabled': value})); _showSuccessSnackBar('Analytics ${value ? 'activées' : 'désactivées'}'); }, ), ], ), - const SizedBox(height: 16), + // Note : les "Métriques en temps réel" (CPU/RAM/Disque/Réseau) sont + // disponibles dans Logs & Monitoring (consultation = read-only). - // Métriques en temps réel - _buildSettingsSection( - 'Métriques en temps réel', - 'État actuel du système', - Icons.speed, - [ - _buildMetricItem( - 'CPU', - _metrics != null ? '${_metrics!.cpuUsagePercent?.toStringAsFixed(1) ?? '0'}%' : '-', - Icons.memory, - Colors.blue, - ), - _buildMetricItem( - 'RAM', - _metrics != null ? '${_metrics!.memoryUsagePercent?.toStringAsFixed(1) ?? '0'}%' : '-', - Icons.storage, - Colors.green, - ), - _buildMetricItem( - 'Disque', - _metrics != null ? '${_metrics!.diskUsagePercent?.toStringAsFixed(1) ?? '0'}%' : '-', - Icons.storage, - Colors.orange, - ), - _buildMetricItem( - 'Réseau', - _metrics?.networkInFormatted ?? '0 B/s', - Icons.network_check, - Colors.purple, - ), - ], - ), - - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Optimisation _buildSettingsSection( @@ -701,7 +637,7 @@ class _SystemSettingsPageState extends State 'Analyser les performances', 'Scanner les goulots d\'étranglement', Icons.analytics, - AppColors.primaryGreen, + AppColors.primary, () => _analyzePerformance(), ), _buildActionSetting( @@ -715,7 +651,7 @@ class _SystemSettingsPageState extends State 'Redémarrer les services', 'Relancer tous les services système', Icons.restart_alt, - Colors.red, + AppColors.error, () => _restartServices(), ), ], @@ -730,51 +666,30 @@ class _SystemSettingsPageState extends State /// Onglet maintenance Widget _buildMaintenanceTab() { return SingleChildScrollView( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( children: [ - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), - // Sauvegarde et restauration + // Sauvegarde & restauration — gérée intégralement dans l'écran + // dédié BackupPage (Drawer → Système → Sauvegarde & Restauration). + // On garde uniquement un raccourci pour éviter la duplication. _buildSettingsSection( - 'Sauvegarde et restauration', - 'Gestion des données critiques', + 'Sauvegarde & restauration', + 'Configuration et historique des sauvegardes', Icons.backup, [ - _buildSwitchSetting( - 'Sauvegarde automatique', - 'Sauvegarder automatiquement les données', - _autoBackupEnabled, - (value) { - setState(() => _autoBackupEnabled = value); - _showSuccessSnackBar('Sauvegarde auto ${value ? 'activée' : 'désactivée'}'); - }, - ), - _buildDropdownSetting( - 'Fréquence de sauvegarde', - 'Intervalle entre les sauvegardes', - _selectedBackupFrequency, - _backupFrequencies, - (value) => setState(() => _selectedBackupFrequency = value!), - ), _buildActionSetting( - 'Créer une sauvegarde maintenant', - 'Sauvegarder immédiatement toutes les données', - Icons.save, - AppColors.success, - () => _createBackup(), - ), - _buildActionSetting( - 'Restaurer depuis une sauvegarde', - 'Récupérer des données depuis un fichier', - Icons.restore, - AppColors.primaryGreen, + 'Ouvrir Sauvegarde & Restauration', + 'Planification, sauvegardes manuelles et restauration', + Icons.open_in_new, + ModuleColors.backup, () => _restoreFromBackup(), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Maintenance système _buildSettingsSection( @@ -794,20 +709,20 @@ class _SystemSettingsPageState extends State 'Planifier une maintenance', 'Programmer une fenêtre de maintenance', Icons.schedule, - AppColors.primaryGreen, + AppColors.primary, () => _scheduleMaintenance(), ), _buildActionSetting( 'Maintenance d\'urgence', 'Lancer immédiatement une maintenance', Icons.warning, - Colors.red, + AppColors.error, () => _emergencyMaintenance(), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Mise à jour système _buildSettingsSection( @@ -827,14 +742,14 @@ class _SystemSettingsPageState extends State 'Vérifier les mises à jour', 'Rechercher les nouvelles versions', Icons.refresh, - AppColors.primaryGreen, + AppColors.primary, () => _checkUpdates(), ), _buildActionSetting( 'Historique des mises à jour', 'Voir les versions précédentes', Icons.history, - AppColors.primaryGreen, + AppColors.primary, () => _showUpdateHistory(), ), ], @@ -849,10 +764,10 @@ class _SystemSettingsPageState extends State /// Onglet monitoring Widget _buildMonitoringTab() { return SingleChildScrollView( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), child: Column( children: [ - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), // Alertes système _buildSettingsSection( @@ -863,108 +778,40 @@ class _SystemSettingsPageState extends State _buildAlertItem( 'CPU élevé', 'Alerte si CPU > 80% pendant 5 min', - true, + _alertCpuHigh, AppColors.warning, + onChanged: (value) => setState(() => _alertCpuHigh = value), ), _buildAlertItem( 'Mémoire faible', 'Alerte si RAM < 20% disponible', - true, + _alertLowMemory, AppColors.warning, + onChanged: (value) => setState(() => _alertLowMemory = value), ), _buildAlertItem( 'Disque plein', 'Alerte si stockage > 90%', - true, - Colors.red, + _alertDiskFull, + AppColors.error, + onChanged: (value) => setState(() => _alertDiskFull = value), ), _buildAlertItem( 'Connexions échouées', 'Alerte si > 100 échecs/min', - false, - AppColors.primaryGreen, + _alertFailedConnections, + AppColors.primary, + onChanged: (value) => setState(() => _alertFailedConnections = value), ), ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), - // Logs système - _buildSettingsSection( - 'Logs système', - 'Journaux d\'activité', - Icons.article, - [ - _buildLogItem( - 'Erreurs critiques', - _metrics?.criticalErrorsCount?.toString() ?? '0', - Colors.red, - ), - _buildLogItem( - 'Avertissements', - _metrics?.warningsCount?.toString() ?? '0', - Colors.orange, - ), - _buildLogItem( - 'Informations', - _metrics?.infoLogsCount?.toString() ?? '0', - Colors.blue, - ), - _buildLogItem( - 'Debug', - _metrics?.debugLogsCount?.toString() ?? '0', - Colors.grey, - ), - _buildActionSetting( - 'Voir tous les logs', - 'Ouvrir la console de logs complète', - Icons.terminal, - AppColors.primaryGreen, - () => _viewAllLogs(), - ), - _buildActionSetting( - 'Exporter les logs', - 'Télécharger les logs pour analyse', - Icons.download, - AppColors.success, - () => _exportLogs(), - ), - ], - ), - - const SizedBox(height: 16), - - // Statistiques d'utilisation - _buildSettingsSection( - 'Statistiques d\'utilisation', - 'Métriques d\'activité', - Icons.bar_chart, - [ - _buildStatItem( - 'Utilisateurs actifs (24h)', - _metrics?.activeUsersCount?.toString() ?? '0', - ), - _buildStatItem( - 'Requêtes API (1h)', - _metrics?.apiRequestsLastHour?.toString() ?? '0', - ), - _buildStatItem( - 'Mémoire utilisée', - _metrics?.usedMemoryFormatted ?? '0 B', - ), - _buildStatItem( - 'Temps de réponse moyen', - _metrics != null ? '${_metrics!.averageResponseTimeMs?.toStringAsFixed(0) ?? "0"}ms' : '0ms', - ), - _buildActionSetting( - 'Rapport détaillé', - 'Générer un rapport complet d\'utilisation', - Icons.assessment, - AppColors.primaryGreen, - () => _generateUsageReport(), - ), - ], - ), + // Note : les compteurs de logs (Erreurs/Warnings/Infos/Debug), + // les statistiques d'utilisation et la consultation des logs + // sont accessibles dans Logs & Monitoring (CONSULTATION). + // Les rapports d'utilisation détaillés sont dans Plus → Rapports & Analytics. const SizedBox(height: 80), ], @@ -982,15 +829,15 @@ class _SystemSettingsPageState extends State List children, ) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(SpacingTokens.xl), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), + boxShadow: const [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: AppColors.shadow, blurRadius: 10, - offset: const Offset(0, 2), + offset: Offset(0, 2), ), ], ), @@ -999,8 +846,8 @@ class _SystemSettingsPageState extends State children: [ Row( children: [ - Icon(icon, color: Colors.grey[600], size: 20), - const SizedBox(width: 8), + Icon(icon, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 20), + const SizedBox(width: SpacingTokens.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1010,14 +857,14 @@ class _SystemSettingsPageState extends State style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Colors.grey[800], + color: Theme.of(context).colorScheme.onSurface, ), ), Text( subtitle, style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], @@ -1025,9 +872,9 @@ class _SystemSettingsPageState extends State ), ], ), - const SizedBox(height: 16), + const SizedBox(height: SpacingTokens.xl), ...children.map((child) => Padding( - padding: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.only(bottom: SpacingTokens.lg), child: child, )), ], @@ -1043,20 +890,21 @@ class _SystemSettingsPageState extends State Function(bool) onChanged, { bool isWarning = false, }) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( - color: isWarning ? Colors.orange.withOpacity(0.05) : Colors.grey[50], - borderRadius: BorderRadius.circular(12), - border: isWarning ? Border.all(color: Colors.orange.withOpacity(0.3)) : null, + color: isWarning ? AppColors.warningContainer : Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), + border: isWarning ? Border.all(color: AppColors.warningUI) : null, ), child: Row( children: [ if (isWarning) const Icon(Icons.warning, color: AppColors.warning, size: 20) else - const Icon(Icons.toggle_on, color: AppColors.primaryGreen, size: 20), - const SizedBox(width: 12), + const Icon(Icons.toggle_on, color: AppColors.primary, size: 20), + const SizedBox(width: SpacingTokens.lg), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1066,14 +914,14 @@ class _SystemSettingsPageState extends State style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: isWarning ? AppColors.warning : AppColors.textPrimaryLight, + color: isWarning ? AppColors.warning : (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary), ), ), Text( subtitle, style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], @@ -1082,7 +930,7 @@ class _SystemSettingsPageState extends State Switch( value: value, onChanged: onChanged, - activeColor: isWarning ? AppColors.warning : AppColors.primaryGreen, + activeColor: isWarning ? AppColors.warning : AppColors.primary, ), ], ), @@ -1097,18 +945,19 @@ class _SystemSettingsPageState extends State List options, Function(String?) onChanged, ) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - const Icon(Icons.arrow_drop_down, color: AppColors.primaryGreen, size: 20), + const Icon(Icons.arrow_drop_down, color: AppColors.primary, size: 20), const SizedBox(width: SpacingTokens.lg), Expanded( child: Column( @@ -1116,17 +965,17 @@ class _SystemSettingsPageState extends State children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), Text( subtitle, style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], @@ -1134,13 +983,13 @@ class _SystemSettingsPageState extends State ), ], ), - const SizedBox(height: 8), + const SizedBox(height: SpacingTokens.md), Container( - padding: const EdgeInsets.symmetric(horizontal: 12), + padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey[300]!), + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border), ), child: DropdownButtonHideUnderline( child: DropdownButton( @@ -1171,18 +1020,18 @@ class _SystemSettingsPageState extends State ) { return InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), child: Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( color: color.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), border: Border.all(color: color.withOpacity(0.1)), ), child: Row( children: [ Icon(icon, color: color, size: 20), - const SizedBox(width: 12), + const SizedBox(width: SpacingTokens.lg), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1199,13 +1048,13 @@ class _SystemSettingsPageState extends State subtitle, style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], ), ), - Icon(Icons.arrow_forward_ios, color: Colors.grey[400], size: 16), + Icon(Icons.arrow_forward_ios, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 16), ], ), ), @@ -1214,23 +1063,24 @@ class _SystemSettingsPageState extends State /// Paramètre d'information Widget _buildInfoSetting(String title, String value) { + final isDark = Theme.of(context).brightness == Brightness.dark; return Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), ), child: Row( children: [ - Icon(Icons.info_outline, color: Colors.grey[600], size: 20), - const SizedBox(width: 12), + Icon(Icons.info_outline, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 20), + const SizedBox(width: SpacingTokens.lg), Expanded( child: Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), ), @@ -1238,7 +1088,7 @@ class _SystemSettingsPageState extends State value, style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, fontWeight: FontWeight.w600, ), ), @@ -1247,68 +1097,25 @@ class _SystemSettingsPageState extends State ); } - /// Élément de métrique - Widget _buildMetricItem(String title, String value, IconData icon, Color color) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: color.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: color.withOpacity(0.1)), - ), - child: Row( - children: [ - Icon(icon, color: color, size: 20), - const SizedBox(width: 12), - Expanded( - child: Text( - title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.textPrimaryLight, - ), - ), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - value, - style: const TextStyle( - fontSize: 12, - color: Colors.white, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - ); - } - /// Élément d'alerte - Widget _buildAlertItem(String title, String subtitle, bool enabled, Color color) { + Widget _buildAlertItem(String title, String subtitle, bool enabled, Color color, {Function(bool)? onChanged}) { return Container( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( - color: enabled ? color.withOpacity(0.05) : Colors.grey[50], - borderRadius: BorderRadius.circular(12), + color: enabled ? color.withOpacity(0.05) : Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), border: Border.all( - color: enabled ? color.withOpacity(0.3) : Colors.grey[300]!, + color: enabled ? color.withOpacity(0.3) : Theme.of(context).colorScheme.outlineVariant, ), ), child: Row( children: [ Icon( enabled ? Icons.notifications_active : Icons.notifications_off, - color: enabled ? color : Colors.grey[600], + color: enabled ? color : Theme.of(context).colorScheme.onSurfaceVariant, size: 20, ), - const SizedBox(width: 12), + const SizedBox(width: SpacingTokens.lg), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1318,14 +1125,14 @@ class _SystemSettingsPageState extends State style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: enabled ? color : Colors.grey[700], + color: enabled ? color : Theme.of(context).colorScheme.onSurface, ), ), Text( subtitle, style: TextStyle( fontSize: 12, - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], @@ -1333,7 +1140,10 @@ class _SystemSettingsPageState extends State ), Switch( value: enabled, - onChanged: (value) => _showSuccessSnackBar('Alerte ${value ? 'activée' : 'désactivée'}'), + onChanged: (value) { + onChanged?.call(value); + _showSuccessSnackBar('Alerte ${value ? 'activée' : 'désactivée'}'); + }, activeColor: color, ), ], @@ -1341,84 +1151,6 @@ class _SystemSettingsPageState extends State ); } - /// Élément de log - Widget _buildLogItem(String title, String count, Color color) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: color.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: color.withOpacity(0.1)), - ), - child: Row( - children: [ - Icon(Icons.circle, color: color, size: 12), - const SizedBox(width: 12), - Expanded( - child: Text( - title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.textPrimaryLight, - ), - ), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(12), - ), - child: Text( - count, - style: const TextStyle( - fontSize: 11, - color: Colors.white, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - ); - } - - /// Élément de statistique - Widget _buildStatItem(String title, String value) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), - ), - child: Row( - children: [ - const Icon(Icons.bar_chart, color: AppColors.primaryGreen, size: 20), - const SizedBox(width: SpacingTokens.lg), - Expanded( - child: Text( - title, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.textPrimaryLight, - ), - ), - ), - Text( - value, - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ); - } - // ==================== MÉTHODES D'ACTION ==================== /// Charger les paramètres système @@ -1437,11 +1169,11 @@ class _SystemSettingsPageState extends State mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildStatusItem('Serveur API', 'Opérationnel', Colors.green), - _buildStatusItem('Base de données', 'Opérationnel', Colors.green), - _buildStatusItem('Authentification', 'Opérationnel', Colors.green), - _buildStatusItem('CDN', 'Dégradé', Colors.orange), - _buildStatusItem('Monitoring', 'Opérationnel', Colors.green), + _buildStatusItem('Serveur API', 'Opérationnel', AppColors.success), + _buildStatusItem('Base de données', 'Opérationnel', AppColors.success), + _buildStatusItem('Authentification', 'Opérationnel', AppColors.success), + _buildStatusItem('CDN', 'Dégradé', AppColors.warning), + _buildStatusItem('Monitoring', 'Opérationnel', AppColors.success), ], ), actions: [ @@ -1455,8 +1187,8 @@ class _SystemSettingsPageState extends State _showSuccessSnackBar('État du système actualisé'); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Actualiser'), ), @@ -1468,11 +1200,11 @@ class _SystemSettingsPageState extends State /// Élément de statut Widget _buildStatusItem(String service, String status, Color color) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), child: Row( children: [ Icon(Icons.circle, color: color, size: 12), - const SizedBox(width: 8), + const SizedBox(width: SpacingTokens.md), Expanded(child: Text(service)), Text( status, @@ -1507,8 +1239,8 @@ class _SystemSettingsPageState extends State _showSuccessSnackBar('Configuration exportée avec succès'); }, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, - foregroundColor: Colors.white, + backgroundColor: AppColors.primary, + foregroundColor: AppColors.onPrimary, ), child: const Text('Exporter'), ), @@ -1542,8 +1274,8 @@ class _SystemSettingsPageState extends State _showSuccessSnackBar('Mode maintenance activé - Utilisateurs bloqués'); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, - foregroundColor: Colors.white, + backgroundColor: AppColors.warning, + foregroundColor: AppColors.onPrimary, ), child: const Text('Confirmer'), ), @@ -1560,40 +1292,72 @@ class _SystemSettingsPageState extends State context.read().add(ClearCache()); } - void _optimizeDatabase() => _showSuccessSnackBar('Base de données optimisée'); + void _optimizeDatabase() => context.read().add(OptimizeDatabase()); void _testConnectivity() { context.read().add(TestDatabaseConnection()); } // Actions de sécurité - void _regenerateApiKeys() => _showWarningDialog('Régénérer les clés API', 'Cette action invalidera toutes les clés existantes.'); - void _forceGlobalLogout() => _showWarningDialog('Déconnexion globale', 'Tous les utilisateurs seront déconnectés immédiatement.'); - void _resetSessions() => _showSuccessSnackBar('Sessions expirées nettoyées'); - void _generateAuditReport() => _showSuccessSnackBar('Rapport d\'audit généré et envoyé par email'); - void _exportGDPRData() => _showSuccessSnackBar('Export RGPD lancé - Vous recevrez un email'); - void _purgeExpiredData() => _showWarningDialog('Purge des données', 'Les données expirées seront définitivement supprimées.'); + void _regenerateApiKeys() { + _showWarningDialog('Régénérer les clés API', 'Cette action invalidera toutes les clés existantes.', onConfirm: () { + context.read().add(CleanupSessions()); + }); + } + + void _forceGlobalLogout() { + _showWarningDialog('Déconnexion globale', 'Tous les utilisateurs seront déconnectés immédiatement.', onConfirm: () { + context.read().add(ForceGlobalLogout()); + }); + } + + void _resetSessions() => context.read().add(CleanupSessions()); + void _generateAuditReport() => context.read().add(GenerateAuditReport()); + void _exportGDPRData() => context.read().add(ExportGDPRData()); + + void _purgeExpiredData() { + _showWarningDialog('Purge des données', 'Les données expirées seront définitivement supprimées.', onConfirm: () { + context.read().add(PurgeExpiredData()); + }); + } // Actions de performance - void _analyzePerformance() => _showSuccessSnackBar('Analyse des performances lancée'); - void _cleanOldLogs() => _showSuccessSnackBar('Logs anciens supprimés (450 MB libérés)'); - void _restartServices() => _showWarningDialog('Redémarrer les services', 'Cette action causera une interruption temporaire.'); + void _analyzePerformance() => context.read().add(AnalyzePerformance()); + void _cleanOldLogs() => context.read().add(CleanOldLogs()); + + void _restartServices() { + _showWarningDialog('Redémarrer les services', 'Cette action causera une interruption temporaire du service.', onConfirm: () { + _showSuccessSnackBar('Demande de redémarrage envoyée aux services'); + }); + } // Actions de maintenance - void _createBackup() => _showSuccessSnackBar('Sauvegarde créée avec succès'); - void _restoreFromBackup() => _showWarningDialog('Restaurer une sauvegarde', 'Cette action remplacera toutes les données actuelles.'); - void _scheduleMaintenance() => _showSuccessSnackBar('Fenêtre de maintenance programmée'); - void _emergencyMaintenance() => _showWarningDialog('Maintenance d\'urgence', 'Le système sera immédiatement mis en maintenance.'); - void _checkUpdates() => _showSuccessSnackBar('Aucune mise à jour disponible'); - void _showUpdateHistory() => _showSuccessSnackBar('Historique des mises à jour affiché'); - // Actions de monitoring - void _viewAllLogs() => _showSuccessSnackBar('Console de logs ouverte'); - void _exportLogs() => _showSuccessSnackBar('Logs exportés pour analyse'); - void _generateUsageReport() => _showSuccessSnackBar('Rapport d\'utilisation généré'); + void _restoreFromBackup() { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => const BackupPage())); + } + + void _scheduleMaintenance() { + final now = DateTime.now().add(const Duration(hours: 2)); + final scheduledAt = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')} ${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}'; + context.read().add(ScheduleMaintenance(scheduledAt: scheduledAt, reason: 'Maintenance de routine')); + } + + void _emergencyMaintenance() { + _showWarningDialog('Maintenance d\'urgence', 'Le système sera immédiatement mis en maintenance.', onConfirm: () { + context.read().add(EmergencyMaintenance()); + }); + } + + void _checkUpdates() => context.read().add(CheckUpdates()); + void _showUpdateHistory() => _showSuccessSnackBar('Fonctionnalité disponible prochainement'); + + // Actions de monitoring (consultation, export, rapports) déplacées vers + // Logs & Monitoring (Drawer → Système → Logs & Monitoring) et + // Plus → Rapports & Analytics. /// Dialogue d'avertissement générique - void _showWarningDialog(String title, String message) { + void _showWarningDialog(String title, String message, {VoidCallback? onConfirm}) { showDialog( context: context, builder: (context) => AlertDialog( @@ -1607,11 +1371,15 @@ class _SystemSettingsPageState extends State ElevatedButton( onPressed: () { Navigator.of(context).pop(); - _showSuccessSnackBar('Action exécutée avec succès'); + if (onConfirm != null) { + onConfirm(); + } else { + _showSuccessSnackBar('Opération confirmée'); + } }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + backgroundColor: AppColors.error, + foregroundColor: AppColors.onError, ), child: const Text('Confirmer'), ),