# Guide d'Implémentation du Design System - AudiOhm Ce guide vous explique comment appliquer le nouveau système de design moderne à votre application Flutter AudiOhm. ## 📋 Sommaire 1. [Vue d'ensemble](#vue-densemble) 2. [Structure du design system](#structure-du-design-system) 3. [Implémentation dans Flutter](#implémentation-dans-flutter) 4. [Migration du code existant](#migration-du-code-existant) 5. [Checklist de validation](#checklist-de-validation) --- ## Vue d'ensemble ### Objectifs ✅ Moderniser l'UI/UX selon les standards 2025 ✅ Préserver l'identité cyberpunk néon ✅ Améliorer l'accessibilité (WCAG AA) ✅ Optimiser les performances ✅ Standardiser les composants ### Changements Majeurs | Aspect | Avant | Après | |--------|-------|-------| | **Contraste** | Parfois faible | Minimum 4.5:1 (WCAG AA) | | **Icônes** | Mixtes | SVG unifiés (Lucide) | | **Transitions** | Instables ou 0ms | 150-300ms standardisées | | **Spacing** | Incohérent | Système de 4px | | **Typography** | Outfit uniquement | Space Grotesk + Outfit | | **Couleurs** | Néon sans structure | Palette sémantique claire | --- ## Structure du Design System ### Fichiers Créés ``` design-system/ ├── MASTER.md # Règles globales (source de vérité) └── pages/ ├── home.md # Override pour page d'accueil ├── search.md # Override pour page de recherche └── player.md # Override pour page lecteur ``` ### Comment Utiliser Pour chaque page/component que vous créez ou modifiez : 1. **Consultez d'abord le MASTER.md** pour les règles de base 2. **Vérifiez s'il existe un override** pour la page spécifique 3. **Si un override existe**, ses règles priment sur le MASTER 4. **Sinon**, appliquez les règles du MASTER **Exemple :** ``` "Je crée la page Player" → Lire design-system/MASTER.md → Lire design-system/pages/player.md → Les règles de player.md priment sur MASTER.md ``` --- ## Implémentation dans Flutter ### 1. Créer le fichier de couleurs Créez `lib/core/theme/colors.dart`: ```dart import 'package:flutter/material.dart'; class AppColors { // Background Colors static const Color background = Color(0xFF0A0E27); static const Color surface = Color(0xFF151932); static const Color surfaceElevated = Color(0xFF1F2342); static const Color border = Color(0xFF2A2F4A); // Neon Accents static const Color primary = Color(0xFF00F0FF); static const Color secondary = Color(0xFFBF00FF); static const Color accent = Color(0xFFFF006E); static const Color success = Color(0xFF00FF94); static const Color warning = Color(0xFFFFB800); static const Color error = Color(0xFFFF3B3B); // Text Colors static const Color textPrimary = Color(0xFFF0F4F8); static const Color textSecondary = Color(0xFF9BA3B8); static const Color textTertiary = Color(0xFF6B7280); static const Color textInverted = Color(0xFF0A0E27); // Gradients static const LinearGradient primaryGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [primary, Color(0xFF00C8FF)], ); static const LinearGradient secondaryGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [accent, error], ); } ``` ### 2. Créer le fichier de typography Créez `lib/core/theme/typography.dart`: ```dart import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class AppTypography { // Heading Font - Space Grotesk static TextStyle get heading { return GoogleFonts.spaceGrotesk( fontWeight: FontWeight.w700, color: AppColors.textPrimary, ); } // Body Font - Outfit static TextStyle get body { return GoogleFonts.outfit( fontWeight: FontWeight.w400, color: AppColors.textPrimary, ); } // Type Scale static const double displaySize = 48.0; static const double h1Size = 36.0; static const double h2Size = 28.0; static const double h3Size = 22.0; static const double bodyLargeSize = 18.0; static const double bodySize = 16.0; static const double bodySmallSize = 14.0; static const double captionSize = 12.0; static const double overlineSize = 11.0; // Text Styles static TextStyle get display => heading.copyWith(fontSize: displaySize); static TextStyle get h1 => heading.copyWith(fontSize: h1Size); static TextStyle get h2 => heading.copyWith( fontSize: h2Size, fontWeight: FontWeight.w600, ); static TextStyle get h3 => heading.copyWith( fontSize: h3Size, fontWeight: FontWeight.w600, ); static TextStyle get bodyLarge => body.copyWith( fontSize: bodyLargeSize, height: 1.5, ); static TextStyle get bodyText => body.copyWith( fontSize: bodySize, height: 1.6, ); static TextStyle get bodySmall => body.copyWith( fontSize: bodySmallSize, height: 1.6, color: AppColors.textSecondary, ); static TextStyle get caption => body.copyWith( fontSize: captionSize, fontWeight: FontWeight.w500, height: 1.5, color: AppColors.textSecondary, ); static TextStyle get overline => body.copyWith( fontSize: overlineSize, fontWeight: FontWeight.w600, height: 1.4, color: AppColors.textPrimary, letterSpacing: 0.5, ); } ``` ### 3. Créer le thème MaterialApp Créez `lib/core/theme/app_theme.dart`: ```dart import 'package:flutter/material.dart'; import 'colors.dart'; import 'typography.dart'; class AppTheme { static ThemeData get darkTheme { return ThemeData( useMaterial3: true, brightness: Brightness.dark, // Color Scheme colorScheme: const ColorScheme.dark( primary: AppColors.primary, secondary: AppColors.secondary, surface: AppColors.surface, error: AppColors.error, onPrimary: AppColors.textInverted, onSecondary: AppColors.textPrimary, onSurface: AppColors.textPrimary, onError: AppColors.textPrimary, ), // Scaffold scaffoldBackgroundColor: AppColors.background, // Typography fontFamily: 'Outfit', textTheme: TextTheme( displayLarge: AppTypography.display, headlineMedium: AppTypography.h1, headlineSmall: AppTypography.h2, titleLarge: AppTypography.h3, bodyLarge: AppTypography.bodyLarge, bodyMedium: AppTypography.bodyText, bodySmall: AppTypography.bodySmall, labelSmall: AppTypography.caption, ), // Card Theme cardTheme: CardTheme( color: AppColors.surface, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), side: BorderSide(color: AppColors.border, width: 1), ), // Input Decoration inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: AppColors.background, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: AppColors.border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: AppColors.border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: AppColors.primary), ), focusColor: AppColors.primary, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), // Elevated Button Theme elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: AppColors.textInverted, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), textStyle: AppTypography.bodyText.copyWith( fontWeight: FontWeight.w600, ), ).copyWith( elevation: MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.pressed)) return 0; if (states.contains(MaterialState.hovered)) return 4; return 0; }), ), ), // Outline Button Theme outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: AppColors.primary, side: BorderSide(color: AppColors.primary), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), textStyle: AppTypography.bodyText.copyWith( fontWeight: FontWeight.w600, ), ), ), // Icon Theme iconTheme: const IconThemeData( color: AppColors.textSecondary, size: 24, ), // Divider dividerTheme: const DividerThemeData( color: AppColors.border, thickness: 1, space: 1, ), ); } } ``` ### 4. Créer des composants réutilisables #### Bouton Primaire avec Glow Créez `lib/widgets/buttons/primary_button.dart`: ```dart import 'package:flutter/material.dart'; import '../core/theme/colors.dart'; class PrimaryButton extends StatelessWidget { final String text; final VoidCallback? onPressed; final bool isLoading; final double? width; const PrimaryButton({ Key? key, required this.text, this.onPressed, this.isLoading = false, this.width, }) : super(key: key); @override Widget build(BuildContext context) { return AnimatedContainer( duration: const Duration(milliseconds: 200), width: width, decoration: BoxDecoration( gradient: AppColors.primaryGradient, borderRadius: BorderRadius.circular(8), boxShadow: onPressed != null ? [ BoxShadow( color: AppColors.primary.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 4), ), ] : null, ), child: Material( color: Colors.transparent, child: InkWell( onTap: isLoading ? null : onPressed, borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Center( child: isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(AppColors.textInverted), ), ) : Text( text, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: AppColors.textInverted, ), ), ), ), ), ), ); } } ``` #### Carte Album avec Hover Créez `lib/widgets/cards/album_card.dart`: ```dart import 'package:flutter/material.dart'; import '../../core/theme/colors.dart'; import '../../core/theme/typography.dart'; class AlbumCard extends StatefulWidget { final String imageUrl; final String title; final String subtitle; final VoidCallback? onTap; const AlbumCard({ Key? key, required this.imageUrl, required this.title, required this.subtitle, this.onTap, }) : super(key: key); @override State createState() => _AlbumCardState(); } class _AlbumCardState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _scaleAnimation; bool _isHovered = false; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 200), vsync: this, ); _scaleAnimation = Tween(begin: 1.0, end: 1.02).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MouseRegion( onEnter: (_) { setState(() => _isHovered = true); _animationController.forward(); }, onExit: (_) { setState(() => _isHovered = false); _animationController.reverse(); }, child: ScaleTransition( scale: _scaleAnimation, child: GestureDetector( onTap: widget.onTap, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Album Art Stack( children: [ Container( width: double.infinity, aspectRatio: 1, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 4), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( widget.imageUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( color: AppColors.surface, child: Icon( Icons.music_note, size: 48, color: AppColors.textTertiary, ), ); }, ), ), ), // Play Overlay if (_isHovered) Positioned.fill( child: Container( decoration: BoxDecoration( color: AppColors.background.withOpacity(0.7), borderRadius: BorderRadius.circular(12), ), child: Center( child: Container( width: 56, height: 56, decoration: BoxDecoration( color: AppColors.primary, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: AppColors.primary.withOpacity(0.4), blurRadius: 24, offset: const Offset(0, 4), ), ], ), child: Icon( Icons.play_arrow, color: AppColors.textInverted, size: 32, ), ), ), ), ), ], ), const SizedBox(height: 12), // Title Text( widget.title, style: AppTypography.h3.copyWith( fontSize: 16, overflow: TextOverflow.ellipsis, ), maxLines: 1, ), const SizedBox(height: 4), // Subtitle Text( widget.subtitle, style: AppTypography.bodySmall, maxLines: 1, ), ], ), ), ), ), } } ``` --- ## Migration du Code Existant ### Priorités de Migration 1. **Phase 1** - Fondation (Priorité Haute) - ✅ Implémenter `colors.dart` - ✅ Implémenter `typography.dart` - ✅ Implémenter `app_theme.dart` - ✅ Mettre à jour `main.dart` avec le nouveau thème 2. **Phase 2** - Composants (Priorité Haute) - ✅ Créer `PrimaryButton` - ✅ Créer `AlbumCard` - ✅ Créer `SearchInput` - ✅ Créer `ProgressBar` (player) 3. **Phase 3** - Pages (Priorité Moyenne) - ✅ Migrer la page Home - ✅ Migrer la page Search - ✅ Migrer la page Player 4. **Phase 4** - Finitions (Priorité Basse) - Animations et transitions - États de loading - États empty ### Checklist par Page #### Page Home - [ ] Hero section avec gradient animé - [ ] Quick picks grid - [ ] Horizontal scroll rows - [ ] Skeleton loading states - [ ] Category pills #### Page Search - [ ] Search bar avec clear button - [ ] Search tabs - [ ] Recent searches - [ ] Trending searches - [ ] Results grid/list #### Page Player - [ ] Large album art avec glow - [ ] Progress bar avec handle - [ ] Control buttons (primary + secondary) - [ ] Volume slider - [ ] Queue panel - [ ] Mini player sticky --- ## Checklist de Validation Avant de considérer une page comme terminée, vérifiez : ### Visuel - [ ] Pas d'emojis comme icônes (SVG seulement) - [ ] Icônes cohérentes (Lucide/Heroicons) - [ ] Hover states sans layout shift - [ ] Couleurs du thème utilisées directement - [ ] Effets néon subtils, pas écrasants ### Interaction - [ ] Tous les éléments cliquables ont `cursor: pointer` - [ ] Hover states fournissent feedback clair - [ ] Transitions 150-300ms - [ ] Focus states visibles ### Accessibilité - [ ] Contraste texte minimum 4.5:1 - [ ] Toutes les images ont alt text - [ ] Inputs ont labels - [ ] Tabulation fonctionne - [ ] `prefers-reduced-motion` respecté ### Responsive - [ ] Fonctionne à 375px (mobile) - [ ] Fonctionne à 768px (tablet) - [ ] Fonctionne à 1024px (desktop) - [ ] Pas de scroll horizontal mobile - [ ] Touch targets min 44x44px ### Performance - [ ] Images WebP avec fallbacks - [ ] Lazy loading pour images larges - [ ] Animations utilisent transform/opacity - [ ] Pas de layout shifts --- ## Ressources Utiles ### Fonts Google - **Space Grotesk**: https://fonts.google.com/specimen/Space+Grotesk - **Outfit**: https://fonts.google.com/specimen/Outfit - **JetBrains Mono**: https://fonts.google.com/specimen/JetBrains+Mono ### Icônes - **Lucide Icons**: https://lucide.dev/ - **Heroicons**: https://heroicons.com/ ### Outils de Contraste - **WebAIM Contrast Checker**: https://webaim.org/resources/contrastchecker/ ### Documentation Flutter - **Theme Data**: https://api.flutter.dev/flutter/material/ThemeData-class.html - **Animation Controller**: https://api.flutter.dev/flutter/animation/AnimationController-class.html --- ## Prochaines Étapes 1. ✅ **Design system créé** - MASTER.md + overrides 2. 🔄 **Implémenter les fichiers de thème** - colors, typography, app_theme 3. 🔄 **Créer les composants de base** - buttons, cards, inputs 4. 🔄 **Migrer page par page** - Commencer par Home 5. 🔄 **Tester et valider** - Accessibilité, responsive, performance --- **Besoin d'aide?** Référez-vous toujours aux fichiers dans `design-system/` pour les règles spécifiques à chaque page.