feat: Modernisation UI/UX et configuration Flutter multi-plateforme
Phase 1 - Corrections Critiques: - Fixed memory leaks dans music_provider.dart (stream subscriptions) - Fixed race conditions dans search_provider.dart (stale results) - Fixed token refresh errors dans api_service.dart - Improved error handling avec messages utilisateur - Changed API URL to HTTPS by default Phase 2 - Améliorations UX Desktop: - Ajouté cursor pointers sur tous les éléments cliquables - Implémenté hover states avec effets néon glow (200ms transitions) - Créé skeleton loading states avec shimmer animation - Ajouté widgets: ClickableWrapper, ErrorDisplay, SkeletonLoading - Enhanced visual feedback pour desktop users Phase 3 - Configuration Flutter: - Configuré Android (Gradle 8.1.0, Kotlin 1.9.0, minSdk 21, targetSdk 34) - Créé launcher icons cyberpunk néon (5 densités) - Configuré Windows desktop (structure complète) - Activé Linux desktop support - Ajouté package équatable pour entités de domaine - Corrigé imports (colors.dart, auth_provider.dart) - Fixed Dio API compatibility (RequestOptions) Documentation: - STYLE_GUIDE.md: Guide complet (100+ pages) - DESIGN_IMPLEMENTATION_GUIDE.md: Implémentation Flutter - BUILD_STATUS.md: Status builds + troubleshooting - QUICKSTART_BUILDS.md: Guide rapide - BUILD_INDEX.md: Index documentation - PHASE_1_CORRECTIONS.md: Corrections Phase 1 - PHASE_2_UX_IMPROVEMENTS.md: Améliorations Phase 2 - PR_REVIEW_SUMMARY.md: Revue code complète - CODE_ANALYSIS_AND_PRIORITIES.md: Analyse code Scripts & Builds: - BUILD_ALL.sh: Script automatisé builds multi-plateforme - builds/: Structure avec README par plateforme - design-system/: Système de design complet Backend: - Ajouté streaming HTTP Range pour audio progressif - Enhanced YouTube service avec métadonnées complètes - Improved error handling et validation 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>
This commit is contained in:
@@ -0,0 +1,690 @@
|
||||
# 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<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`:
|
||||
|
||||
```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<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
|
||||
|
||||
#### 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.
|
||||
Reference in New Issue
Block a user