# [SHOPPING_BAGS] Flutter E-Commerce — Projet Fil Rouge Complet
### Guide Ultra-Détaillé pour Grand Débutant

---

## [IMPORTANT] Présentation du Projet

Bienvenue dans ce guide complet d'apprentissage de **Flutter** à travers un projet concret : une **application e-commerce mobile** appelée **"ShopFlutter"**.

Ce projet est conçu pour un **grand débutant** souhaitant maîtriser Flutter de A à Z, en progressant chapitre par chapitre, tout en construisant une vraie application fonctionnelle.

---

## [OBJECTIF] Objectifs Pédagogiques

À la fin de ce projet, vous serez capable de :

- [OK] Écrire du code **Dart** propre et structuré
- [OK] Construire des **interfaces Flutter** complexes et responsives
- [OK] Gérer la **navigation** entre écrans de manière professionnelle
- [OK] Implémenter un **state management** avec Provider/Riverpod
- [OK] Connecter votre app à **Firebase** (auth, base de données, stockage)
- [OK] Déployer une **application e-commerce** complète et fonctionnelle

---

## [CONSTRUCTION] L'Application : **ShopFlutter**

### Fonctionnalités de l'application finale :

| Module | Fonctionnalités |
|--------|----------------|
| [SECURISE] Authentification | Inscription, Connexion, Déconnexion |
| [ACCUEIL] Accueil | Bannières, Catégories, Produits en vedette |
| [SHOPPING_TROLLEY] Catalogue | Liste produits, Filtres, Recherche |
| [PACKAGE] Produit | Détail produit, Galerie photos, Avis |
| [SHOPPING_BAGS] Panier | Ajout/suppression, Quantités, Total |
| [HEAVY_BLACK_HEART] Favoris | Liste de souhaits persistante |
| [UTILISATEUR] Profil | Informations utilisateur, Commandes |
| [CARTE] Commande | Processus de commande, Confirmation |

---

## [DOCS] Structure des Chapitres

```
[DOSSIER] Flutter E-Commerce — ShopFlutter
│
├── [FICHIER] 00_INDEX.md               <- Vous êtes ici (Vue d'ensemble)
│
├── [FICHIER] 01_DART_ET_BASES.md       <- Chapitre 1 : Dart & Fondations Flutter
│   ├── Syntaxe Dart
│   ├── POO en Dart
│   ├── Installation & Configuration
│   └── Premier widget Flutter
│
├── [FICHIER] 02_WIDGETS_ET_UI.md       <- Chapitre 2 : Widgets & Interface Utilisateur
│   ├── Widgets fondamentaux
│   ├── Layout & Positionnement
│   ├── Listes & Grilles
│   └── Thème & Styles
│
├── [FICHIER] 03_NAVIGATION_ET_STATE.md <- Chapitre 3 : Navigation & State Management
│   ├── Navigation entre écrans
│   ├── Passage de données
│   ├── Provider / Riverpod
│   └── Gestion du panier
│
└── [FICHIER] 04_FIREBASE_ET_BACKEND.md <- Chapitre 4 : Firebase & Backend
    ├── Configuration Firebase
    ├── Authentication
    ├── Firestore Database
    └── Storage & Déploiement
```

---

## [WORLD_MAP] Roadmap Visuelle de Progression

```
SEMAINE 1           SEMAINE 2           SEMAINE 3           SEMAINE 4
┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  CHAPITRE 1 │ ->  │  CHAPITRE 2 │ ->  │  CHAPITRE 3 │ ->  │  CHAPITRE 4 │
│             │    │             │    │             │    │             │
│ Dart & Base │    │ Widgets &UI │    │ Nav & State │    │ Firebase    │
│             │    │             │    │             │    │             │
│ • Variables │    │ • Layouts   │    │ • Routes    │    │ • Auth      │
│ • Fonctions │    │ • Listes    │    │ • Provider  │    │ • Firestore │
│ • Classes   │    │ • Thèmes    │    │ • Panier    │    │ • Storage   │
│ • Widgets   │    │ • Formulair │    │ • Favoris   │    │ • Deploy    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
     v                   v                  v                   v
  Page d'accueil    UI complète        App navigable      App complète
  statique          avec données       avec état          avec backend
```

---

## [OUTILS] Environnement de Développement

### Logiciels nécessaires

| Outil | Version recommandée | Lien |
|-------|-------------------|------|
| Flutter SDK | 3.x ou supérieur | flutter.dev |
| Dart SDK | Inclus avec Flutter | — |
| Android Studio | Electric Eel+ | developer.android.com |
| VS Code | Dernière version | code.visualstudio.com |
| Git | Dernière version | git-scm.com |

### Extensions VS Code indispensables

```
1. Flutter (par Dart Code)
2. Dart (par Dart Code)
3. Flutter Widget Snippets
4. Pubspec Assist
5. Error Lens
6. GitLens
```

---

## [PACKAGE] Structure du Projet ShopFlutter

Voici l'architecture finale de votre projet :

```
shop_flutter/
├── lib/
│   ├── main.dart                    # Point d'entrée
│   ├── app.dart                     # Configuration app
│   │
│   ├── core/
│   │   ├── constants/
│   │   │   ├── app_colors.dart      # Couleurs de l'app
│   │   │   ├── app_strings.dart     # Textes / traductions
│   │   │   └── app_routes.dart      # Noms des routes
│   │   ├── theme/
│   │   │   └── app_theme.dart       # Thème global
│   │   └── utils/
│   │       └── helpers.dart         # Fonctions utilitaires
│   │
│   ├── models/
│   │   ├── product.dart             # Modèle Produit
│   │   ├── user.dart                # Modèle Utilisateur
│   │   ├── cart_item.dart           # Modèle Item panier
│   │   └── order.dart               # Modèle Commande
│   │
│   ├── providers/
│   │   ├── auth_provider.dart       # État authentification
│   │   ├── products_provider.dart   # État produits
│   │   ├── cart_provider.dart       # État panier
│   │   └── favorites_provider.dart  # État favoris
│   │
│   ├── services/
│   │   ├── auth_service.dart        # Service Firebase Auth
│   │   ├── firestore_service.dart   # Service Firestore
│   │   └── storage_service.dart     # Service Storage
│   │
│   ├── screens/
│   │   ├── auth/
│   │   │   ├── login_screen.dart
│   │   │   └── register_screen.dart
│   │   ├── home/
│   │   │   └── home_screen.dart
│   │   ├── products/
│   │   │   ├── products_list_screen.dart
│   │   │   └── product_detail_screen.dart
│   │   ├── cart/
│   │   │   └── cart_screen.dart
│   │   ├── favorites/
│   │   │   └── favorites_screen.dart
│   │   └── profile/
│   │       └── profile_screen.dart
│   │
│   └── widgets/
│       ├── common/
│       │   ├── custom_button.dart
│       │   ├── custom_text_field.dart
│       │   └── loading_widget.dart
│       ├── product/
│       │   ├── product_card.dart
│       │   └── product_grid.dart
│       └── cart/
│           └── cart_item_widget.dart
│
├── assets/
│   ├── images/
│   └── icons/
│
├── pubspec.yaml                     # Dépendances
└── README.md
```

---

## [DESIGN] Charte Graphique de ShopFlutter

```dart
// Palette de couleurs principale
Primary Color    : #6C63FF  (Violet moderne)
Secondary Color  : #FF6584  (Rose accent)
Background       : #F8F9FA  (Gris très clair)
Surface          : #FFFFFF  (Blanc)
Text Primary     : #2D3436  (Noir doux)
Text Secondary   : #636E72  (Gris moyen)
Success          : #00B894  (Vert)
Error            : #FF7675  (Rouge doux)
```

---

## [LISTE] Convention de Nommage

| Élément | Convention | Exemple |
|---------|-----------|---------|
| Fichiers | snake_case | `product_card.dart` |
| Classes | PascalCase | `ProductCard` |
| Variables | camelCase | `productName` |
| Constantes | SCREAMING_SNAKE | `MAX_ITEMS` |
| Fonctions | camelCase | `fetchProducts()` |
| Widgets | PascalCase | `HomeScreen` |

---

## [IDEE] Comment Utiliser ce Guide

> **[JAPANESE_SYMBOL_FOR_BEGINNER] Pour les débutants absolus :** Suivez les chapitres dans l'ordre, ne sautez aucune section. Chaque chapitre s'appuie sur le précédent.

> **[NOTE] Méthode recommandée :** Lisez d'abord le chapitre entier, puis recodez les exemples **de mémoire** sans copier-coller.

> **[BUG] En cas de bugs :** C'est normal et formateur ! Lisez les messages d'erreur attentivement, ils sont très explicites en Flutter.

> **[ALARM_CLOCK] Temps estimé :** 4 semaines à raison de 2-3h par jour pour maîtriser l'ensemble.

---

*Commencez par le **Chapitre 1 -> 01_DART_ET_BASES.md***


# [LIVRE] Chapitre 1 — Dart & Bases Flutter
## Projet ShopFlutter | Guide Ultra-Détaillé

---

## [OBJECTIF] Objectifs de ce Chapitre

À la fin de ce chapitre, vous serez capable de :
- Comprendre et écrire la syntaxe Dart de base
- Maîtriser la Programmation Orientée Objet (POO) en Dart
- Installer et configurer votre environnement Flutter
- Créer votre premier projet Flutter
- Comprendre l'architecture d'un widget Flutter

**[ALARM_CLOCK] Durée estimée :** 5-7 jours (2-3h/jour)

---

## PARTIE 1 — Introduction à Dart

### 1.1 Pourquoi Dart ?

Dart est le langage de programmation créé par Google, utilisé exclusivement par Flutter. Avant d'écrire la moindre ligne Flutter, vous devez comprendre Dart.

**Caractéristiques de Dart :**
- Langage **typé** (chaque variable a un type)
- **Compilé** (rapide en production)
- Syntaxe proche du Java et du JavaScript (facile à apprendre)
- Supporte la **POO** et la **programmation fonctionnelle**

---

### 1.2 Variables et Types de Données

En Dart, toute variable a un type. Voici les types fondamentaux :

```dart
// ═══════════════════════════════════════════════════
//  LES TYPES PRIMITIFS EN DART
// ═══════════════════════════════════════════════════

// int -> nombres entiers (sans virgule)
int age = 25;
int nombreDeProduits = 0;

// double -> nombres décimaux (avec virgule)
double prix = 29.99;
double tva = 0.20;

// String -> chaînes de caractères (texte)
// Utiliser des guillemets simples OU doubles
String nomProduit = 'Nike Air Max';
String description = "Chaussures de sport confortables";

// bool -> vrai ou faux uniquement
bool estConnecte = false;
bool produitEnStock = true;

// ═══════════════════════════════════════════════════
//  INFÉRENCE DE TYPE AVEC "var"
// ═══════════════════════════════════════════════════

// Dart peut DEVINER le type automatiquement grâce à "var"
var quantite = 3;         // Dart comprend : c'est un int
var nomClient = "Marie";  // Dart comprend : c'est un String
var prixTotal = 89.97;    // Dart comprend : c'est un double

// ATTENTION : une fois le type inféré, il ne peut pas changer !
// var quantite = 3;
// quantite = "trois"; // [X] ERREUR : on ne peut pas changer int en String

// ═══════════════════════════════════════════════════
//  VARIABLES NULLABLES (peuvent être null/vides)
// ═══════════════════════════════════════════════════

// Par défaut, une variable Dart NE PEUT PAS être null
// Pour autoriser null, ajoutez "?" après le type
String? adresseLivraison;  // Peut être null (pas encore remplie)
int? codeReduction;        // Peut être null (pas de code promo)

// ═══════════════════════════════════════════════════
//  CONSTANTES (valeurs qui ne changent jamais)
// ═══════════════════════════════════════════════════

// "final" : valeur assignée une seule fois (à l'exécution)
final String idUtilisateur = 'user_123';
final DateTime dateCreation = DateTime.now();

// "const" : valeur connue à la COMPILATION (encore plus strict)
const double TVA = 0.20;
const int MAX_ARTICLES_PANIER = 10;
const String NOM_APP = 'ShopFlutter';
```

> [IDEE] **Conseil débutant :** Utilisez `final` quand la valeur est définie une seule fois mais calculée à l'exécution. Utilisez `const` pour les vraies constantes que vous connaissez en écrivant le code.

---

### 1.3 Collections (Listes, Maps, Sets)

Les collections permettent de stocker plusieurs valeurs ensemble.

```dart
// ═══════════════════════════════════════════════════
//  LIST -> liste ordonnée (comme un tableau)
// ═══════════════════════════════════════════════════

// Créer une liste de String
List<String> categories = ['Chaussures', 'Vêtements', 'Accessoires', 'Sport'];

// Créer une liste vide (pour la remplir plus tard)
List<String> articlesEnFavori = [];

// Accéder à un élément (l'index commence à 0)
String premiereCat = categories[0];  // 'Chaussures'
String deuxiemeCat = categories[1];  // 'Vêtements'

// Ajouter un élément
categories.add('Électronique');

// Supprimer un élément
categories.remove('Sport');

// Connaître la taille
int nbCategories = categories.length;  // 4

// Parcourir une liste avec for-each
for (String categorie in categories) {
  print('Catégorie : $categorie');  // Le $ permet d'insérer une variable dans un texte
}

// Version courte avec forEach
categories.forEach((cat) => print(cat));

// ═══════════════════════════════════════════════════
//  MAP -> dictionnaire clé-valeur (comme un objet JSON)
// ═══════════════════════════════════════════════════

// Un produit représenté sous forme de Map
Map<String, dynamic> produit = {
  'id': 'prod_001',
  'nom': 'Nike Air Max 270',
  'prix': 129.99,
  'stock': 15,
  'enPromotion': true,
};

// Accéder à une valeur
String nomProduit = produit['nom'];    // 'Nike Air Max 270'
double prixProduit = produit['prix'];  // 129.99

// Ajouter/modifier une clé
produit['couleur'] = 'Noir';          // Ajouter
produit['prix'] = 99.99;              // Modifier

// Vérifier si une clé existe
if (produit.containsKey('promo')) {
  print('Ce produit est en promotion');
}

// ═══════════════════════════════════════════════════
//  SET -> collection SANS DOUBLONS
// ═══════════════════════════════════════════════════

// Utile pour les tags, tailles uniques, etc.
Set<String> tailles = {'XS', 'S', 'M', 'L', 'XL', 'XXL'};
tailles.add('M');  // [ATTENTION] Ignoré car 'M' existe déjà !
print(tailles.length);  // Toujours 6
```

---

### 1.4 Fonctions

Les fonctions sont des blocs de code réutilisables.

```dart
// ═══════════════════════════════════════════════════
//  FONCTIONS DE BASE
// ═══════════════════════════════════════════════════

// Syntaxe : typeRetour nomFonction(typeParam param) { ... }

// Fonction qui ne retourne rien (void)
void afficherBonjour() {
  print('Bonjour depuis ShopFlutter !');
}

// Fonction qui retourne une valeur
double calculerPrixTTC(double prixHT) {
  double tva = prixHT * 0.20;
  return prixHT + tva;
}

// Fonction avec plusieurs paramètres
String formaterPrix(double prix, String devise) {
  return '${prix.toStringAsFixed(2)} $devise';
  // toStringAsFixed(2) -> affiche 2 décimales : "29.99"
}

// Appels de fonctions
afficherBonjour();                          // Affiche "Bonjour..."
double prixFinal = calculerPrixTTC(100.0);  // prixFinal = 120.0
String affichage = formaterPrix(29.99, '€'); // "29.99 €"

// ═══════════════════════════════════════════════════
//  PARAMÈTRES NOMMÉS (très utilisés en Flutter !)
// ═══════════════════════════════════════════════════

// Les {} rendent les paramètres nommés et optionnels
void creerProduit({
  required String nom,    // "required" = obligatoire
  required double prix,   // "required" = obligatoire
  int stock = 0,          // Paramètre optionnel avec valeur par défaut
  bool enVedette = false, // Paramètre optionnel avec valeur par défaut
}) {
  print('Produit créé : $nom à $prix€ (stock: $stock)');
}

// Appel avec paramètres nommés (l'ordre n'importe pas)
creerProduit(
  nom: 'Adidas Ultra Boost',
  prix: 159.99,
  stock: 25,
  // enVedette est omis -> prend la valeur false par défaut
);

// ═══════════════════════════════════════════════════
//  FONCTIONS FLÉCHÉES (syntaxe courte)
// ═══════════════════════════════════════════════════

// Si la fonction tient en une ligne, utilisez =>
double calculerTVA(double prix) => prix * 0.20;
bool estEnStock(int stock) => stock > 0;
String saluer(String prenom) => 'Bonjour, $prenom !';

// ═══════════════════════════════════════════════════
//  FONCTIONS ANONYMES & LAMBDAS
// ═══════════════════════════════════════════════════

// Très utilisées pour filtrer, trier, transformer des listes
List<double> prix = [29.99, 99.99, 14.99, 199.99, 49.99];

// Trier par ordre croissant
prix.sort((a, b) => a.compareTo(b));

// Filtrer les produits < 50€
List<double> prixAbordables = prix.where((p) => p < 50).toList();

// Transformer : appliquer une réduction de 10%
List<double> prixReduits = prix.map((p) => p * 0.9).toList();

// ═══════════════════════════════════════════════════
//  FONCTIONS ASYNCHRONES (pour Firebase, API, etc.)
// ═══════════════════════════════════════════════════

// "async" et "await" pour les opérations qui prennent du temps
// Future<T> = promesse de retourner un T dans le futur

Future<List<String>> chargerProduits() async {
  // Simule un délai réseau de 2 secondes
  await Future.delayed(Duration(seconds: 2));
  
  // Dans la vraie app, ici vous appelez Firebase ou une API
  return ['Nike Air Max', 'Adidas Ultra', 'Puma RS'];
}

// Appel d'une fonction async
void main() async {
  print('Chargement en cours...');
  List<String> produits = await chargerProduits();  // Attend que ce soit fini
  print('Produits chargés : $produits');
}
```

---

### 1.5 Programmation Orientée Objet (POO) en Dart

La POO est **fondamentale** en Flutter. Tout est objet : les widgets, les couleurs, les routes...

```dart
// ═══════════════════════════════════════════════════
//  CLASSES DE BASE
// ═══════════════════════════════════════════════════

// Dans ShopFlutter, chaque produit sera une instance de cette classe
class Produit {
  // ── Propriétés (attributs) ──────────────────────
  final String id;
  final String nom;
  final String description;
  final double prix;
  final String imageUrl;
  final String categorie;
  int stock;
  bool estEnFavori;

  // ── Constructeur ────────────────────────────────
  // Syntaxe spéciale Dart : this.propriete initialise automatiquement
  Produit({
    required this.id,
    required this.nom,
    required this.description,
    required this.prix,
    required this.imageUrl,
    required this.categorie,
    this.stock = 0,
    this.estEnFavori = false,
  });

  // ── Méthodes ────────────────────────────────────

  // Vérifie si le produit est disponible
  bool get estDisponible => stock > 0;

  // Calcule le prix avec TVA
  double get prixTTC => prix * 1.20;

  // Applique une réduction
  double calculerPrixApresReduction(double pourcentage) {
    if (pourcentage < 0 || pourcentage > 100) {
      throw ArgumentError('Le pourcentage doit être entre 0 et 100');
    }
    return prix * (1 - pourcentage / 100);
  }

  // Méthode toString : comment afficher l'objet en texte
  @override
  String toString() {
    return 'Produit(id: $id, nom: $nom, prix: $prix€)';
  }

  // ── Fabrique depuis Firebase (fromMap) ──────────
  // Convertit un Map (données Firebase) en objet Produit
  factory Produit.fromMap(Map<String, dynamic> map) {
    return Produit(
      id: map['id'] ?? '',
      nom: map['nom'] ?? 'Produit inconnu',
      description: map['description'] ?? '',
      prix: (map['prix'] as num).toDouble(),
      imageUrl: map['imageUrl'] ?? '',
      categorie: map['categorie'] ?? 'Autre',
      stock: map['stock'] ?? 0,
    );
  }

  // ── Conversion vers Map (pour Firebase) ─────────
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'nom': nom,
      'description': description,
      'prix': prix,
      'imageUrl': imageUrl,
      'categorie': categorie,
      'stock': stock,
      'estEnFavori': estEnFavori,
    };
  }
}

// ─────────────────────────────────────────────────
// Utilisation de la classe Produit
// ─────────────────────────────────────────────────
void exempleUtilisation() {
  // Créer un produit
  Produit chaussure = Produit(
    id: 'prod_001',
    nom: 'Nike Air Max 270',
    description: 'Chaussures légères et confortables',
    prix: 129.99,
    imageUrl: 'https://example.com/nike.jpg',
    categorie: 'Chaussures',
    stock: 15,
  );

  // Utiliser les propriétés
  print(chaussure.nom);           // Nike Air Max 270
  print(chaussure.prixTTC);       // 155.988 (avec TVA)
  print(chaussure.estDisponible); // true

  // Appeler une méthode
  double prixSolde = chaussure.calculerPrixApresReduction(20);
  print('Prix soldé : ${prixSolde}€'); // Prix soldé : 103.992€
}

// ═══════════════════════════════════════════════════
//  HÉRITAGE
// ═══════════════════════════════════════════════════

// Un produit vestimentaire hérite de Produit et ajoute des tailles
class ProduitVestimentaire extends Produit {
  final List<String> tailles;
  final List<String> couleurs;
  String tailleSelectionnee;

  ProduitVestimentaire({
    required super.id,         // "super." transmet au parent
    required super.nom,
    required super.description,
    required super.prix,
    required super.imageUrl,
    required super.categorie,
    required this.tailles,
    required this.couleurs,
    this.tailleSelectionnee = 'M',
    super.stock,
  });

  // Méthode propre à cette classe
  bool tailleDisponible(String taille) {
    return tailles.contains(taille);
  }

  // Surcharge (override) du getter estDisponible
  @override
  bool get estDisponible => stock > 0 && tailles.isNotEmpty;
}

// ═══════════════════════════════════════════════════
//  CLASSES ABSTRAITES & INTERFACES
// ═══════════════════════════════════════════════════

// Classe abstraite : ne peut pas être instanciée directement
abstract class BaseService {
  // Méthode abstraite : doit être implémentée par les sous-classes
  Future<void> initialiser();
  Future<bool> estConnecte();
}

class FirebaseService extends BaseService {
  @override
  Future<void> initialiser() async {
    // Initialisation Firebase
    await Future.delayed(Duration(seconds: 1));
    print('Firebase initialisé !');
  }

  @override
  Future<bool> estConnecte() async {
    // Vérification connexion
    return true;
  }
}
```

---

### 1.6 Gestion des Erreurs en Dart

```dart
// ═══════════════════════════════════════════════════
//  TRY / CATCH / FINALLY
// ═══════════════════════════════════════════════════

Future<Produit?> chargerProduit(String id) async {
  try {
    // Code susceptible d'échouer
    // Dans la vraie app : appel Firebase
    if (id.isEmpty) {
      throw ArgumentError('L\'ID ne peut pas être vide');
    }
    
    // Simule un résultat
    return Produit(
      id: id,
      nom: 'Produit test',
      description: 'Description',
      prix: 0,
      imageUrl: '',
      categorie: 'Test',
    );
    
  } on ArgumentError catch (e) {
    // Erreur spécifique : mauvais argument
    print('Erreur de paramètre : ${e.message}');
    return null;
    
  } catch (e, stackTrace) {
    // Toute autre erreur
    print('Erreur inattendue : $e');
    print('Trace : $stackTrace');
    return null;
    
  } finally {
    // Toujours exécuté, erreur ou pas
    print('Tentative de chargement terminée');
  }
}
```

---

## PARTIE 2 — Installation & Configuration Flutter

### 2.1 Installation de Flutter

**Étape 1 : Télécharger Flutter SDK**

1. Allez sur [flutter.dev/docs/get-started/install](https://flutter.dev/docs/get-started/install)
2. Choisissez votre OS (Windows, macOS, Linux)
3. Téléchargez l'archive `.zip` ou `.tar.xz`

**Étape 2 : Extraire et configurer le PATH**

```bash
# Sur macOS/Linux, ajoutez dans votre ~/.bashrc ou ~/.zshrc :
export PATH="$PATH:/chemin/vers/flutter/bin"

# Vérifier l'installation
flutter --version
```

**Étape 3 : Vérifier tout est OK**

```bash
# Cette commande liste tout ce qui manque
flutter doctor

# Résultat typique :
# [[OK]] Flutter (Channel stable, 3.x.x)
# [[OK]] Android toolchain (Android SDK version x.x)
# [[OK]] Xcode (macOS seulement)
# [[OK]] VS Code (avec extension Flutter)
# [[OK]] Connected device (émulateur ou téléphone)
```

### 2.2 Création du Projet ShopFlutter

```bash
# Créer le projet (dans votre dossier de travail)
flutter create shop_flutter

# Se déplacer dans le projet
cd shop_flutter

# Ouvrir dans VS Code
code .

# Lancer l'application (avec un émulateur ouvert ou téléphone connecté)
flutter run
```

### 2.3 Structure Initiale du Projet

Quand Flutter crée un projet, voici ce que vous obtenez :

```
shop_flutter/
├── android/          # Code natif Android (ne pas modifier)
├── ios/              # Code natif iOS (ne pas modifier)
├── lib/
│   └── main.dart     # <- VOTRE point d'entrée !
├── test/             # Tests automatisés
├── pubspec.yaml      # <- Fichier de configuration et dépendances
└── README.md
```

### 2.4 Comprendre pubspec.yaml

```yaml
# pubspec.yaml — Le "package.json" de Flutter

name: shop_flutter
description: Application e-commerce ShopFlutter
version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'     # Version Dart requise
  flutter: '>=3.0.0'          # Version Flutter requise

dependencies:
  flutter:
    sdk: flutter

  # ─── Dépendances que nous allons ajouter ───────
  
  # State Management
  provider: ^6.1.1            # Gestion d'état (Chapitre 3)

  # Firebase
  firebase_core: ^2.24.2      # Base Firebase (Chapitre 4)
  firebase_auth: ^4.15.3      # Authentification (Chapitre 4)
  cloud_firestore: ^4.13.6    # Base de données (Chapitre 4)
  firebase_storage: ^11.5.6   # Stockage fichiers (Chapitre 4)

  # Navigation
  go_router: ^13.0.0          # Navigation avancée (Chapitre 3)

  # Interface
  cached_network_image: ^3.3.0  # Chargement d'images (Chapitre 2)
  shimmer: ^3.0.0               # Effet de chargement (Chapitre 2)
  carousel_slider: ^4.2.1       # Carrousel (Chapitre 2)

  # Utilitaires
  intl: ^0.19.0               # Formatage dates/monnaies

flutter:
  uses-material-design: true

  # Assets (images, icônes locaux)
  assets:
    - assets/images/
    - assets/icons/
```

---

## PARTIE 3 — Votre Premier Widget Flutter

### 3.1 Comprendre le fichier main.dart

```dart
// lib/main.dart — Le point d'entrée de toute application Flutter

import 'package:flutter/material.dart';  // Importe Material Design

// main() est LA première fonction appelée quand l'app démarre
void main() {
  // runApp() lance l'application Flutter
  // Elle prend UN widget en argument : le widget racine
  runApp(const MyApp());
}

// MyApp est un StatelessWidget (widget sans état)
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // build() retourne l'arbre de widgets qui compose l'UI
    
    return MaterialApp(
      // Nom de l'application (affiché dans le gestionnaire de tâches)
      title: 'ShopFlutter',
      
      // Cache la bannière "DEBUG" rouge en haut à droite
      debugShowCheckedModeBanner: false,
      
      // Thème global de l'application
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6C63FF),  // Notre violet principal
        ),
        useMaterial3: true,  // Utilise Material Design 3 (le plus récent)
      ),
      
      // Widget affiché en premier (l'écran d'accueil)
      home: const HomeScreen(),
    );
  }
}
```

### 3.2 StatelessWidget vs StatefulWidget

C'est **LA distinction fondamentale** de Flutter :

```dart
// ═══════════════════════════════════════════════════
//  STATELESS WIDGET — Widget SANS état interne
// ═══════════════════════════════════════════════════

// Utiliser quand : l'interface ne change JAMAIS après sa création
// Exemples : Logo, Titre, Texte fixe, Icône

class LogoWidget extends StatelessWidget {
  // Les propriétés sont passées via le constructeur
  final String nomApp;
  final double taille;

  const LogoWidget({
    super.key,
    required this.nomApp,
    this.taille = 40.0,
  });

  @override
  Widget build(BuildContext context) {
    // build() est appelé UNE SEULE FOIS
    return Row(
      children: [
        Icon(Icons.shopping_bag, size: taille, color: Colors.purple),
        Text(nomApp, style: TextStyle(fontSize: taille * 0.5)),
      ],
    );
  }
}

// ═══════════════════════════════════════════════════
//  STATEFUL WIDGET — Widget AVEC état interne
// ═══════════════════════════════════════════════════

// Utiliser quand : l'interface doit changer (compteur, favoris, etc.)
// Exemples : Compteur panier, Bouton like, Formulaire

class BoutonFavori extends StatefulWidget {
  final String produitId;

  const BoutonFavori({super.key, required this.produitId});

  @override
  State<BoutonFavori> createState() => _BoutonFavoriState();
  //                                    ^ Convention : _ = privé
}

// L'état du widget est dans cette classe séparée
class _BoutonFavoriState extends State<BoutonFavori> {
  // ─── Variables d'état ─────────────────────────
  bool _estFavori = false;  // _ = variable privée

  // ─── Méthode pour changer l'état ──────────────
  void _toggleFavori() {
    // setState() est OBLIGATOIRE pour que Flutter
    // sache qu'il doit redesiner le widget
    setState(() {
      _estFavori = !_estFavori;  // Inverse le booléen
    });
    
    // Ici vous appelleriez votre Provider/Firebase pour persister
    print('Produit ${widget.produitId} : favori = $_estFavori');
    //      ^ "widget." pour accéder aux props du StatefulWidget
  }

  @override
  Widget build(BuildContext context) {
    // build() est rappelé à chaque setState()
    return IconButton(
      onPressed: _toggleFavori,
      icon: Icon(
        _estFavori ? Icons.favorite : Icons.favorite_border,
        color: _estFavori ? Colors.red : Colors.grey,
      ),
      tooltip: _estFavori ? 'Retirer des favoris' : 'Ajouter aux favoris',
    );
  }
}
```

---

### 3.3 Création de l'Écran d'Accueil Initial (ShopFlutter)

Voici notre premier vrai écran pour ShopFlutter :

```dart
// lib/screens/home/home_screen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ── AppBar (barre du haut) ──────────────────
      appBar: AppBar(
        backgroundColor: const Color(0xFF6C63FF),
        title: const Row(
          children: [
            Icon(Icons.shopping_bag, color: Colors.white),
            SizedBox(width: 8),  // Espace entre l'icône et le texte
            Text(
              'ShopFlutter',
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
        actions: [
          // Icône panier dans la barre du haut
          IconButton(
            onPressed: () => print('Panier cliqué'),
            icon: const Badge(
              label: Text('3'),         // Nombre d'articles
              child: Icon(Icons.shopping_cart, color: Colors.white),
            ),
          ),
        ],
      ),

      // ── Corps principal ─────────────────────────
      body: SingleChildScrollView(
        // SingleChildScrollView permet le défilement vertical
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Section 1 : Bannière de bienvenue
            _buildBanniereBienvenue(),
            
            // Espace
            const SizedBox(height: 16),
            
            // Section 2 : Catégories
            _buildSectionCategories(),
            
            const SizedBox(height: 16),
            
            // Section 3 : Produits en vedette
            _buildSectionProduits(),
          ],
        ),
      ),

      // ── Barre de navigation du bas ──────────────
      bottomNavigationBar: NavigationBar(
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home), label: 'Accueil'),
          NavigationDestination(icon: Icon(Icons.search), label: 'Recherche'),
          NavigationDestination(icon: Icon(Icons.favorite), label: 'Favoris'),
          NavigationDestination(icon: Icon(Icons.person), label: 'Profil'),
        ],
      ),
    );
  }

  // ─── Widgets privés (méthodes qui retournent des widgets) ────────────

  Widget _buildBanniereBienvenue() {
    return Container(
      width: double.infinity,  // Prend toute la largeur
      margin: const EdgeInsets.all(16),
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Color(0xFF6C63FF), Color(0xFF9C8DFF)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Bienvenue sur\nShopFlutter ! [WAVING_HAND_SIGN]',
            style: TextStyle(
              color: Colors.white,
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          const Text(
            'Découvrez nos meilleures offres',
            style: TextStyle(color: Colors.white70, fontSize: 14),
          ),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () => print('Voir les offres'),
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.white,
              foregroundColor: const Color(0xFF6C63FF),
            ),
            child: const Text('Voir les offres'),
          ),
        ],
      ),
    );
  }

  Widget _buildSectionCategories() {
    final categories = [
      {'icone': Icons.directions_run, 'label': 'Sport', 'couleur': 0xFFFF6584},
      {'icone': Icons.checkroom, 'label': 'Mode', 'couleur': 0xFF6C63FF},
      {'icone': Icons.devices, 'label': 'Tech', 'couleur': 0xFF00B894},
      {'icone': Icons.home, 'label': 'Maison', 'couleur': 0xFFFF9F43},
    ];

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 16),
          child: Text(
            'Catégories',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        const SizedBox(height: 12),
        SizedBox(
          height: 90,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,  // Défilement horizontal
            padding: const EdgeInsets.symmetric(horizontal: 16),
            itemCount: categories.length,
            itemBuilder: (context, index) {
              final cat = categories[index];
              return Container(
                margin: const EdgeInsets.only(right: 12),
                width: 80,
                decoration: BoxDecoration(
                  color: Color(cat['couleur'] as int).withOpacity(0.1),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Icon(cat['icone'] as IconData,
                        color: Color(cat['couleur'] as int), size: 28),
                    const SizedBox(height: 4),
                    Text(cat['label'] as String,
                        style: const TextStyle(fontSize: 12)),
                  ],
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  Widget _buildSectionProduits() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 16),
          child: Text(
            'Produits en vedette',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        const SizedBox(height: 12),
        // Grille 2 colonnes
        GridView.builder(
          shrinkWrap: true,              // Important ! Sinon erreur avec Column
          physics: const NeverScrollableScrollPhysics(), // Défilement géré par SingleChildScrollView
          padding: const EdgeInsets.symmetric(horizontal: 16),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,           // 2 colonnes
            childAspectRatio: 0.75,      // Ratio hauteur/largeur des cellules
            crossAxisSpacing: 12,        // Espace horizontal entre cellules
            mainAxisSpacing: 12,         // Espace vertical entre cellules
          ),
          itemCount: 4,                  // 4 produits fictifs
          itemBuilder: (context, index) {
            return _buildCarteProduiit(index);
          },
        ),
        const SizedBox(height: 24),
      ],
    );
  }

  Widget _buildCarteProduiit(int index) {
    final nomsTest = ['Nike Air Max', 'Adidas Ultra', 'Puma RS', 'Reebok Classic'];
    final prixTest = [129.99, 159.99, 89.99, 99.99];

    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Image du produit
          Expanded(
            child: Container(
              width: double.infinity,
              decoration: BoxDecoration(
                color: Colors.grey.shade100,
                borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
              ),
              child: Icon(Icons.shopping_bag, size: 60, color: Colors.grey.shade400),
            ),
          ),
          // Infos produit
          Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  nomsTest[index],
                  style: const TextStyle(fontWeight: FontWeight.bold),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
                Text(
                  '${prixTest[index]}€',
                  style: const TextStyle(
                    color: Color(0xFF6C63FF),
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
```

---

## [NOTE] Exercices du Chapitre 1

### Exercice 1.1 — Modèle de données
Créez une classe `CartItem` (article dans le panier) avec :
- `produit` (de type `Produit`)
- `quantite` (int)
- Un getter `sousTotal` qui retourne `produit.prix * quantite`
- Une méthode `toMap()` et `fromMap()`

### Exercice 1.2 — Fonction utilitaire
Créez une fonction `formaterDevise(double montant)` qui retourne `"129,99 €"` (avec virgule comme séparateur décimal et signe € à la fin).

### Exercice 1.3 — Widget personnalisé
Créez un `StatefulWidget` appelé `CompteurQuantite` qui affiche des boutons + et - pour changer une quantité, avec une valeur minimale de 1 et maximale de 99.

---

## [OK] Récapitulatif du Chapitre 1

| Concept | Maîtrisé ? |
|---------|-----------|
| Variables et types Dart | [ ] |
| Collections (List, Map, Set) | [ ] |
| Fonctions et lambdas | [ ] |
| Classes et POO | [ ] |
| Async/Await | [ ] |
| Installation Flutter | [ ] |
| StatelessWidget | [ ] |
| StatefulWidget + setState | [ ] |
| Premier écran Scaffold | [ ] |

---

**-> Chapitre suivant : `02_WIDGETS_ET_UI.md`**


# [LIVRE] Chapitre 2 — Widgets & Interface Utilisateur
## Projet ShopFlutter | Guide Ultra-Détaillé

---

## [OBJECTIF] Objectifs de ce Chapitre

À la fin de ce chapitre, vous serez capable de :
- Maîtriser les widgets fondamentaux de Flutter
- Créer des layouts complexes (Row, Column, Stack, Grid)
- Implémenter des listes et grilles performantes
- Construire des formulaires interactifs
- Appliquer un thème cohérent à toute l'application
- Créer tous les écrans UI de ShopFlutter

**[ALARM_CLOCK] Durée estimée :** 6-8 jours (2-3h/jour)

---

## PARTIE 1 — Les Widgets Fondamentaux

### 1.1 Tout est Widget en Flutter !

En Flutter, **tout ce que vous voyez à l'écran est un widget**. Un widget est simplement une classe Dart qui décrit comment un élément de l'interface doit être affiché.

```
L'arbre de widgets de ShopFlutter (simplifié) :
─────────────────────────────────────────────
MaterialApp
└── Scaffold
    ├── AppBar
    │   └── Text ("ShopFlutter")
    ├── Body
    │   └── Column
    │       ├── Text ("Bienvenue")
    │       ├── SizedBox
    │       └── GridView
    │           ├── ProductCard
    │           ├── ProductCard
    │           └── ProductCard
    └── BottomNavigationBar
```

### 1.2 Widgets de Texte

```dart
// ═══════════════════════════════════════════════════
//  TEXT — Afficher du texte
// ═══════════════════════════════════════════════════

// Texte simple
const Text('Bonjour ShopFlutter')

// Texte stylisé (pour les titres, prix, descriptions)
Text(
  'Nike Air Max 270',
  style: TextStyle(
    fontSize: 18,                    // Taille en pixels logiques
    fontWeight: FontWeight.bold,     // Gras
    fontStyle: FontStyle.italic,     // Italique
    color: Color(0xFF2D3436),        // Couleur hexadécimale
    letterSpacing: 0.5,              // Espacement entre lettres
    height: 1.5,                     // Interligne (multiplicateur)
    decoration: TextDecoration.underline,  // Souligné
    decorationColor: Colors.purple,
  ),
  maxLines: 2,                       // Limiter le nombre de lignes
  overflow: TextOverflow.ellipsis,   // "..." si texte trop long
  textAlign: TextAlign.center,       // Alignement
)

// ─── Texte avec parties différentes ──────────────────────────
// RichText permet d'avoir plusieurs styles dans un même texte
RichText(
  text: const TextSpan(
    // Style par défaut pour tout le texte
    style: TextStyle(color: Colors.black, fontSize: 14),
    children: [
      TextSpan(text: 'Prix : '),
      TextSpan(
        text: '129,99 €',
        style: TextStyle(
          color: Color(0xFF6C63FF),
          fontWeight: FontWeight.bold,
          fontSize: 18,
        ),
      ),
      TextSpan(
        text: '  -20%',
        style: TextStyle(
          color: Colors.green,
          fontWeight: FontWeight.w600,
        ),
      ),
    ],
  ),
)

// ─── Texte dans un champ éditable ─────────────────────────────
// TextField pour les formulaires (login, recherche, etc.)
TextField(
  decoration: InputDecoration(
    labelText: 'Email',
    hintText: 'votre@email.com',
    prefixIcon: Icon(Icons.email),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
    filled: true,
    fillColor: Colors.grey.shade50,
  ),
  keyboardType: TextInputType.emailAddress,
  onChanged: (valeur) => print('Saisi : $valeur'),
)
```

### 1.3 Widgets de Conteneur & Décoration

```dart
// ═══════════════════════════════════════════════════
//  CONTAINER — Le couteau suisse des conteneurs
// ═══════════════════════════════════════════════════

// Container est le widget le plus polyvalent pour la mise en page
Container(
  // ── Dimensions ─────────────────────────────
  width: 200,                    // Largeur fixe
  height: 100,                   // Hauteur fixe
  // OU
  width: double.infinity,        // Prend toute la largeur disponible
  
  // ── Espacement externe (margin) ─────────────
  // margin = espace ENTRE ce widget et les autres
  margin: const EdgeInsets.all(16),          // 16px de tous côtés
  margin: const EdgeInsets.symmetric(
    horizontal: 16, vertical: 8,             // Horizontal ET vertical différents
  ),
  margin: const EdgeInsets.only(
    top: 8, left: 16, right: 16,            // Côtés spécifiques
  ),
  
  // ── Espacement interne (padding) ────────────
  // padding = espace ENTRE la bordure et le contenu
  padding: const EdgeInsets.all(24),
  
  // ── Décoration visuelle ─────────────────────
  decoration: BoxDecoration(
    // Couleur de fond
    color: Colors.white,
    
    // OU dégradé (ne pas combiner avec color !)
    gradient: const LinearGradient(
      colors: [Color(0xFF6C63FF), Color(0xFF9C8DFF)],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
    
    // Coins arrondis
    borderRadius: BorderRadius.circular(16),
    // OU coins différents :
    borderRadius: const BorderRadius.only(
      topLeft: Radius.circular(20),
      topRight: Radius.circular(20),
    ),
    
    // Bordure
    border: Border.all(color: Colors.grey.shade200, width: 1),
    
    // Ombre portée
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 10,      // Flou de l'ombre
        offset: const Offset(0, 4),  // Décalage (x, y)
        spreadRadius: 2,     // Expansion de l'ombre
      ),
    ],
    
    // Image de fond
    image: const DecorationImage(
      image: NetworkImage('https://example.com/bg.jpg'),
      fit: BoxFit.cover,     // Couvre tout le container
    ),
  ),
  
  // ── Alignement du contenu ───────────────────
  alignment: Alignment.center,
  
  child: const Text('Contenu du container'),
)
```

### 1.4 Widgets d'Image

```dart
// ═══════════════════════════════════════════════════
//  IMAGES — Afficher des images
// ═══════════════════════════════════════════════════

// Image depuis Internet
Image.network(
  'https://example.com/produit.jpg',
  width: 200,
  height: 200,
  fit: BoxFit.cover,        // cover, contain, fill, fitWidth, fitHeight
  loadingBuilder: (context, child, progress) {
    if (progress == null) return child;  // Image chargée !
    return const Center(child: CircularProgressIndicator());
  },
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.broken_image, size: 60, color: Colors.grey);
  },
)

// Image locale (assets)
Image.asset(
  'assets/images/logo.png',
  width: 120,
)

// ─── Avec CachedNetworkImage (recommandé pour la perf) ────────
// Nécessite le package : cached_network_image

import 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: produit.imageUrl,
  width: double.infinity,
  fit: BoxFit.cover,
  placeholder: (context, url) => Shimmer.fromColors(
    baseColor: Colors.grey.shade300,
    highlightColor: Colors.grey.shade100,
    child: Container(color: Colors.white),
  ),
  errorWidget: (context, url, error) => const Icon(Icons.error),
)

// ─── Image circulaire (pour photo profil) ─────────────────────
ClipRRect(
  borderRadius: BorderRadius.circular(50),  // Parfaitement rond
  child: CachedNetworkImage(
    imageUrl: utilisateur.photoUrl,
    width: 80,
    height: 80,
    fit: BoxFit.cover,
  ),
)

// OU plus simplement avec CircleAvatar
CircleAvatar(
  radius: 40,
  backgroundImage: NetworkImage(utilisateur.photoUrl),
  backgroundColor: Colors.grey.shade200,
  child: utilisateur.photoUrl.isEmpty
    ? Text(utilisateur.prenom[0])  // Initiale si pas de photo
    : null,
)
```

---

## PARTIE 2 — Layouts & Positionnement

### 2.1 Column & Row — Les Piliers du Layout

```dart
// ═══════════════════════════════════════════════════
//  COLUMN — Empiler des widgets VERTICALEMENT
// ═══════════════════════════════════════════════════

Column(
  // MainAxisAlignment : axe principal (vertical pour Column)
  mainAxisAlignment: MainAxisAlignment.start,   // Début (haut)
  // MainAxisAlignment.center                    // Centré
  // MainAxisAlignment.end                       // Fin (bas)
  // MainAxisAlignment.spaceBetween              // Espace entre
  // MainAxisAlignment.spaceAround               // Espace autour
  // MainAxisAlignment.spaceEvenly               // Espace égal

  // CrossAxisAlignment : axe secondaire (horizontal pour Column)
  crossAxisAlignment: CrossAxisAlignment.start,  // Aligné à gauche
  // CrossAxisAlignment.center                    // Centré
  // CrossAxisAlignment.end                       // Aligné à droite
  // CrossAxisAlignment.stretch                   // Étire en largeur

  // MainAxisSize : taille sur l'axe principal
  mainAxisSize: MainAxisSize.max,    // Prend toute la hauteur
  // MainAxisSize.min                // Prend le minimum nécessaire

  children: [
    Text('Titre'),
    SizedBox(height: 8),   // Espace entre éléments
    Text('Sous-titre'),
    SizedBox(height: 16),
    ElevatedButton(onPressed: () {}, child: Text('Bouton')),
  ],
)

// ═══════════════════════════════════════════════════
//  ROW — Aligner des widgets HORIZONTALEMENT
// ═══════════════════════════════════════════════════

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,  // Prix à gauche, bouton à droite
  crossAxisAlignment: CrossAxisAlignment.center,

  children: [
    // ─── Expanded : prend l'espace restant ──────
    Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Nike Air Max', style: TextStyle(fontWeight: FontWeight.bold)),
          Text('Chaussures', style: TextStyle(color: Colors.grey)),
        ],
      ),
    ),
    
    // Prix (taille fixe implicite)
    Text('129,99 €', style: TextStyle(
      color: Color(0xFF6C63FF),
      fontWeight: FontWeight.bold,
      fontSize: 16,
    )),
    
    SizedBox(width: 8),
    
    // Bouton d'ajout au panier
    CircleButton(onPressed: () {}),
  ],
)

// ─── Flexible vs Expanded ─────────────────────────────────────
// Expanded : prend TOUT l'espace restant (flex: 1 par défaut)
// Flexible : prend AU MAX l'espace défini (peut être plus petit)

Row(
  children: [
    Expanded(flex: 2, child: Text('Nom du produit long...')),  // 2/3 de l'espace
    Expanded(flex: 1, child: Text('Prix')),                    // 1/3 de l'espace
  ],
)
```

### 2.2 Stack — Superposer des Widgets

```dart
// ═══════════════════════════════════════════════════
//  STACK — Superposer des widgets (comme des calques Photoshop)
// ═══════════════════════════════════════════════════

// Cas d'usage : badge sur une icône, texte sur une image, bouton flottant

Stack(
  children: [
    // ─── Couche du bas : l'image produit ───────
    ClipRRect(
      borderRadius: BorderRadius.circular(12),
      child: Image.network(
        produit.imageUrl,
        width: double.infinity,
        height: 200,
        fit: BoxFit.cover,
      ),
    ),
    
    // ─── Couche du milieu : dégradé sombre ─────
    Positioned.fill(
      child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12),
          gradient: LinearGradient(
            colors: [Colors.transparent, Colors.black.withOpacity(0.6)],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
          ),
        ),
      ),
    ),
    
    // ─── Couche supérieure : badge promo ───────
    Positioned(
      top: 8,       // Distance du bord supérieur
      left: 8,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        decoration: BoxDecoration(
          color: Colors.red,
          borderRadius: BorderRadius.circular(6),
        ),
        child: const Text(
          '-20%',
          style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
        ),
      ),
    ),
    
    // ─── Texte en bas de l'image ────────────────
    Positioned(
      bottom: 12,
      left: 12,
      right: 12,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            produit.nom,
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 16,
            ),
          ),
          Text(
            '${produit.prix} €',
            style: const TextStyle(color: Colors.white70),
          ),
        ],
      ),
    ),
    
    // ─── Bouton favori en haut à droite ─────────
    Positioned(
      top: 8,
      right: 8,
      child: CircleAvatar(
        backgroundColor: Colors.white,
        radius: 18,
        child: IconButton(
          padding: EdgeInsets.zero,
          icon: const Icon(Icons.favorite_border, size: 18),
          onPressed: () {},
        ),
      ),
    ),
  ],
)
```

### 2.3 Listes & Grilles Performantes

```dart
// ═══════════════════════════════════════════════════
//  LISTVIEW.BUILDER — Liste EFFICACE pour données dynamiques
// ═══════════════════════════════════════════════════

// [ATTENTION] NE JAMAIS utiliser ListView avec des children pour de longues listes
// [OK] TOUJOURS utiliser ListView.builder pour les données depuis Firebase

ListView.builder(
  // ── Comportement ────────────────────────────
  physics: const BouncingScrollPhysics(),    // Effet élastique iOS
  // physics: const ClampingScrollPhysics(),  // Comportement Android
  
  padding: const EdgeInsets.all(16),
  
  // ── Données ─────────────────────────────────
  itemCount: produits.length,  // Nombre total d'éléments
  
  // ── Construction de chaque item ─────────────
  itemBuilder: (BuildContext context, int index) {
    final produit = produits[index];  // Récupère le produit à cet index
    
    return ProductListTile(
      produit: produit,
      onTap: () => Navigator.push(
        context,
        MaterialPageRoute(
          builder: (_) => ProductDetailScreen(produitId: produit.id),
        ),
      ),
    );
  },
)

// ─── ListView avec sections ────────────────────────────────────
// Afficher des catégories comme séparateurs entre produits

ListView.builder(
  itemCount: produits.length + categories.length,  // Total des items + headers
  itemBuilder: (context, index) {
    // Logique pour déterminer si c'est un header ou un produit
    // (voir implémentation complète dans le projet)
    return const SizedBox();  // Placeholder
  },
)

// ═══════════════════════════════════════════════════
//  GRIDVIEW.BUILDER — Grille performante
// ═══════════════════════════════════════════════════

GridView.builder(
  padding: const EdgeInsets.all(16),
  
  // ── Configuration de la grille ───────────────
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,        // 2 colonnes
    childAspectRatio: 0.7,    // Hauteur = Largeur / 0.7
    crossAxisSpacing: 12,     // Espace horizontal entre colonnes
    mainAxisSpacing: 12,      // Espace vertical entre lignes
  ),
  
  // OU grille avec largeur de colonne fixe (nombre de colonnes auto)
  // gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
  //   maxCrossAxisExtent: 200,  // Largeur max d'une colonne
  //   childAspectRatio: 0.7,
  //   crossAxisSpacing: 12,
  //   mainAxisSpacing: 12,
  // ),
  
  itemCount: produits.length,
  itemBuilder: (context, index) {
    return ProductCard(produit: produits[index]);
  },
)
```

---

## PARTIE 3 — Widgets Spécifiques à ShopFlutter

### 3.1 La Carte Produit (ProductCard)

```dart
// lib/widgets/product/product_card.dart

import 'package:flutter/material.dart';
import '../../models/product.dart';

class ProductCard extends StatelessWidget {
  final Produit produit;
  final VoidCallback? onTap;
  final VoidCallback? onFavoriTap;

  const ProductCard({
    super.key,
    required this.produit,
    this.onTap,
    this.onFavoriTap,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.06),
              blurRadius: 10,
              offset: const Offset(0, 4),
            ),
          ],
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // ─── Image + badge + favori ──────────
            _buildImageSection(),
            
            // ─── Infos produit ───────────────────
            _buildInfoSection(context),
          ],
        ),
      ),
    );
  }

  Widget _buildImageSection() {
    return Expanded(
      child: Stack(
        children: [
          // Image du produit
          ClipRRect(
            borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
            child: Container(
              width: double.infinity,
              color: Colors.grey.shade100,
              child: produit.imageUrl.isNotEmpty
                ? Image.network(
                    produit.imageUrl,
                    fit: BoxFit.cover,
                    width: double.infinity,
                  )
                : Icon(
                    Icons.shopping_bag,
                    size: 60,
                    color: Colors.grey.shade400,
                  ),
            ),
          ),
          
          // Badge "Nouveau" ou "Promo"
          if (!produit.estDisponible)
            Positioned(
              top: 8,
              left: 8,
              child: _buildBadge('Rupture', Colors.red.shade700),
            ),
          
          // Bouton favori
          Positioned(
            top: 4,
            right: 4,
            child: _buildBoutonFavori(),
          ),
        ],
      ),
    );
  }

  Widget _buildBadge(String texte, Color couleur) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: couleur,
        borderRadius: BorderRadius.circular(6),
      ),
      child: Text(
        texte,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 10,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }

  Widget _buildBoutonFavori() {
    return Material(
      color: Colors.white,
      borderRadius: BorderRadius.circular(20),
      child: InkWell(
        borderRadius: BorderRadius.circular(20),
        onTap: onFavoriTap,
        child: Padding(
          padding: const EdgeInsets.all(6),
          child: Icon(
            produit.estEnFavori ? Icons.favorite : Icons.favorite_border,
            color: produit.estEnFavori ? Colors.red : Colors.grey,
            size: 20,
          ),
        ),
      ),
    );
  }

  Widget _buildInfoSection(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Nom du produit
          Text(
            produit.nom,
            style: const TextStyle(
              fontWeight: FontWeight.w600,
              fontSize: 13,
            ),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          
          const SizedBox(height: 4),
          
          // Catégorie
          Text(
            produit.categorie,
            style: TextStyle(
              color: Colors.grey.shade500,
              fontSize: 11,
            ),
          ),
          
          const SizedBox(height: 8),
          
          // Prix + Bouton ajout panier
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                '${produit.prix.toStringAsFixed(2)} €',
                style: TextStyle(
                  color: Theme.of(context).colorScheme.primary,
                  fontWeight: FontWeight.bold,
                  fontSize: 15,
                ),
              ),
              
              // Mini bouton "+"
              GestureDetector(
                onTap: () {}, // Ajouter au panier
                child: Container(
                  width: 28,
                  height: 28,
                  decoration: BoxDecoration(
                    color: Theme.of(context).colorScheme.primary,
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Icon(Icons.add, color: Colors.white, size: 18),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}
```

### 3.2 L'Écran de Détail Produit

```dart
// lib/screens/products/product_detail_screen.dart

import 'package:flutter/material.dart';

class ProductDetailScreen extends StatefulWidget {
  final String produitId;

  const ProductDetailScreen({super.key, required this.produitId});

  @override
  State<ProductDetailScreen> createState() => _ProductDetailScreenState();
}

class _ProductDetailScreenState extends State<ProductDetailScreen> {
  int _quantite = 1;
  String _tailleSelectionnee = 'M';
  int _imageSelectionnee = 0;
  
  final List<String> _tailles = ['XS', 'S', 'M', 'L', 'XL', 'XXL'];
  final List<String> _images = [
    'https://via.placeholder.com/400',
    'https://via.placeholder.com/400/FF0000',
    'https://via.placeholder.com/400/0000FF',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ── AppBar personnalisée ────────────────────────
      appBar: AppBar(
        // Bouton retour personnalisé
        leading: IconButton(
          icon: Container(
            padding: const EdgeInsets.all(6),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(10),
              boxShadow: [
                BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 4)
              ],
            ),
            child: const Icon(Icons.arrow_back_ios_new, size: 16),
          ),
          onPressed: () => Navigator.pop(context),
        ),
        backgroundColor: Colors.transparent,
        elevation: 0,
        actions: [
          // Bouton partage
          IconButton(
            icon: const Icon(Icons.share_outlined),
            onPressed: () {},
          ),
        ],
      ),
      
      extendBodyBehindAppBar: true,  // Body passe derrière la AppBar

      // ── Corps de la page ───────────────────────────
      body: CustomScrollView(
        // CustomScrollView permet de mélanger Slivers (zones scrollables)
        slivers: [
          // ─── Zone image (en haut) ───────────
          SliverToBoxAdapter(
            child: _buildGalerieImages(),
          ),
          
          // ─── Informations produit ────────────
          SliverToBoxAdapter(
            child: _buildInfosProduit(),
          ),
        ],
      ),

      // ── Barre d'action du bas ──────────────────────
      bottomNavigationBar: _buildBarreAction(),
    );
  }

  // ─── Galerie d'images ─────────────────────────────────────────
  Widget _buildGalerieImages() {
    return SizedBox(
      height: 350,
      child: Stack(
        children: [
          // Image principale
          PageView.builder(
            itemCount: _images.length,
            onPageChanged: (index) => setState(() => _imageSelectionnee = index),
            itemBuilder: (context, index) {
              return Image.network(
                _images[index],
                fit: BoxFit.cover,
                width: double.infinity,
              );
            },
          ),
          
          // Indicateurs de page (points en bas de l'image)
          Positioned(
            bottom: 16,
            left: 0,
            right: 0,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(
                _images.length,
                (index) => AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  margin: const EdgeInsets.symmetric(horizontal: 3),
                  width: _imageSelectionnee == index ? 20 : 8,
                  height: 8,
                  decoration: BoxDecoration(
                    color: _imageSelectionnee == index
                        ? Colors.white
                        : Colors.white54,
                    borderRadius: BorderRadius.circular(4),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  // ─── Informations produit ──────────────────────────────────────
  Widget _buildInfosProduit() {
    return Container(
      decoration: const BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
      ),
      padding: const EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Catégorie + Favori
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
                decoration: BoxDecoration(
                  color: const Color(0xFF6C63FF).withOpacity(0.1),
                  borderRadius: BorderRadius.circular(20),
                ),
                child: const Text(
                  'Chaussures',
                  style: TextStyle(color: Color(0xFF6C63FF), fontSize: 12),
                ),
              ),
              const Icon(Icons.favorite_border, color: Colors.grey),
            ],
          ),
          
          const SizedBox(height: 12),
          
          // Nom du produit
          const Text(
            'Nike Air Max 270',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          
          const SizedBox(height: 8),
          
          // Note et avis
          Row(
            children: [
              const Icon(Icons.star, color: Colors.amber, size: 18),
              const SizedBox(width: 4),
              const Text('4.8', style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(width: 4),
              Text('(128 avis)', style: TextStyle(color: Colors.grey.shade500)),
            ],
          ),
          
          const SizedBox(height: 20),
          
          // Sélection de taille
          const Text(
            'Taille',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          _buildSelecteurTaille(),
          
          const SizedBox(height: 20),
          
          // Sélecteur de quantité
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              const Text(
                'Quantité',
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
              ),
              _buildSelecteurQuantite(),
            ],
          ),
          
          const SizedBox(height: 20),
          
          // Description
          const Text(
            'Description',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          Text(
            'La Nike Air Max 270 est dotée d\'une semelle Air redessinée pour une absorption des chocs maximale. Idéale pour une utilisation quotidienne confortable.',
            style: TextStyle(
              color: Colors.grey.shade600,
              height: 1.6,
            ),
          ),
          
          // Espace pour la barre du bas
          const SizedBox(height: 80),
        ],
      ),
    );
  }

  // ─── Sélecteur de taille ──────────────────────────────────────
  Widget _buildSelecteurTaille() {
    return Wrap(
      spacing: 10,
      children: _tailles.map((taille) {
        final estSelectionnee = taille == _tailleSelectionnee;
        return GestureDetector(
          onTap: () => setState(() => _tailleSelectionnee = taille),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 200),
            width: 48,
            height: 48,
            decoration: BoxDecoration(
              color: estSelectionnee ? const Color(0xFF6C63FF) : Colors.transparent,
              border: Border.all(
                color: estSelectionnee ? const Color(0xFF6C63FF) : Colors.grey.shade300,
              ),
              borderRadius: BorderRadius.circular(10),
            ),
            child: Center(
              child: Text(
                taille,
                style: TextStyle(
                  color: estSelectionnee ? Colors.white : Colors.black,
                  fontWeight: FontWeight.w600,
                  fontSize: 13,
                ),
              ),
            ),
          ),
        );
      }).toList(),
    );
  }

  // ─── Sélecteur de quantité ────────────────────────────────────
  Widget _buildSelecteurQuantite() {
    return Row(
      children: [
        // Bouton -
        _buildBoutonQuantite(
          icone: Icons.remove,
          onTap: () {
            if (_quantite > 1) setState(() => _quantite--);
          },
        ),
        
        // Affichage quantité
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Text(
            '$_quantite',
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        
        // Bouton +
        _buildBoutonQuantite(
          icone: Icons.add,
          onTap: () {
            if (_quantite < 10) setState(() => _quantite++);
          },
          estPrimaire: true,
        ),
      ],
    );
  }

  Widget _buildBoutonQuantite({
    required IconData icone,
    required VoidCallback onTap,
    bool estPrimaire = false,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: 36,
        height: 36,
        decoration: BoxDecoration(
          color: estPrimaire ? const Color(0xFF6C63FF) : Colors.grey.shade100,
          borderRadius: BorderRadius.circular(10),
        ),
        child: Icon(
          icone,
          color: estPrimaire ? Colors.white : Colors.black,
          size: 18,
        ),
      ),
    );
  }

  // ─── Barre d'action (Ajouter au panier) ──────────────────────
  Widget _buildBarreAction() {
    return Container(
      padding: const EdgeInsets.fromLTRB(20, 12, 20, 30),
      decoration: BoxDecoration(
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 10,
            offset: const Offset(0, -4),
          ),
        ],
      ),
      child: Row(
        children: [
          // Prix
          Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text('Prix total', style: TextStyle(color: Colors.grey.shade500, fontSize: 12)),
              Text(
                '${(129.99 * _quantite).toStringAsFixed(2)} €',
                style: const TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF6C63FF),
                ),
              ),
            ],
          ),
          
          const SizedBox(width: 20),
          
          // Bouton "Ajouter au panier"
          Expanded(
            child: ElevatedButton.icon(
              onPressed: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('Nike Air Max 270 ajouté au panier !'),
                    backgroundColor: const Color(0xFF00B894),
                    behavior: SnackBarBehavior.floating,
                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
                  ),
                );
              },
              icon: const Icon(Icons.shopping_cart_outlined),
              label: const Text('Ajouter au panier'),
              style: ElevatedButton.styleFrom(
                backgroundColor: const Color(0xFF6C63FF),
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(vertical: 14),
                shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
```

---

## PARTIE 4 — Thème Global de l'Application

### 4.1 Configuration du Thème Material 3

```dart
// lib/core/theme/app_theme.dart

import 'package:flutter/material.dart';

class AppTheme {
  // ── Couleurs principales ──────────────────────────────────────
  static const Color primary = Color(0xFF6C63FF);
  static const Color secondary = Color(0xFFFF6584);
  static const Color success = Color(0xFF00B894);
  static const Color error = Color(0xFFFF7675);
  static const Color warning = Color(0xFFFF9F43);
  static const Color background = Color(0xFFF8F9FA);
  static const Color surface = Colors.white;
  static const Color textPrimary = Color(0xFF2D3436);
  static const Color textSecondary = Color(0xFF636E72);

  // ── Thème clair ───────────────────────────────────────────────
  static ThemeData get lightTheme {
    return ThemeData(
      useMaterial3: true,
      
      // ─── Schéma de couleurs ──────────────────
      colorScheme: const ColorScheme.light(
        primary: primary,
        secondary: secondary,
        error: error,
        background: background,
        surface: surface,
        onPrimary: Colors.white,
        onSecondary: Colors.white,
        onBackground: textPrimary,
        onSurface: textPrimary,
      ),
      
      // ─── Typographie ─────────────────────────
      textTheme: const TextTheme(
        // Titres principaux (ex: "ShopFlutter" dans l'AppBar)
        headlineLarge: TextStyle(
          fontSize: 32, fontWeight: FontWeight.bold, color: textPrimary,
        ),
        headlineMedium: TextStyle(
          fontSize: 24, fontWeight: FontWeight.bold, color: textPrimary,
        ),
        headlineSmall: TextStyle(
          fontSize: 20, fontWeight: FontWeight.bold, color: textPrimary,
        ),
        
        // Titres de sections
        titleLarge: TextStyle(
          fontSize: 18, fontWeight: FontWeight.w600, color: textPrimary,
        ),
        titleMedium: TextStyle(
          fontSize: 16, fontWeight: FontWeight.w500, color: textPrimary,
        ),
        
        // Corps du texte
        bodyLarge: TextStyle(fontSize: 16, color: textPrimary, height: 1.5),
        bodyMedium: TextStyle(fontSize: 14, color: textPrimary, height: 1.5),
        bodySmall: TextStyle(fontSize: 12, color: textSecondary),
        
        // Labels (boutons, badges)
        labelLarge: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
      ),
      
      // ─── AppBar ──────────────────────────────
      appBarTheme: const AppBarTheme(
        backgroundColor: surface,
        foregroundColor: textPrimary,
        elevation: 0,
        centerTitle: false,
        titleTextStyle: TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.bold,
          color: textPrimary,
        ),
      ),
      
      // ─── Boutons ─────────────────────────────
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          backgroundColor: primary,
          foregroundColor: Colors.white,
          minimumSize: const Size(double.infinity, 52),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
          textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
          elevation: 0,
        ),
      ),
      
      outlinedButtonTheme: OutlinedButtonThemeData(
        style: OutlinedButton.styleFrom(
          foregroundColor: primary,
          minimumSize: const Size(double.infinity, 52),
          side: const BorderSide(color: primary, width: 1.5),
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
          textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
        ),
      ),
      
      // ─── Champs de texte (TextField) ─────────
      inputDecorationTheme: InputDecorationTheme(
        filled: true,
        fillColor: Colors.grey.shade50,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: BorderSide(color: Colors.grey.shade200),
        ),
        enabledBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: BorderSide(color: Colors.grey.shade200),
        ),
        focusedBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: const BorderSide(color: primary, width: 1.5),
        ),
        errorBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(14),
          borderSide: const BorderSide(color: error),
        ),
        contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
        hintStyle: TextStyle(color: Colors.grey.shade400),
      ),
      
      // ─── Cards ───────────────────────────────
      cardTheme: CardTheme(
        elevation: 0,
        color: surface,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        margin: EdgeInsets.zero,
      ),
      
      // ─── NavigationBar (barre du bas) ────────
      navigationBarTheme: NavigationBarThemeData(
        backgroundColor: surface,
        indicatorColor: primary.withOpacity(0.1),
        labelTextStyle: MaterialStateProperty.all(
          const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
        ),
      ),
      
      // ─── SnackBar ────────────────────────────
      snackBarTheme: SnackBarThemeData(
        behavior: SnackBarBehavior.floating,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      ),
    );
  }
}
```

---

## PARTIE 5 — Formulaires : L'Écran de Connexion

```dart
// lib/screens/auth/login_screen.dart

import 'package:flutter/material.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  // ─── Clé du formulaire (pour la validation) ───────────────────
  final _formKey = GlobalKey<FormState>();
  
  // ─── Contrôleurs de champs ────────────────────────────────────
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  
  // ─── État ────────────────────────────────────────────────────
  bool _passwordVisible = false;
  bool _isLoading = false;
  
  @override
  void dispose() {
    // IMPORTANT : toujours nettoyer les contrôleurs !
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  // ─── Validation et soumission ─────────────────────────────────
  Future<void> _connexion() async {
    // Valide tous les champs du formulaire
    if (!_formKey.currentState!.validate()) return;
    
    setState(() => _isLoading = true);
    
    try {
      // Dans le chapitre 4, ici on appellera Firebase Auth
      await Future.delayed(const Duration(seconds: 2)); // Simulation
      
      if (mounted) {
        Navigator.pushReplacementNamed(context, '/home');
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Erreur : ${e.toString()}')),
        );
      }
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(height: 40),
              
              // Logo
              const Center(
                child: Icon(Icons.shopping_bag, size: 70, color: Color(0xFF6C63FF)),
              ),
              
              const SizedBox(height: 32),
              
              // Titre
              const Text('Bon retour ! [WAVING_HAND_SIGN]',
                  style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
              const SizedBox(height: 8),
              Text('Connectez-vous pour continuer vos achats',
                  style: TextStyle(color: Colors.grey.shade600)),
              
              const SizedBox(height: 40),
              
              // ─── Formulaire ──────────────────────
              Form(
                key: _formKey,
                child: Column(
                  children: [
                    // Champ email
                    TextFormField(
                      controller: _emailController,
                      keyboardType: TextInputType.emailAddress,
                      decoration: const InputDecoration(
                        labelText: 'Email',
                        hintText: 'votre@email.com',
                        prefixIcon: Icon(Icons.email_outlined),
                      ),
                      validator: (valeur) {
                        if (valeur == null || valeur.isEmpty) {
                          return 'Veuillez entrer votre email';
                        }
                        if (!valeur.contains('@')) {
                          return 'Email invalide';
                        }
                        return null; // null = valide
                      },
                    ),
                    
                    const SizedBox(height: 16),
                    
                    // Champ mot de passe
                    TextFormField(
                      controller: _passwordController,
                      obscureText: !_passwordVisible,
                      decoration: InputDecoration(
                        labelText: 'Mot de passe',
                        hintText: '••••••••',
                        prefixIcon: const Icon(Icons.lock_outlined),
                        suffixIcon: IconButton(
                          icon: Icon(_passwordVisible
                              ? Icons.visibility_off
                              : Icons.visibility),
                          onPressed: () {
                            setState(() => _passwordVisible = !_passwordVisible);
                          },
                        ),
                      ),
                      validator: (valeur) {
                        if (valeur == null || valeur.isEmpty) {
                          return 'Veuillez entrer votre mot de passe';
                        }
                        if (valeur.length < 6) {
                          return 'Minimum 6 caractères';
                        }
                        return null;
                      },
                    ),
                    
                    const SizedBox(height: 12),
                    
                    // Lien "mot de passe oublié"
                    Align(
                      alignment: Alignment.centerRight,
                      child: TextButton(
                        onPressed: () {},
                        child: const Text('Mot de passe oublié ?'),
                      ),
                    ),
                    
                    const SizedBox(height: 24),
                    
                    // Bouton connexion
                    ElevatedButton(
                      onPressed: _isLoading ? null : _connexion,
                      child: _isLoading
                          ? const SizedBox(
                              height: 20,
                              width: 20,
                              child: CircularProgressIndicator(
                                color: Colors.white,
                                strokeWidth: 2,
                              ),
                            )
                          : const Text('Se connecter'),
                    ),
                    
                    const SizedBox(height: 16),
                    
                    // Lien "créer un compte"
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text('Pas encore de compte ?',
                            style: TextStyle(color: Colors.grey.shade600)),
                        TextButton(
                          onPressed: () => Navigator.pushNamed(context, '/register'),
                          child: const Text('S\'inscrire'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
```

---

## [NOTE] Exercices du Chapitre 2

### Exercice 2.1 — Widget personnalisé
Créez un widget `PriceTag` qui affiche un prix avec l'ancien prix barré et le nouveau prix réduit.

### Exercice 2.2 — Écran favoris
Créez l'écran `FavoritesScreen` affichant les produits favoris en liste avec image, nom et prix.

### Exercice 2.3 — Formulaire d'inscription
Créez `RegisterScreen` avec les champs : Prénom, Nom, Email, Mot de passe, Confirmation, et une case à cocher pour les CGU.

---

## [OK] Récapitulatif du Chapitre 2

| Concept | Maîtrisé ? |
|---------|-----------|
| Container & décoration | [ ] |
| Column, Row, Stack | [ ] |
| Expanded & Flexible | [ ] |
| ListView.builder | [ ] |
| GridView.builder | [ ] |
| Images réseau & cache | [ ] |
| GestureDetector | [ ] |
| Formulaires & validation | [ ] |
| Thème global AppTheme | [ ] |
| PageView & indicateurs | [ ] |

---

**-> Chapitre suivant : `03_NAVIGATION_ET_STATE.md`**

# [LIVRE] Chapitre 3 — Navigation & State Management
## Projet ShopFlutter | Guide Ultra-Détaillé

---

## [OBJECTIF] Objectifs de ce Chapitre

À la fin de ce chapitre, vous serez capable de :
- Naviguer entre tous les écrans de l'application
- Passer des données entre les écrans
- Comprendre le cycle de vie d'un widget
- Maîtriser le state management avec **Provider**
- Implémenter le panier et les favoris avec un état global
- Utiliser **go_router** pour une navigation professionnelle

**[ALARM_CLOCK] Durée estimée :** 6-8 jours (2-3h/jour)

---

## PARTIE 1 — Navigation Fondamentale

### 1.1 Le Navigator : Comprendre la Pile d'Écrans

Flutter gère la navigation comme une **pile (stack)** d'écrans. Imaginez une pile de cartes :
- `push` = poser une carte sur la pile (aller vers un écran)
- `pop` = retirer la carte du dessus (revenir en arrière)

```
État initial :          Après push :           Après pop :
┌───────────┐          ┌───────────┐          ┌───────────┐
│           │          │  Detail   │          │           │
│   Home    │  push ->  │  Screen   │   pop ->  │   Home    │
│  Screen   │          ├───────────┤          │  Screen   │
│           │          │   Home    │          │           │
└───────────┘          │  Screen   │          └───────────┘
                       └───────────┘
```

### 1.2 Navigator Basique

```dart
// ═══════════════════════════════════════════════════
//  NAVIGATION AVEC NAVIGATOR (méthode classique)
// ═══════════════════════════════════════════════════

// ─── push : aller vers un écran ───────────────────────────────
// MaterialPageRoute crée une transition animée standard

// Méthode 1 : Navigation anonyme (simple)
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProductDetailScreen(produitId: 'prod_001'),
  ),
);

// Méthode 2 : Navigation nommée (avec routes définies dans MaterialApp)
Navigator.pushNamed(context, '/product-detail', arguments: 'prod_001');

// ─── pop : revenir en arrière ──────────────────────────────────
Navigator.pop(context);

// pop AVEC un résultat (retourner une valeur à l'écran précédent)
Navigator.pop(context, 'produit_ajoute');

// ─── pushReplacement : remplacer l'écran actuel ────────────────
// Utile pour : après login -> Home (pas de retour possible)
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => const HomeScreen()),
);

// ─── pushNamedAndRemoveUntil : vider la pile ───────────────────
// Utile pour : déconnexion -> vider toute l'historique
Navigator.pushNamedAndRemoveUntil(
  context,
  '/login',              // Destination
  (route) => false,      // Supprimer TOUTES les routes précédentes
);

// ─── Récupérer le résultat d'un écran ─────────────────────────
// "await" attend que l'utilisateur revienne de l'écran de détail
Future<void> _allerVersDetail(String produitId) async {
  final resultat = await Navigator.push<String>(
    context,
    MaterialPageRoute(
      builder: (context) => ProductDetailScreen(produitId: produitId),
    ),
  );
  
  if (resultat == 'produit_ajoute') {
    // L'utilisateur a ajouté le produit au panier !
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('Article ajouté au panier !')),
    );
  }
}
```

### 1.3 Configuration des Routes Nommées

```dart
// lib/main.dart — Configuration avec routes nommées

import 'package:flutter/material.dart';
import 'screens/auth/login_screen.dart';
import 'screens/home/home_screen.dart';
import 'screens/products/product_detail_screen.dart';

void main() {
  runApp(const ShopFlutterApp());
}

class ShopFlutterApp extends StatelessWidget {
  const ShopFlutterApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ShopFlutter',
      debugShowCheckedModeBanner: false,
      
      // ─── Écran initial ────────────────────────
      initialRoute: '/login',
      
      // ─── Définition de toutes les routes ──────
      routes: {
        '/login':          (ctx) => const LoginScreen(),
        '/register':       (ctx) => const RegisterScreen(),
        '/home':           (ctx) => const HomeScreen(),
        '/cart':           (ctx) => const CartScreen(),
        '/favorites':      (ctx) => const FavoritesScreen(),
        '/profile':        (ctx) => const ProfileScreen(),
      },
      
      // ─── Route dynamique (avec paramètres) ────
      onGenerateRoute: (settings) {
        // Gère les routes avec des paramètres dynamiques
        if (settings.name == '/product-detail') {
          final produitId = settings.arguments as String;
          return MaterialPageRoute(
            builder: (ctx) => ProductDetailScreen(produitId: produitId),
            settings: settings,
          );
        }
        
        if (settings.name == '/category') {
          final categorie = settings.arguments as String;
          return MaterialPageRoute(
            builder: (ctx) => CategoryScreen(categorie: categorie),
            settings: settings,
          );
        }
        
        // Route inconnue -> page 404
        return MaterialPageRoute(
          builder: (ctx) => const NotFoundScreen(),
        );
      },
    );
  }
}
```

---

## PARTIE 2 — Navigation Avancée avec go_router

### 2.1 Pourquoi go_router ?

`go_router` est le package de navigation officiel recommandé par Flutter pour les applications de production. Il offre :
- Navigation par URL (comme un site web)
- Navigation imbriquée (onglets + sous-pages)
- Redirections automatiques (si non connecté -> page login)
- Deep links (ouvrir l'app directement sur un produit)

```dart
// lib/core/constants/app_routes.dart

class AppRoutes {
  static const String login = '/login';
  static const String register = '/register';
  static const String home = '/home';
  static const String products = '/products';
  static const String productDetail = '/products/:id';
  static const String cart = '/cart';
  static const String favorites = '/favorites';
  static const String profile = '/profile';
  static const String checkout = '/checkout';
}
```

```dart
// lib/app.dart — Configuration go_router

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

// ─── Configuration du routeur ─────────────────────────────────────
final GoRouter _router = GoRouter(
  initialLocation: '/login',
  
  // ─── Redirection globale (garde de navigation) ─────────────────
  redirect: (context, state) {
    // Vérifie si l'utilisateur est connecté
    final estConnecte = false; // En chapitre 4 : utilise Firebase Auth
    
    final enCoursDeConnexion = state.matchedLocation == '/login' ||
                               state.matchedLocation == '/register';
    
    // Si pas connecté et pas sur login/register -> rediriger vers login
    if (!estConnecte && !enCoursDeConnexion) {
      return '/login';
    }
    
    // Si connecté et sur login -> rediriger vers home
    if (estConnecte && enCoursDeConnexion) {
      return '/home';
    }
    
    return null; // Pas de redirection nécessaire
  },
  
  // ─── Définition des routes ─────────────────────────────────────
  routes: [
    // Route Login
    GoRoute(
      path: '/login',
      name: 'login',
      builder: (context, state) => const LoginScreen(),
    ),
    
    // Route Register
    GoRoute(
      path: '/register',
      name: 'register',
      builder: (context, state) => const RegisterScreen(),
    ),
    
    // Shell Route : écran avec barre de navigation du bas
    ShellRoute(
      builder: (context, state, child) {
        return MainScaffold(child: child);  // Le scaffold avec la BottomNavBar
      },
      routes: [
        // Home
        GoRoute(
          path: '/home',
          name: 'home',
          builder: (context, state) => const HomeScreen(),
          
          // Sous-routes de Home
          routes: [
            GoRoute(
              path: 'products/:id',  // /home/products/prod_001
              name: 'product-detail',
              builder: (context, state) {
                final id = state.pathParameters['id']!;  // Récupère l'ID de l'URL
                return ProductDetailScreen(produitId: id);
              },
            ),
          ],
        ),
        
        // Favorites
        GoRoute(
          path: '/favorites',
          name: 'favorites',
          builder: (context, state) => const FavoritesScreen(),
        ),
        
        // Cart
        GoRoute(
          path: '/cart',
          name: 'cart',
          builder: (context, state) => const CartScreen(),
          routes: [
            GoRoute(
              path: 'checkout',
              name: 'checkout',
              builder: (context, state) => const CheckoutScreen(),
            ),
          ],
        ),
        
        // Profile
        GoRoute(
          path: '/profile',
          name: 'profile',
          builder: (context, state) => const ProfileScreen(),
        ),
      ],
    ),
  ],
);

// ─── App principale avec go_router ────────────────────────────────
class ShopFlutterApp extends StatelessWidget {
  const ShopFlutterApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(  // Note : .router au lieu de MaterialApp
      title: 'ShopFlutter',
      routerConfig: _router,    // Utilise go_router
      theme: AppTheme.lightTheme,
    );
  }
}
```

### 2.2 Utilisation de go_router dans les Widgets

```dart
// Navigation avec go_router (syntaxe simplifiée)

// ─── Aller vers une route ──────────────────────────────────────
// go() remplace l'écran actuel dans la pile de navigation
context.go('/home');

// push() empile un nouvel écran (garde l'historique)
context.push('/cart');

// pushNamed() avec le nom de la route
context.pushNamed('product-detail', pathParameters: {'id': produit.id});

// ─── Revenir en arrière ────────────────────────────────────────
context.pop();

// ─── Avec des paramètres ───────────────────────────────────────
context.push('/home/products/${produit.id}');
// OU
context.pushNamed(
  'product-detail',
  pathParameters: {'id': produit.id},
  extra: produit,  // Passer un objet complet (non visible dans l'URL)
);

// ─── Récupérer les paramètres ──────────────────────────────────
// Dans le builder du GoRoute :
final id = state.pathParameters['id']!;
final produit = state.extra as Produit?;
```

### 2.3 Le MainScaffold avec BottomNavigationBar

```dart
// lib/widgets/common/main_scaffold.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class MainScaffold extends StatelessWidget {
  final Widget child;
  
  const MainScaffold({super.key, required this.child});

  // Calcule l'index actif selon la route courante
  int _calculerIndexActif(BuildContext context) {
    final location = GoRouterState.of(context).uri.path;
    
    if (location.startsWith('/home')) return 0;
    if (location.startsWith('/favorites')) return 1;
    if (location.startsWith('/cart')) return 2;
    if (location.startsWith('/profile')) return 3;
    
    return 0;
  }

  @override
  Widget build(BuildContext context) {
    final indexActif = _calculerIndexActif(context);
    
    return Scaffold(
      // Le child est la page courante gérée par go_router
      body: child,
      
      bottomNavigationBar: NavigationBar(
        selectedIndex: indexActif,
        
        onDestinationSelected: (index) {
          // Navigation selon l'onglet sélectionné
          switch (index) {
            case 0:
              context.go('/home');
            case 1:
              context.go('/favorites');
            case 2:
              context.go('/cart');
            case 3:
              context.go('/profile');
          }
        },
        
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.home_outlined),
            selectedIcon: Icon(Icons.home),
            label: 'Accueil',
          ),
          NavigationDestination(
            icon: Icon(Icons.favorite_outline),
            selectedIcon: Icon(Icons.favorite),
            label: 'Favoris',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_cart_outlined),
            selectedIcon: Icon(Icons.shopping_cart),
            label: 'Panier',
          ),
          NavigationDestination(
            icon: Icon(Icons.person_outline),
            selectedIcon: Icon(Icons.person),
            label: 'Profil',
          ),
        ],
      ),
    );
  }
}
```

---

## PARTIE 3 — State Management avec Provider

### 3.1 Pourquoi le State Management ?

Sans state management, le problème est le suivant :

```
PROBLÈME sans state management :

HomeScreen -> ProductCard -> BoutonFavori
    ^                           v
FavoritesScreen            (doit savoir si favori)
    ^
CartScreen

-> Comment FavoritesScreen sait quels produits sont en favoris ?
-> Comment CartScreen sait combien d'articles sont dans le panier ?
-> Si l'état est dans HomeScreen, comment le passer à tous les autres écrans ?
```

La solution : un **état global** accessible partout dans l'application.

```
SOLUTION avec Provider :

                  ┌─────────────────────┐
                  │   CartProvider      │
                  │   (état global)     │
                  └────────┬────────────┘
                           │ (accessible via context)
              ┌────────────┼────────────┐
              v            v            v
         HomeScreen   ProductDetail  CartScreen
         (badge nb)   (bouton ajout) (liste items)
```

### 3.2 Installation et Configuration de Provider

```yaml
# pubspec.yaml
dependencies:
  provider: ^6.1.1
```

```dart
// lib/main.dart — Envelopper l'app avec les Providers

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    // MultiProvider permet d'avoir plusieurs providers en même temps
    MultiProvider(
      providers: [
        // ChangeNotifierProvider : le plus courant
        // Écoute les changements et reconstruit les widgets concernés
        ChangeNotifierProvider(create: (_) => CartProvider()),
        ChangeNotifierProvider(create: (_) => FavoritesProvider()),
        ChangeNotifierProvider(create: (_) => ProductsProvider()),
        ChangeNotifierProvider(create: (_) => AuthProvider()),
      ],
      child: const ShopFlutterApp(),
    ),
  );
}
```

### 3.3 Le CartProvider — Gestion du Panier

```dart
// lib/providers/cart_provider.dart

import 'package:flutter/foundation.dart';
import '../models/product.dart';
import '../models/cart_item.dart';

// ChangeNotifier : classe de base pour les Providers
// Elle notifie les widgets abonnés quand l'état change

class CartProvider with ChangeNotifier {
  // ─── État interne (privé) ──────────────────────────────────────
  // Map<String, CartItem> : clé = ID du produit, valeur = CartItem
  final Map<String, CartItem> _items = {};
  
  // ─── Getters (lecture de l'état) ──────────────────────────────
  
  // Retourne une COPIE non modifiable des articles
  Map<String, CartItem> get items => {..._items};
  
  // Nombre total d'articles (toutes quantités confondues)
  int get nombreArticles {
    return _items.values.fold(0, (total, item) => total + item.quantite);
  }
  
  // Nombre de produits distincts
  int get nombreProduits => _items.length;
  
  // Prix total du panier
  double get total {
    return _items.values.fold(
      0.0,
      (total, item) => total + item.sousTotal,
    );
  }
  
  // Frais de livraison (exemple : gratuit > 50€)
  double get fraisLivraison => total > 50 ? 0.0 : 5.99;
  
  // Total à payer (produits + livraison)
  double get totalAvecLivraison => total + fraisLivraison;
  
  // Vérifie si un produit est dans le panier
  bool contientProduit(String produitId) => _items.containsKey(produitId);
  
  // ─── Méthodes de mutation (modification de l'état) ─────────────
  
  // Ajouter un produit au panier
  void ajouterAuPanier(Produit produit, {int quantite = 1}) {
    if (_items.containsKey(produit.id)) {
      // Produit déjà dans le panier -> augmenter la quantité
      _items.update(
        produit.id,
        (existant) => CartItem(
          produit: existant.produit,
          quantite: existant.quantite + quantite,
        ),
      );
    } else {
      // Nouveau produit -> l'ajouter
      _items[produit.id] = CartItem(produit: produit, quantite: quantite);
    }
    
    // [ATTENTION] OBLIGATOIRE : notifie tous les widgets abonnés
    notifyListeners();
  }
  
  // Retirer un article du panier (diminue la quantité)
  void retirerUnArticle(String produitId) {
    if (!_items.containsKey(produitId)) return;
    
    if (_items[produitId]!.quantite > 1) {
      // Diminue la quantité
      _items.update(
        produitId,
        (existant) => CartItem(
          produit: existant.produit,
          quantite: existant.quantite - 1,
        ),
      );
    } else {
      // Si quantité = 1, supprimer l'article
      _items.remove(produitId);
    }
    
    notifyListeners();
  }
  
  // Supprimer complètement un produit du panier
  void supprimerDuPanier(String produitId) {
    _items.remove(produitId);
    notifyListeners();
  }
  
  // Vider complètement le panier
  void viderPanier() {
    _items.clear();
    notifyListeners();
  }
  
  // Mettre à jour la quantité d'un article directement
  void mettreAJourQuantite(String produitId, int nouvelleQuantite) {
    if (!_items.containsKey(produitId)) return;
    
    if (nouvelleQuantite <= 0) {
      supprimerDuPanier(produitId);
      return;
    }
    
    _items.update(
      produitId,
      (existant) => CartItem(
        produit: existant.produit,
        quantite: nouvelleQuantite,
      ),
    );
    
    notifyListeners();
  }
}
```

### 3.4 Le CartItem Model

```dart
// lib/models/cart_item.dart

import 'product.dart';

class CartItem {
  final Produit produit;
  final int quantite;

  const CartItem({
    required this.produit,
    required this.quantite,
  });

  // Prix total pour cette ligne du panier
  double get sousTotal => produit.prix * quantite;

  // Créer une copie avec une propriété modifiée
  CartItem copyWith({Produit? produit, int? quantite}) {
    return CartItem(
      produit: produit ?? this.produit,
      quantite: quantite ?? this.quantite,
    );
  }

  Map<String, dynamic> toMap() {
    return {
      'produitId': produit.id,
      'produitNom': produit.nom,
      'prix': produit.prix,
      'quantite': quantite,
    };
  }
}
```

### 3.5 Le FavoritesProvider

```dart
// lib/providers/favorites_provider.dart

import 'package:flutter/foundation.dart';
import '../models/product.dart';

class FavoritesProvider with ChangeNotifier {
  // Liste des produits favoris (Set = pas de doublons)
  final Set<String> _favorisIds = {};
  final List<Produit> _favoris = [];

  List<Produit> get favoris => List.unmodifiable(_favoris);
  int get nombreFavoris => _favoris.length;

  // Vérifie si un produit est en favori
  bool estEnFavori(String produitId) => _favorisIds.contains(produitId);

  // Ajouter/retirer un favori (toggle)
  void toggleFavori(Produit produit) {
    if (_favorisIds.contains(produit.id)) {
      _favorisIds.remove(produit.id);
      _favoris.removeWhere((p) => p.id == produit.id);
    } else {
      _favorisIds.add(produit.id);
      _favoris.add(produit);
    }
    notifyListeners();
  }

  // Supprimer des favoris
  void supprimerFavori(String produitId) {
    _favorisIds.remove(produitId);
    _favoris.removeWhere((p) => p.id == produitId);
    notifyListeners();
  }
}
```

### 3.6 Le ProductsProvider

```dart
// lib/providers/products_provider.dart

import 'package:flutter/foundation.dart';
import '../models/product.dart';

// Énumération pour l'état de chargement
enum EtatChargement { initial, chargement, charge, erreur }

class ProductsProvider with ChangeNotifier {
  // ─── État ──────────────────────────────────────────────────────
  List<Produit> _produits = [];
  List<Produit> _produitsFiltres = [];
  EtatChargement _etat = EtatChargement.initial;
  String _erreur = '';
  String _categorieSelectionnee = 'Tous';
  String _recherche = '';

  // ─── Getters ───────────────────────────────────────────────────
  List<Produit> get produits => _produitsFiltres;
  EtatChargement get etat => _etat;
  String get erreur => _erreur;
  bool get estEnChargement => _etat == EtatChargement.chargement;
  String get categorieSelectionnee => _categorieSelectionnee;

  List<String> get categories {
    final cats = _produits.map((p) => p.categorie).toSet().toList();
    cats.insert(0, 'Tous');
    return cats;
  }

  // ─── Chargement des produits ────────────────────────────────────
  Future<void> chargerProduits() async {
    _etat = EtatChargement.chargement;
    notifyListeners();

    try {
      // En chapitre 4 : remplacer par l'appel Firestore
      await Future.delayed(const Duration(seconds: 1));
      
      _produits = _genererProduitsFictifs();
      _appliquerFiltres();
      _etat = EtatChargement.charge;
    } catch (e) {
      _erreur = e.toString();
      _etat = EtatChargement.erreur;
    }
    
    notifyListeners();
  }

  // ─── Filtrage ──────────────────────────────────────────────────
  void filtrerParCategorie(String categorie) {
    _categorieSelectionnee = categorie;
    _appliquerFiltres();
    notifyListeners();
  }

  void rechercher(String texte) {
    _recherche = texte.toLowerCase();
    _appliquerFiltres();
    notifyListeners();
  }

  void _appliquerFiltres() {
    _produitsFiltres = _produits.where((produit) {
      // Filtre par catégorie
      final matchCategorie = _categorieSelectionnee == 'Tous' ||
          produit.categorie == _categorieSelectionnee;
      
      // Filtre par recherche
      final matchRecherche = _recherche.isEmpty ||
          produit.nom.toLowerCase().contains(_recherche) ||
          produit.description.toLowerCase().contains(_recherche);
      
      return matchCategorie && matchRecherche;
    }).toList();
  }

  // ─── Données fictives (remplacées par Firebase en Chapitre 4) ──
  List<Produit> _genererProduitsFictifs() {
    return [
      Produit(
        id: 'p1', nom: 'Nike Air Max 270',
        description: 'Chaussures légères et confortables', prix: 129.99,
        imageUrl: 'https://via.placeholder.com/300', categorie: 'Chaussures', stock: 15,
      ),
      Produit(
        id: 'p2', nom: 'Adidas Ultra Boost',
        description: 'Performance et style', prix: 159.99,
        imageUrl: 'https://via.placeholder.com/300', categorie: 'Chaussures', stock: 8,
      ),
      Produit(
        id: 'p3', nom: 'T-Shirt Premium',
        description: 'Coton bio 100%', prix: 39.99,
        imageUrl: 'https://via.placeholder.com/300', categorie: 'Vêtements', stock: 50,
      ),
      // ... plus de produits
    ];
  }
}
```

---

## PARTIE 4 — Utiliser les Providers dans les Widgets

### 4.1 Les 3 Façons d'Accéder à un Provider

```dart
// ═══════════════════════════════════════════════════
//  MÉTHODE 1 : context.watch<T>() — S'abonner aux changements
// ═══════════════════════════════════════════════════
// -> Le widget se RECONSTRUIT quand le provider change
// -> À utiliser dans build()

class CartIconWidget extends StatelessWidget {
  const CartIconWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // watch() écoute les changements -> reconstruction auto
    final cart = context.watch<CartProvider>();
    
    return Badge(
      label: Text('${cart.nombreArticles}'),
      isLabelVisible: cart.nombreArticles > 0,
      child: const Icon(Icons.shopping_cart),
    );
  }
}

// ═══════════════════════════════════════════════════
//  MÉTHODE 2 : context.read<T>() — Lecture unique (sans abonnement)
// ═══════════════════════════════════════════════════
// -> N'abonne PAS aux changements (plus performant)
// -> À utiliser dans les callbacks (onPressed, onTap, etc.)

ElevatedButton(
  onPressed: () {
    // read() pour accéder au provider dans un callback
    final cart = context.read<CartProvider>();
    cart.ajouterAuPanier(produit);
  },
  child: const Text('Ajouter au panier'),
)

// ═══════════════════════════════════════════════════
//  MÉTHODE 3 : context.select<T, R>() — Écoute sélective
// ═══════════════════════════════════════════════════
// -> Reconstruit UNIQUEMENT si la valeur sélectionnée change
// -> Très performant pour des widgets complexes

Widget build(BuildContext context) {
  // Ne reconstruit que si "nombreArticles" change
  final nbArticles = context.select<CartProvider, int>(
    (cart) => cart.nombreArticles,
  );
  
  return Text('$nbArticles articles');
}
```

### 4.2 L'Écran du Panier Complet

```dart
// lib/screens/cart/cart_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/cart_provider.dart';

class CartScreen extends StatelessWidget {
  const CartScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Mon Panier'),
        actions: [
          // Bouton vider le panier
          TextButton.icon(
            onPressed: () => _confirmerVidagePanier(context),
            icon: const Icon(Icons.delete_outline, color: Colors.red),
            label: const Text('Vider', style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
      body: Consumer<CartProvider>(
        // Consumer<T> : abonne le widget aux changements du provider
        // Reconstruit automatiquement quand notifyListeners() est appelé
        builder: (context, cart, child) {
          // Si panier vide
          if (cart.items.isEmpty) {
            return _buildPanierVide(context);
          }
          
          return Column(
            children: [
              // Liste des articles
              Expanded(
                child: ListView.separated(
                  padding: const EdgeInsets.all(16),
                  itemCount: cart.items.length,
                  separatorBuilder: (_, __) => const SizedBox(height: 12),
                  itemBuilder: (context, index) {
                    final item = cart.items.values.toList()[index];
                    return CartItemWidget(item: item);
                  },
                ),
              ),
              
              // Récapitulatif et bouton commander
              _buildRecapitulatif(context, cart),
            ],
          );
        },
      ),
    );
  }

  Widget _buildPanierVide(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.shopping_cart_outlined, size: 100, color: Colors.grey.shade300),
          const SizedBox(height: 16),
          const Text('Votre panier est vide',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Text('Découvrez nos produits et ajoutez-en !',
              style: TextStyle(color: Colors.grey.shade500)),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: () => context.go('/home'),
            child: const Text('Continuer mes achats'),
          ),
        ],
      ),
    );
  }

  Widget _buildRecapitulatif(BuildContext context, CartProvider cart) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 10,
            offset: const Offset(0, -4),
          ),
        ],
      ),
      child: Column(
        children: [
          // Ligne sous-total
          _buildLigneTotale('Sous-total (${cart.nombreProduits} articles)',
              '${cart.total.toStringAsFixed(2)} €'),
          
          const SizedBox(height: 8),
          
          // Ligne livraison
          _buildLigneTotale(
            'Livraison',
            cart.fraisLivraison == 0 ? 'Gratuit [OK]' : '${cart.fraisLivraison} €',
            couleur: cart.fraisLivraison == 0 ? Colors.green : null,
          ),
          
          const Divider(height: 24),
          
          // Total final
          _buildLigneTotale(
            'Total TTC',
            '${cart.totalAvecLivraison.toStringAsFixed(2)} €',
            gras: true,
            taille: 18,
          ),
          
          const SizedBox(height: 16),
          
          // Bouton commander
          ElevatedButton(
            onPressed: () => context.push('/cart/checkout'),
            child: const Text('Commander'),
          ),
          
          // Information livraison gratuite
          if (cart.fraisLivraison > 0) ...[
            const SizedBox(height: 8),
            Text(
              'Plus que ${(50 - cart.total).toStringAsFixed(2)} € pour la livraison gratuite !',
              style: TextStyle(color: Colors.orange.shade700, fontSize: 12),
              textAlign: TextAlign.center,
            ),
          ],
        ],
      ),
    );
  }

  Widget _buildLigneTotale(String label, String valeur, {
    bool gras = false,
    double taille = 14,
    Color? couleur,
  }) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: TextStyle(
          fontSize: taille,
          fontWeight: gras ? FontWeight.bold : FontWeight.normal,
        )),
        Text(valeur, style: TextStyle(
          fontSize: taille,
          fontWeight: gras ? FontWeight.bold : FontWeight.w500,
          color: couleur ?? (gras ? const Color(0xFF6C63FF) : null),
        )),
      ],
    );
  }

  void _confirmerVidagePanier(BuildContext context) {
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('Vider le panier'),
        content: const Text('Voulez-vous vraiment supprimer tous les articles ?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('Annuler'),
          ),
          ElevatedButton(
            onPressed: () {
              context.read<CartProvider>().viderPanier();
              Navigator.pop(ctx);
            },
            style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
            child: const Text('Vider'),
          ),
        ],
      ),
    );
  }
}
```

### 4.3 Le Widget CartItem

```dart
// lib/widgets/cart/cart_item_widget.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../models/cart_item.dart';
import '../../providers/cart_provider.dart';

class CartItemWidget extends StatelessWidget {
  final CartItem item;

  const CartItemWidget({super.key, required this.item});

  @override
  Widget build(BuildContext context) {
    return Dismissible(
      // Dismissible : permet de swiper pour supprimer (effet mail)
      key: ValueKey(item.produit.id),
      direction: DismissDirection.endToStart,  // Swipe de droite à gauche
      
      // Fond rouge affiché pendant le swipe
      background: Container(
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 20),
        decoration: BoxDecoration(
          color: Colors.red.shade100,
          borderRadius: BorderRadius.circular(16),
        ),
        child: const Icon(Icons.delete, color: Colors.red, size: 28),
      ),
      
      onDismissed: (_) {
        context.read<CartProvider>().supprimerDuPanier(item.produit.id);
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('${item.produit.nom} retiré'),
            action: SnackBarAction(
              label: 'Annuler',
              onPressed: () {
                context.read<CartProvider>().ajouterAuPanier(item.produit);
              },
            ),
          ),
        );
      },
      
      child: Container(
        padding: const EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.05),
              blurRadius: 8,
              offset: const Offset(0, 2),
            ),
          ],
        ),
        child: Row(
          children: [
            // Image du produit
            ClipRRect(
              borderRadius: BorderRadius.circular(10),
              child: Container(
                width: 80,
                height: 80,
                color: Colors.grey.shade100,
                child: const Icon(Icons.shopping_bag, color: Colors.grey),
              ),
            ),
            
            const SizedBox(width: 12),
            
            // Infos + contrôle quantité
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    item.produit.nom,
                    style: const TextStyle(fontWeight: FontWeight.bold),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  ),
                  Text(
                    '${item.produit.prix.toStringAsFixed(2)} € / unité',
                    style: TextStyle(color: Colors.grey.shade500, fontSize: 12),
                  ),
                  const SizedBox(height: 8),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      // Sous-total
                      Text(
                        '${item.sousTotal.toStringAsFixed(2)} €',
                        style: const TextStyle(
                          color: Color(0xFF6C63FF),
                          fontWeight: FontWeight.bold,
                          fontSize: 16,
                        ),
                      ),
                      
                      // Contrôle quantité
                      _QuantiteControl(item: item),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _QuantiteControl extends StatelessWidget {
  final CartItem item;
  
  const _QuantiteControl({required this.item});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // Bouton -
        _bouton(
          context: context,
          icone: Icons.remove,
          onTap: () => context.read<CartProvider>().retirerUnArticle(item.produit.id),
        ),
        
        // Quantité
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12),
          child: Text(
            '${item.quantite}',
            style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
          ),
        ),
        
        // Bouton +
        _bouton(
          context: context,
          icone: Icons.add,
          onTap: () => context.read<CartProvider>().ajouterAuPanier(item.produit),
          primaire: true,
        ),
      ],
    );
  }

  Widget _bouton({
    required BuildContext context,
    required IconData icone,
    required VoidCallback onTap,
    bool primaire = false,
  }) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: 30,
        height: 30,
        decoration: BoxDecoration(
          color: primaire ? const Color(0xFF6C63FF) : Colors.grey.shade100,
          borderRadius: BorderRadius.circular(8),
        ),
        child: Icon(icone,
          color: primaire ? Colors.white : Colors.black87, size: 16),
      ),
    );
  }
}
```

---

## PARTIE 5 — Cycle de Vie des Widgets

### 5.1 Comprendre le Cycle de Vie

```dart
// Très important pour charger des données au bon moment !

class ProductsScreen extends StatefulWidget {
  const ProductsScreen({super.key});

  @override
  State<ProductsScreen> createState() => _ProductsScreenState();
}

class _ProductsScreenState extends State<ProductsScreen> {

  // ─── 1. initState ─────────────────────────────────────────────
  // Appelé UNE SEULE FOIS quand le widget est créé
  // -> Parfait pour : charger des données, initialiser des contrôleurs
  @override
  void initState() {
    super.initState();  // TOUJOURS appeler super.initState() en premier !
    
    // [ATTENTION] context n'est PAS encore disponible ici directement
    // Utiliser addPostFrameCallback pour accéder aux providers
    WidgetsBinding.instance.addPostFrameCallback((_) {
      context.read<ProductsProvider>().chargerProduits();
    });
  }

  // ─── 2. didChangeDependencies ─────────────────────────────────
  // Appelé quand les dépendances du widget changent
  // -> context est disponible ici
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // Peut être appelé plusieurs fois !
  }

  // ─── 3. build ────────────────────────────────────────────────
  // Appelé à chaque rebuild du widget
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Consumer<ProductsProvider>(
        builder: (context, products, _) {
          switch (products.etat) {
            case EtatChargement.chargement:
              return const Center(child: CircularProgressIndicator());
            
            case EtatChargement.erreur:
              return Center(child: Text('Erreur : ${products.erreur}'));
            
            case EtatChargement.charge:
              return GridView.builder(
                itemCount: products.produits.length,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  childAspectRatio: 0.7,
                ),
                itemBuilder: (ctx, i) => ProductCard(produit: products.produits[i]),
              );
              
            default:
              return const SizedBox.shrink();
          }
        },
      ),
    );
  }

  // ─── 4. dispose ───────────────────────────────────────────────
  // Appelé quand le widget est supprimé de l'arbre
  // -> Nettoyer : controllers, streams, timers
  @override
  void dispose() {
    // Exemple : si on avait un TextEditingController
    // _searchController.dispose();
    super.dispose();  // TOUJOURS appeler super.dispose() en dernier !
  }
}
```

---

## [NOTE] Exercices du Chapitre 3

### Exercice 3.1 — Badges dynamiques
Ajoutez un badge sur l'icône panier de la BottomNavigationBar qui affiche le nombre d'articles en temps réel, grâce au `CartProvider`.

### Exercice 3.2 — Page favoris
Créez la page `FavoritesScreen` qui affiche les produits favoris en utilisant `FavoritesProvider` (avec Consumer). Permettez le retrait d'un favori par swipe.

### Exercice 3.3 — Filtres produits
Dans `HomeScreen`, ajoutez des chips de catégories cliquables qui filtrent la grille de produits en utilisant `ProductsProvider.filtrerParCategorie()`.

---

## [OK] Récapitulatif du Chapitre 3

| Concept | Maîtrisé ? |
|---------|-----------|
| Navigator push/pop | [ ] |
| Routes nommées | [ ] |
| Passage de données entre écrans | [ ] |
| go_router configuration | [ ] |
| ShellRoute + BottomNavBar | [ ] |
| Qu'est-ce que Provider | [ ] |
| ChangeNotifier + notifyListeners | [ ] |
| context.watch vs context.read | [ ] |
| Consumer\<T\> widget | [ ] |
| Cycle de vie (initState, dispose) | [ ] |
| CartProvider complet | [ ] |
| Dismissible widget | [ ] |

---

**-> Chapitre suivant : `04_FIREBASE_ET_BACKEND.md`**

# [LIVRE] Chapitre 4 — Firebase & Backend
## Projet ShopFlutter | Guide Ultra-Détaillé

---

## [OBJECTIF] Objectifs de ce Chapitre

À la fin de ce chapitre, vous serez capable de :
- Configurer Firebase dans un projet Flutter
- Implémenter l'authentification complète (email, Google)
- Créer et gérer une base de données Firestore
- Stocker des images avec Firebase Storage
- Connecter tous vos Providers à des données réelles
- Déployer une application e-commerce fonctionnelle

**[ALARM_CLOCK] Durée estimée :** 7-10 jours (2-3h/jour)

---

## PARTIE 1 — Configuration Firebase

### 1.1 Qu'est-ce que Firebase ?

Firebase est une plateforme de développement de Google qui fournit un backend complet sans avoir à coder un serveur. Pour ShopFlutter, nous utiliserons :

| Service Firebase | Utilisation dans ShopFlutter |
|-----------------|------------------------------|
| **Authentication** | Connexion / Inscription utilisateurs |
| **Firestore** | Stockage des produits, commandes, profils |
| **Storage** | Images des produits et photos de profil |
| **Analytics** | Statistiques d'utilisation (bonus) |

```
Architecture ShopFlutter avec Firebase :

┌────────────────────┐          ┌──────────────────────┐
│   Application      │          │       Firebase        │
│   Flutter          │          │                       │
│                    │  HTTPS   │  ┌─────────────────┐  │
│  ┌─────────────┐   │ <-──────-> │  │  Authentication │  │
│  │  AuthService│   │          │  └─────────────────┘  │
│  └─────────────┘   │          │                       │
│                    │          │  ┌─────────────────┐  │
│  ┌─────────────┐   │ <-──────-> │  │    Firestore    │  │
│  │  Firestore  │   │          │  │    Database     │  │
│  │  Service    │   │          │  └─────────────────┘  │
│  └─────────────┘   │          │                       │
│                    │          │  ┌─────────────────┐  │
│  ┌─────────────┐   │ <-──────-> │  │    Storage      │  │
│  │  Storage    │   │          │  │    (Images)     │  │
│  │  Service    │   │          │  └─────────────────┘  │
│  └─────────────┘   │          │                       │
└────────────────────┘          └──────────────────────┘
```

### 1.2 Créer un Projet Firebase

**Étape 1 : Aller sur Firebase Console**
1. Rendez-vous sur [console.firebase.google.com](https://console.firebase.google.com)
2. Connectez-vous avec votre compte Google
3. Cliquez sur **"Créer un projet"**
4. Nommez-le : `shop-flutter-projet`
5. Désactivez Google Analytics (pour simplifier) -> **"Créer le projet"**

**Étape 2 : Installer la CLI Firebase et FlutterFire**

```bash
# Installer Firebase CLI (outil en ligne de commande)
npm install -g firebase-tools

# Se connecter à Firebase
firebase login

# Installer FlutterFire CLI (outil spécifique Flutter)
dart pub global activate flutterfire_cli

# Dans votre projet Flutter, configurer automatiquement Firebase
flutterfire configure

# Cette commande va :
# 1. Vous demander de choisir votre projet Firebase
# 2. Choisir les plateformes (Android, iOS, Web)
# 3. Générer automatiquement le fichier firebase_options.dart
```

**Étape 3 : Ajouter les packages Firebase**

```yaml
# pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  # ─── Firebase ────────────────────────────────────
  firebase_core: ^2.24.2        # OBLIGATOIRE - Initialisation
  firebase_auth: ^4.15.3        # Authentification
  cloud_firestore: ^4.13.6      # Base de données NoSQL
  firebase_storage: ^11.5.6     # Stockage fichiers
  
  # ─── State Management ────────────────────────────
  provider: ^6.1.1
  
  # ─── Navigation ──────────────────────────────────
  go_router: ^13.0.0
  
  # ─── UI Helpers ──────────────────────────────────
  cached_network_image: ^3.3.0
  image_picker: ^1.0.5          # Sélection d'images (profil)
  intl: ^0.19.0                 # Formatage
```

```bash
# Mettre à jour les packages
flutter pub get
```

### 1.3 Initialisation de Firebase dans main.dart

```dart
// lib/main.dart — CONFIGURATION FINALE avec Firebase

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart';  // Généré par flutterfire configure
import 'providers/auth_provider.dart';
import 'providers/cart_provider.dart';
import 'providers/favorites_provider.dart';
import 'providers/products_provider.dart';
import 'app.dart';

void main() async {
  // ─── IMPORTANT : toujours appeler ceci avant Firebase ──────────
  WidgetsFlutterBinding.ensureInitialized();
  
  // ─── Initialiser Firebase ──────────────────────────────────────
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,  // Depuis firebase_options.dart
  );
  
  // ─── Lancer l'application ──────────────────────────────────────
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => AuthProvider()),
        ChangeNotifierProvider(create: (_) => ProductsProvider()),
        ChangeNotifierProvider(create: (_) => CartProvider()),
        ChangeNotifierProvider(create: (_) => FavoritesProvider()),
      ],
      child: const ShopFlutterApp(),
    ),
  );
}
```

---

## PARTIE 2 — Authentification Firebase

### 2.1 Activer l'Authentification dans Firebase Console

1. Dans Firebase Console -> **"Authentication"** -> **"Commencer"**
2. Onglet **"Sign-in method"**
3. Activez **"Email/Mot de passe"** -> Enregistrer
4. (Optionnel) Activez **"Google"** -> Enregistrer

### 2.2 Le Service d'Authentification

```dart
// lib/services/auth_service.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class AuthService {
  // ─── Instances Firebase (Singletons) ──────────────────────────
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  // ─── Stream de l'état d'authentification ──────────────────────
  // Stream = flux de données en temps réel
  // Émet null quand déconnecté, User quand connecté
  Stream<User?> get authStateChanges => _auth.authStateChanges();

  // Utilisateur actuellement connecté
  User? get currentUser => _auth.currentUser;
  bool get isConnected => _auth.currentUser != null;

  // ─── INSCRIPTION ───────────────────────────────────────────────
  Future<UserCredential> inscrire({
    required String email,
    required String password,
    required String prenom,
    required String nom,
  }) async {
    try {
      // 1. Créer le compte Firebase Auth
      UserCredential credential = await _auth.createUserWithEmailAndPassword(
        email: email,
        password: password,
      );
      
      // 2. Mettre à jour le profil Auth (nom d'affichage)
      await credential.user!.updateDisplayName('$prenom $nom');
      
      // 3. Créer le document utilisateur dans Firestore
      await _firestore.collection('users').doc(credential.user!.uid).set({
        'uid': credential.user!.uid,
        'email': email,
        'prenom': prenom,
        'nom': nom,
        'photoUrl': '',
        'adresse': '',
        'telephone': '',
        'createdAt': FieldValue.serverTimestamp(),  // Timestamp serveur Firebase
        'updatedAt': FieldValue.serverTimestamp(),
      });
      
      return credential;
    } on FirebaseAuthException catch (e) {
      // Traduire les erreurs Firebase en messages lisibles
      throw _traduireErreurAuth(e);
    }
  }

  // ─── CONNEXION ────────────────────────────────────────────────
  Future<UserCredential> connexion({
    required String email,
    required String password,
  }) async {
    try {
      return await _auth.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      throw _traduireErreurAuth(e);
    }
  }

  // ─── DÉCONNEXION ──────────────────────────────────────────────
  Future<void> deconnecter() async {
    await _auth.signOut();
  }

  // ─── RÉINITIALISATION MOT DE PASSE ───────────────────────────
  Future<void> reinitialiserMotDePasse(String email) async {
    try {
      await _auth.sendPasswordResetEmail(email: email);
    } on FirebaseAuthException catch (e) {
      throw _traduireErreurAuth(e);
    }
  }

  // ─── TRADUCTION DES ERREURS FIREBASE ─────────────────────────
  String _traduireErreurAuth(FirebaseAuthException e) {
    switch (e.code) {
      case 'email-already-in-use':
        return 'Cet email est déjà utilisé par un autre compte.';
      case 'invalid-email':
        return 'L\'adresse email est invalide.';
      case 'operation-not-allowed':
        return 'Cette méthode de connexion n\'est pas activée.';
      case 'weak-password':
        return 'Le mot de passe est trop faible (minimum 6 caractères).';
      case 'user-disabled':
        return 'Ce compte a été désactivé.';
      case 'user-not-found':
        return 'Aucun compte trouvé avec cet email.';
      case 'wrong-password':
        return 'Mot de passe incorrect.';
      case 'too-many-requests':
        return 'Trop de tentatives. Réessayez dans quelques minutes.';
      default:
        return 'Une erreur est survenue : ${e.message}';
    }
  }
}
```

### 2.3 Le AuthProvider avec Firebase

```dart
// lib/providers/auth_provider.dart

import 'package:flutter/foundation.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../services/auth_service.dart';
import '../models/user.dart' as app_models;  // Alias pour éviter le conflit

class AuthProvider with ChangeNotifier {
  final AuthService _authService = AuthService();
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  // ─── État ──────────────────────────────────────────────────────
  User? _firebaseUser;              // Utilisateur Firebase Auth
  app_models.AppUser? _appUser;    // Notre modèle utilisateur enrichi
  bool _isLoading = false;
  String? _erreur;

  // ─── Getters ───────────────────────────────────────────────────
  User? get firebaseUser => _firebaseUser;
  app_models.AppUser? get appUser => _appUser;
  bool get isConnected => _firebaseUser != null;
  bool get isLoading => _isLoading;
  String? get erreur => _erreur;

  // ─── Constructeur ──────────────────────────────────────────────
  AuthProvider() {
    // Écoute les changements d'état Firebase en temps réel
    _authService.authStateChanges.listen(_onAuthStateChanged);
  }

  // ─── Callback quand l'état auth change ────────────────────────
  Future<void> _onAuthStateChanged(User? user) async {
    _firebaseUser = user;
    
    if (user != null) {
      // Utilisateur connecté -> charger ses données depuis Firestore
      await _chargerDonneesUtilisateur(user.uid);
    } else {
      // Déconnecté -> réinitialiser
      _appUser = null;
    }
    
    notifyListeners();
  }

  Future<void> _chargerDonneesUtilisateur(String uid) async {
    try {
      final doc = await _firestore.collection('users').doc(uid).get();
      if (doc.exists) {
        _appUser = app_models.AppUser.fromMap(doc.data()!);
      }
    } catch (e) {
      print('Erreur chargement utilisateur: $e');
    }
  }

  // ─── Inscription ───────────────────────────────────────────────
  Future<bool> inscrire({
    required String email,
    required String password,
    required String prenom,
    required String nom,
  }) async {
    _isLoading = true;
    _erreur = null;
    notifyListeners();

    try {
      await _authService.inscrire(
        email: email,
        password: password,
        prenom: prenom,
        nom: nom,
      );
      return true;
    } catch (e) {
      _erreur = e.toString();
      return false;
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  // ─── Connexion ────────────────────────────────────────────────
  Future<bool> connecter({
    required String email,
    required String password,
  }) async {
    _isLoading = true;
    _erreur = null;
    notifyListeners();

    try {
      await _authService.connexion(email: email, password: password);
      return true;
    } catch (e) {
      _erreur = e.toString();
      return false;
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  // ─── Déconnexion ──────────────────────────────────────────────
  Future<void> deconnecter() async {
    await _authService.deconnecter();
  }
}
```

### 2.4 Connexion de l'écran Login à Firebase

```dart
// lib/screens/auth/login_screen.dart — VERSION FIREBASE

// Mise à jour de la méthode _connexion dans LoginScreen :

Future<void> _connexion() async {
  if (!_formKey.currentState!.validate()) return;
  
  setState(() => _isLoading = true);
  
  // Utiliser le AuthProvider pour se connecter
  final authProvider = context.read<AuthProvider>();
  final succes = await authProvider.connecter(
    email: _emailController.text.trim(),
    password: _passwordController.text,
  );
  
  if (mounted) {
    setState(() => _isLoading = false);
    
    if (succes) {
      // Connexion réussie -> aller à l'accueil
      context.go('/home');
    } else {
      // Afficher l'erreur
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(authProvider.erreur ?? 'Erreur de connexion'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }
}
```

---

## PARTIE 3 — Base de Données Firestore

### 3.1 Structure de la Base de Données

Firestore est une base de données **NoSQL** orientée documents. Structure de ShopFlutter :

```
firestore-database/
│
├── users/                          # Collection "users"
│   └── {userId}/                   # Document par utilisateur
│       ├── uid: "abc123"
│       ├── email: "user@email.com"
│       ├── prenom: "Marie"
│       ├── nom: "Dupont"
│       └── photoUrl: "https://..."
│
├── products/                       # Collection "products"
│   └── {productId}/                # Document par produit
│       ├── id: "prod_001"
│       ├── nom: "Nike Air Max"
│       ├── prix: 129.99
│       ├── categorie: "Chaussures"
│       ├── stock: 15
│       ├── imageUrl: "https://..."
│       └── createdAt: Timestamp
│
├── orders/                         # Collection "orders"
│   └── {orderId}/                  # Document par commande
│       ├── userId: "abc123"
│       ├── items: [...]            # Array de CartItem
│       ├── total: 259.98
│       ├── statut: "en_attente"
│       └── createdAt: Timestamp
│
└── reviews/                        # Collection "reviews"
    └── {reviewId}/
        ├── produitId: "prod_001"
        ├── userId: "abc123"
        ├── note: 5
        └── commentaire: "Excellent !"
```

### 3.2 Le Service Firestore

```dart
// lib/services/firestore_service.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/product.dart';
import '../models/order.dart' as app_models;

class FirestoreService {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  // ═══════════════════════════════════════════════════
  //  PRODUITS
  // ═══════════════════════════════════════════════════

  // ─── Stream de produits en temps réel ─────────────────────────
  // Stream<T> = flux de données qui se met à jour automatiquement
  Stream<List<Produit>> getProduitsStream() {
    return _db
        .collection('products')
        .orderBy('createdAt', descending: true)
        .snapshots()  // snapshots() = écoute en temps réel
        .map((snapshot) {
          // Convertit chaque document en objet Produit
          return snapshot.docs.map((doc) {
            return Produit.fromMap({...doc.data(), 'id': doc.id});
          }).toList();
        });
  }

  // ─── Produits par catégorie ────────────────────────────────────
  Stream<List<Produit>> getProduitsParCategorie(String categorie) {
    Query query = _db.collection('products');
    
    if (categorie != 'Tous') {
      query = query.where('categorie', isEqualTo: categorie);
    }
    
    return query.snapshots().map((snapshot) {
      return snapshot.docs.map((doc) {
        return Produit.fromMap({...doc.data() as Map<String, dynamic>, 'id': doc.id});
      }).toList();
    });
  }

  // ─── Récupérer UN produit ──────────────────────────────────────
  Future<Produit?> getProduit(String id) async {
    final doc = await _db.collection('products').doc(id).get();
    if (!doc.exists) return null;
    return Produit.fromMap({...doc.data()!, 'id': doc.id});
  }

  // ─── Rechercher des produits ───────────────────────────────────
  Future<List<Produit>> rechercherProduits(String recherche) async {
    // Firestore ne supporte pas la recherche fulltext native
    // Pour une vraie recherche, utiliser Algolia ou Cloud Functions
    // Cette solution simple fonctionne pour les petits catalogues :
    final snapshot = await _db.collection('products').get();
    final rechercheLower = recherche.toLowerCase();
    
    return snapshot.docs
        .map((doc) => Produit.fromMap({...doc.data(), 'id': doc.id}))
        .where((p) =>
            p.nom.toLowerCase().contains(rechercheLower) ||
            p.description.toLowerCase().contains(rechercheLower))
        .toList();
  }

  // ─── Ajouter un produit (admin) ────────────────────────────────
  Future<String> ajouterProduit(Produit produit) async {
    final docRef = await _db.collection('products').add({
      ...produit.toMap(),
      'createdAt': FieldValue.serverTimestamp(),
      'updatedAt': FieldValue.serverTimestamp(),
    });
    return docRef.id;  // Retourne l'ID généré par Firebase
  }

  // ─── Mettre à jour un produit ──────────────────────────────────
  Future<void> mettreAJourProduit(String id, Map<String, dynamic> data) async {
    await _db.collection('products').doc(id).update({
      ...data,
      'updatedAt': FieldValue.serverTimestamp(),
    });
  }

  // ═══════════════════════════════════════════════════
  //  COMMANDES
  // ═══════════════════════════════════════════════════

  // ─── Créer une commande ────────────────────────────────────────
  Future<String> creerCommande({
    required String userId,
    required List<Map<String, dynamic>> items,
    required double total,
    required Map<String, dynamic> adresseLivraison,
  }) async {
    final docRef = await _db.collection('orders').add({
      'userId': userId,
      'items': items,
      'total': total,
      'statut': 'en_attente',     // en_attente, confirmee, en_transit, livree
      'adresseLivraison': adresseLivraison,
      'createdAt': FieldValue.serverTimestamp(),
      'updatedAt': FieldValue.serverTimestamp(),
    });
    
    // Mettre à jour le stock de chaque produit
    for (final item in items) {
      await _db.collection('products').doc(item['produitId']).update({
        'stock': FieldValue.increment(-item['quantite']),
        // FieldValue.increment() : opération atomique (thread-safe)
      });
    }
    
    return docRef.id;
  }

  // ─── Commandes d'un utilisateur ───────────────────────────────
  Stream<List<app_models.Order>> getCommandesUtilisateur(String userId) {
    return _db
        .collection('orders')
        .where('userId', isEqualTo: userId)
        .orderBy('createdAt', descending: true)
        .snapshots()
        .map((snapshot) => snapshot.docs
            .map((doc) => app_models.Order.fromMap({...doc.data(), 'id': doc.id}))
            .toList());
  }

  // ═══════════════════════════════════════════════════
  //  FAVORIS (persistés dans Firestore)
  // ═══════════════════════════════════════════════════

  // ─── Ajouter aux favoris ──────────────────────────────────────
  Future<void> ajouterFavori(String userId, String produitId) async {
    await _db
        .collection('users')
        .doc(userId)
        .collection('favorites')  // Sous-collection "favorites"
        .doc(produitId)
        .set({'produitId': produitId, 'addedAt': FieldValue.serverTimestamp()});
  }

  // ─── Retirer des favoris ──────────────────────────────────────
  Future<void> supprimerFavori(String userId, String produitId) async {
    await _db
        .collection('users')
        .doc(userId)
        .collection('favorites')
        .doc(produitId)
        .delete();
  }

  // ─── Stream des IDs favoris ───────────────────────────────────
  Stream<Set<String>> getFavorisIds(String userId) {
    return _db
        .collection('users')
        .doc(userId)
        .collection('favorites')
        .snapshots()
        .map((snapshot) => snapshot.docs.map((doc) => doc.id).toSet());
  }
}
```

### 3.3 Mise à jour du ProductsProvider avec Firestore

```dart
// lib/providers/products_provider.dart — VERSION FIRESTORE

import 'package:flutter/foundation.dart';
import 'dart:async';
import '../models/product.dart';
import '../services/firestore_service.dart';

class ProductsProvider with ChangeNotifier {
  final FirestoreService _firestoreService = FirestoreService();
  
  List<Produit> _produits = [];
  List<Produit> _produitsFiltres = [];
  bool _isLoading = false;
  String? _erreur;
  String _categorieSelectionnee = 'Tous';
  String _recherche = '';
  
  // ─── Stream subscription (important pour mémoire !) ────────────
  StreamSubscription<List<Produit>>? _streamSubscription;

  // ─── Getters ───────────────────────────────────────────────────
  List<Produit> get produits => _produitsFiltres;
  bool get isLoading => _isLoading;
  String? get erreur => _erreur;

  // ─── Démarrer l'écoute Firestore ──────────────────────────────
  void demarrerEcoute() {
    _isLoading = true;
    notifyListeners();
    
    // Annuler l'écoute précédente si elle existe
    _streamSubscription?.cancel();
    
    // S'abonner au stream Firestore
    _streamSubscription = _firestoreService
        .getProduitsStream()
        .listen(
          (produits) {
            _produits = produits;
            _appliquerFiltres();
            _isLoading = false;
            _erreur = null;
            notifyListeners();
          },
          onError: (error) {
            _erreur = error.toString();
            _isLoading = false;
            notifyListeners();
          },
        );
  }

  // ─── Filtres ───────────────────────────────────────────────────
  void filtrerParCategorie(String categorie) {
    _categorieSelectionnee = categorie;
    _appliquerFiltres();
    notifyListeners();
  }

  void rechercher(String texte) {
    _recherche = texte.toLowerCase();
    _appliquerFiltres();
    notifyListeners();
  }

  void _appliquerFiltres() {
    _produitsFiltres = _produits.where((p) {
      final matchCat = _categorieSelectionnee == 'Tous' ||
          p.categorie == _categorieSelectionnee;
      final matchSearch = _recherche.isEmpty ||
          p.nom.toLowerCase().contains(_recherche);
      return matchCat && matchSearch;
    }).toList();
  }

  // ─── IMPORTANT : nettoyer le stream ───────────────────────────
  @override
  void dispose() {
    _streamSubscription?.cancel();  // Stopper l'écoute Firestore
    super.dispose();
  }
}
```

---

## PARTIE 4 — Firebase Storage (Images)

### 4.1 Le Service Storage

```dart
// lib/services/storage_service.dart

import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:image_picker/image_picker.dart';

class StorageService {
  final FirebaseStorage _storage = FirebaseStorage.instance;
  final ImagePicker _picker = ImagePicker();

  // ─── Sélectionner une image depuis la galerie ──────────────────
  Future<File?> selectionnerImage({
    ImageSource source = ImageSource.gallery,
    int maxWidth = 800,
    int maxHeight = 800,
    int imageQuality = 80,
  }) async {
    final XFile? pickedFile = await _picker.pickImage(
      source: source,
      maxWidth: maxWidth.toDouble(),
      maxHeight: maxHeight.toDouble(),
      imageQuality: imageQuality,
    );
    
    if (pickedFile != null) {
      return File(pickedFile.path);
    }
    return null;
  }

  // ─── Uploader une photo de profil ──────────────────────────────
  Future<String> uploadPhotoProfi({
    required String userId,
    required File imageFile,
    Function(double)? onProgress,  // Callback pour afficher la progression
  }) async {
    final ref = _storage.ref().child('users/$userId/profile.jpg');
    
    final uploadTask = ref.putFile(
      imageFile,
      SettableMetadata(contentType: 'image/jpeg'),
    );
    
    // Écouter la progression
    uploadTask.snapshotEvents.listen((snapshot) {
      final progress = snapshot.bytesTransferred / snapshot.totalBytes;
      onProgress?.call(progress);
    });
    
    // Attendre la fin de l'upload
    await uploadTask;
    
    // Récupérer l'URL de téléchargement
    final downloadUrl = await ref.getDownloadURL();
    return downloadUrl;
  }

  // ─── Uploader une image de produit ────────────────────────────
  Future<String> uploadImageProduit({
    required String produitId,
    required File imageFile,
    int imageIndex = 0,
  }) async {
    final ref = _storage.ref().child('products/$produitId/image_$imageIndex.jpg');
    
    await ref.putFile(imageFile);
    return await ref.getDownloadURL();
  }

  // ─── Supprimer une image ───────────────────────────────────────
  Future<void> supprimerImage(String imageUrl) async {
    try {
      final ref = _storage.refFromURL(imageUrl);
      await ref.delete();
    } catch (e) {
      print('Impossible de supprimer l\'image : $e');
    }
  }
}
```

### 4.2 Mise à jour du Profil Utilisateur

```dart
// lib/screens/profile/profile_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:io';
import '../../providers/auth_provider.dart';
import '../../services/storage_service.dart';

class ProfileScreen extends StatefulWidget {
  const ProfileScreen({super.key});

  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  final StorageService _storageService = StorageService();
  bool _uploadEnCours = false;
  double _progressionUpload = 0.0;

  // ─── Changer la photo de profil ───────────────────────────────
  Future<void> _changerPhoto() async {
    // 1. Sélectionner l'image
    final File? image = await _storageService.selectionnerImage();
    if (image == null) return;  // L'utilisateur a annulé
    
    setState(() {
      _uploadEnCours = true;
      _progressionUpload = 0.0;
    });
    
    try {
      final authProvider = context.read<AuthProvider>();
      
      // 2. Uploader vers Firebase Storage
      final url = await _storageService.uploadPhotoProfi(
        userId: authProvider.firebaseUser!.uid,
        imageFile: image,
        onProgress: (progress) {
          setState(() => _progressionUpload = progress);
        },
      );
      
      // 3. Mettre à jour l'URL dans Firestore et Auth
      await authProvider.mettreAJourPhoto(url);
      
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Photo mise à jour !')),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Erreur : $e')),
        );
      }
    } finally {
      if (mounted) setState(() => _uploadEnCours = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Mon Profil'),
        actions: [
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: () async {
              await context.read<AuthProvider>().deconnecter();
              if (mounted) context.go('/login');
            },
          ),
        ],
      ),
      body: Consumer<AuthProvider>(
        builder: (context, auth, _) {
          if (auth.appUser == null) {
            return const Center(child: CircularProgressIndicator());
          }
          
          return SingleChildScrollView(
            padding: const EdgeInsets.all(20),
            child: Column(
              children: [
                // ─── Photo de profil ─────────────────────
                Stack(
                  children: [
                    CircleAvatar(
                      radius: 60,
                      backgroundColor: Colors.grey.shade200,
                      backgroundImage: auth.appUser!.photoUrl.isNotEmpty
                          ? NetworkImage(auth.appUser!.photoUrl)
                          : null,
                      child: auth.appUser!.photoUrl.isEmpty
                          ? Text(
                              auth.appUser!.prenom[0].toUpperCase(),
                              style: const TextStyle(fontSize: 40, color: Colors.grey),
                            )
                          : null,
                    ),
                    
                    // Bouton modifier la photo
                    Positioned(
                      bottom: 0,
                      right: 0,
                      child: GestureDetector(
                        onTap: _uploadEnCours ? null : _changerPhoto,
                        child: Container(
                          width: 36,
                          height: 36,
                          decoration: BoxDecoration(
                            color: const Color(0xFF6C63FF),
                            shape: BoxShape.circle,
                            border: Border.all(color: Colors.white, width: 2),
                          ),
                          child: _uploadEnCours
                              ? CircularProgressIndicator(
                                  value: _progressionUpload,
                                  strokeWidth: 2,
                                  color: Colors.white,
                                )
                              : const Icon(Icons.camera_alt, size: 18, color: Colors.white),
                        ),
                      ),
                    ),
                  ],
                ),
                
                const SizedBox(height: 16),
                
                // Nom de l'utilisateur
                Text(
                  '${auth.appUser!.prenom} ${auth.appUser!.nom}',
                  style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
                ),
                Text(
                  auth.appUser!.email,
                  style: TextStyle(color: Colors.grey.shade500),
                ),
                
                const SizedBox(height: 32),
                
                // Menu options profil
                _buildMenuProfil(context, auth),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildMenuProfil(BuildContext context, AuthProvider auth) {
    return Column(
      children: [
        _buildOptionMenu(Icons.person_outline, 'Informations personnelles', () {}),
        _buildOptionMenu(Icons.location_on_outlined, 'Adresses de livraison', () {}),
        _buildOptionMenu(Icons.shopping_bag_outlined, 'Mes commandes', () => context.push('/orders')),
        _buildOptionMenu(Icons.payment_outlined, 'Méthodes de paiement', () {}),
        _buildOptionMenu(Icons.notifications_outlined, 'Notifications', () {}),
        _buildOptionMenu(Icons.help_outline, 'Aide & Support', () {}),
        const Divider(),
        _buildOptionMenu(Icons.logout, 'Se déconnecter', () async {
          await auth.deconnecter();
          if (context.mounted) context.go('/login');
        }, couleur: Colors.red),
      ],
    );
  }

  Widget _buildOptionMenu(IconData icone, String label, VoidCallback onTap, {Color? couleur}) {
    return ListTile(
      leading: Icon(icone, color: couleur ?? const Color(0xFF6C63FF)),
      title: Text(label, style: TextStyle(color: couleur)),
      trailing: const Icon(Icons.chevron_right, color: Colors.grey),
      onTap: onTap,
    );
  }
}
```

---

## PARTIE 5 — Règles de Sécurité Firestore

### 5.1 Configurer les Règles de Sécurité

```javascript
// firestore.rules — Règles de sécurité Firestore
// À configurer dans Firebase Console -> Firestore -> Rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // ─── Fonctions utilitaires ──────────────────────────────────
    
    // Vérifie si l'utilisateur est authentifié
    function estAuthentifie() {
      return request.auth != null;
    }
    
    // Vérifie si l'utilisateur est le propriétaire du document
    function estProprio(userId) {
      return request.auth.uid == userId;
    }

    // ─── Collection : users ─────────────────────────────────────
    match /users/{userId} {
      // Lecture : seulement le propriétaire peut lire son profil
      allow read: if estAuthentifie() && estProprio(userId);
      
      // Écriture : seulement le propriétaire peut modifier
      allow write: if estAuthentifie() && estProprio(userId);
      
      // Sous-collection favorites
      match /favorites/{favId} {
        allow read, write: if estAuthentifie() && estProprio(userId);
      }
    }
    
    // ─── Collection : products ──────────────────────────────────
    match /products/{productId} {
      // Lecture : tout utilisateur authentifié peut lire les produits
      allow read: if estAuthentifie();
      
      // Écriture : INTERDIT pour les utilisateurs normaux
      // Seul un admin (Cloud Function) peut créer/modifier des produits
      allow write: if false;
    }
    
    // ─── Collection : orders ────────────────────────────────────
    match /orders/{orderId} {
      // Un utilisateur peut lire SES propres commandes
      allow read: if estAuthentifie() && 
                     resource.data.userId == request.auth.uid;
      
      // Un utilisateur peut créer UNE commande pour lui-même
      allow create: if estAuthentifie() && 
                       request.resource.data.userId == request.auth.uid;
      
      // Modification : interdit (géré par Cloud Functions)
      allow update, delete: if false;
    }
    
    // ─── Collection : reviews ───────────────────────────────────
    match /reviews/{reviewId} {
      // Tout le monde peut lire les avis
      allow read: if estAuthentifie();
      
      // Un utilisateur peut créer un avis
      allow create: if estAuthentifie() && 
                       request.resource.data.userId == request.auth.uid;
      
      // On ne peut modifier/supprimer que ses propres avis
      allow update, delete: if estAuthentifie() && 
                               resource.data.userId == request.auth.uid;
    }
  }
}
```

---

## PARTIE 6 — Peupler la Base de Données (Seed Data)

### 6.1 Script pour Ajouter des Produits de Test

```dart
// lib/utils/seed_data.dart — À utiliser UNE SEULE FOIS pour initialiser

import 'package:cloud_firestore/cloud_firestore.dart';

class SeedData {
  static final FirebaseFirestore _db = FirebaseFirestore.instance;

  static Future<void> peuplerProduits() async {
    final produits = [
      {
        'nom': 'Nike Air Max 270',
        'description': 'La Nike Air Max 270 offre une semelle Air maximale pour une absorption des chocs exceptionnelle. Design moderne et confort tout au long de la journée.',
        'prix': 129.99,
        'categorie': 'Chaussures',
        'imageUrl': 'https://via.placeholder.com/400/FF6584/FFFFFF?text=Nike+AM270',
        'stock': 25,
        'note': 4.8,
        'nbAvis': 128,
        'createdAt': FieldValue.serverTimestamp(),
      },
      {
        'nom': 'Adidas Ultra Boost 22',
        'description': 'Les Adidas Ultra Boost 22 combinent performance et style. La semelle Boost offre un retour d\'énergie exceptionnel.',
        'prix': 159.99,
        'categorie': 'Chaussures',
        'imageUrl': 'https://via.placeholder.com/400/6C63FF/FFFFFF?text=Adidas+UB',
        'stock': 18,
        'note': 4.7,
        'nbAvis': 94,
        'createdAt': FieldValue.serverTimestamp(),
      },
      {
        'nom': 'T-Shirt Premium Coton Bio',
        'description': 'T-shirt en coton biologique certifié GOTS. Coupe décontractée, doux sur la peau, respectueux de l\'environnement.',
        'prix': 39.99,
        'categorie': 'Vêtements',
        'imageUrl': 'https://via.placeholder.com/400/00B894/FFFFFF?text=T-Shirt',
        'stock': 50,
        'note': 4.5,
        'nbAvis': 67,
        'createdAt': FieldValue.serverTimestamp(),
      },
      {
        'nom': 'Jean Slim Fit',
        'description': 'Jean slim en denim stretch de haute qualité. Coupe moderne et confortable, idéal pour toutes les occasions.',
        'prix': 79.99,
        'categorie': 'Vêtements',
        'imageUrl': 'https://via.placeholder.com/400/2D3436/FFFFFF?text=Jean',
        'stock': 35,
        'note': 4.3,
        'nbAvis': 42,
        'createdAt': FieldValue.serverTimestamp(),
      },
      {
        'nom': 'Apple AirPods Pro 2',
        'description': 'Annulation active du bruit de nouvelle génération. Audio adaptatif, charge MagSafe, autonomie 30h.',
        'prix': 279.99,
        'categorie': 'Électronique',
        'imageUrl': 'https://via.placeholder.com/400/636E72/FFFFFF?text=AirPods',
        'stock': 10,
        'note': 4.9,
        'nbAvis': 312,
        'createdAt': FieldValue.serverTimestamp(),
      },
    ];

    // Batch write : écrire plusieurs documents en une seule requête (plus efficace)
    final batch = _db.batch();
    
    for (final produit in produits) {
      final docRef = _db.collection('products').doc();  // ID auto-généré
      batch.set(docRef, produit);
    }
    
    await batch.commit();
    print('[OK] ${produits.length} produits ajoutés avec succès !');
  }
}

// Utilisation (dans un écran d'admin ou main.dart temporairement) :
// await SeedData.peuplerProduits();
```

---

## PARTIE 7 — Gestion du Processus de Commande

### 7.1 L'Écran de Commande (Checkout)

```dart
// lib/screens/cart/checkout_screen.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/cart_provider.dart';
import '../../providers/auth_provider.dart';
import '../../services/firestore_service.dart';

class CheckoutScreen extends StatefulWidget {
  const CheckoutScreen({super.key});

  @override
  State<CheckoutScreen> createState() => _CheckoutScreenState();
}

class _CheckoutScreenState extends State<CheckoutScreen> {
  final _formKey = GlobalKey<FormState>();
  final _adresseController = TextEditingController();
  final _villeController = TextEditingController();
  final _codePostalController = TextEditingController();
  bool _commandeEnCours = false;

  @override
  Widget build(BuildContext context) {
    final cart = context.watch<CartProvider>();
    final auth = context.read<AuthProvider>();

    return Scaffold(
      appBar: AppBar(title: const Text('Finaliser la commande')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // ─── Récapitulatif articles ───────────────────
              const Text('Récapitulatif', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              const SizedBox(height: 12),
              
              ...cart.items.values.map((item) => Padding(
                padding: const EdgeInsets.only(bottom: 8),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text('${item.produit.nom} ×${item.quantite}',
                        style: const TextStyle(fontSize: 14)),
                    Text('${item.sousTotal.toStringAsFixed(2)} €',
                        style: const TextStyle(fontWeight: FontWeight.bold)),
                  ],
                ),
              )),
              
              const Divider(),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text('Total', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                  Text('${cart.totalAvecLivraison.toStringAsFixed(2)} €',
                      style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF6C63FF))),
                ],
              ),
              
              const SizedBox(height: 24),
              
              // ─── Adresse de livraison ─────────────────────
              const Text('Adresse de livraison',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              const SizedBox(height: 12),
              
              TextFormField(
                controller: _adresseController,
                decoration: const InputDecoration(labelText: 'Adresse'),
                validator: (v) => v!.isEmpty ? 'Adresse requise' : null,
              ),
              const SizedBox(height: 12),
              
              Row(
                children: [
                  Expanded(
                    child: TextFormField(
                      controller: _codePostalController,
                      decoration: const InputDecoration(labelText: 'Code postal'),
                      keyboardType: TextInputType.number,
                      validator: (v) => v!.length != 5 ? 'Code postal invalide' : null,
                    ),
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    flex: 2,
                    child: TextFormField(
                      controller: _villeController,
                      decoration: const InputDecoration(labelText: 'Ville'),
                      validator: (v) => v!.isEmpty ? 'Ville requise' : null,
                    ),
                  ),
                ],
              ),
              
              const SizedBox(height: 32),
              
              // ─── Bouton commander ─────────────────────────
              ElevatedButton(
                onPressed: _commandeEnCours
                    ? null
                    : () => _passerCommande(context, cart, auth),
                child: _commandeEnCours
                    ? const Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          SizedBox(
                            width: 20, height: 20,
                            child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
                          ),
                          SizedBox(width: 12),
                          Text('Traitement en cours...'),
                        ],
                      )
                    : const Text('Confirmer la commande'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _passerCommande(
    BuildContext context,
    CartProvider cart,
    AuthProvider auth,
  ) async {
    if (!_formKey.currentState!.validate()) return;
    
    setState(() => _commandeEnCours = true);
    
    try {
      final firestoreService = FirestoreService();
      
      // Convertir les articles du panier en format Firestore
      final items = cart.items.values.map((item) => item.toMap()).toList();
      
      // Créer la commande dans Firestore
      final orderId = await firestoreService.creerCommande(
        userId: auth.firebaseUser!.uid,
        items: items,
        total: cart.totalAvecLivraison,
        adresseLivraison: {
          'adresse': _adresseController.text,
          'ville': _villeController.text,
          'codePostal': _codePostalController.text,
        },
      );
      
      // Vider le panier
      cart.viderPanier();
      
      if (mounted) {
        // Afficher la confirmation
        showDialog(
          context: context,
          barrierDismissible: false,
          builder: (ctx) => AlertDialog(
            icon: const Icon(Icons.check_circle, color: Colors.green, size: 60),
            title: const Text('Commande confirmée !'),
            content: Text('Votre commande #${orderId.substring(0, 8).toUpperCase()} a été passée avec succès.'),
            actions: [
              ElevatedButton(
                onPressed: () {
                  Navigator.pop(ctx);
                  context.go('/home');
                },
                child: const Text('Retour à l\'accueil'),
              ),
            ],
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Erreur : $e'), backgroundColor: Colors.red),
        );
      }
    } finally {
      if (mounted) setState(() => _commandeEnCours = false);
    }
  }

  @override
  void dispose() {
    _adresseController.dispose();
    _villeController.dispose();
    _codePostalController.dispose();
    super.dispose();
  }
}
```

---

## PARTIE 8 — Déploiement et Prochaines Étapes

### 8.1 Checklist Avant Déploiement

```
[OK] SÉCURITÉ
  [ ] Règles Firestore configurées et testées
  [ ] Règles Storage configurées
  [ ] Clés API protégées (pas dans le code source)
  [ ] .gitignore inclut google-services.json et GoogleService-Info.plist

[OK] PERFORMANCE
  [ ] Images optimisées et en cache (CachedNetworkImage)
  [ ] Pagination Firestore (pour les longues listes)
  [ ] Indexes Firestore créés pour les requêtes complexes

[OK] EXPÉRIENCE UTILISATEUR
  [ ] États de chargement sur toutes les actions réseau
  [ ] Gestion des erreurs et messages d'erreur clairs
  [ ] Mode hors-ligne géré (Firestore cache local)

[OK] TESTS
  [ ] Tests unitaires des Providers
  [ ] Tests des services Firebase (avec émulateurs)
  [ ] Tests sur émulateur Android et iOS
```

### 8.2 Build de Production

```bash
# ─── Android ──────────────────────────────────────────────────
# Build APK (pour test direct)
flutter build apk --release

# Build App Bundle (pour Google Play Store)
flutter build appbundle --release

# L'APK est dans : build/app/outputs/apk/release/app-release.apk
# Le Bundle est dans : build/app/outputs/bundle/release/app-release.aab

# ─── iOS (macOS uniquement) ──────────────────────────────────
flutter build ios --release
# Puis ouvrir Xcode pour archiver et soumettre sur App Store

# ─── Web (bonus) ─────────────────────────────────────────────
flutter build web --release
firebase deploy --only hosting  # Déployer sur Firebase Hosting
```

---

## [NOTE] Exercices du Chapitre 4

### Exercice 4.1 — Authentification complète
Connectez la page `RegisterScreen` au `AuthProvider`. Gérez les cas d'erreur (email déjà utilisé, mot de passe faible) avec des messages clairs.

### Exercice 4.2 — Stream de produits
Dans `HomeScreen`, utilisez un `StreamBuilder` connecté à `FirestoreService.getProduitsStream()` pour afficher les produits en temps réel.

### Exercice 4.3 — Historique des commandes
Créez une page `OrdersScreen` qui affiche l'historique des commandes de l'utilisateur connecté depuis Firestore, avec le statut de chaque commande.

---

## [OK] Récapitulatif du Chapitre 4

| Concept | Maîtrisé ? |
|---------|-----------|
| Configuration Firebase | [ ] |
| Firebase Authentication | [ ] |
| AuthProvider avec Firebase | [ ] |
| Structure Firestore | [ ] |
| CRUD Firestore | [ ] |
| Stream Firestore temps réel | [ ] |
| Firebase Storage upload | [ ] |
| Règles de sécurité | [ ] |
| Seed data et peuplement | [ ] |
| Processus de commande | [ ] |
| Build de production | [ ] |

---

## [COURS] Félicitations ! Vous avez terminé le cours !

Vous avez maintenant construit **ShopFlutter**, une application e-commerce complète avec :
- [DESIGN] Une UI professionnelle avec Material Design 3
- [COMPASS] Navigation fluide avec go_router
- [CONVENIENCE_STORE] State management avec Provider
- [HOT] Backend complet avec Firebase
- [SECURISE] Authentification sécurisée
- [SAUVEGARDE] Base de données temps réel avec Firestore

### Prochaines étapes pour aller plus loin :

1. **Paiement** : Intégrer Stripe pour les paiements réels
2. **Notifications** : Ajouter Firebase Cloud Messaging (FCM)
3. **Recherche avancée** : Intégrer Algolia pour la recherche fulltext
4. **Analytics** : Firebase Analytics pour comprendre vos utilisateurs
5. **Tests** : Écrire des tests unitaires et d'intégration
6. **Riverpod** : Migrer vers Riverpod pour un state management encore plus robuste
