From 7cd7c6fc9eea7851fa55c9959ea5d327aeeb9ab3 Mon Sep 17 00:00:00 2001 From: dahoud <41957584+DahoudG@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:27:23 +0000 Subject: [PATCH] feat(shared): legacy presentation/ + shared design system + widgets - lib/presentation : pages legacy (explore/network, notifications) avec BLoC - lib/shared/design_system : UnionFlow Design System v2 (tokens, components) + MD3 tokens + module_colors par feature - lib/shared/widgets : widgets transversaux (core_card, core_shimmer, error_widget, loading_widget, powered_by_lions_dev, etc.) - lib/shared/constants + utils --- lib/presentation/dashboard/finance_page.dart | 6 +- lib/presentation/explore/network_page.dart | 10 +- lib/presentation/feed/unified_feed_page.dart | 8 +- .../notifications/notification_page.dart | 6 +- .../widgets/shared/mini_header_bar.dart | 2 +- .../widgets/shared/mini_metric_widget.dart | 4 +- .../widgets/shared/profile_drawer.dart | 10 +- .../components/buttons/uf_primary_button.dart | 6 +- .../buttons/uf_secondary_button.dart | 6 +- .../components/cards/uf_card.dart | 16 +- .../components/cards/uf_info_card.dart | 6 +- .../components/cards/uf_stat_card.dart | 12 +- .../components/inputs/uf_dropdown_tile.dart | 8 +- .../components/inputs/uf_switch_tile.dart | 8 +- .../design_system/components/uf_app_bar.dart | 150 +++-- .../components/uf_container.dart | 2 +- .../design_system/components/uf_header.dart | 2 +- .../components/uf_page_header.dart | 16 +- .../dashboard_theme_manager.dart | 48 +- lib/shared/design_system/theme/app_theme.dart | 293 ++++++++-- .../theme/app_theme_sophisticated.dart | 466 ++++++++++----- .../design_system/tokens/app_colors.dart | 540 +++++++++++++++++- .../design_system/tokens/color_tokens.dart | 268 ++++----- .../design_system/tokens/module_colors.dart | 218 +++++++ .../tokens/unionflow_colors.dart | 242 +++----- .../unionflow_design_system.dart | 93 +-- lib/shared/utils/snackbar_helper.dart | 41 +- lib/shared/widgets/action_row.dart | 8 +- lib/shared/widgets/core_card.dart | 9 +- lib/shared/widgets/core_text_field.dart | 12 +- lib/shared/widgets/dynamic_fab.dart | 4 +- lib/shared/widgets/error_display_widget.dart | 27 +- lib/shared/widgets/info_badge.dart | 2 +- lib/shared/widgets/loading_widget.dart | 30 +- lib/shared/widgets/powered_by_lions_dev.dart | 90 +++ lib/shared/widgets/validated_text_field.dart | 58 +- 36 files changed, 1890 insertions(+), 837 deletions(-) create mode 100644 lib/shared/design_system/tokens/module_colors.dart create mode 100644 lib/shared/widgets/powered_by_lions_dev.dart diff --git a/lib/presentation/dashboard/finance_page.dart b/lib/presentation/dashboard/finance_page.dart index 08eb3d6..238e46c 100644 --- a/lib/presentation/dashboard/finance_page.dart +++ b/lib/presentation/dashboard/finance_page.dart @@ -76,7 +76,7 @@ class _FinanceView extends StatelessWidget { alignment: CrossAxisAlignment.center, ), ), - const VerticalDivider(color: AppColors.lightBorder), + const VerticalDivider(color: AppColors.border), Expanded( child: MiniMetricWidget( label: 'En attente', @@ -85,7 +85,7 @@ class _FinanceView extends StatelessWidget { alignment: CrossAxisAlignment.center, ), ), - const VerticalDivider(color: AppColors.lightBorder), + const VerticalDivider(color: AppColors.border), Expanded( child: MiniMetricWidget( label: 'Épargne', @@ -106,7 +106,7 @@ class _FinanceView extends StatelessWidget { style: AppTypography.badgeText.copyWith( color: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark - : AppColors.textSecondaryLight, + : AppColors.textSecondary, ), ), ), diff --git a/lib/presentation/explore/network_page.dart b/lib/presentation/explore/network_page.dart index e70fff2..8c2c31f 100644 --- a/lib/presentation/explore/network_page.dart +++ b/lib/presentation/explore/network_page.dart @@ -63,7 +63,7 @@ class _NetworkViewState extends State<_NetworkView> { color: Theme.of(context).scaffoldBackgroundColor, border: Border( bottom: BorderSide( - color: isDark ? AppColors.darkBorder : AppColors.lightBorder, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1, ), ), @@ -75,9 +75,9 @@ class _NetworkViewState extends State<_NetworkView> { decoration: InputDecoration( hintText: 'Rechercher des membres, organisations...', hintStyle: AppTypography.subtitleSmall, - prefixIcon: const Icon(Icons.search, size: 20, color: AppColors.textSecondaryLight), + prefixIcon: const Icon(Icons.search, size: 20, color: AppColors.textSecondary), filled: true, - fillColor: isDark ? AppColors.darkSurface : AppColors.lightSurface, + fillColor: isDark ? AppColors.surfaceDark : AppColors.surface, contentPadding: const EdgeInsets.symmetric(vertical: 0), // Garder petit border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), @@ -160,8 +160,8 @@ class _NetworkViewState extends State<_NetworkView> { }, child: InfoBadge( text: item.isConnected ? 'Connecté' : 'Suivre', - backgroundColor: item.isConnected ? AppColors.lightSurface : AppColors.primaryGreen, - textColor: item.isConnected ? AppColors.textPrimaryLight : Colors.white, + backgroundColor: item.isConnected ? AppColors.surface : AppColors.primary, + textColor: item.isConnected ? AppColors.textPrimary : Colors.white, ), ), ], diff --git a/lib/presentation/feed/unified_feed_page.dart b/lib/presentation/feed/unified_feed_page.dart index b909dd9..c39fecb 100644 --- a/lib/presentation/feed/unified_feed_page.dart +++ b/lib/presentation/feed/unified_feed_page.dart @@ -96,7 +96,7 @@ class _UnifiedFeedViewState extends State<_UnifiedFeedView> { ), ), body: RefreshIndicator( - color: AppColors.primaryGreen, + color: AppColors.primary, backgroundColor: Theme.of(context).scaffoldBackgroundColor, onRefresh: () async { context.read().add(const LoadFeedRequested(isRefresh: true)); @@ -189,8 +189,8 @@ class _UnifiedFeedViewState extends State<_UnifiedFeedView> { const SizedBox(width: 6), InfoBadge( text: item.type.name.toUpperCase(), - backgroundColor: AppColors.primaryGreen.withOpacity(0.1), - textColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary.withOpacity(0.1), + textColor: AppColors.primary, ), ] ], @@ -206,7 +206,7 @@ class _UnifiedFeedViewState extends State<_UnifiedFeedView> { size: 16, color: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark - : AppColors.textSecondaryLight, + : AppColors.textSecondary, ), onPressed: () => _showPostOptionsMenu(context, item), ) diff --git a/lib/presentation/notifications/notification_page.dart b/lib/presentation/notifications/notification_page.dart index a5dfa6d..d88f425 100644 --- a/lib/presentation/notifications/notification_page.dart +++ b/lib/presentation/notifications/notification_page.dart @@ -116,7 +116,7 @@ class _NotificationView extends StatelessWidget { itemCount: state.items.length, itemBuilder: (context, index) { final item = state.items[index]; - final unreadColor = isDark ? const Color(0xFF1B2E26) : const Color(0xFFE8F5E9); + final unreadColor = isDark ? const Color(0xFF1A1F2E) : const Color(0xFFEFF6FF); return InkWell( onTap: () { @@ -136,8 +136,8 @@ class _NotificationView extends StatelessWidget { Icon( item.category == 'finance' ? Icons.payment : Icons.event, color: item.isRead - ? (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight) - : AppColors.primaryGreen, + ? (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary) + : AppColors.primary, size: 20, ), const SizedBox(width: 12), diff --git a/lib/presentation/widgets/shared/mini_header_bar.dart b/lib/presentation/widgets/shared/mini_header_bar.dart index a9b810e..838dcb0 100644 --- a/lib/presentation/widgets/shared/mini_header_bar.dart +++ b/lib/presentation/widgets/shared/mini_header_bar.dart @@ -48,7 +48,7 @@ class MiniHeaderBar extends StatelessWidget implements PreferredSizeWidget { Text( title, style: AppTypography.headerSmall.copyWith( - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), diff --git a/lib/presentation/widgets/shared/mini_metric_widget.dart b/lib/presentation/widgets/shared/mini_metric_widget.dart index 2d718fb..e6a679c 100644 --- a/lib/presentation/widgets/shared/mini_metric_widget.dart +++ b/lib/presentation/widgets/shared/mini_metric_widget.dart @@ -29,7 +29,7 @@ class MiniMetricWidget extends StatelessWidget { style: AppTypography.badgeText.copyWith( color: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark - : AppColors.textSecondaryLight, + : AppColors.textSecondary, ), ), const SizedBox(height: 2), @@ -38,7 +38,7 @@ class MiniMetricWidget extends StatelessWidget { style: AppTypography.actionText.copyWith( color: valueColor ?? (Theme.of(context).brightness == Brightness.dark ? AppColors.textPrimaryDark - : AppColors.textPrimaryLight), + : AppColors.textPrimary), ), ), ], diff --git a/lib/presentation/widgets/shared/profile_drawer.dart b/lib/presentation/widgets/shared/profile_drawer.dart index 18e9383..ae6af32 100644 --- a/lib/presentation/widgets/shared/profile_drawer.dart +++ b/lib/presentation/widgets/shared/profile_drawer.dart @@ -42,7 +42,7 @@ class ProfileDrawer extends StatelessWidget { Text( name, style: AppTypography.headerSmall.copyWith( - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), const SizedBox(height: 4), @@ -68,7 +68,7 @@ class ProfileDrawer extends StatelessWidget { }, ), - Divider(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, height: 1), + Divider(color: isDark ? AppColors.borderDark : AppColors.border, height: 1), // Liens / Actions (factorisés) Expanded( @@ -84,7 +84,7 @@ class ProfileDrawer extends StatelessWidget { ), ), - Divider(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, height: 1), + Divider(color: isDark ? AppColors.borderDark : AppColors.border, height: 1), // Bouton Déconnexion Padding( @@ -124,14 +124,14 @@ class ProfileDrawer extends StatelessWidget { Icon( icon, size: 22, - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), const SizedBox(width: 20), Text( title, style: AppTypography.headerSmall.copyWith( fontWeight: FontWeight.w500, - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), ], diff --git a/lib/shared/design_system/components/buttons/uf_primary_button.dart b/lib/shared/design_system/components/buttons/uf_primary_button.dart index f35c6dc..35a914a 100644 --- a/lib/shared/design_system/components/buttons/uf_primary_button.dart +++ b/lib/shared/design_system/components/buttons/uf_primary_button.dart @@ -63,12 +63,12 @@ class UFPrimaryButton extends StatelessWidget { child: ElevatedButton( onPressed: isLoading ? null : onPressed, style: ElevatedButton.styleFrom( - backgroundColor: backgroundColor ?? AppColors.primaryGreen, + backgroundColor: backgroundColor ?? AppColors.primary, foregroundColor: textColor ?? Colors.white, - disabledBackgroundColor: (backgroundColor ?? AppColors.primaryGreen).withOpacity(0.5), + disabledBackgroundColor: (backgroundColor ?? AppColors.primary).withOpacity(0.5), disabledForegroundColor: (textColor ?? Colors.white).withOpacity(0.7), elevation: SpacingTokens.elevationSm, - shadowColor: AppColors.darkBorder.withOpacity(0.1), + shadowColor: AppColors.borderDark.withOpacity(0.1), padding: const EdgeInsets.symmetric( horizontal: SpacingTokens.buttonPaddingHorizontal, vertical: 10, diff --git a/lib/shared/design_system/components/buttons/uf_secondary_button.dart b/lib/shared/design_system/components/buttons/uf_secondary_button.dart index fa2e709..f2a6e0f 100644 --- a/lib/shared/design_system/components/buttons/uf_secondary_button.dart +++ b/lib/shared/design_system/components/buttons/uf_secondary_button.dart @@ -34,12 +34,12 @@ class UFSecondaryButton extends StatelessWidget { child: ElevatedButton( onPressed: isLoading ? null : onPressed, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.brandGreen, + backgroundColor: AppColors.primaryDark, foregroundColor: Colors.white, - disabledBackgroundColor: AppColors.brandGreen.withOpacity(0.5), + disabledBackgroundColor: AppColors.primaryDark.withOpacity(0.5), disabledForegroundColor: Colors.white.withOpacity(0.7), elevation: SpacingTokens.elevationSm, - shadowColor: AppColors.darkBorder.withOpacity(0.1), + shadowColor: AppColors.borderDark.withOpacity(0.1), padding: const EdgeInsets.symmetric( horizontal: SpacingTokens.buttonPaddingHorizontal, vertical: 10, diff --git a/lib/shared/design_system/components/cards/uf_card.dart b/lib/shared/design_system/components/cards/uf_card.dart index 12a31b9..c68e8cf 100644 --- a/lib/shared/design_system/components/cards/uf_card.dart +++ b/lib/shared/design_system/components/cards/uf_card.dart @@ -89,7 +89,7 @@ class UFCard extends StatelessWidget { Widget content = Container( padding: effectivePadding, - decoration: _getDecoration(effectiveBorderRadius), + decoration: _getDecoration(effectiveBorderRadius, context), child: child, ); @@ -108,27 +108,31 @@ class UFCard extends StatelessWidget { ); } - BoxDecoration _getDecoration(double radius) { + BoxDecoration _getDecoration(double radius, BuildContext context) { + final surfaceColor = Theme.of(context).colorScheme.surface; + final isDark = Theme.of(context).brightness == Brightness.dark; + final effectiveBorderColor = borderColor ?? (isDark ? AppColors.borderDark : AppColors.border); + switch (style) { case UFCardStyle.elevated: return BoxDecoration( - color: color ?? AppColors.lightSurface, + color: color ?? surfaceColor, borderRadius: BorderRadius.circular(radius), ); case UFCardStyle.outlined: return BoxDecoration( - color: color ?? AppColors.lightSurface, + color: color ?? surfaceColor, borderRadius: BorderRadius.circular(radius), border: Border.all( - color: borderColor ?? AppColors.lightBorder, + color: effectiveBorderColor, width: borderWidth ?? 1.0, ), ); case UFCardStyle.filled: return BoxDecoration( - color: color ?? AppColors.lightSurface, + color: color ?? surfaceColor, borderRadius: BorderRadius.circular(radius), ); } diff --git a/lib/shared/design_system/components/cards/uf_info_card.dart b/lib/shared/design_system/components/cards/uf_info_card.dart index 7cf69f5..a71a378 100644 --- a/lib/shared/design_system/components/cards/uf_info_card.dart +++ b/lib/shared/design_system/components/cards/uf_info_card.dart @@ -50,13 +50,13 @@ class UFInfoCard extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final effectiveIconColor = iconColor ?? AppColors.primaryGreen; + final effectiveIconColor = iconColor ?? AppColors.primary; final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.lg); return Container( padding: effectivePadding, decoration: BoxDecoration( - color: isDark ? AppColors.darkSurface : Colors.white, + color: isDark ? AppColors.surfaceDark : Colors.white, borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), child: Column( @@ -71,7 +71,7 @@ class UFInfoCard extends StatelessWidget { child: Text( title, style: AppTypography.headerSmall.copyWith( - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), ), diff --git a/lib/shared/design_system/components/cards/uf_stat_card.dart b/lib/shared/design_system/components/cards/uf_stat_card.dart index a473016..ad3992f 100644 --- a/lib/shared/design_system/components/cards/uf_stat_card.dart +++ b/lib/shared/design_system/components/cards/uf_stat_card.dart @@ -56,13 +56,13 @@ class UFStatCard extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final effectiveIconColor = iconColor ?? AppColors.primaryGreen; + final effectiveIconColor = iconColor ?? AppColors.primary; final effectiveIconBgColor = iconBackgroundColor ?? effectiveIconColor.withOpacity(0.1); return Card( elevation: SpacingTokens.elevationSm, - shadowColor: AppColors.darkBorder.withOpacity(0.1), + shadowColor: AppColors.borderDark.withOpacity(0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), @@ -97,7 +97,7 @@ class UFStatCard extends StatelessWidget { Icon( Icons.arrow_forward_ios, size: 16, - color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ], ), @@ -108,7 +108,7 @@ class UFStatCard extends StatelessWidget { Text( title, style: AppTypography.badgeText.copyWith( - color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ), @@ -118,7 +118,7 @@ class UFStatCard extends StatelessWidget { Text( value, style: AppTypography.headerSmall.copyWith( - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), @@ -128,7 +128,7 @@ class UFStatCard extends StatelessWidget { Text( subtitle!, style: AppTypography.subtitleSmall.copyWith( - color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ), ], diff --git a/lib/shared/design_system/components/inputs/uf_dropdown_tile.dart b/lib/shared/design_system/components/inputs/uf_dropdown_tile.dart index 13b0cdf..8af2671 100644 --- a/lib/shared/design_system/components/inputs/uf_dropdown_tile.dart +++ b/lib/shared/design_system/components/inputs/uf_dropdown_tile.dart @@ -50,7 +50,7 @@ class UFDropdownTile extends StatelessWidget { Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final effectiveBgColor = backgroundColor ?? - (isDark ? AppColors.darkSurface : AppColors.lightSurface); + (isDark ? AppColors.surfaceDark : AppColors.surface); final effectiveItemBuilder = itemBuilder ?? (item) => item.toString(); return Container( @@ -67,16 +67,16 @@ class UFDropdownTile extends StatelessWidget { title, style: AppTypography.bodyTextSmall.copyWith( fontWeight: FontWeight.w600, - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg), decoration: BoxDecoration( - color: isDark ? AppColors.darkBackground : Colors.white, + color: isDark ? AppColors.backgroundDark : Colors.white, borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), - border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder), + border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border), ), child: DropdownButtonHideUnderline( child: DropdownButton( diff --git a/lib/shared/design_system/components/inputs/uf_switch_tile.dart b/lib/shared/design_system/components/inputs/uf_switch_tile.dart index ccc9c5d..40ebbd3 100644 --- a/lib/shared/design_system/components/inputs/uf_switch_tile.dart +++ b/lib/shared/design_system/components/inputs/uf_switch_tile.dart @@ -46,7 +46,7 @@ class UFSwitchTile extends StatelessWidget { Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final effectiveBgColor = backgroundColor ?? - (isDark ? AppColors.darkSurface : AppColors.lightSurface); + (isDark ? AppColors.surfaceDark : AppColors.surface); return Container( margin: const EdgeInsets.only(bottom: SpacingTokens.lg), @@ -65,13 +65,13 @@ class UFSwitchTile extends StatelessWidget { title, style: AppTypography.bodyTextSmall.copyWith( fontWeight: FontWeight.w600, - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, ), ), Text( subtitle, style: AppTypography.subtitleSmall.copyWith( - color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), ), ], @@ -80,7 +80,7 @@ class UFSwitchTile extends StatelessWidget { Switch( value: value, onChanged: onChanged, - activeColor: AppColors.primaryGreen, + activeColor: AppColors.primary, ), ], ), diff --git a/lib/shared/design_system/components/uf_app_bar.dart b/lib/shared/design_system/components/uf_app_bar.dart index 25dde2a..c293441 100644 --- a/lib/shared/design_system/components/uf_app_bar.dart +++ b/lib/shared/design_system/components/uf_app_bar.dart @@ -7,21 +7,45 @@ import '../unionflow_design_system.dart'; /// Composant AppBar unifié pour toutes les pages de détail/formulaire. /// Garantit la cohérence visuelle et l'expérience utilisateur. /// -/// Si [mergeLeadingWithTitle] est true et que la route peut être quittée, -/// le bouton retour et le titre sont fusionnés en une seule ligne (retour -/// toujours visible avec la même couleur que le titre). +/// ### Mode module (identité chromatique) +/// +/// Passez [moduleColor] ou [moduleGradient] pour afficher un AppBar avec +/// le gradient signature du module, rappelant visuellement à l'utilisateur +/// dans quelle section de l'application il se trouve. +/// +/// ```dart +/// UFAppBar( +/// title: 'Gestion des Organisations', +/// moduleGradient: ModuleColors.organisationsGradient, +/// ) +/// ``` +/// +/// ### Mode standard +/// +/// Sans [moduleColor] ni [moduleGradient], le comportement existant est +/// préservé intégralement (backgroundColor, foregroundColor, etc.). class UFAppBar extends StatelessWidget implements PreferredSizeWidget { final String title; final List? actions; final Widget? leading; final bool automaticallyImplyLeading; - /// Fusionne le bouton retour et le titre en une seule zone (retour visible, même couleur que le titre). + + /// Fusionne le bouton retour et le titre en une seule zone (retour visible, + /// même couleur que le titre). final bool mergeLeadingWithTitle; final PreferredSizeWidget? bottom; final Color? backgroundColor; final Color? foregroundColor; final double elevation; + /// Couleur primaire du module — génère automatiquement un gradient subtil. + /// Prend le dessus sur [backgroundColor] si renseigné. + final Color? moduleColor; + + /// Gradient du module (liste de 2 couleurs) — ex: ModuleColors.organisationsGradient. + /// Prend le dessus sur [moduleColor] et [backgroundColor] si renseigné. + final List? moduleGradient; + const UFAppBar({ super.key, required this.title, @@ -33,8 +57,21 @@ class UFAppBar extends StatelessWidget implements PreferredSizeWidget { this.backgroundColor, this.foregroundColor, this.elevation = 0, + this.moduleColor, + this.moduleGradient, }); + bool get _isModuleMode => moduleGradient != null || moduleColor != null; + + List get _resolvedGradient { + if (moduleGradient != null) return moduleGradient!; + final c = moduleColor!; + // Génère un gradient sombre → clair depuis la couleur du module + final hsl = HSLColor.fromColor(c); + final dark = hsl.withLightness((hsl.lightness - 0.06).clamp(0.0, 1.0)).toColor(); + return [dark, c]; + } + @override Widget build(BuildContext context) { final canPop = ModalRoute.of(context)?.canPop ?? false; @@ -44,38 +81,80 @@ class UFAppBar extends StatelessWidget implements PreferredSizeWidget { final isTransparent = backgroundColor == Colors.transparent || (backgroundColor != null && backgroundColor!.opacity < 0.1); - return AppBar( - title: useMergedTitle - ? Row( - children: [ - Material( - color: isTransparent ? Colors.black26 : Colors.transparent, - borderRadius: BorderRadius.circular(20), - child: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), + Widget titleWidget = useMergedTitle + ? Row( + children: [ + Material( + color: isTransparent ? Colors.black26 : Colors.transparent, + borderRadius: BorderRadius.circular(20), + child: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.of(context).pop(), + color: fg, + tooltip: 'Retour', + style: IconButton.styleFrom( + foregroundColor: fg, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + title, + style: AppTypography.headerSmall.copyWith( color: fg, - tooltip: 'Retour', - style: IconButton.styleFrom( - foregroundColor: fg, - ), + fontWeight: FontWeight.w600, ), + overflow: TextOverflow.ellipsis, ), - const SizedBox(width: 8), - Expanded( - child: Text( - title, - style: AppTypography.headerSmall.copyWith( - color: fg, - fontWeight: FontWeight.w600, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ) - : Text(title), - backgroundColor: backgroundColor ?? AppColors.primaryGreen, + ), + ], + ) + : Text(title); + + // Mode module : gradient via flexibleSpace + // iconTheme + actionsIconTheme explicites pour garantir la visibilité + // des icônes sur fond gradient sombre (foregroundColor seul ne suffit pas + // quand backgroundColor est transparent). + if (_isModuleMode) { + final gradient = _resolvedGradient; + return AppBar( + title: titleWidget, + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + elevation: elevation, + leading: useMergedTitle ? null : leading, + automaticallyImplyLeading: useMergedTitle ? false : automaticallyImplyLeading, + actions: actions, + bottom: bottom, + iconTheme: const IconThemeData(color: Colors.white, size: 22), + actionsIconTheme: const IconThemeData(color: Colors.white, size: 22), + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.light, + statusBarBrightness: Brightness.dark, + ), + centerTitle: false, + titleTextStyle: AppTypography.headerSmall.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + flexibleSpace: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: gradient, + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + ), + ); + } + + // Mode standard (comportement existant préservé) + return AppBar( + title: titleWidget, + backgroundColor: backgroundColor ?? AppColors.primary, foregroundColor: fg, elevation: elevation, leading: useMergedTitle ? null : leading, @@ -97,7 +176,6 @@ class UFAppBar extends StatelessWidget implements PreferredSizeWidget { @override Size get preferredSize => Size.fromHeight( - kToolbarHeight + (bottom?.preferredSize.height ?? 0.0), - ); + kToolbarHeight + (bottom?.preferredSize.height ?? 0.0), + ); } - diff --git a/lib/shared/design_system/components/uf_container.dart b/lib/shared/design_system/components/uf_container.dart index 13a6d00..6c96f43 100644 --- a/lib/shared/design_system/components/uf_container.dart +++ b/lib/shared/design_system/components/uf_container.dart @@ -125,7 +125,7 @@ class UFContainer extends StatelessWidget { alignment: alignment, constraints: constraints, decoration: BoxDecoration( - color: gradient == null ? (color ?? AppColors.lightSurface) : null, + color: gradient == null ? (color ?? Theme.of(context).colorScheme.surface) : null, gradient: gradient, borderRadius: BorderRadius.circular(borderRadius), border: border, diff --git a/lib/shared/design_system/components/uf_header.dart b/lib/shared/design_system/components/uf_header.dart index f50c874..a00ec9d 100644 --- a/lib/shared/design_system/components/uf_header.dart +++ b/lib/shared/design_system/components/uf_header.dart @@ -31,7 +31,7 @@ class UFHeader extends StatelessWidget { padding: const EdgeInsets.all(SpacingTokens.lg), decoration: BoxDecoration( gradient: const LinearGradient( - colors: [AppColors.primaryGreen, AppColors.brandGreenLight], + colors: [AppColors.primary, AppColors.primaryLight], ), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), diff --git a/lib/shared/design_system/components/uf_page_header.dart b/lib/shared/design_system/components/uf_page_header.dart index 3a7f68c..897bf77 100644 --- a/lib/shared/design_system/components/uf_page_header.dart +++ b/lib/shared/design_system/components/uf_page_header.dart @@ -35,7 +35,7 @@ class UFPageHeader extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final effectiveIconColor = iconColor ?? AppColors.primaryGreen; + final effectiveIconColor = iconColor ?? AppColors.primary; return Column( children: [ @@ -66,7 +66,7 @@ class UFPageHeader extends StatelessWidget { child: Text( title, style: AppTypography.headerSmall.copyWith( - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), @@ -83,7 +83,7 @@ class UFPageHeader extends StatelessWidget { Divider( height: 1, thickness: 1, - color: isDark ? AppColors.darkBorder : AppColors.lightBorder, + color: isDark ? AppColors.borderDark : AppColors.border, ), ], ); @@ -112,7 +112,7 @@ class UFPageHeaderWithStats extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final effectiveIconColor = iconColor ?? AppColors.primaryGreen; + final effectiveIconColor = iconColor ?? AppColors.primary; return Column( children: [ @@ -146,7 +146,7 @@ class UFPageHeaderWithStats extends StatelessWidget { child: Text( title, style: AppTypography.headerSmall.copyWith( - color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight, + color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), @@ -185,14 +185,14 @@ class UFPageHeaderWithStats extends StatelessWidget { Divider( height: 1, thickness: 1, - color: isDark ? AppColors.darkBorder : AppColors.lightBorder, + color: isDark ? AppColors.borderDark : AppColors.border, ), ], ); } Widget _buildStatItem(UFHeaderStat stat, bool isDark) { - final effectiveColor = stat.color ?? AppColors.primaryGreen; + final effectiveColor = stat.color ?? AppColors.primary; return UFContainer.rounded( padding: const EdgeInsets.symmetric( horizontal: SpacingTokens.md, @@ -213,7 +213,7 @@ class UFPageHeaderWithStats extends StatelessWidget { Text( stat.label, style: AppTypography.badgeText.copyWith( - color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/shared/design_system/dashboard_theme_manager.dart b/lib/shared/design_system/dashboard_theme_manager.dart index 8a47aa0..ed141c5 100644 --- a/lib/shared/design_system/dashboard_theme_manager.dart +++ b/lib/shared/design_system/dashboard_theme_manager.dart @@ -1,21 +1,22 @@ import 'package:flutter/material.dart'; +import 'tokens/color_tokens.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Gestionnaire de thèmes personnalisables pour le Dashboard class DashboardThemeManager { static const String _themeKey = 'dashboard_theme'; - static DashboardThemeData _currentTheme = DashboardThemeData.royalTeal(); + static DashboardThemeData _currentTheme = DashboardThemeData.unionFlow(); static SharedPreferences? _prefs; - + /// Initialise le gestionnaire de thèmes static Future initialize() async { _prefs = await SharedPreferences.getInstance(); await _loadSavedTheme(); } - + /// Charge le thème sauvegardé static Future _loadSavedTheme() async { - final themeName = _prefs?.getString(_themeKey) ?? 'royalTeal'; + final themeName = _prefs?.getString(_themeKey) ?? 'unionFlow'; _currentTheme = _getThemeByName(themeName); } @@ -31,6 +32,8 @@ class DashboardThemeManager { /// Obtient un thème par son nom static DashboardThemeData _getThemeByName(String name) { switch (name) { + case 'unionFlow': + return DashboardThemeData.unionFlow(); case 'royalTeal': return DashboardThemeData.royalTeal(); case 'oceanBlue': @@ -44,12 +47,13 @@ class DashboardThemeManager { case 'darkMode': return DashboardThemeData.darkMode(); default: - return DashboardThemeData.royalTeal(); + return DashboardThemeData.unionFlow(); } } - + /// Obtient la liste des thèmes disponibles static List get availableThemes => [ + ThemeOption('unionFlow', 'UnionFlow (défaut)', DashboardThemeData.unionFlow()), ThemeOption('royalTeal', 'Bleu Roi & Pétrole', DashboardThemeData.royalTeal()), ThemeOption('oceanBlue', 'Bleu Océan', DashboardThemeData.oceanBlue()), ThemeOption('forestGreen', 'Vert Forêt', DashboardThemeData.forestGreen()), @@ -108,7 +112,29 @@ class DashboardThemeData { this.isDark = false, }); - /// Thème Bleu Roi & Pétrole (par défaut) + /// Thème UnionFlow officiel — Bleu #297FFF → Violet #7616E8 (logo signature) + factory DashboardThemeData.unionFlow() { + return const DashboardThemeData( + name: 'UnionFlow', + primaryColor: Color(0xFF297FFF), + secondaryColor: Color(0xFF7616E8), + primaryLight: Color(0xFF69B7FF), + primaryDark: Color(0xFF2170E7), + secondaryLight: Color(0xFF9B59F0), + secondaryDark: Color(0xFF5B0FBA), + backgroundColor: Color(0xFFF5F8FF), + surfaceColor: Color(0xFFFFFFFF), + cardColor: Color(0xFFFFFFFF), + textPrimary: Color(0xFF111827), + textSecondary: Color(0xFF6B7280), + success: Color(0xFF22C55E), + warning: Color(0xFFF59E0B), + error: Color(0xFFEF4444), + info: Color(0xFF297FFF), + ); + } + + /// Thème Bleu Roi & Pétrole factory DashboardThemeData.royalTeal() { return const DashboardThemeData( name: 'Bleu Roi & Pétrole', @@ -123,7 +149,7 @@ class DashboardThemeData { cardColor: Color(0xFFFFFFFF), textPrimary: Color(0xFF111827), textSecondary: Color(0xFF6B7280), - success: Color(0xFF10B981), + success: ColorTokens.successLight, warning: Color(0xFFF59E0B), error: Color(0xFFEF4444), info: Color(0xFF3B82F6), @@ -158,7 +184,7 @@ class DashboardThemeData { name: 'Vert Forêt', primaryColor: Color(0xFF059669), secondaryColor: Color(0xFF047857), - primaryLight: Color(0xFF10B981), + primaryLight: ColorTokens.successLight, primaryDark: Color(0xFF065F46), secondaryLight: Color(0xFF059669), secondaryDark: Color(0xFF064E3B), @@ -167,7 +193,7 @@ class DashboardThemeData { cardColor: Color(0xFFFFFFFF), textPrimary: Color(0xFF064E3B), textSecondary: Color(0xFF6B7280), - success: Color(0xFF10B981), + success: ColorTokens.successLight, warning: Color(0xFFF59E0B), error: Color(0xFFEF4444), info: Color(0xFF3B82F6), @@ -227,7 +253,7 @@ class DashboardThemeData { primaryLight: Color(0xFF93C5FD), primaryDark: Color(0xFF3B82F6), secondaryLight: Color(0xFF6EE7B7), - secondaryDark: Color(0xFF10B981), + secondaryDark: ColorTokens.successLight, backgroundColor: Color(0xFF111827), surfaceColor: Color(0xFF1F2937), cardColor: Color(0xFF374151), diff --git a/lib/shared/design_system/theme/app_theme.dart b/lib/shared/design_system/theme/app_theme.dart index e57246e..6079ba1 100644 --- a/lib/shared/design_system/theme/app_theme.dart +++ b/lib/shared/design_system/theme/app_theme.dart @@ -1,93 +1,197 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import '../tokens/app_colors.dart'; import '../tokens/app_typography.dart'; -/// UnionFlow Mobile App - Thème Global -/// Utilise la charte stricte (Vert/Blanc/Noir OLED) et force la petite typographie. +/// UnionFlow Mobile App — Thème Global +/// +/// Palette WCAG 2.1 validée : +/// Primary : #2563EB [5.7:1 ✅ WCAG AA] +/// Dark mode: #0A0D1A [Navy profond] class AppTheme { - - // --- THÈME CLAIR (Mode Jour) --- + + // ─── THÈME CLAIR ────────────────────────────────────────────────────────── + static final ThemeData lightTheme = ThemeData( useMaterial3: true, brightness: Brightness.light, - primaryColor: AppColors.primaryGreen, - scaffoldBackgroundColor: AppColors.lightSurface, + primaryColor: AppColors.primary, + scaffoldBackgroundColor: AppColors.background, colorScheme: const ColorScheme.light( - primary: AppColors.primaryGreen, - secondary: AppColors.brandGreenLight, - surface: AppColors.lightBackground, - error: AppColors.error, + primary: AppColors.primary, onPrimary: Colors.white, + primaryContainer: AppColors.primaryContainer, + onPrimaryContainer: Color(0xFF1E3A8A), + secondary: AppColors.accent, onSecondary: Colors.white, - onSurface: AppColors.textPrimaryLight, + tertiary: Color(0xFF5297FF), + surface: AppColors.surface, + onSurface: AppColors.textPrimary, + error: AppColors.error, onError: Colors.white, + outline: AppColors.border, ), - - // Forcer la typographie standardisée + textTheme: const TextTheme( titleMedium: AppTypography.headerSmall, bodyMedium: AppTypography.bodyTextSmall, bodySmall: AppTypography.subtitleSmall, labelLarge: AppTypography.actionText, labelSmall: AppTypography.badgeText, + ).apply( + bodyColor: AppColors.textPrimary, + displayColor: AppColors.textPrimary, + ).copyWith( + // .apply() ne propage pas bodyColor aux styles label* (comportement Flutter documenté) + // → on les force explicitement ici pour le light mode + titleMedium: AppTypography.headerSmall.copyWith(color: AppColors.textTitle), + labelLarge: AppTypography.actionText.copyWith(color: AppColors.textTitle), + labelSmall: AppTypography.badgeText.copyWith(color: AppColors.textPrimary), ), - // Personnalisation des AppBar (Garder la minimaliste) - appBarTheme: const AppBarTheme( - backgroundColor: AppColors.lightBackground, - foregroundColor: AppColors.textPrimaryLight, + appBarTheme: AppBarTheme( + backgroundColor: AppColors.surface, + foregroundColor: AppColors.textPrimary, elevation: 0, + scrolledUnderElevation: 0, centerTitle: true, - iconTheme: IconThemeData(color: AppColors.textPrimaryLight, size: 20), - titleTextStyle: AppTypography.headerSmall, + surfaceTintColor: Colors.transparent, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.dark, + ), + iconTheme: const IconThemeData( + color: AppColors.textPrimary, + size: 20, + ), + titleTextStyle: AppTypography.headerSmall.copyWith( + color: AppColors.textPrimary, + ), ), - // Boutons par défaut elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 0, textStyle: AppTypography.actionText, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - minimumSize: const Size(64, 32), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + minimumSize: const Size(64, 40), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), ), ), - - // BottomNavigationBar ultra-compacte + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: AppColors.primary, + side: const BorderSide(color: AppColors.primary), + textStyle: AppTypography.actionText, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + minimumSize: const Size(64, 40), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + ), + ), + + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: AppColors.primary, + textStyle: AppTypography.actionText, + ), + ), + + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.surface, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.border), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.border), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.primary, width: 2), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.error), + ), + labelStyle: const TextStyle(color: AppColors.textSecondary), + hintStyle: const TextStyle(color: AppColors.textTertiary), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( - backgroundColor: AppColors.lightBackground, - selectedItemColor: AppColors.primaryGreen, - unselectedItemColor: AppColors.textSecondaryLight, + backgroundColor: AppColors.surface, + selectedItemColor: AppColors.primary, + unselectedItemColor: AppColors.textSecondary, showSelectedLabels: false, showUnselectedLabels: false, elevation: 8, type: BottomNavigationBarType.fixed, ), - + + chipTheme: ChipThemeData( + backgroundColor: AppColors.primaryContainer, + selectedColor: AppColors.primary, + labelStyle: AppTypography.labelMedium, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + dividerTheme: const DividerThemeData( - color: AppColors.lightBorder, + color: AppColors.border, thickness: 1, space: 1, ), + + cardTheme: CardThemeData( + color: AppColors.surface, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: const BorderSide(color: AppColors.border), + ), + margin: EdgeInsets.zero, + ), + + snackBarTheme: SnackBarThemeData( + backgroundColor: AppColors.textPrimary, + contentTextStyle: AppTypography.bodyTextSmall.copyWith( + color: Colors.white, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + behavior: SnackBarBehavior.fixed, + ), ); - // --- THÈME SOMBRE (Mode Nuit OLED) --- + // ─── THÈME SOMBRE (Navy Profond) ────────────────────────────────────────── + static final ThemeData darkTheme = ThemeData( useMaterial3: true, brightness: Brightness.dark, - primaryColor: AppColors.primaryGreen, - scaffoldBackgroundColor: AppColors.darkBackground, // Noir OLED + primaryColor: AppColors.primaryLight, + scaffoldBackgroundColor: AppColors.backgroundDark, colorScheme: const ColorScheme.dark( - primary: AppColors.primaryGreen, - secondary: AppColors.brandGreenLight, - surface: AppColors.darkSurface, // Gris très sombre - error: AppColors.error, - onPrimary: Colors.white, - onSecondary: Colors.white, + primary: AppColors.primaryLight, + onPrimary: Color(0xFF1E3A8A), + primaryContainer: Color(0xFF1A2350), + onPrimaryContainer: Color(0xFFD6E8FF), + secondary: AppColors.accentLight, + onSecondary: Color(0xFF2A006F), + surface: AppColors.surfaceDark, onSurface: AppColors.textPrimaryDark, + error: AppColors.error, onError: Colors.white, + outline: AppColors.borderDark, ), textTheme: const TextTheme( @@ -99,43 +203,126 @@ class AppTheme { ).apply( bodyColor: AppColors.textPrimaryDark, displayColor: AppColors.textPrimaryDark, + ).copyWith( + // .apply() ne propage pas bodyColor aux styles label* (comportement Flutter documenté) + // → on les force explicitement ici pour le dark mode + titleMedium: AppTypography.headerSmall.copyWith(color: AppColors.textTitleDark), + labelLarge: AppTypography.actionText.copyWith(color: AppColors.textTitleDark), + labelSmall: AppTypography.badgeText.copyWith(color: AppColors.textPrimaryDark), ), - appBarTheme: const AppBarTheme( - backgroundColor: AppColors.darkBackground, + appBarTheme: AppBarTheme( + backgroundColor: AppColors.backgroundDark, foregroundColor: AppColors.textPrimaryDark, elevation: 0, + scrolledUnderElevation: 0, centerTitle: true, - iconTheme: IconThemeData(color: AppColors.textPrimaryDark, size: 20), - titleTextStyle: AppTypography.headerSmall, // Remplace titleTextStyle + surfaceTintColor: Colors.transparent, + systemOverlayStyle: const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.light, + ), + iconTheme: const IconThemeData( + color: AppColors.textPrimaryDark, + size: 20, + ), + titleTextStyle: AppTypography.headerSmall.copyWith( + color: AppColors.textPrimaryDark, + ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 0, textStyle: AppTypography.actionText, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - minimumSize: const Size(64, 32), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + minimumSize: const Size(64, 40), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), ), ), - + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: AppColors.primaryLight, + side: const BorderSide(color: AppColors.primaryLight), + textStyle: AppTypography.actionText, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + minimumSize: const Size(64, 40), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + ), + ), + + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: AppColors.primaryLight, + textStyle: AppTypography.actionText, + ), + ), + + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: AppColors.surfaceDark, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.borderDark), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.borderDark), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.primaryLight, width: 2), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.error), + ), + labelStyle: const TextStyle(color: AppColors.textSecondaryDark), + hintStyle: TextStyle(color: AppColors.textSecondaryDark.withOpacity(0.6)), + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( - backgroundColor: AppColors.darkBackground, - selectedItemColor: AppColors.primaryGreen, + backgroundColor: AppColors.backgroundDark, + selectedItemColor: AppColors.primaryLight, unselectedItemColor: AppColors.textSecondaryDark, showSelectedLabels: false, showUnselectedLabels: false, elevation: 8, type: BottomNavigationBarType.fixed, ), - + dividerTheme: const DividerThemeData( - color: AppColors.darkBorder, + color: AppColors.borderDark, thickness: 1, space: 1, ), + + cardTheme: CardThemeData( + color: AppColors.surfaceDark, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: const BorderSide(color: AppColors.borderDark), + ), + margin: EdgeInsets.zero, + ), + + snackBarTheme: SnackBarThemeData( + backgroundColor: AppColors.surfaceDark, + contentTextStyle: AppTypography.bodyTextSmall.copyWith( + color: AppColors.textPrimaryDark, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + behavior: SnackBarBehavior.fixed, + ), ); } diff --git a/lib/shared/design_system/theme/app_theme_sophisticated.dart b/lib/shared/design_system/theme/app_theme_sophisticated.dart index 19aeddc..d341e6d 100644 --- a/lib/shared/design_system/theme/app_theme_sophisticated.dart +++ b/lib/shared/design_system/theme/app_theme_sophisticated.dart @@ -1,7 +1,6 @@ -/// Thème Sophistiqué UnionFlow -/// -/// Implémentation complète du design system avec les dernières tendances UI/UX 2024-2025 -/// Architecture modulaire et tokens de design cohérents +/// Thème Sophistiqué UnionFlow — Material Design 3 +/// +/// Palette WCAG 2.1 validée : #2563EB (primary) + #7616E8 (accent) library app_theme_sophisticated; import 'package:flutter/material.dart'; @@ -10,197 +9,389 @@ import '../tokens/color_tokens.dart'; import '../tokens/typography_tokens.dart'; import '../tokens/spacing_tokens.dart'; -/// Thème principal de l'application UnionFlow class AppThemeSophisticated { AppThemeSophisticated._(); // ═══════════════════════════════════════════════════════════════════════════ - // THÈME PRINCIPAL - Configuration complète + // THÈME CLAIR // ═══════════════════════════════════════════════════════════════════════════ - - /// Thème clair principal + static ThemeData get lightTheme { return ThemeData( useMaterial3: true, brightness: Brightness.light, - - // Couleurs principales colorScheme: _lightColorScheme, - - // Typographie (Playfair Display display + Inter body) textTheme: _textTheme, - - // Configuration de l'AppBar appBarTheme: _appBarTheme, - - // Configuration des cartes cardTheme: _cardTheme, - - // Configuration des boutons elevatedButtonTheme: _elevatedButtonTheme, filledButtonTheme: _filledButtonTheme, outlinedButtonTheme: _outlinedButtonTheme, textButtonTheme: _textButtonTheme, - - // Configuration des champs de saisie inputDecorationTheme: _inputDecorationTheme, - - // Configuration de la navigation navigationBarTheme: _navigationBarTheme, navigationDrawerTheme: _navigationDrawerTheme, - - // Configuration des dialogues dialogTheme: _dialogTheme, - - // Configuration des snackbars snackBarTheme: _snackBarTheme, - - // Configuration des puces chipTheme: _chipTheme, - - // Configuration des listes listTileTheme: _listTileTheme, - - // Configuration des onglets tabBarTheme: _tabBarTheme, - - // Configuration des dividers dividerTheme: _dividerTheme, - - // Configuration des icônes iconTheme: _iconTheme, - - // Configuration des surfaces - scaffoldBackgroundColor: ColorTokens.surface, + scaffoldBackgroundColor: ColorTokens.background, canvasColor: ColorTokens.surface, - - // Configuration des animations pageTransitionsTheme: _pageTransitionsTheme, - - // Configuration des extensions - extensions: const [ - _customColors, - _customSpacing, - ], + extensions: const [_customColors, _customSpacing], ); } - /// Thème sombre — Vert Ardoise (#1A2E1A) + // ═══════════════════════════════════════════════════════════════════════════ + // THÈME SOMBRE — Navy Profond (#0A0D1A) + // ═══════════════════════════════════════════════════════════════════════════ + static ThemeData get darkTheme { return ThemeData( useMaterial3: true, brightness: Brightness.dark, - colorScheme: const ColorScheme.dark( - primary: Color(0xFF4CAF50), // Vert clair sur fond sombre - onPrimary: Color(0xFF003908), - primaryContainer: Color(0xFF1E3A1E), - onPrimaryContainer: Color(0xFFB9F0B9), - secondary: Color(0xFFA5D6A7), - onSecondary: Color(0xFF002106), - surface: Color(0xFF1A2E1A), // Vert ardoise - onSurface: Color(0xFFE0F2E0), - surfaceContainerHighest: Color(0xFF243824), - onSurfaceVariant: Color(0xFF90B890), - error: Color(0xFFEF4444), - onError: Colors.white, - outline: Color(0xFF3A5E3A), - shadow: Color(0xFF0F1A0F), - ), - scaffoldBackgroundColor: const Color(0xFF0F1A0F), + colorScheme: _darkColorScheme, + textTheme: _textThemeDark, + scaffoldBackgroundColor: ColorTokens.backgroundDark, + canvasColor: ColorTokens.surfaceDark, appBarTheme: const AppBarTheme( elevation: 0, + scrolledUnderElevation: 0, backgroundColor: Colors.transparent, - foregroundColor: Color(0xFFE0F2E0), + foregroundColor: ColorTokens.onSurfaceDark, surfaceTintColor: Colors.transparent, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.light, // icônes blanches en dark + statusBarBrightness: Brightness.dark, // pour iOS + systemNavigationBarColor: Colors.transparent, + ), ), + dividerTheme: const DividerThemeData( + color: ColorTokens.outlineDark, + thickness: 1.0, + space: SpacingTokens.md, + ), + cardTheme: CardThemeData( + color: ColorTokens.surfaceDark, + elevation: 0, + shadowColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), + side: const BorderSide(color: ColorTokens.outlineDark), + ), + margin: const EdgeInsets.all(SpacingTokens.cardMargin), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + elevation: SpacingTokens.elevationSm, + backgroundColor: ColorTokens.primaryLight, + foregroundColor: ColorTokens.onPrimaryContainer, + textStyle: TypographyTokens.buttonMedium, + padding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.buttonPaddingHorizontal, + vertical: SpacingTokens.buttonPaddingVertical, + ), + minimumSize: const Size(SpacingTokens.minButtonWidth, SpacingTokens.buttonHeightMedium), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + ), + ), + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: ColorTokens.primaryLight, + foregroundColor: ColorTokens.onPrimaryContainer, + textStyle: TypographyTokens.buttonMedium, + padding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.buttonPaddingHorizontal, + vertical: SpacingTokens.buttonPaddingVertical, + ), + minimumSize: const Size(SpacingTokens.minButtonWidth, SpacingTokens.buttonHeightMedium), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: ColorTokens.primaryLight, + textStyle: TypographyTokens.buttonMedium, + padding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.buttonPaddingHorizontal, + vertical: SpacingTokens.buttonPaddingVertical, + ), + minimumSize: const Size(SpacingTokens.minButtonWidth, SpacingTokens.buttonHeightMedium), + side: const BorderSide(color: ColorTokens.outlineDark), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + ), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: ColorTokens.primaryLight, + textStyle: TypographyTokens.buttonMedium, + padding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.buttonPaddingHorizontal, + vertical: SpacingTokens.buttonPaddingVertical, + ), + minimumSize: const Size(SpacingTokens.minButtonWidth, SpacingTokens.buttonHeightMedium), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + ), + ), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: ColorTokens.surfaceContainerDark, + labelStyle: TypographyTokens.inputLabel.copyWith( + color: ColorTokens.onSurfaceVariantDark, + ), + hintStyle: TypographyTokens.inputHint.copyWith( + color: ColorTokens.onSurfaceVariantDark, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + borderSide: const BorderSide(color: ColorTokens.outlineDark), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + borderSide: const BorderSide(color: ColorTokens.outlineDark), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + borderSide: const BorderSide(color: ColorTokens.primaryLight, width: 2.0), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + borderSide: const BorderSide(color: ColorTokens.error), + ), + contentPadding: const EdgeInsets.all(SpacingTokens.formPadding), + ), + navigationBarTheme: NavigationBarThemeData( + backgroundColor: ColorTokens.navigationBackgroundDark, + indicatorColor: ColorTokens.navigationIndicatorDark, + labelTextStyle: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return TypographyTokens.navigationLabelSelected.copyWith( + color: ColorTokens.navigationSelectedDark, + ); + } + return TypographyTokens.navigationLabel.copyWith( + color: ColorTokens.navigationUnselectedDark, + ); + }), + iconTheme: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return const IconThemeData(color: ColorTokens.navigationSelectedDark); + } + return const IconThemeData(color: ColorTokens.navigationUnselectedDark); + }), + ), + navigationDrawerTheme: NavigationDrawerThemeData( + backgroundColor: ColorTokens.surfaceContainerDark, + elevation: SpacingTokens.elevationMd, + shadowColor: Colors.black, + surfaceTintColor: Colors.transparent, + indicatorColor: ColorTokens.navigationIndicatorDark, + labelTextStyle: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return TypographyTokens.navigationLabelSelected.copyWith( + color: ColorTokens.navigationSelectedDark, + ); + } + return TypographyTokens.navigationLabel.copyWith( + color: ColorTokens.navigationUnselectedDark, + ); + }), + ), + dialogTheme: DialogThemeData( + backgroundColor: ColorTokens.surfaceDark, + elevation: SpacingTokens.elevationLg, + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), + ), + titleTextStyle: TypographyTokens.headlineSmall.copyWith( + color: ColorTokens.onSurfaceDark, + ), + contentTextStyle: TypographyTokens.bodyMedium.copyWith( + color: ColorTokens.onSurfaceVariantDark, + ), + ), + snackBarTheme: SnackBarThemeData( + backgroundColor: ColorTokens.surfaceVariantDark, + contentTextStyle: TypographyTokens.bodyMedium.copyWith( + color: ColorTokens.onSurfaceDark, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + ), + behavior: SnackBarBehavior.fixed, + ), + chipTheme: ChipThemeData( + backgroundColor: ColorTokens.surfaceContainerDark, + selectedColor: ColorTokens.navigationIndicatorDark, + labelStyle: TypographyTokens.labelMedium.copyWith( + color: ColorTokens.onSurfaceDark, + ), + padding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.md, + vertical: SpacingTokens.sm, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + ), + ), + listTileTheme: ListTileThemeData( + contentPadding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.xl, + vertical: SpacingTokens.md, + ), + tileColor: ColorTokens.surfaceDark, + titleTextStyle: TypographyTokens.titleMedium.copyWith( + color: ColorTokens.onSurfaceDark, + ), + subtitleTextStyle: TypographyTokens.bodyMedium.copyWith( + color: ColorTokens.onSurfaceVariantDark, + ), + minVerticalPadding: SpacingTokens.md, + ), + tabBarTheme: TabBarThemeData( + labelColor: ColorTokens.primaryLight, + unselectedLabelColor: ColorTokens.onSurfaceVariantDark, + labelStyle: TypographyTokens.titleSmall, + unselectedLabelStyle: TypographyTokens.titleSmall, + indicator: UnderlineTabIndicator( + borderSide: const BorderSide(color: ColorTokens.primaryLight, width: 2.0), + borderRadius: BorderRadius.circular(SpacingTokens.radiusXs), + ), + ), + iconTheme: const IconThemeData( + color: ColorTokens.onSurfaceVariantDark, + size: 24.0, + ), + pageTransitionsTheme: _pageTransitionsTheme, + extensions: const [_customColors, _customSpacing], ); } // ═══════════════════════════════════════════════════════════════════════════ - // SCHÉMA DE COULEURS + // COLOR SCHEME SOMBRE — Navy profond OLED // ═══════════════════════════════════════════════════════════════════════════ - + + static const ColorScheme _darkColorScheme = ColorScheme.dark( + primary: ColorTokens.primaryLight, // #60A5FA sur fond sombre + onPrimary: ColorTokens.onPrimaryContainer, // #1E3A8A + primaryContainer: ColorTokens.navigationIndicatorDark, // #1A2350 + onPrimaryContainer: Color(0xFFD6E8FF), + secondary: ColorTokens.secondaryLight, // #9B59F0 + onSecondary: ColorTokens.onSecondaryContainer, // #2A006F + secondaryContainer: Color(0xFF3B1F6A), + onSecondaryContainer: Color(0xFFEAD5FF), + surface: ColorTokens.surfaceDark, // #161B26 + onSurface: ColorTokens.onSurfaceDark, // #F1F5FF + surfaceContainerHighest: ColorTokens.surfaceVariantDark, // #1A1F2E + onSurfaceVariant: ColorTokens.onSurfaceVariantDark, // #94A3B8 + error: ColorTokens.error, + onError: Colors.white, + errorContainer: Color(0xFF5C1111), + onErrorContainer: ColorTokens.errorLight, + outline: ColorTokens.outlineDark, // #2D3554 + outlineVariant: Color(0xFF1F2B3E), + shadow: ColorTokens.backgroundDark, + scrim: Colors.black, + inverseSurface: ColorTokens.onSurfaceDark, + onInverseSurface: ColorTokens.surfaceDark, + inversePrimary: ColorTokens.primary, + ); + + // ═══════════════════════════════════════════════════════════════════════════ + // COLOR SCHEME CLAIR + // ═══════════════════════════════════════════════════════════════════════════ + static const ColorScheme _lightColorScheme = ColorScheme.light( - // Couleurs primaires primary: ColorTokens.primary, onPrimary: ColorTokens.onPrimary, primaryContainer: ColorTokens.primaryContainer, onPrimaryContainer: ColorTokens.onPrimaryContainer, - - // Couleurs secondaires secondary: ColorTokens.secondary, onSecondary: ColorTokens.onSecondary, secondaryContainer: ColorTokens.secondaryContainer, onSecondaryContainer: ColorTokens.onSecondaryContainer, - - // Couleurs tertiaires tertiary: ColorTokens.tertiary, onTertiary: ColorTokens.onTertiary, tertiaryContainer: ColorTokens.tertiaryContainer, onTertiaryContainer: ColorTokens.onTertiaryContainer, - - // Couleurs d'erreur error: ColorTokens.error, onError: ColorTokens.onError, errorContainer: ColorTokens.errorContainer, onErrorContainer: ColorTokens.onErrorContainer, - - // Couleurs de surface surface: ColorTokens.surface, onSurface: ColorTokens.onSurface, surfaceContainerHighest: ColorTokens.surfaceVariant, onSurfaceVariant: ColorTokens.onSurfaceVariant, - - // Couleurs de contour outline: ColorTokens.outline, outlineVariant: ColorTokens.outlineVariant, - - // Couleurs d'ombre shadow: ColorTokens.shadow, scrim: ColorTokens.shadow, - - // Couleurs d'inversion inverseSurface: ColorTokens.onSurface, onInverseSurface: ColorTokens.surface, inversePrimary: ColorTokens.primaryLight, ); // ═══════════════════════════════════════════════════════════════════════════ - // THÈME TYPOGRAPHIQUE + // TYPOGRAPHIE // ═══════════════════════════════════════════════════════════════════════════ - + static TextTheme get _textTheme => TextTheme( - // Display styles — Playfair Display (GoogleFonts, non-const) displayLarge: TypographyTokens.displayLarge, displayMedium: TypographyTokens.displayMedium, displaySmall: TypographyTokens.displaySmall, - - // Headline styles headlineLarge: TypographyTokens.headlineLarge, headlineMedium: TypographyTokens.headlineMedium, headlineSmall: TypographyTokens.headlineSmall, - - // Title styles titleLarge: TypographyTokens.titleLarge, titleMedium: TypographyTokens.titleMedium, titleSmall: TypographyTokens.titleSmall, - - // Label styles labelLarge: TypographyTokens.labelLarge, labelMedium: TypographyTokens.labelMedium, labelSmall: TypographyTokens.labelSmall, - - // Body styles bodyLarge: TypographyTokens.bodyLarge, bodyMedium: TypographyTokens.bodyMedium, bodySmall: TypographyTokens.bodySmall, ); + /// Thème typographique dark — même structure que _textTheme mais avec + /// ColorTokens.onSurfaceDark (#F1F5FF) pour que le texte soit lisible + /// sur les fonds sombres (#0A0D1A / #161B26). + static TextTheme get _textThemeDark => TextTheme( + displayLarge: TypographyTokens.displayLarge.copyWith(color: ColorTokens.onSurfaceDark), + displayMedium: TypographyTokens.displayMedium.copyWith(color: ColorTokens.onSurfaceDark), + displaySmall: TypographyTokens.displaySmall.copyWith(color: ColorTokens.onSurfaceDark), + headlineLarge: TypographyTokens.headlineLarge.copyWith(color: ColorTokens.onSurfaceDark), + headlineMedium: TypographyTokens.headlineMedium.copyWith(color: ColorTokens.onSurfaceDark), + headlineSmall: TypographyTokens.headlineSmall.copyWith(color: ColorTokens.onSurfaceDark), + titleLarge: TypographyTokens.titleLarge.copyWith(color: ColorTokens.onSurfaceDark), + titleMedium: TypographyTokens.titleMedium.copyWith(color: ColorTokens.onSurfaceDark), + titleSmall: TypographyTokens.titleSmall.copyWith(color: ColorTokens.onSurfaceDark), + labelLarge: TypographyTokens.labelLarge.copyWith(color: ColorTokens.onSurfaceDark), + labelMedium: TypographyTokens.labelMedium.copyWith(color: ColorTokens.onSurfaceDark), + labelSmall: TypographyTokens.labelSmall.copyWith(color: ColorTokens.onSurfaceDark), + bodyLarge: TypographyTokens.bodyLarge.copyWith(color: ColorTokens.onSurfaceDark), + bodyMedium: TypographyTokens.bodyMedium.copyWith(color: ColorTokens.onSurfaceDark), + bodySmall: TypographyTokens.bodySmall.copyWith(color: ColorTokens.onSurfaceDark), + ); + // ═══════════════════════════════════════════════════════════════════════════ - // THÈMES DE COMPOSANTS + // COMPOSANTS // ═══════════════════════════════════════════════════════════════════════════ - - /// Configuration AppBar moderne (sans AppBar traditionnelle) + static const AppBarTheme _appBarTheme = AppBarTheme( elevation: 0, scrolledUnderElevation: 0, @@ -213,20 +404,21 @@ class AppThemeSophisticated { statusBarBrightness: Brightness.light, ), ); - - /// Configuration des cartes sophistiquées + static final CardThemeData _cardTheme = CardThemeData( elevation: SpacingTokens.elevationSm, shadowColor: ColorTokens.shadow, - surfaceTintColor: ColorTokens.surfaceContainer, + surfaceTintColor: Colors.transparent, + color: ColorTokens.surface, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(SpacingTokens.radiusLg), + side: const BorderSide(color: ColorTokens.outline), ), margin: const EdgeInsets.all(SpacingTokens.cardMargin), ); - - /// Configuration des boutons élevés - static final ElevatedButtonThemeData _elevatedButtonTheme = ElevatedButtonThemeData( + + static final ElevatedButtonThemeData _elevatedButtonTheme = + ElevatedButtonThemeData( style: ElevatedButton.styleFrom( elevation: SpacingTokens.elevationSm, shadowColor: ColorTokens.shadow, @@ -246,9 +438,9 @@ class AppThemeSophisticated { ), ), ); - - /// Configuration des boutons remplis - static final FilledButtonThemeData _filledButtonTheme = FilledButtonThemeData( + + static final FilledButtonThemeData _filledButtonTheme = + FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: ColorTokens.primary, foregroundColor: ColorTokens.onPrimary, @@ -266,9 +458,9 @@ class AppThemeSophisticated { ), ), ); - - /// Configuration des boutons avec contour - static final OutlinedButtonThemeData _outlinedButtonTheme = OutlinedButtonThemeData( + + static final OutlinedButtonThemeData _outlinedButtonTheme = + OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: ColorTokens.primary, textStyle: TypographyTokens.buttonMedium, @@ -280,17 +472,13 @@ class AppThemeSophisticated { SpacingTokens.minButtonWidth, SpacingTokens.buttonHeightMedium, ), - side: const BorderSide( - color: ColorTokens.outline, - width: 1.0, - ), + side: const BorderSide(color: ColorTokens.outline), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), ), ), ); - - /// Configuration des boutons texte + static final TextButtonThemeData _textButtonTheme = TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: ColorTokens.primary, @@ -309,8 +497,8 @@ class AppThemeSophisticated { ), ); - /// Configuration des champs de saisie - static final InputDecorationTheme _inputDecorationTheme = InputDecorationTheme( + static final InputDecorationTheme _inputDecorationTheme = + InputDecorationTheme( filled: true, fillColor: ColorTokens.surfaceContainer, labelStyle: TypographyTokens.inputLabel, @@ -334,8 +522,8 @@ class AppThemeSophisticated { contentPadding: const EdgeInsets.all(SpacingTokens.formPadding), ); - /// Configuration de la barre de navigation - static final NavigationBarThemeData _navigationBarTheme = NavigationBarThemeData( + static final NavigationBarThemeData _navigationBarTheme = + NavigationBarThemeData( backgroundColor: ColorTokens.navigationBackground, indicatorColor: ColorTokens.navigationIndicator, labelTextStyle: WidgetStateProperty.resolveWith((states) { @@ -352,12 +540,12 @@ class AppThemeSophisticated { }), ); - /// Configuration du drawer de navigation - static final NavigationDrawerThemeData _navigationDrawerTheme = NavigationDrawerThemeData( + static final NavigationDrawerThemeData _navigationDrawerTheme = + NavigationDrawerThemeData( backgroundColor: ColorTokens.surfaceContainer, elevation: SpacingTokens.elevationMd, shadowColor: ColorTokens.shadow, - surfaceTintColor: ColorTokens.surfaceContainer, + surfaceTintColor: Colors.transparent, indicatorColor: ColorTokens.primaryContainer, labelTextStyle: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { @@ -367,12 +555,11 @@ class AppThemeSophisticated { }), ); - /// Configuration des dialogues static final DialogThemeData _dialogTheme = DialogThemeData( - backgroundColor: ColorTokens.surfaceContainer, + backgroundColor: ColorTokens.surface, elevation: SpacingTokens.elevationLg, shadowColor: ColorTokens.shadow, - surfaceTintColor: ColorTokens.surfaceContainer, + surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(SpacingTokens.radiusXl), ), @@ -380,7 +567,6 @@ class AppThemeSophisticated { contentTextStyle: TypographyTokens.bodyMedium, ); - /// Configuration des snackbars (fixed pour éviter "Floating SnackBar off screen" avec bottomNavigationBar) static final SnackBarThemeData _snackBarTheme = SnackBarThemeData( backgroundColor: ColorTokens.onSurface, contentTextStyle: TypographyTokens.bodyMedium.copyWith( @@ -392,7 +578,6 @@ class AppThemeSophisticated { behavior: SnackBarBehavior.fixed, ); - /// Configuration des puces static final ChipThemeData _chipTheme = ChipThemeData( backgroundColor: ColorTokens.surfaceVariant, selectedColor: ColorTokens.primaryContainer, @@ -406,7 +591,6 @@ class AppThemeSophisticated { ), ); - /// Configuration des éléments de liste static const ListTileThemeData _listTileTheme = ListTileThemeData( contentPadding: EdgeInsets.symmetric( horizontal: SpacingTokens.xl, @@ -418,7 +602,6 @@ class AppThemeSophisticated { minVerticalPadding: SpacingTokens.md, ); - /// Configuration des onglets static final TabBarThemeData _tabBarTheme = TabBarThemeData( labelColor: ColorTokens.primary, unselectedLabelColor: ColorTokens.onSurfaceVariant, @@ -433,56 +616,43 @@ class AppThemeSophisticated { ), ); - /// Configuration des dividers static const DividerThemeData _dividerTheme = DividerThemeData( color: ColorTokens.outline, thickness: 1.0, space: SpacingTokens.md, ); - /// Configuration des icônes static const IconThemeData _iconTheme = IconThemeData( color: ColorTokens.onSurfaceVariant, size: 24.0, ); - /// Configuration des transitions de page - static const PageTransitionsTheme _pageTransitionsTheme = PageTransitionsTheme( + static const PageTransitionsTheme _pageTransitionsTheme = + PageTransitionsTheme( builders: { TargetPlatform.android: CupertinoPageTransitionsBuilder(), TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), }, ); - /// Extensions personnalisées - Couleurs static const CustomColors _customColors = CustomColors(); - - /// Extensions personnalisées - Espacements static const CustomSpacing _customSpacing = CustomSpacing(); } -/// Extension de couleurs personnalisées class CustomColors extends ThemeExtension { const CustomColors(); - @override CustomColors copyWith() => const CustomColors(); - @override - CustomColors lerp(ThemeExtension? other, double t) { - return const CustomColors(); - } + CustomColors lerp(ThemeExtension? other, double t) => + const CustomColors(); } -/// Extension d'espacements personnalisés class CustomSpacing extends ThemeExtension { const CustomSpacing(); - @override CustomSpacing copyWith() => const CustomSpacing(); - @override - CustomSpacing lerp(ThemeExtension? other, double t) { - return const CustomSpacing(); - } + CustomSpacing lerp(ThemeExtension? other, double t) => + const CustomSpacing(); } diff --git a/lib/shared/design_system/tokens/app_colors.dart b/lib/shared/design_system/tokens/app_colors.dart index aef9848..7240e71 100644 --- a/lib/shared/design_system/tokens/app_colors.dart +++ b/lib/shared/design_system/tokens/app_colors.dart @@ -1,38 +1,524 @@ import 'package:flutter/material.dart'; -/// UnionFlow Mobile App - Couleurs Globales (DRY) -/// Palette : Vert Forêt (Jour) / Vert Ardoise (Nuit) +/// UnionFlow Mobile App — Source unique de vérité pour toutes les couleurs +/// +/// Palette WCAG 2.1 validée, alignée sur le logo SVG (gradient #297FFF→#7616E8) +/// et le frontend web Freya. +/// +/// ╔══════════════════════════════════════════════════════════════════════════╗ +/// ║ RÈGLES D'USAGE ║ +/// ║ • Texte sur fond clair → textPrimary / textTitle (WCAG AAA/AA) ║ +/// ║ • Texte sur fond sombre → textPrimaryDark / textTitleDark ║ +/// ║ • Boutons / icônes UI → primary (WCAG AA) ║ +/// ║ • Texte sur bouton coloré → onPrimary / onError / onGradient ║ +/// ║ • Gradient/branding → logoGradient (décoratif) ║ +/// ║ • Accent violet UI → accent (≥3:1) ║ +/// ║ • Fond carte/surface → surface / surfaceDark ║ +/// ║ • Fond page → background / backgroundDark ║ +/// ║ • Fond légèrement teinté → backgroundSubtle / backgroundSubtleDark ║ +/// ║ • Bordures → border / borderDark ║ +/// ║ • Ombres → shadow / shadowMedium / softShadow ║ +/// ╚══════════════════════════════════════════════════════════════════════════╝ class AppColors { - // --- Branding --- - static const Color primaryGreen = Color(0xFF2E7D32); // Vert forêt professionnel - static const Color brandGreen = Color(0xFF1B5E20); // Vert foncé / gradient top - static const Color brandGreenLight = Color(0xFF4CAF50); // Vert d'accentuation - static const Color brandMint = Color(0xFFA5D6A7); // Vert menthe / secondaire + AppColors._(); - // --- Mode Jour (Light) --- - static const Color lightBackground = Color(0xFFF1F8E9); // Teinte verte très légère - static const Color lightSurface = Color(0xFFFFFFFF); // Blanc pur pour les cartes - static const Color lightBorder = Color(0xFFC8E6C9); // Bordure vert pâle + // ═══════════════════════════════════════════════════════════════════════════ + // LOGO + // ═══════════════════════════════════════════════════════════════════════════ - // --- Mode Nuit (Dark) --- - static const Color darkBackground = Color(0xFF0F1A0F); // Vert noir profond - static const Color darkSurface = Color(0xFF1A2E1A); // Vert ardoise - static const Color darkBorder = Color(0xFF3A5E3A); // Bordure sombre + /// #297FFF — Bleu logo (début du gradient décoratif — décoratif uniquement) + static const Color logoBlue = Color(0xFF297FFF); - // --- Texte --- - static const Color textPrimaryLight = Color(0xFF1C2B1C); // Vert très foncé / quasi noir - static const Color textSecondaryLight = Color(0xFF4E6B4E); // Vert gris moyen - static const Color textPrimaryDark = Color(0xFFE0F2E0); // Blanc verdâtre - static const Color textSecondaryDark = Color(0xFF90B890); // Vert gris clair + // ═══════════════════════════════════════════════════════════════════════════ + // PRIMAIRE — Bleu UnionFlow + // ═══════════════════════════════════════════════════════════════════════════ - // --- Sémantique --- + /// #2563EB — Bleu primaire UI [5.7:1 sur blanc ✅ WCAG AA] + static const Color primary = Color(0xFF2563EB); + + /// #60A5FA — Bleu clair (dark mode, fonds colorés) + static const Color primaryLight = Color(0xFF60A5FA); + + /// #1D4ED8 — Bleu foncé [7.2:1 sur blanc ✅ WCAG AAA] + static const Color primaryDark = Color(0xFF1D4ED8); + + /// #EFF6FF — Container / fond clair teinté bleu + static const Color primaryContainer = Color(0xFFEFF6FF); + + /// #1E3A8A — Contenu sur container primaire + static const Color onPrimaryContainer = Color(0xFF1E3A8A); + + // ═══════════════════════════════════════════════════════════════════════════ + // ACCENT / SECONDAIRE — Violet logo + // ═══════════════════════════════════════════════════════════════════════════ + + /// #7616E8 — Violet accent (gradient fin logo, UI ≥3:1, NON pour texte) + static const Color accent = Color(0xFF7616E8); + + /// #9B59F0 — Violet clair + static const Color accentLight = Color(0xFF9B59F0); + + /// #5B0FBA — Violet foncé [4.8:1 sur blanc ✅ pour texte] + static const Color accentDark = Color(0xFF5B0FBA); + + /// #F3ECFF — Container accent clair + static const Color accentContainer = Color(0xFFF3ECFF); + + /// #2A006F — Contenu sur container accent + static const Color onAccentContainer = Color(0xFF2A006F); + + // ═══════════════════════════════════════════════════════════════════════════ + // TERTIAIRE — Bleu intermédiaire (MD3) + // ═══════════════════════════════════════════════════════════════════════════ + + /// #5297FF — Bleu intermédiaire (mi-chemin logo) + static const Color tertiary = Color(0xFF5297FF); + + /// #85BAFF — Tertiaire clair + static const Color tertiaryLight = Color(0xFF85BAFF); + + /// #DBEAFF — Container tertiaire + static const Color tertiaryContainer = Color(0xFFDBEAFF); + + // ═══════════════════════════════════════════════════════════════════════════ + // SURFACES — Mode Jour + // ═══════════════════════════════════════════════════════════════════════════ + + /// #EFF6FF — Fond principal (légèrement teinté bleu) + static const Color background = Color(0xFFEFF6FF); + + /// #F8FAFC — Fond subtil (zones secondaires, headers de liste) + static const Color backgroundSubtle = Color(0xFFF8FAFC); + + /// #FFFFFF — Surface pure (cards, dialogs) + static const Color surface = Color(0xFFFFFFFF); + + /// #F1F5F9 — Surface variante (sections internes) + static const Color surfaceVariant = Color(0xFFF1F5F9); + + /// #F5F8FF — Surface container (légèrement teintée) + static const Color surfaceContainer = Color(0xFFF5F8FF); + + /// #EAF0FF — Surface container élevée + static const Color surfaceContainerHigh = Color(0xFFEAF0FF); + + /// #E2E8F0 — Bordures subtiles + static const Color border = Color(0xFFE2E8F0); + + /// #CBD5E1 — Bordures plus marquées (séparateurs, inputs) + static const Color borderStrong = Color(0xFFCBD5E1); + + // ═══════════════════════════════════════════════════════════════════════════ + // SURFACES — Mode Nuit + // ═══════════════════════════════════════════════════════════════════════════ + + /// #0A0D1A — Navy profond OLED [20:1 ✅] + static const Color backgroundDark = Color(0xFF0A0D1A); + + /// #121824 — Fond subtil dark + static const Color backgroundSubtleDark = Color(0xFF121824); + + /// #161B26 — Surface principale dark + static const Color surfaceDark = Color(0xFF161B26); + + /// #1E2535 — Surface variante dark + static const Color surfaceVariantDark = Color(0xFF1E2535); + + /// #1E2438 — Surface container dark (cards) + static const Color surfaceContainerDark = Color(0xFF1E2438); + + /// #2D3554 — Bordures dark + static const Color borderDark = Color(0xFF2D3554); + + /// #3D4B6E — Bordures plus marquées dark + static const Color borderStrongDark = Color(0xFF3D4B6E); + + // ═══════════════════════════════════════════════════════════════════════════ + // TEXTE — Mode Jour + // ═══════════════════════════════════════════════════════════════════════════ + + /// #0F172A — Texte principal [19.1:1 ✅ WCAG AAA] + static const Color textPrimary = Color(0xFF0F172A); + + /// #1E293B — Titres (hiérarchie entre primary et body) + static const Color textTitle = Color(0xFF1E293B); + + /// #475569 — Texte secondaire [7.2:1 ✅ WCAG AA] + static const Color textSecondary = Color(0xFF475569); + + /// #64748B — Texte tertiaire / icônes secondaires [4.8:1 ✅] + static const Color textTertiary = Color(0xFF64748B); + + /// #94A3B8 — Texte désactivé / captions [3.3:1, hints uniquement] + static const Color textDisabled = Color(0xFF94A3B8); + + // ═══════════════════════════════════════════════════════════════════════════ + // TEXTE — Mode Nuit + // ═══════════════════════════════════════════════════════════════════════════ + + /// #F1F5FF — Texte principal dark + static const Color textPrimaryDark = Color(0xFFF1F5FF); + + /// #FFFFFF — Titres dark (lumineux pour la hiérarchie) + static const Color textTitleDark = Color(0xFFFFFFFF); + + /// #94A3B8 — Texte secondaire dark + static const Color textSecondaryDark = Color(0xFF94A3B8); + + /// #64748B — Texte désactivé dark + static const Color textDisabledDark = Color(0xFF64748B); + + // ═══════════════════════════════════════════════════════════════════════════ + // ON-COLORS — Contenu SUR une couleur + // NE PAS hardcoder Colors.white — utiliser ces tokens. + // ═══════════════════════════════════════════════════════════════════════════ + + /// Blanc pur — texte/icônes sur [primary], gradient, fond coloré + static const Color onPrimary = Color(0xFFFFFFFF); + + /// Blanc pur — texte/icônes sur [accent] + static const Color onAccent = Color(0xFFFFFFFF); + + /// Blanc pur — texte/icônes sur [error] + static const Color onError = Color(0xFFFFFFFF); + + /// Blanc pur — texte/icônes sur [success] + static const Color onSuccess = Color(0xFFFFFFFF); + + /// Blanc pur — texte/icônes sur [warning] foncé + static const Color onWarning = Color(0xFFFFFFFF); + + /// Blanc pur — texte/icônes sur gradient logo / module headers + static const Color onGradient = Color(0xFFFFFFFF); + + // ═══════════════════════════════════════════════════════════════════════════ + // SÉMANTIQUE — Succès, Erreur, Avertissement, Info + // ═══════════════════════════════════════════════════════════════════════════ + + /// #15803D — Succès texte [6.6:1 ✅ WCAG AA] + static const Color success = Color(0xFF15803D); + + /// #166534 — Succès foncé + static const Color successDark = Color(0xFF166534); + + /// #22C55E — Succès UI/icônes (≥3:1) + static const Color successUI = Color(0xFF22C55E); + + /// #DCFCE7 — Fond succès light (badges, chips) + static const Color successContainer = Color(0xFFDCFCE7); + + /// #052E16 — Contenu sur container succès + static const Color onSuccessContainer = Color(0xFF052E16); + + /// #DC2626 — Erreur [4.5:1 ✅ WCAG AA] static const Color error = Color(0xFFDC2626); - static const Color success = Color(0xFF2E7D32); - static const Color warning = Color(0xFFF59E0B); - static const Color info = Color(0xFF0288D1); - // --- Utilitaires --- + /// #B91C1C — Erreur foncée + static const Color errorDark = Color(0xFFB91C1C); + + /// #FCA5A5 — Erreur claire (icônes sur fond blanc) + static const Color errorLight = Color(0xFFFCA5A5); + + /// #FEE2E2 — Fond erreur light + static const Color errorContainer = Color(0xFFFEE2E2); + + /// #410002 — Contenu sur container erreur + static const Color onErrorContainer = Color(0xFF410002); + + /// #B45309 — Warning texte [5.8:1 ✅ WCAG AA] + static const Color warning = Color(0xFFB45309); + + /// #92400E — Warning foncé + static const Color warningDark = Color(0xFF92400E); + + /// #F59E0B — Warning UI/icônes (décoratif) + static const Color warningUI = Color(0xFFF59E0B); + + /// #FEF3C7 — Fond warning light + static const Color warningContainer = Color(0xFFFEF3C7); + + /// #2D1B00 — Contenu sur container warning + static const Color onWarningContainer = Color(0xFF2D1B00); + + /// #2563EB — Info (identique au primaire) + static const Color info = Color(0xFF2563EB); + + /// #1D4ED8 — Info foncée + static const Color infoDark = Color(0xFF1D4ED8); + + /// #60A5FA — Info claire + static const Color infoLight = Color(0xFF60A5FA); + + /// #DBEAFE — Fond info light + static const Color infoContainer = Color(0xFFDBEAFE); + + /// #1E3A8A — Contenu sur container info + static const Color onInfoContainer = Color(0xFF1E3A8A); + + // ═══════════════════════════════════════════════════════════════════════════ + // NAVIGATION + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color navigationBackground = Color(0xFFFFFFFF); + static const Color navigationSelected = Color(0xFF2563EB); + static const Color navigationUnselected = Color(0xFF64748B); + static const Color navigationIndicator = Color(0xFFEFF6FF); + + static const Color navigationBackgroundDark = Color(0xFF161B26); + static const Color navigationSelectedDark = Color(0xFF60A5FA); + static const Color navigationUnselectedDark = Color(0xFF64748B); + static const Color navigationIndicatorDark = Color(0xFF1A2350); + + // ═══════════════════════════════════════════════════════════════════════════ + // GLASS / EFFETS + // ═══════════════════════════════════════════════════════════════════════════ + + /// Fond verre translucide (glassmorphism) + static const Color glassBackground = Color(0x80FFFFFF); + + /// Bordure verre translucide + static const Color glassBorder = Color(0x33FFFFFF); + + /// Overlay verre très subtil + static const Color glassOverlay = Color(0x0DFFFFFF); + + // ═══════════════════════════════════════════════════════════════════════════ + // OMBRES — Color tokens + // ═══════════════════════════════════════════════════════════════════════════ + + /// Ombre très subtile (élévation 1-2) + static const Color shadow = Color(0x0A000000); + + /// Ombre légère (élévation 3-4) + static const Color shadowMedium = Color(0x1A000000); + + /// Ombre marquée (élévation 6+) + static const Color shadowStrong = Color(0x33000000); + + /// Ombre intense (modales, drawers) + static const Color shadowHigh = Color(0x4D000000); + + // ═══════════════════════════════════════════════════════════════════════════ + // OMBRES — BoxShadow getters (prêts à l'emploi) + // ═══════════════════════════════════════════════════════════════════════════ + + /// Ombre douce — cards standards + static List get softShadow => [ + BoxShadow( + color: const Color(0xFF0F172A).withOpacity(0.08), + blurRadius: 24, + offset: const Offset(0, 8), + ), + ]; + + /// Ombre moyenne — éléments surélevés, sheets + static List get mediumShadow => [ + BoxShadow( + color: const Color(0xFF0F172A).withOpacity(0.12), + blurRadius: 32, + offset: const Offset(0, 12), + ), + ]; + + /// Halo bleu primaire — boutons CTA, éléments actifs + static List get blueGlowShadow => [ + BoxShadow( + color: primary.withOpacity(0.25), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ]; + + /// Halo violet accent — éléments premium, gradients + static List get purpleGlowShadow => [ + BoxShadow( + color: accent.withOpacity(0.2), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ]; + + // ═══════════════════════════════════════════════════════════════════════════ + // PALETTE BRAND — Couleurs expressives (dashboards, UI premium) + // ═══════════════════════════════════════════════════════════════════════════ + + /// #F4A261 — Ambre chaleureux (dashboards consultant, UI premium) + static const Color amber = Color(0xFFF4A261); + + /// #F8C590 — Ambre clair + static const Color amberLight = Color(0xFFF8C590); + + /// #E9DCC9 — Sable doux (fonds neutres chauds) + static const Color sand = Color(0xFFE9DCC9); + + /// #F5F0E8 — Sable clair + static const Color sandLight = Color(0xFFF5F0E8); + + /// #E07A5F — Terracotta (accents événements, UI chaleureux) + static const Color terracotta = Color(0xFFE07A5F); + + /// #F2AC99 — Terracotta clair + static const Color terracottaLight = Color(0xFFF2AC99); + + /// #FFF3F0 — Terracotta pale (containers, badges) + static const Color terracottaPale = Color(0xFFFFF3F0); + + /// #1E1A44 — Indigo profond (fonds sombres premium) + static const Color indigo = Color(0xFF1E1A44); + + /// #3A3A6B — Indigo moyen + static const Color indigoLight = Color(0xFF3A3A6B); + + // ═══════════════════════════════════════════════════════════════════════════ + // GRADIENTS — List (pour ShaderMask, charts, etc.) + // ═══════════════════════════════════════════════════════════════════════════ + + /// Gradient logo signature : #297FFF → #7616E8 (décoratif — non pour texte) + static const List logoGradientColors = [ + Color(0xFF297FFF), + Color(0xFF7616E8), + ]; + + /// Gradient primaire UI (accessible) + static const List primaryGradientColors = [ + Color(0xFF1D4ED8), + Color(0xFF2563EB), + Color(0xFF5297FF), + ]; + + /// Gradient login (fond immersif) + static const List loginGradientColors = [ + Color(0xFF1D4ED8), + Color(0xFF2563EB), + Color(0xFF7616E8), + ]; + + /// Gradient dark mode + static const List darkGradientColors = [ + Color(0xFF0A0D1A), + Color(0xFF161B26), + Color(0xFF1E2438), + ]; + + /// Gradient succès + static const List successGradientColors = [ + Color(0xFF166534), + Color(0xFF15803D), + ]; + + /// Gradient warm (terracotta → ambre) + static const List warmGradientColors = [ + Color(0xFFE07A5F), + Color(0xFFF4A261), + ]; + + // ═══════════════════════════════════════════════════════════════════════════ + // GRADIENTS — LinearGradient (prêts à l'emploi) + // ═══════════════════════════════════════════════════════════════════════════ + + /// Gradient logo signature (décoratif — NE PAS utiliser pour du texte) + static const LinearGradient logoGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF297FFF), Color(0xFF7616E8)], + ); + + /// Gradient primaire UI (version accessible) + static const LinearGradient primaryGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF1D4ED8), Color(0xFF2563EB)], + ); + + /// Gradient login (fond immersif plein écran) + static const LinearGradient loginGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF1D4ED8), Color(0xFF2563EB), Color(0xFF7616E8)], + stops: [0.0, 0.55, 1.0], + ); + + /// Gradient subtil (backgrounds de sections, headers légers) + static const LinearGradient subtleGradient = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFFF5F8FF), Color(0xFFEFF6FF)], + ); + + /// Gradient warm terracotta → ambre (UI premium, dashboards) + static const LinearGradient warmGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFFE07A5F), Color(0xFFF4A261)], + ); + + // ═══════════════════════════════════════════════════════════════════════════ + // UTILITAIRES + // ═══════════════════════════════════════════════════════════════════════════ + + /// Applique une opacité à une couleur + static Color withOpacity(Color color, double opacity) => + color.withOpacity(opacity); + + /// Éclaircit une couleur d'un facteur donné (HSL) + static Color lighten(Color color, [double amount = 0.1]) { + final hsl = HSLColor.fromColor(color); + return hsl + .withLightness((hsl.lightness + amount).clamp(0.0, 1.0)) + .toColor(); + } + + /// Assombrit une couleur d'un facteur donné (HSL) + static Color darken(Color color, [double amount = 0.1]) { + final hsl = HSLColor.fromColor(color); + return hsl + .withLightness((hsl.lightness - amount).clamp(0.0, 1.0)) + .toColor(); + } + + // ═══════════════════════════════════════════════════════════════════════════ + // ALIAS DE COMPATIBILITÉ (@deprecated — à migrer progressivement) + // ═══════════════════════════════════════════════════════════════════════════ + + /// @deprecated → [primary] + static const Color primaryGreen = primary; + + /// @deprecated → [primaryDark] + static const Color brandGreen = primaryDark; + + /// @deprecated → [primaryLight] + static const Color brandGreenLight = primaryLight; + + /// @deprecated → [primaryContainer] + static const Color brandMint = primaryContainer; + + /// @deprecated → [background] + static const Color lightBackground = background; + + /// @deprecated → [surface] + static const Color lightSurface = surface; + + /// @deprecated → [border] + static const Color lightBorder = border; + + /// @deprecated → [backgroundDark] + static const Color darkBackground = backgroundDark; + + /// @deprecated → [surfaceDark] + static const Color darkSurface = surfaceDark; + + /// @deprecated → [borderDark] + static const Color darkBorder = borderDark; + + /// @deprecated → [textPrimary] + static const Color textPrimaryLight = textPrimary; + + /// @deprecated → [textSecondary] + static const Color textSecondaryLight = textSecondary; + + // ═══════════════════════════════════════════════════════════════════════════ + // DIVERS + // ═══════════════════════════════════════════════════════════════════════════ + static const Color transparent = Colors.transparent; - static const Color surface = lightSurface; - static const Color background = lightBackground; } diff --git a/lib/shared/design_system/tokens/color_tokens.dart b/lib/shared/design_system/tokens/color_tokens.dart index 4a9b45e..f23030a 100644 --- a/lib/shared/design_system/tokens/color_tokens.dart +++ b/lib/shared/design_system/tokens/color_tokens.dart @@ -1,190 +1,126 @@ -/// Design Tokens - Couleurs UnionFlow +/// Facade de compatibilité — redirige vers [AppColors] /// -/// Palette Vert Zen / Santé — Professionnelle et apaisante -/// MODE JOUR : Vert Forêt (#2E7D32) — Forest Green -/// MODE NUIT : Vert Ardoise (#1A2E1A) — Slate Green +/// Cette classe existe uniquement pour ne pas casser le code existant. +/// Pour tout nouveau code, utiliser [AppColors] directement. +/// +/// @deprecated Utiliser [AppColors] library color_tokens; import 'package:flutter/material.dart'; +import 'app_colors.dart'; -/// Tokens de couleurs UnionFlow - Design System Unifié class ColorTokens { ColorTokens._(); - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS PRIMAIRES - MODE JOUR (Vert Forêt) - // ═══════════════════════════════════════════════════════════════════════════ + // ── Primaire ──────────────────────────────────────────────────────────── + static const Color primary = AppColors.primary; + static const Color primaryLight = AppColors.primaryLight; + static const Color primaryDark = AppColors.primaryDark; + static const Color primaryContainer = AppColors.primaryContainer; + static const Color onPrimary = AppColors.onPrimary; + static const Color onPrimaryContainer = AppColors.onPrimaryContainer; - /// Couleur primaire principale - Vert Forêt - static const Color primary = Color(0xFF2E7D32); - static const Color primaryLight = Color(0xFF4CAF50); - static const Color primaryDark = Color(0xFF1B5E20); - static const Color primaryContainer = Color(0xFFE8F5E9); - static const Color onPrimary = Color(0xFFFFFFFF); - static const Color onPrimaryContainer = Color(0xFF003908); + // ── Secondaire (= Accent) ──────────────────────────────────────────────── + static const Color secondary = AppColors.accent; + static const Color secondaryLight = AppColors.accentLight; + static const Color secondaryDark = AppColors.accentDark; + static const Color secondaryContainer = AppColors.accentContainer; + static const Color onSecondary = AppColors.onAccent; + static const Color onSecondaryContainer = AppColors.onAccentContainer; - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS PRIMAIRES - MODE NUIT (Vert Ardoise) - // ═══════════════════════════════════════════════════════════════════════════ + // ── Tertiaire ──────────────────────────────────────────────────────────── + static const Color tertiary = AppColors.tertiary; + static const Color tertiaryLight = AppColors.tertiaryLight; + static const Color tertiaryDark = AppColors.primaryDark; + static const Color tertiaryContainer = AppColors.tertiaryContainer; + static const Color onTertiary = AppColors.onPrimary; + static const Color onTertiaryContainer = AppColors.onPrimaryContainer; - static const Color primaryDarkMode = Color(0xFF1A2E1A); - static const Color primaryLightDarkMode = Color(0xFF2E4D2E); - static const Color primaryDarkDarkMode = Color(0xFF0F1A0F); - static const Color primaryContainerDarkMode = Color(0xFF1E3A1E); - static const Color onPrimaryDarkMode = Color(0xFFE0F2E0); + // ── Surfaces Mode Jour ─────────────────────────────────────────────────── + static const Color background = AppColors.background; + static const Color surface = AppColors.surface; + static const Color surfaceVariant = AppColors.surfaceContainer; + static const Color surfaceContainer = AppColors.surface; + static const Color surfaceContainerHigh = AppColors.surfaceContainer; + static const Color surfaceContainerHighest = AppColors.surfaceContainerHigh; + static const Color onSurface = AppColors.textPrimary; + static const Color onSurfaceVariant = AppColors.textSecondary; + static const Color textSecondary = AppColors.textTertiary; + static const Color outline = AppColors.border; + static const Color outlineVariant = AppColors.borderStrong; - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS SECONDAIRES - Vert Menthe / Sauge - // ═══════════════════════════════════════════════════════════════════════════ + // ── Surfaces Mode Nuit ─────────────────────────────────────────────────── + static const Color backgroundDark = AppColors.backgroundDark; + static const Color surfaceDark = AppColors.surfaceDark; + static const Color surfaceVariantDark = AppColors.surfaceVariantDark; + static const Color surfaceContainerDark = AppColors.surfaceContainerDark; + static const Color onSurfaceDark = AppColors.textPrimaryDark; + static const Color onSurfaceVariantDark = AppColors.textSecondaryDark; + static const Color outlineDark = AppColors.borderDark; - static const Color secondary = Color(0xFF66BB6A); - static const Color secondaryLight = Color(0xFFA5D6A7); - static const Color secondaryDark = Color(0xFF388E3C); - static const Color secondaryContainer = Color(0xFFC8E6C9); - static const Color onSecondary = Color(0xFFFFFFFF); - static const Color onSecondaryContainer = Color(0xFF002106); + // ── Sémantique ─────────────────────────────────────────────────────────── + static const Color success = AppColors.success; + static const Color successLight = AppColors.successUI; + static const Color successDark = AppColors.successDark; + static const Color successContainer = AppColors.successContainer; + static const Color onSuccess = AppColors.onSuccess; + static const Color onSuccessContainer = AppColors.onSuccessContainer; - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS TERTIAIRES - Vert Lime / Accent - // ═══════════════════════════════════════════════════════════════════════════ + static const Color error = AppColors.error; + static const Color errorLight = AppColors.errorLight; + static const Color errorDark = AppColors.errorDark; + static const Color errorContainer = AppColors.errorContainer; + static const Color onError = AppColors.onError; + static const Color onErrorContainer = AppColors.onErrorContainer; - static const Color tertiary = Color(0xFF8BC34A); - static const Color tertiaryLight = Color(0xFFAED581); - static const Color tertiaryDark = Color(0xFF558B2F); - static const Color tertiaryContainer = Color(0xFFDCEDC8); - static const Color onTertiary = Color(0xFFFFFFFF); - static const Color onTertiaryContainer = Color(0xFF1B3A00); + static const Color warning = AppColors.warning; + static const Color warningLight = AppColors.warningUI; + static const Color warningDark = AppColors.warningDark; + static const Color warningContainer = AppColors.warningContainer; + static const Color onWarning = AppColors.onWarning; + static const Color onWarningContainer = AppColors.onWarningContainer; - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS NEUTRES - MODE JOUR - // ═══════════════════════════════════════════════════════════════════════════ + static const Color info = AppColors.info; + static const Color infoLight = AppColors.infoLight; + static const Color infoDark = AppColors.infoDark; + static const Color infoContainer = AppColors.infoContainer; + static const Color onInfo = AppColors.onPrimary; + static const Color onInfoContainer = AppColors.onInfoContainer; - static const Color surface = Color(0xFFFFFFFF); - static const Color surfaceVariant = Color(0xFFF1F8E9); - static const Color surfaceContainer = Color(0xFFFFFFFF); - static const Color surfaceContainerHigh = Color(0xFFF1F8E9); - static const Color surfaceContainerHighest = Color(0xFFDCEDC8); - static const Color background = Color(0xFFF1F8E9); + // ── Navigation ─────────────────────────────────────────────────────────── + static const Color navigationBackground = AppColors.navigationBackground; + static const Color navigationSelected = AppColors.navigationSelected; + static const Color navigationUnselected = AppColors.navigationUnselected; + static const Color navigationIndicator = AppColors.navigationIndicator; + static const Color navigationBackgroundDark = AppColors.navigationBackgroundDark; + static const Color navigationSelectedDark = AppColors.navigationSelectedDark; + static const Color navigationUnselectedDark = AppColors.navigationUnselectedDark; + static const Color navigationIndicatorDark = AppColors.navigationIndicatorDark; - static const Color onSurface = Color(0xFF1C2B1C); - static const Color onSurfaceVariant = Color(0xFF4E6B4E); - static const Color textSecondary = Color(0xFF4E6B4E); - static const Color outline = Color(0xFFC8E6C9); - static const Color outlineVariant = Color(0xFFDCEDC8); + // ── Ombres & Effets ────────────────────────────────────────────────────── + static const Color shadow = AppColors.shadow; + static const Color shadowMedium = AppColors.shadowMedium; + static const Color shadowHigh = AppColors.shadowHigh; - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS NEUTRES - MODE NUIT - // ═══════════════════════════════════════════════════════════════════════════ + static const Color glassBackground = AppColors.glassBackground; + static const Color glassBorder = AppColors.glassBorder; + static const Color glassOverlay = AppColors.glassOverlay; - static const Color surfaceDarkMode = Color(0xFF1A2E1A); - static const Color surfaceVariantDarkMode = Color(0xFF243824); - static const Color backgroundDarkMode = Color(0xFF0F1A0F); + // ── Gradients ──────────────────────────────────────────────────────────── + static const List logoGradient = AppColors.logoGradientColors; + static const List primaryGradient = AppColors.primaryGradientColors; + static const List loginGradient = AppColors.loginGradientColors; + static const List primaryGradientDark = AppColors.darkGradientColors; + static const List secondaryGradient = [AppColors.tertiary, AppColors.accent]; + static const List successGradient = AppColors.successGradientColors; - static const Color onSurfaceDarkMode = Color(0xFFE0F2E0); - static const Color onSurfaceVariantDarkMode = Color(0xFF90B890); - static const Color outlineDarkMode = Color(0xFF3A5E3A); + // ── Utilitaires ────────────────────────────────────────────────────────── + static Color withOpacity(Color color, double opacity) => + AppColors.withOpacity(color, opacity); - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS SÉMANTIQUES - // ═══════════════════════════════════════════════════════════════════════════ + static Color lighten(Color color, [double amount = 0.1]) => + AppColors.lighten(color, amount); - static const Color success = Color(0xFF2E7D32); - static const Color successLight = Color(0xFF4CAF50); - static const Color successDark = Color(0xFF1B5E20); - static const Color successContainer = Color(0xFFE8F5E9); - static const Color onSuccess = Color(0xFFFFFFFF); - static const Color onSuccessContainer = Color(0xFF003908); - - static const Color error = Color(0xFFDC2626); - static const Color errorLight = Color(0xFFEF4444); - static const Color errorDark = Color(0xFFB91C1C); - static const Color errorContainer = Color(0xFFFEF2F2); - static const Color onError = Color(0xFFFFFFFF); - static const Color onErrorContainer = Color(0xFF410002); - - static const Color warning = Color(0xFFF59E0B); - static const Color warningLight = Color(0xFFFBBF24); - static const Color warningDark = Color(0xFFD97706); - static const Color warningContainer = Color(0xFFFEF3C7); - static const Color onWarning = Color(0xFFFFFFFF); - static const Color onWarningContainer = Color(0xFF2D1B00); - - static const Color info = Color(0xFF0288D1); - static const Color infoLight = Color(0xFF29B6F6); - static const Color infoDark = Color(0xFF01579B); - static const Color infoContainer = Color(0xFFE1F5FE); - static const Color onInfo = Color(0xFFFFFFFF); - static const Color onInfoContainer = Color(0xFF001D36); - - // ═══════════════════════════════════════════════════════════════════════════ - // COULEURS DE NAVIGATION - // ═══════════════════════════════════════════════════════════════════════════ - - static const Color navigationBackground = Color(0xFFFFFFFF); - static const Color navigationSelected = Color(0xFF2E7D32); - static const Color navigationUnselected = Color(0xFF4E6B4E); - static const Color navigationIndicator = Color(0xFFE8F5E9); - - static const Color navigationBackgroundDarkMode = Color(0xFF1A2E1A); - static const Color navigationSelectedDarkMode = Color(0xFFA5D6A7); - static const Color navigationUnselectedDarkMode = Color(0xFF90B890); - static const Color navigationIndicatorDarkMode = Color(0xFF2E4D2E); - - // ═══════════════════════════════════════════════════════════════════════════ - // OMBRES ET EFFETS - // ═══════════════════════════════════════════════════════════════════════════ - - static const Color shadow = Color(0x1A1C2B1C); - static const Color shadowMedium = Color(0x331C2B1C); - static const Color shadowHigh = Color(0x4D1C2B1C); - - static const Color glassBackground = Color(0x80FFFFFF); - static const Color glassBorder = Color(0x33FFFFFF); - static const Color glassOverlay = Color(0x0DFFFFFF); - - // ═══════════════════════════════════════════════════════════════════════════ - // GRADIENTS - // ═══════════════════════════════════════════════════════════════════════════ - - static const List primaryGradient = [ - Color(0xFF1B5E20), - Color(0xFF2E7D32), - Color(0xFF388E3C), - ]; - - static const List primaryGradientDarkMode = [ - Color(0xFF0F1A0F), - Color(0xFF1A2E1A), - Color(0xFF243824), - ]; - - static const List secondaryGradient = [ - Color(0xFF388E3C), - Color(0xFF66BB6A), - ]; - - static const List successGradient = [ - Color(0xFF2E7D32), - Color(0xFF4CAF50), - ]; - - // ═══════════════════════════════════════════════════════════════════════════ - // MÉTHODES UTILITAIRES - // ═══════════════════════════════════════════════════════════════════════════ - - static Color withOpacity(Color color, double opacity) { - return color.withOpacity(opacity); - } - - static Color lighten(Color color, [double amount = 0.1]) { - final hsl = HSLColor.fromColor(color); - final lightness = (hsl.lightness + amount).clamp(0.0, 1.0); - return hsl.withLightness(lightness).toColor(); - } - - static Color darken(Color color, [double amount = 0.1]) { - final hsl = HSLColor.fromColor(color); - final lightness = (hsl.lightness - amount).clamp(0.0, 1.0); - return hsl.withLightness(lightness).toColor(); - } + static Color darken(Color color, [double amount = 0.1]) => + AppColors.darken(color, amount); } diff --git a/lib/shared/design_system/tokens/module_colors.dart b/lib/shared/design_system/tokens/module_colors.dart new file mode 100644 index 0000000..1f68b89 --- /dev/null +++ b/lib/shared/design_system/tokens/module_colors.dart @@ -0,0 +1,218 @@ +/// Système d'identité chromatique par module — UnionFlow +/// +/// Chaque module/section de l'application possède : +/// • Une couleur primaire distinctive (WCAG AA validée) +/// • Un gradient directionnel (foncé → clair) +/// • Une couleur de container claire (fonds, badges) +/// • La couleur sur laquelle écrire en blanc +/// +/// Principe : quand l'utilisateur entre dans un écran d'un module, +/// le top bar reflète intelligemment la couleur de ce module. +library module_colors; + +import 'package:flutter/material.dart'; +import 'app_colors.dart'; + +class ModuleColors { + ModuleColors._(); + + // ═══════════════════════════════════════════════════════════════════════════ + // ORGANISATIONS — Bleu primaire UnionFlow [5.7:1 ✅] + // Bouton dashboard : Icons.business_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color organisations = Color(0xFF2563EB); + static const Color organisationsDark = Color(0xFF1D4ED8); + static const Color organisationsLight = Color(0xFFEFF6FF); + static const Color organisationsOnColor = AppColors.onGradient; + static const List organisationsGradient = [Color(0xFF1D4ED8), Color(0xFF2563EB)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // MEMBRES — Violet accent [4.8:1 ✅ pour texte] + // Bouton dashboard : Icons.people_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color membres = Color(0xFF7616E8); + static const Color membresDark = Color(0xFF5B0FBA); + static const Color membresLight = Color(0xFFF3ECFF); + static const Color membresOnColor = AppColors.onGradient; + static const List membresGradient = [Color(0xFF5B0FBA), Color(0xFF7616E8)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // COTISATIONS / FINANCE — Ambre doré [5.8:1 ✅] + // Bouton dashboard : Icons.payments_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color cotisations = Color(0xFFB45309); + static const Color cotisationsDark = Color(0xFF92400E); + static const Color cotisationsLight = Color(0xFFFEF3C7); + static const Color cotisationsOnColor = AppColors.onGradient; + static const List cotisationsGradient = [Color(0xFF92400E), Color(0xFFB45309)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // ÉVÉNEMENTS — Terracotta vibrant + // Bouton dashboard : Icons.event_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color evenements = Color(0xFFD4551C); + static const Color evenementsDark = Color(0xFFB84315); + static const Color evenementsLight = Color(0xFFFFF3F0); + static const Color evenementsOnColor = AppColors.onGradient; + static const List evenementsGradient = [Color(0xFFB84315), Color(0xFFD4551C)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // SOLIDARITÉ / AIDE — Rouge compassion [4.5:1 ✅] + // Bouton dashboard : Icons.favorite_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color solidarite = Color(0xFFDC2626); + static const Color solidariteDark = Color(0xFFB91C1C); + static const Color solidariteLight = Color(0xFFFEF2F2); + static const Color solidariteOnColor = AppColors.onGradient; + static const List solidariteGradient = [Color(0xFFB91C1C), Color(0xFFDC2626)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // ÉPARGNE — Vert croissance [6.6:1 ✅] + // Bouton dashboard : Icons.savings_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color epargne = Color(0xFF15803D); + static const Color epargneDark = Color(0xFF166534); + static const Color epargneLight = Color(0xFFF0FDF4); + static const Color epargneOnColor = AppColors.onGradient; + static const List epargneGradient = [Color(0xFF166534), Color(0xFF15803D)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // ADHÉSIONS — Émeraude [4.8:1 ✅] + // Bouton dashboard : Icons.how_to_reg_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color adhesions = Color(0xFF047857); + static const Color adhesionsDark = Color(0xFF065F46); + static const Color adhesionsLight = Color(0xFFECFDF5); + static const Color adhesionsOnColor = AppColors.onGradient; + static const List adhesionsGradient = [Color(0xFF065F46), Color(0xFF047857)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // COMMUNICATION — Cyan dynamique + // Bouton dashboard : Icons.chat_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color communication = Color(0xFF0891B2); + static const Color communicationDark = Color(0xFF0E7490); + static const Color communicationLight = Color(0xFFECFEFF); + static const Color communicationOnColor = AppColors.onGradient; + static const List communicationGradient = [Color(0xFF0E7490), Color(0xFF0891B2)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // RAPPORTS / ANALYTICS — Indigo analytique + // Bouton dashboard : Icons.analytics_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color rapports = Color(0xFF4338CA); + static const Color rapportsDark = Color(0xFF3730A3); + static const Color rapportsLight = Color(0xFFEEF2FF); + static const Color rapportsOnColor = AppColors.onGradient; + static const List rapportsGradient = [Color(0xFF3730A3), Color(0xFF4338CA)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // PROFIL — Gradient signature logo (identité personnelle) + // Bouton dashboard : Icons.person_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color profil = Color(0xFF7616E8); + static const Color profilDark = Color(0xFF297FFF); + static const Color profilLight = Color(0xFFF3ECFF); + static const Color profilOnColor = AppColors.onGradient; + static const List profilGradient = [Color(0xFF297FFF), Color(0xFF7616E8)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // SYSTÈME / ADMIN — Navy autorité + // Bouton dashboard : Icons.settings_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color systeme = Color(0xFF1E293B); + static const Color systemeDark = Color(0xFF0F172A); + static const Color systemeLight = Color(0xFFF1F5F9); + static const Color systemeOnColor = AppColors.onGradient; + static const List systemeGradient = [Color(0xFF0F172A), Color(0xFF1E293B)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // BACKUP — Ambre attention + // Bouton dashboard : Icons.backup_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color backup = Color(0xFFD97706); + static const Color backupDark = Color(0xFFB45309); + static const Color backupLight = Color(0xFFFEF3C7); + static const Color backupOnColor = AppColors.onGradient; + static const List backupGradient = [Color(0xFFB45309), Color(0xFFD97706)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // LOGS / MONITORING — Slate technique + // Bouton dashboard : Icons.terminal_rounded + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color logs = Color(0xFF475569); + static const Color logsDark = Color(0xFF334155); + static const Color logsLight = Color(0xFFF8FAFC); + static const Color logsOnColor = AppColors.onGradient; + static const List logsGradient = [Color(0xFF334155), Color(0xFF475569)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // NOTIFICATIONS — Violet vif + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color notifications = Color(0xFF6366F1); + static const Color notificationsDark = Color(0xFF4F46E5); + static const Color notificationsLight = Color(0xFFEEF2FF); + static const Color notificationsOnColor = AppColors.onGradient; + static const List notificationsGradient = [Color(0xFF4F46E5), Color(0xFF6366F1)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // PARAMÈTRES — Slate neutre + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color parametres = Color(0xFF334155); + static const Color parametresDark = Color(0xFF1E293B); + static const Color parametresLight = Color(0xFFF1F5F9); + static const Color parametresOnColor = AppColors.onGradient; + static const List parametresGradient = [Color(0xFF1E293B), Color(0xFF334155)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // FINANCE WORKFLOW (Approbations, Budgets) + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color financeWorkflow = Color(0xFF0F766E); + static const Color financeWorkflowDark = Color(0xFF134E4A); + static const Color financeWorkflowLight = Color(0xFFF0FDFA); + static const Color financeWorkflowOnColor = AppColors.onGradient; + static const List financeWorkflowGradient = [Color(0xFF134E4A), Color(0xFF0F766E)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // AIDE / SUPPORT + // ═══════════════════════════════════════════════════════════════════════════ + + static const Color support = Color(0xFF2563EB); + static const Color supportDark = Color(0xFF1D4ED8); + static const Color supportLight = Color(0xFFEFF6FF); + static const Color supportOnColor = AppColors.onGradient; + static const List supportGradient = [Color(0xFF1D4ED8), Color(0xFF2563EB)]; + + // ═══════════════════════════════════════════════════════════════════════════ + // UTILITAIRES + // ═══════════════════════════════════════════════════════════════════════════ + + /// Retourne le gradient LinearGradient depuis la liste de couleurs d'un module + static LinearGradient gradient( + List colors, { + AlignmentGeometry begin = Alignment.centerLeft, + AlignmentGeometry end = Alignment.centerRight, + }) { + return LinearGradient(colors: colors, begin: begin, end: end); + } + + /// Retourne le gradient vertical (pour AppBar) + static LinearGradient verticalGradient(List colors) => + gradient(colors, begin: Alignment.topLeft, end: Alignment.bottomRight); +} diff --git a/lib/shared/design_system/tokens/unionflow_colors.dart b/lib/shared/design_system/tokens/unionflow_colors.dart index 285c134..82ee549 100644 --- a/lib/shared/design_system/tokens/unionflow_colors.dart +++ b/lib/shared/design_system/tokens/unionflow_colors.dart @@ -1,185 +1,93 @@ import 'package:flutter/material.dart'; +import 'app_colors.dart'; -/// UnionFlow Color System - Palette Signature -/// Inspirée des valeurs de solidarité, prospérité et modernité africaine +/// Facade de compatibilité — redirige vers [AppColors] +/// +/// Cette classe existe uniquement pour ne pas casser le code existant. +/// Pour tout nouveau code, utiliser [AppColors] directement. +/// +/// @deprecated Utiliser [AppColors] class UnionFlowColors { UnionFlowColors._(); - // ═══════════════════════════════════════════════════════════════ - // COULEURS PRIMAIRES (Identité UnionFlow) - // ═══════════════════════════════════════════════════════════════ + // ── Primaire ──────────────────────────────────────────────────────────── + static const Color unionBlue = AppColors.primary; + static const Color unionBlueLight = AppColors.primaryLight; + static const Color unionBlueDark = AppColors.primaryDark; + static const Color unionBluePale = AppColors.primaryContainer; + static const Color unionBlueLogo = AppColors.logoBlue; - /// Vert UnionFlow - Symbole de croissance et prospérité - static const Color unionGreen = Color(0xFF0F6B4F); - static const Color unionGreenLight = Color(0xFF1F8A67); - static const Color unionGreenPale = Color(0xFFEEF5F2); + // ── Accent ────────────────────────────────────────────────────────────── + static const Color unionPurple = AppColors.accent; + static const Color unionPurpleLight = AppColors.accentLight; + static const Color unionPurpleDark = AppColors.accentDark; + static const Color unionPurplePale = AppColors.accentContainer; - /// Or - Symbole de richesse et communauté - static const Color gold = Color(0xFFD4A017); - static const Color goldLight = Color(0xFFE8C568); - static const Color goldPale = Color(0xFFFFF9E6); + // ── Sémantique ────────────────────────────────────────────────────────── + static const Color success = AppColors.success; + static const Color successLight = AppColors.successUI; + static const Color successPale = AppColors.successContainer; - /// Indigo - Modernité et confiance - static const Color indigo = Color(0xFF1E2A44); - static const Color indigoLight = Color(0xFF3A4A6B); + static const Color warning = AppColors.warning; + static const Color warningLight = AppColors.warningUI; + static const Color warningPale = AppColors.warningContainer; - // ═══════════════════════════════════════════════════════════════ - // COULEURS SECONDAIRES (Accents culturels) - // ═══════════════════════════════════════════════════════════════ + static const Color error = AppColors.error; + static const Color errorLight = AppColors.errorLight; + static const Color errorPale = AppColors.errorContainer; - /// Terracotta - Chaleur et tradition - static const Color terracotta = Color(0xFFE07A5F); - static const Color terracottaLight = Color(0xFFF2AC99); - static const Color terracottaPale = Color(0xFFFFF3F0); + static const Color info = AppColors.info; + static const Color infoLight = AppColors.infoLight; + static const Color infoPale = AppColors.infoContainer; - /// Ambre - Énergie et optimisme - static const Color amber = Color(0xFFF4A261); - static const Color amberLight = Color(0xFFF8C590); + // ── Surfaces ──────────────────────────────────────────────────────────── + static const Color background = AppColors.background; + static const Color surface = AppColors.surface; + static const Color surfaceVariant = AppColors.surfaceContainer; + static const Color surfaceContainer = AppColors.surfaceContainerHigh; + static const Color surfaceGlass = AppColors.backgroundSubtle; - /// Sable - Neutralité élégante - static const Color sand = Color(0xFFE9DCC9); - static const Color sandLight = Color(0xFFF5F0E8); + // ── Texte & Bordures ───────────────────────────────────────────────────── + static const Color textPrimary = AppColors.textPrimary; + static const Color textSecondary = AppColors.textSecondary; + static const Color textTertiary = AppColors.textDisabled; - // ═══════════════════════════════════════════════════════════════ - // COULEURS SÉMANTIQUES - // ═══════════════════════════════════════════════════════════════ + static const Color border = AppColors.border; + static const Color borderMedium = AppColors.borderStrong; + static const Color borderStrong = AppColors.textDisabled; - /// Succès - Validation, confirmation - static const Color success = Color(0xFF22C55E); - static const Color successLight = Color(0xFF86EFAC); - static const Color successPale = Color(0xFFF0FDF4); + // ── Palette brand ──────────────────────────────────────────────────────── + static const Color amber = AppColors.amber; + static const Color amberLight = AppColors.amberLight; + static const Color gold = AppColors.accent; + static const Color goldLight = AppColors.accentLight; + static const Color goldPale = AppColors.accentContainer; + static const Color terracotta = AppColors.terracotta; + static const Color terracottaLight = AppColors.terracottaLight; + static const Color terracottaPale = AppColors.terracottaPale; + static const Color indigo = AppColors.indigo; + static const Color indigoLight = AppColors.indigoLight; + static const Color sand = AppColors.sand; + static const Color sandLight = AppColors.sandLight; - /// Attention - Avertissements - static const Color warning = Color(0xFFF59E0B); - static const Color warningLight = Color(0xFFFBBF24); - static const Color warningPale = Color(0xFFFEFCE8); + // ── Alias historiques ──────────────────────────────────────────────────── + static const Color unionGreen = AppColors.primary; + static const Color unionGreenLight = AppColors.primaryLight; + static const Color unionGreenPale = AppColors.primaryContainer; - /// Erreur - Actions négatives - static const Color error = Color(0xFFEF4444); - static const Color errorLight = Color(0xFFFCA5A5); - static const Color errorPale = Color(0xFFFEF2F2); + // ── Gradients ──────────────────────────────────────────────────────────── + static const LinearGradient logoGradient = AppColors.logoGradient; + static const LinearGradient primaryGradient = AppColors.primaryGradient; + static const LinearGradient loginGradient = AppColors.loginGradient; + static const LinearGradient subtleGradient = AppColors.subtleGradient; + static const LinearGradient warmGradient = AppColors.warmGradient; + static const LinearGradient goldGradient = AppColors.logoGradient; - /// Info - Informations neutres - static const Color info = Color(0xFF3B82F6); - static const Color infoLight = Color(0xFF93C5FD); - static const Color infoPale = Color(0xFFEFF6FF); - - // ═══════════════════════════════════════════════════════════════ - // COULEURS DE SURFACE - // ═══════════════════════════════════════════════════════════════ - - /// Background principal - static const Color background = Color(0xFFF7F9FA); - - /// Surface des cards - static const Color surface = Color(0xFFFFFFFF); - - /// Surface variant (légèrement teintée) - static const Color surfaceVariant = Color(0xFFF5F7F8); - - /// Surface avec effet glassmorphism - static const Color surfaceGlass = Color(0xFFFAFBFC); - - // ═══════════════════════════════════════════════════════════════ - // TEXTE & BORDURES - // ═══════════════════════════════════════════════════════════════ - - /// Texte principal - static const Color textPrimary = Color(0xFF111827); - - /// Texte secondaire - static const Color textSecondary = Color(0xFF6B7280); - - /// Texte tertiaire - static const Color textTertiary = Color(0xFF9CA3AF); - - /// Bordures subtiles - static const Color border = Color(0xFFE5E7EB); - - /// Bordures moyennes - static const Color borderMedium = Color(0xFFD1D5DB); - - /// Bordures fortes - static const Color borderStrong = Color(0xFF9CA3AF); - - // ═══════════════════════════════════════════════════════════════ - // GRADIENTS SIGNATURE - // ═══════════════════════════════════════════════════════════════ - - /// Gradient principal (Vert → Or) - static const LinearGradient primaryGradient = LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [unionGreen, unionGreenLight], - ); - - /// Gradient chaleureux (Terracotta → Ambre) - static const LinearGradient warmGradient = LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [terracotta, amber], - ); - - /// Gradient or (Gold → Gold Light) - static const LinearGradient goldGradient = LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [gold, goldLight], - ); - - /// Gradient subtil pour backgrounds - static const LinearGradient subtleGradient = LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0xFFFAFBFC), Color(0xFFF7F9FA)], - ); - - // ═══════════════════════════════════════════════════════════════ - // OMBRES SIGNATURE - // ═══════════════════════════════════════════════════════════════ - - /// Ombre douce (cards, buttons) - static List get softShadow => [ - BoxShadow( - color: const Color(0xFF000000).withOpacity(0.08), - blurRadius: 24, - offset: const Offset(0, 8), - ), - ]; - - /// Ombre moyenne (modals, floating elements) - static List get mediumShadow => [ - BoxShadow( - color: const Color(0xFF000000).withOpacity(0.12), - blurRadius: 32, - offset: const Offset(0, 12), - ), - ]; - - /// Ombre forte (dialogs, overlays) - static List get strongShadow => [ - BoxShadow( - color: const Color(0xFF000000).withOpacity(0.16), - blurRadius: 48, - offset: const Offset(0, 16), - ), - ]; - - /// Ombre colorée verte (pour CTAs) - static List get greenGlowShadow => [ - BoxShadow( - color: unionGreen.withOpacity(0.2), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ]; - - /// Ombre colorée dorée (pour éléments premium) - static List get goldGlowShadow => [ - BoxShadow( - color: gold.withOpacity(0.25), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ]; + // ── Ombres ─────────────────────────────────────────────────────────────── + static List get softShadow => AppColors.softShadow; + static List get mediumShadow => AppColors.mediumShadow; + static List get blueGlowShadow => AppColors.blueGlowShadow; + static List get purpleGlowShadow => AppColors.purpleGlowShadow; + static List get greenGlowShadow => AppColors.blueGlowShadow; + static List get goldGlowShadow => AppColors.purpleGlowShadow; } diff --git a/lib/shared/design_system/unionflow_design_system.dart b/lib/shared/design_system/unionflow_design_system.dart index 0b75b56..dc45b9e 100644 --- a/lib/shared/design_system/unionflow_design_system.dart +++ b/lib/shared/design_system/unionflow_design_system.dart @@ -6,93 +6,33 @@ import 'tokens/app_typography.dart'; import 'tokens/spacing_tokens.dart'; // ═══════════════════════════════════════════════════════════════════════════ -// EXPORTS - Point d'entrée unique (DRY) +// EXPORTS — Point d'entrée unique du design system // ═══════════════════════════════════════════════════════════════════════════ export 'tokens/app_colors.dart'; export 'tokens/app_typography.dart'; export 'tokens/spacing_tokens.dart'; +export 'tokens/color_tokens.dart'; // ← Facade de compat → AppColors +export 'tokens/unionflow_colors.dart'; // ← Facade de compat → AppColors +export 'tokens/module_colors.dart'; // ← Identité chromatique par module export 'theme/app_theme.dart'; export 'components/components.dart'; // ═══════════════════════════════════════════════════════════════════════════ -// SHIMS DE COMPATIBILITÉ — Migration progressive vers design system unifié +// SHADOW TOKENS // ═══════════════════════════════════════════════════════════════════════════ -/// Shim ColorTokens — palette Vert Forêt/Ardoise -class ColorTokens { - // Primaires - static const Color primary = AppColors.primaryGreen; // #2E7D32 - static const Color primaryLight = AppColors.brandGreenLight; // #4CAF50 - static const Color primaryDark = AppColors.brandGreen; // #1B5E20 - static const Color primaryContainer = Color(0xFFE8F5E9); - static const Color onPrimary = Colors.white; - static const Color onPrimaryContainer = AppColors.textPrimaryLight; - - // Secondaires - static const Color secondary = AppColors.brandGreenLight; - static const Color secondaryContainer = Color(0xFFC8E6C9); - static const Color onSecondary = Colors.white; - static const Color onSecondaryContainer = AppColors.textPrimaryLight; - - // Tertiaires - static const Color tertiary = AppColors.brandMint; - static const Color tertiaryContainer = Color(0xFFDCEDC8); - static const Color onTertiary = Colors.white; - - // Surfaces - static const Color surface = AppColors.lightSurface; - static const Color surfaceVariant = AppColors.lightBackground; - static const Color surfaceContainer = AppColors.lightSurface; - static const Color background = AppColors.lightBackground; - static const Color onSurface = AppColors.textPrimaryLight; - static const Color onSurfaceVariant = AppColors.textSecondaryLight; - static const Color outline = AppColors.lightBorder; - static const Color outlineVariant = Color(0xFFDCEDC8); - - // Erreur / succès - static const Color error = AppColors.error; - static const Color onError = Colors.white; - static const Color errorContainer = Color(0xFFFEF2F2); - static const Color success = AppColors.success; - static const Color onSuccess = Colors.white; - static const Color warning = AppColors.warning; - static const Color info = AppColors.info; - - // Navigation - static const Color navigationBackground = AppColors.lightSurface; - static const Color navigationSelected = AppColors.primaryGreen; - static const Color navigationUnselected = AppColors.textSecondaryLight; - static const Color navigationIndicator = Color(0xFFE8F5E9); - - // Ombres - static const Color shadow = Color(0x1A1C2B1C); - static const Color shadowMedium = Color(0x331C2B1C); - - // Verre / glassmorphism - static const Color glassBackground = Color(0x80FFFFFF); - static const Color glassBorder = Color(0x33FFFFFF); - - // Gradients - static const List primaryGradient = [ - AppColors.brandGreen, - AppColors.primaryGreen, - Color(0xFF388E3C), - ]; -} - -/// Shim ShadowTokens class ShadowTokens { static const List sm = [ BoxShadow( - color: Color(0x1A1C2B1C), + color: AppColors.shadowMedium, blurRadius: 4, offset: Offset(0, 2), ), ]; static const List md = [ BoxShadow( - color: Color(0x261C2B1C), + color: AppColors.shadowStrong, blurRadius: 8, offset: Offset(0, 4), ), @@ -100,7 +40,10 @@ class ShadowTokens { static const List primary = md; } -/// Shim RadiusTokens +// ═══════════════════════════════════════════════════════════════════════════ +// RADIUS TOKENS +// ═══════════════════════════════════════════════════════════════════════════ + class RadiusTokens { static const double sm = SpacingTokens.radiusSm; static const double md = SpacingTokens.radiusMd; @@ -110,48 +53,42 @@ class RadiusTokens { static const double round = SpacingTokens.radiusCircular; } -/// Shim TypographyTokens +// ═══════════════════════════════════════════════════════════════════════════ +// TYPOGRAPHY TOKENS (shim — renvoie vers AppTypography) +// ═══════════════════════════════════════════════════════════════════════════ + class TypographyTokens { - // Display (Playfair Display via AppTypography getters — non-const) static TextStyle get displayLarge => AppTypography.displayLarge; static TextStyle get displayMedium => AppTypography.displayMedium; static TextStyle get displaySmall => AppTypography.displaySmall; - // Headlines static const TextStyle headlineLarge = AppTypography.headerLarge; static const TextStyle headlineMedium = AppTypography.headerSmall; static const TextStyle headlineSmall = AppTypography.titleMedium; - // Titles static const TextStyle titleLarge = AppTypography.headerSmall; static const TextStyle titleMedium = AppTypography.titleMedium; static const TextStyle titleSmall = AppTypography.titleSmall; - // Body static const TextStyle bodyLarge = AppTypography.bodyLarge; static const TextStyle bodyMedium = AppTypography.bodyMedium; static const TextStyle bodySmall = AppTypography.bodyTextSmall; - // Labels static const TextStyle labelLarge = AppTypography.actionText; static const TextStyle labelMedium = AppTypography.labelMedium; static const TextStyle labelSmall = AppTypography.badgeText; - // Buttons static const TextStyle buttonLarge = AppTypography.buttonLabel; static const TextStyle buttonMedium = AppTypography.actionText; - // Cards static const TextStyle cardTitle = AppTypography.headerSmall; static const TextStyle cardSubtitle = AppTypography.bodyTextSmall; static const TextStyle cardValue = AppTypography.headerLarge; - // Inputs static const TextStyle inputLabel = AppTypography.labelMedium; static const TextStyle inputText = AppTypography.bodyLarge; static const TextStyle inputHint = AppTypography.bodyTextSmall; - // Navigation static const TextStyle navigationLabel = AppTypography.navLabel; static const TextStyle navigationLabelSelected = AppTypography.navLabelSelected; } diff --git a/lib/shared/utils/snackbar_helper.dart b/lib/shared/utils/snackbar_helper.dart index 7b3ba03..33b28c6 100644 --- a/lib/shared/utils/snackbar_helper.dart +++ b/lib/shared/utils/snackbar_helper.dart @@ -3,6 +3,7 @@ library snackbar_helper; import 'package:flutter/material.dart'; import '../../core/error/failures.dart'; +import '../design_system/tokens/app_colors.dart'; /// Helper class for showing consistent Snackbar messages throughout the app class SnackbarHelper { @@ -16,12 +17,12 @@ class SnackbarHelper { final snackBar = SnackBar( content: Row( children: [ - const Icon(Icons.check_circle, color: Colors.white), + const Icon(Icons.check_circle, color: AppColors.onPrimary), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), - backgroundColor: Colors.green[700], + backgroundColor: AppColors.success, behavior: SnackBarBehavior.floating, duration: duration, action: action, @@ -39,18 +40,18 @@ class SnackbarHelper { final snackBar = SnackBar( content: Row( children: [ - const Icon(Icons.error_outline, color: Colors.white), + const Icon(Icons.error_outline, color: AppColors.onPrimary), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), - backgroundColor: Colors.red[700], + backgroundColor: AppColors.error, behavior: SnackBarBehavior.floating, duration: duration, action: onRetry != null ? SnackBarAction( label: 'Réessayer', - textColor: Colors.white, + textColor: AppColors.onPrimary, onPressed: onRetry, ) : null, @@ -67,12 +68,12 @@ class SnackbarHelper { final snackBar = SnackBar( content: Row( children: [ - const Icon(Icons.warning_amber, color: Colors.white), + const Icon(Icons.warning_amber, color: AppColors.onPrimary), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), - backgroundColor: Colors.orange[700], + backgroundColor: AppColors.warning, behavior: SnackBarBehavior.floating, duration: duration, ); @@ -88,12 +89,12 @@ class SnackbarHelper { final snackBar = SnackBar( content: Row( children: [ - const Icon(Icons.info_outline, color: Colors.white), + const Icon(Icons.info_outline, color: AppColors.onPrimary), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), - backgroundColor: Colors.blue[700], + backgroundColor: AppColors.info, behavior: SnackBarBehavior.floating, duration: duration, ); @@ -112,12 +113,12 @@ class SnackbarHelper { final snackBar = SnackBar( content: Row( children: [ - const Icon(Icons.construction, color: Colors.white), + const Icon(Icons.construction, color: AppColors.onPrimary), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), - backgroundColor: Colors.blue[700], + backgroundColor: AppColors.info, behavior: SnackBarBehavior.floating, duration: const Duration(seconds: 4), ); @@ -140,26 +141,26 @@ class SnackbarHelper { IconData icon; if (failure is NetworkFailure) { - backgroundColor = Colors.orange[700]!; + backgroundColor = AppColors.warning; icon = Icons.wifi_off; } else if (failure is UnauthorizedFailure) { - backgroundColor = Colors.red[700]!; + backgroundColor = AppColors.error; icon = Icons.lock_outline; } else if (failure is ForbiddenFailure) { - backgroundColor = Colors.deepOrange[700]!; + backgroundColor = AppColors.warning; icon = Icons.block; } else if (failure is ValidationFailure) { - backgroundColor = Colors.amber[700]!; + backgroundColor = AppColors.warningUI; icon = Icons.error_outline; } else { - backgroundColor = Colors.red[700]!; + backgroundColor = AppColors.error; icon = Icons.error_outline; } final snackBar = SnackBar( content: Row( children: [ - Icon(icon, color: Colors.white), + Icon(icon, color: AppColors.onPrimary), const SizedBox(width: 12), Expanded(child: Text(failure.getUserMessage())), ], @@ -170,7 +171,7 @@ class SnackbarHelper { action: failure.isRetryable && onRetry != null ? SnackBarAction( label: 'Réessayer', - textColor: Colors.white, + textColor: AppColors.onPrimary, onPressed: onRetry, ) : null, @@ -192,14 +193,14 @@ class SnackbarHelper { height: 16, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), + valueColor: AlwaysStoppedAnimation(AppColors.onPrimary), ), ), const SizedBox(width: 12), Expanded(child: Text(message)), ], ), - backgroundColor: Colors.grey[800], + backgroundColor: AppColors.surfaceDark, behavior: SnackBarBehavior.floating, duration: duration, ); diff --git a/lib/shared/widgets/action_row.dart b/lib/shared/widgets/action_row.dart index 768d780..4b60812 100644 --- a/lib/shared/widgets/action_row.dart +++ b/lib/shared/widgets/action_row.dart @@ -33,7 +33,7 @@ class ActionRow extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - final iconColor = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight; + final iconColor = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary; return Padding( padding: const EdgeInsets.only(top: 8.0), @@ -75,18 +75,18 @@ class ActionRow extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: AppColors.primaryGreen.withOpacity(0.1), + color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ if (customActionIcon != null) ...[ - Icon(customActionIcon, size: 14, color: AppColors.primaryGreen), + Icon(customActionIcon, size: 14, color: AppColors.primary), const SizedBox(width: 4), ], Text( customActionLabel ?? '', - style: AppTypography.badgeText.copyWith(color: AppColors.primaryGreen), + style: AppTypography.badgeText.copyWith(color: AppColors.primary), ), ], ), diff --git a/lib/shared/widgets/core_card.dart b/lib/shared/widgets/core_card.dart index a19d06b..df4e038 100644 --- a/lib/shared/widgets/core_card.dart +++ b/lib/shared/widgets/core_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import '../design_system/tokens/app_colors.dart'; /// UnionFlow Mobile - Composant DRY Centralisé : CoreCard /// Le seul et unique conteneur d'affichage (Posts, Événements, Profils). @@ -22,21 +21,21 @@ class CoreCard extends StatelessWidget { @override Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; + final scheme = Theme.of(context).colorScheme; return Container( width: double.infinity, margin: margin, decoration: BoxDecoration( - color: backgroundColor ?? (isDark ? AppColors.darkSurface : Colors.white), + color: backgroundColor ?? scheme.surface, borderRadius: BorderRadius.circular(10.0), border: Border.all( - color: isDark ? AppColors.darkBorder.withOpacity(0.5) : AppColors.lightBorder, + color: scheme.outlineVariant.withOpacity(0.6), width: 0.8, ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(isDark ? 0.15 : 0.04), + color: scheme.shadow.withOpacity(0.06), blurRadius: 6, offset: const Offset(0, 2), ), diff --git a/lib/shared/widgets/core_text_field.dart b/lib/shared/widgets/core_text_field.dart index f574578..173f03e 100644 --- a/lib/shared/widgets/core_text_field.dart +++ b/lib/shared/widgets/core_text_field.dart @@ -37,32 +37,32 @@ class CoreTextField extends StatelessWidget { decoration: InputDecoration( hintText: hintText, hintStyle: AppTypography.subtitleSmall.copyWith( - color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, + color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, ), prefixIcon: prefixIcon != null - ? Icon(prefixIcon, size: 20, color: AppColors.primaryGreen) + ? Icon(prefixIcon, size: 20, color: AppColors.primary) : null, filled: true, - fillColor: isDark ? AppColors.darkSurface : AppColors.lightSurface, + fillColor: isDark ? AppColors.surfaceDark : AppColors.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( - color: isDark ? AppColors.darkBorder : AppColors.lightBorder, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( - color: isDark ? AppColors.darkBorder : AppColors.lightBorder, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide( - color: AppColors.primaryGreen, + color: AppColors.primary, width: 2, ), ), diff --git a/lib/shared/widgets/dynamic_fab.dart b/lib/shared/widgets/dynamic_fab.dart index ebc3314..aced860 100644 --- a/lib/shared/widgets/dynamic_fab.dart +++ b/lib/shared/widgets/dynamic_fab.dart @@ -21,7 +21,7 @@ class DynamicFAB extends StatelessWidget { if (label != null) { return FloatingActionButton.extended( onPressed: onPressed, - backgroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 4, icon: Icon(icon, size: 20), @@ -34,7 +34,7 @@ class DynamicFAB extends StatelessWidget { return FloatingActionButton( onPressed: onPressed, - backgroundColor: AppColors.primaryGreen, + backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 4, child: Icon(icon, size: 24), diff --git a/lib/shared/widgets/error_display_widget.dart b/lib/shared/widgets/error_display_widget.dart index 4496e25..f2f92b5 100644 --- a/lib/shared/widgets/error_display_widget.dart +++ b/lib/shared/widgets/error_display_widget.dart @@ -3,6 +3,7 @@ library error_display_widget; import 'package:flutter/material.dart'; import '../../core/error/failures.dart'; +import '../design_system/tokens/app_colors.dart'; /// Error display widget that shows failures in a user-friendly way class ErrorDisplayWidget extends StatelessWidget { @@ -48,7 +49,7 @@ class ErrorDisplayWidget extends StatelessWidget { Text( failure.getUserMessage(), style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.grey[600], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), @@ -98,15 +99,15 @@ class ErrorDisplayWidget extends StatelessWidget { /// Get appropriate color for error type Color _getErrorColor(BuildContext context) { if (failure is NetworkFailure) { - return Colors.orange; + return AppColors.warning; } else if (failure is UnauthorizedFailure) { - return Colors.red; + return AppColors.error; } else if (failure is ForbiddenFailure) { - return Colors.deepOrange; + return AppColors.warning; } else if (failure is ValidationFailure) { - return Colors.amber; + return AppColors.warningUI; } else if (failure is NotImplementedFailure) { - return Colors.blue[700]!; + return AppColors.info; } else { return Theme.of(context).colorScheme.error; } @@ -183,7 +184,7 @@ class ErrorBanner extends StatelessWidget { failure.getUserMessage(), style: TextStyle( fontSize: 13, - color: Colors.grey[700], + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ], @@ -228,15 +229,15 @@ class ErrorBanner extends StatelessWidget { Color _getErrorColor(BuildContext context) { if (failure is NetworkFailure) { - return Colors.orange; + return AppColors.warning; } else if (failure is UnauthorizedFailure) { - return Colors.red; + return AppColors.error; } else if (failure is ForbiddenFailure) { - return Colors.deepOrange; + return AppColors.warning; } else if (failure is ValidationFailure) { - return Colors.amber; + return AppColors.warningUI; } else if (failure is NotImplementedFailure) { - return Colors.blue[700]!; + return AppColors.info; } else { return Theme.of(context).colorScheme.error; } @@ -282,7 +283,7 @@ void showErrorSnackBar( ), ], ), - backgroundColor: failure is NetworkFailure ? Colors.orange : Colors.red, + backgroundColor: failure is NetworkFailure ? AppColors.warning : AppColors.error, behavior: SnackBarBehavior.floating, action: failure.isRetryable && onRetry != null ? SnackBarAction( diff --git a/lib/shared/widgets/info_badge.dart b/lib/shared/widgets/info_badge.dart index 166201b..8cd8950 100644 --- a/lib/shared/widgets/info_badge.dart +++ b/lib/shared/widgets/info_badge.dart @@ -13,7 +13,7 @@ class InfoBadge extends StatelessWidget { const InfoBadge({ Key? key, required this.text, - this.backgroundColor = AppColors.brandGreenLight, + this.backgroundColor = AppColors.primaryLight, this.textColor = Colors.white, this.icon, }) : super(key: key); diff --git a/lib/shared/widgets/loading_widget.dart b/lib/shared/widgets/loading_widget.dart index 6a8b777..23ff287 100644 --- a/lib/shared/widgets/loading_widget.dart +++ b/lib/shared/widgets/loading_widget.dart @@ -67,12 +67,12 @@ class ShimmerListLoading extends StatelessWidget { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, + baseColor: Theme.of(context).colorScheme.surfaceContainerHighest, + highlightColor: Theme.of(context).colorScheme.surface, child: Container( height: itemHeight, decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8), ), ), @@ -97,13 +97,13 @@ class ShimmerCardLoading extends StatelessWidget { @override Widget build(BuildContext context) { return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, + baseColor: Theme.of(context).colorScheme.surfaceContainerHighest, + highlightColor: Theme.of(context).colorScheme.surface, child: Container( height: height, width: width, decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), ), ), @@ -137,11 +137,11 @@ class ShimmerGridLoading extends StatelessWidget { itemCount: itemCount, itemBuilder: (context, index) { return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, + baseColor: Theme.of(context).colorScheme.surfaceContainerHighest, + highlightColor: Theme.of(context).colorScheme.surface, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), ), ), @@ -158,8 +158,8 @@ class ShimmerDetailLoading extends StatelessWidget { @override Widget build(BuildContext context) { return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, + baseColor: Theme.of(context).colorScheme.surfaceContainerHighest, + highlightColor: Theme.of(context).colorScheme.surface, child: Padding( padding: const EdgeInsets.all(10), child: Column( @@ -169,7 +169,7 @@ class ShimmerDetailLoading extends StatelessWidget { Container( height: 200, decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), ), ), @@ -178,14 +178,14 @@ class ShimmerDetailLoading extends StatelessWidget { Container( height: 24, width: double.infinity, - color: Colors.white, + color: Theme.of(context).colorScheme.surface, ), const SizedBox(height: 8), // Subtitle Container( height: 16, width: 200, - color: Colors.white, + color: Theme.of(context).colorScheme.surface, ), const SizedBox(height: 12), // Content lines @@ -195,7 +195,7 @@ class ShimmerDetailLoading extends StatelessWidget { child: Container( height: 12, width: double.infinity, - color: Colors.white, + color: Theme.of(context).colorScheme.surface, ), ); }), diff --git a/lib/shared/widgets/powered_by_lions_dev.dart b/lib/shared/widgets/powered_by_lions_dev.dart new file mode 100644 index 0000000..b1d51c4 --- /dev/null +++ b/lib/shared/widgets/powered_by_lions_dev.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../design_system/unionflow_design_system.dart'; + +/// Widget "Powered by Lions Dev" — affiche le logo Lions Dev adaptatif (dark/light) +/// avec lien cliquable vers https://www.lions.dev +/// +/// Usage: +/// ```dart +/// const PoweredByLionsDev() +/// // ou compact: +/// const PoweredByLionsDev(compact: true) +/// ``` +class PoweredByLionsDev extends StatelessWidget { + /// Si true, affichage compact (logo plus petit, sans label "Powered by") + final bool compact; + + /// Couleur du label "Powered by" (par défaut : couleur secondaire du thème) + final Color? labelColor; + + /// Hauteur du logo (par défaut : 28 normal, 20 compact) + final double? logoHeight; + + /// Force une variante (utile sur fond toujours sombre/clair comme login). + /// Si null, suit le thème courant. + final Brightness? forceBrightness; + + const PoweredByLionsDev({ + super.key, + this.compact = false, + this.labelColor, + this.logoHeight, + this.forceBrightness, + }); + + Future _openLionsDev() async { + final uri = Uri.parse('https://www.lions.dev'); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } + + @override + Widget build(BuildContext context) { + final brightness = forceBrightness ?? Theme.of(context).brightness; + final isDark = brightness == Brightness.dark; + // Logo blanc sur fond sombre, logo noir sur fond clair + final logoAsset = isDark + ? 'assets/images/branding/lions_dev_white.png' + : 'assets/images/branding/lions_dev_dark.png'; + + final effectiveLabelColor = labelColor ?? + (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary); + + final effectiveHeight = logoHeight ?? (compact ? 20.0 : 28.0); + + return InkWell( + onTap: _openLionsDev, + borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: SpacingTokens.md, + vertical: SpacingTokens.sm, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (!compact) ...[ + Text( + 'Powered by', + style: TextStyle( + fontSize: 11, + color: effectiveLabelColor, + letterSpacing: 0.3, + ), + ), + const SizedBox(width: SpacingTokens.sm), + ], + Image.asset( + logoAsset, + height: effectiveHeight, + fit: BoxFit.contain, + ), + ], + ), + ), + ); + } +} diff --git a/lib/shared/widgets/validated_text_field.dart b/lib/shared/widgets/validated_text_field.dart index 45d3020..b3b04a1 100644 --- a/lib/shared/widgets/validated_text_field.dart +++ b/lib/shared/widgets/validated_text_field.dart @@ -3,6 +3,7 @@ library validated_text_field; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import '../design_system/tokens/app_colors.dart'; /// Validated text field with consistent styling and behavior class ValidatedTextField extends StatelessWidget { @@ -61,6 +62,7 @@ class ValidatedTextField extends StatelessWidget { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return TextFormField( controller: controller, initialValue: initialValue, @@ -73,30 +75,32 @@ class ValidatedTextField extends StatelessWidget { border: const OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.grey.shade400, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1.0, ), ), - focusedBorder: const OutlineInputBorder( + focusedBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.blue, + color: AppColors.primary, width: 2.0, ), ), - errorBorder: const OutlineInputBorder( + errorBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.red, + color: AppColors.error, width: 1.0, ), ), - focusedErrorBorder: const OutlineInputBorder( + focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.red, + color: AppColors.error, width: 2.0, ), ), filled: !enabled, - fillColor: !enabled ? Colors.grey.shade100 : null, + fillColor: !enabled + ? (isDark ? AppColors.surfaceVariantDark : AppColors.backgroundSubtle) + : null, counterText: showCounter ? null : '', ), validator: validator, @@ -165,10 +169,10 @@ class ValidatedAmountField extends StatelessWidget { padding: const EdgeInsets.all(12.0), child: Text( currencySymbol, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: Colors.grey, + color: AppColors.textTertiary, ), ), ), @@ -206,6 +210,7 @@ class ValidatedDropdownField extends StatelessWidget { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return DropdownButtonFormField( value: value, items: items, @@ -216,24 +221,26 @@ class ValidatedDropdownField extends StatelessWidget { border: const OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.grey.shade400, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1.0, ), ), - focusedBorder: const OutlineInputBorder( + focusedBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.blue, + color: AppColors.primary, width: 2.0, ), ), - errorBorder: const OutlineInputBorder( + errorBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.red, + color: AppColors.error, width: 1.0, ), ), filled: !enabled, - fillColor: !enabled ? Colors.grey.shade100 : null, + fillColor: !enabled + ? (isDark ? AppColors.surfaceVariantDark : AppColors.backgroundSubtle) + : null, ), validator: validator, onChanged: enabled ? onChanged : null, @@ -269,6 +276,7 @@ class ValidatedDateField extends StatelessWidget { @override Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; return InkWell( onTap: enabled ? () async { @@ -292,24 +300,26 @@ class ValidatedDateField extends StatelessWidget { border: const OutlineInputBorder(), enabledBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.grey.shade400, + color: isDark ? AppColors.borderDark : AppColors.border, width: 1.0, ), ), - focusedBorder: const OutlineInputBorder( + focusedBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.blue, + color: AppColors.primary, width: 2.0, ), ), - errorBorder: const OutlineInputBorder( + errorBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.red, + color: AppColors.error, width: 1.0, ), ), filled: !enabled, - fillColor: !enabled ? Colors.grey.shade100 : null, + fillColor: !enabled + ? (isDark ? AppColors.surfaceVariantDark : AppColors.backgroundSubtle) + : null, errorText: validator != null ? validator!(selectedDate) : null, ), child: Text( @@ -317,7 +327,9 @@ class ValidatedDateField extends StatelessWidget { ? '${selectedDate!.day}/${selectedDate!.month}/${selectedDate!.year}' : hintText ?? 'Sélectionner une date', style: TextStyle( - color: selectedDate != null ? Colors.black87 : Colors.grey, + color: selectedDate != null + ? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary) + : AppColors.textTertiary, ), ), ),