Files
AudiOhm/archives/docs/DESIGN_IMPLEMENTATION_GUIDE.md
root 801e6a050b prod: UI Optimisée mise en production
- Documentation archivée et réorganisée
- Backend: Ajout tests, migrations, library service, rate limiting
- Frontend: Suppression Flutter, focus sur interface web HTML/JS
- Tailwind CSS ajouté pour le style
- Améliorations UX et corrections bugs

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-01-20 09:56:39 +00:00

20 KiB

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
  2. Structure du design system
  3. Implémentation dans Flutter
  4. Migration du code existant
  5. 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:

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:

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:

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<double>((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:

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:

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<AlbumCard> createState() => _AlbumCardState();
}

class _AlbumCardState extends State<AlbumCard>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _scaleAnimation;
  bool _isHovered = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );
    _scaleAnimation = Tween<double>(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
  • 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

Icônes

Outils de Contraste

Documentation Flutter


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.