Files
unionflow-mobile-apps/lib/shared/design_system/theme/app_theme.dart
dahoud 7cd7c6fc9e 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
2026-04-15 20:27:23 +00:00

329 lines
11 KiB
Dart

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
///
/// Palette WCAG 2.1 validée :
/// Primary : #2563EB [5.7:1 ✅ WCAG AA]
/// Dark mode: #0A0D1A [Navy profond]
class AppTheme {
// ─── THÈME CLAIR ──────────────────────────────────────────────────────────
static final ThemeData lightTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primaryColor: AppColors.primary,
scaffoldBackgroundColor: AppColors.background,
colorScheme: const ColorScheme.light(
primary: AppColors.primary,
onPrimary: Colors.white,
primaryContainer: AppColors.primaryContainer,
onPrimaryContainer: Color(0xFF1E3A8A),
secondary: AppColors.accent,
onSecondary: Colors.white,
tertiary: Color(0xFF5297FF),
surface: AppColors.surface,
onSurface: AppColors.textPrimary,
error: AppColors.error,
onError: Colors.white,
outline: AppColors.border,
),
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),
),
appBarTheme: AppBarTheme(
backgroundColor: AppColors.surface,
foregroundColor: AppColors.textPrimary,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
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,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
textStyle: AppTypography.actionText,
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.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.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.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 (Navy Profond) ──────────────────────────────────────────
static final ThemeData darkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
primaryColor: AppColors.primaryLight,
scaffoldBackgroundColor: AppColors.backgroundDark,
colorScheme: const ColorScheme.dark(
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(
titleMedium: AppTypography.headerSmall,
bodyMedium: AppTypography.bodyTextSmall,
bodySmall: AppTypography.subtitleSmall,
labelLarge: AppTypography.actionText,
labelSmall: AppTypography.badgeText,
).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: AppBarTheme(
backgroundColor: AppColors.backgroundDark,
foregroundColor: AppColors.textPrimaryDark,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
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.primary,
foregroundColor: Colors.white,
elevation: 0,
textStyle: AppTypography.actionText,
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.backgroundDark,
selectedItemColor: AppColors.primaryLight,
unselectedItemColor: AppColors.textSecondaryDark,
showSelectedLabels: false,
showUnselectedLabels: false,
elevation: 8,
type: BottomNavigationBarType.fixed,
),
dividerTheme: const DividerThemeData(
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,
),
);
}