SafeBite/docs/flux-UX.md
2026-04-25 10:26:13 -04:00

41 KiB
Raw Permalink Blame History

📱 APPLICATION IOS/ANDROID SPÉCIFICATION UX/UI DÉTAILLÉE

Document technique pour implémentation • Version 1.0 • 25 avril 2026


🎯 PRINCIPES FONDATEURS (GUIDING PRINCIPLES)

Ces règles priment sur toute décision d'implémentation.

# Principe Règle stricte
P1 2 taps max L'action scanner est accessible en ≤ 2 taps depuis n'importe quel écran
P2 Verdict immédiat Après scan, le verdict de sécurité s'affiche en < 500ms (perçues)
P3 Feu tricolore 3 couleurs sémantiques max + neutres jamais de bleu
P4 Icônes + couleurs Aucune information critique ne repose uniquement sur la couleur
P5 Guidage positif Pas de messages d'erreur bruts toujours une action de repli proposée
P6 Mobile-first Conception pour une main, pouce accessible (zone de confort inférieure)

🎨 DESIGN SYSTEM

2.1 Palette de couleurs

COULEURS SÉMANTIQUES (Système feu tricolore)
─────────────────────────────────────────────
🟢 Vert sécurité    #2ECC71    → Produit compatible, OK famille
🟠 Orange attention  #E67E22    → Allergène détecté, vigilance
🔴 Rouge danger      #E74C3C    → Allergène critique, interdit

NEUTRES
─────────────────────────────────────────────
Fond principal       #F5F5F0    → Gris chaud (réduit la fatigue oculaire)
Surface carte        #FFFFFF    → Blanc pur pour les cartes
Texte principal      #2D3436    → Noir doux (pas #000, trop agressif)
Texte secondaire     #636E72    → Gris moyen
Séparateurs          #DFE6E9    → Gris clair

ÉTATS
─────────────────────────────────────────────
Appui (pressed)      Assombrir de 15% la couleur de base
Désactivé            Opacité 40% + grisé
Focus (accessibilité)  Anneau #2ECC71 de 2px (offset 2px)

2.2 Typographie

platform_android:
  font_family: "Google Sans"  # ou Inter en fallback
  scale:
    display: {size: 28sp, weight: 700, line_height: 36sp, usage: "Titre écran"}
    headline: {size: 22sp, weight: 600, line_height: 28sp, usage: "Titre section"}
    body: {size: 16sp, weight: 400, line_height: 24sp, usage: "Texte principal"}
    caption: {size: 13sp, weight: 400, line_height: 18sp, usage: "Légende, méta"}
    button: {size: 15sp, weight: 600, line_height: 20sp, usage: "CTA, boutons"}

platform_ios:
  font_family: "SF Pro"
  # Mêmes échelles, adaptées en pt

2.3 Iconographie

system_icons:
  ✅ sécurité_ok: "checkmark.shield.fill"     # Vert - Produit sûr
  ⚠️ vigilance: "exclamationmark.shield.fill" # Orange - Attention
  ❌ danger: "xmark.shield.fill"             # Rouge - Interdit
  
  📷 scanner: "camera.viewfinder"            # Action principale
  📋 listes: "list.clipboard"                # Onglet listes
  📊 suivi: "chart.bar.fill"                 # Onglet suivi
  👤 profil: "person.circle"                 # Onglet famille/profil

icon_principles:
  - Toujours accompagner la couleur d'une forme distincte
  - Formes : cercle (OK), triangle (⚠️), losange (❌)
  - Taille minimale des zones tactiles : 48x48dp (WCAG)

2.4 Élévation & Ombres (Android)

/* Carte standard */
.card {
  elevation: 2dp;
  /* Ombre: 0 1px 3px rgba(0,0,0,0.08) */
}

/* Bouton flottant (FAB) */
.fab {
  elevation: 6dp;
  /* Ombre: 0 3px 8px rgba(0,0,0,0.16) */
}

/* Bottom sheet */
.bottom-sheet {
  elevation: 16dp;
  /* Ombre: 0 8px 24px rgba(0,0,0,0.20) */
}

🏗️ ARCHITECTURE DE NAVIGATION

3.1 Structure globale

┌─────────────────────────────────────────────────────┐
│                   APPLICATION                        │
├─────────────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────┐ │
│  │  Scanner │  │  Listes  │  │  Suivi   │  │Famille│ │
│  │    📷    │  │    📋    │  │   📊    │  │  👤   │ │
│  └──────────┘  └──────────┘  └──────────┘  └──────┘ │
│       ▲                                             │
│       │                                             │
│  ┌────┴─────────┐   <-- Bouton flottant central     │
│  │    SCANNER   │       (FAB, 56dp, toujours visible)│
│  └──────────────┘       Sauf pendant le scan lui-même│
└─────────────────────────────────────────────────────┘

Navigation: BottomNavigationView (4 items)
FAB: FloatingActionButton centré, légèrement au-dessus de la barre

3.2 Bottom Navigation Bar Spécification

// Spécification technique BottomNavigation
data class BottomNavItem(
    val id: String,
    val icon: ImageVector,      // Icône remplie (selected) / outline (unselected)
    val label: String,
    val contentDescription: String  // Obligatoire pour TalkBack
)

val bottomNavItems = listOf(
    BottomNavItem("scanner",   Icons.Filled.Camera,  "Scanner",   "Scanner un produit"),
    BottomNavItem("listes",    Icons.Filled.List,     "Listes",    "Mes listes de courses"),
    BottomNavItem("suivi",     Icons.Filled.Chart,    "Suivi",     "Statistiques et historique"),
    BottomNavItem("famille",   Icons.Filled.Person,   "Famille",   "Profils et réglages")
)

// État visuel
// Selected : icon color = #2D3436 (noir doux), label visible, indicator top
// Unselected : icon color = #B2BEC3, label visible
// Badge : point rouge 8dp si notification non lue

3.3 FAB (Floating Action Button) Spécification

fab:
  size: 56dp  # Standard Material Design
  icon: 📷 camera_viewfinder (24dp)
  color: "#2D3436"  # Noir doux  contraste avec le fond
  icon_color: "#FFFFFF"
  elevation: 6dp
  position:
    horizontal: center  # Centré horizontalement
    vertical_offset: -28dp  # Chevauchant la bottom bar
  behavior:
    - Visible sur tous les onglets SAUF pendant le scan actif
    - Disparition: scale_down + fade_out (200ms)
    - Apparition: scale_up + fade_in (200ms)
    - Haptique légère (15ms) au tap
  accessibility:
    label: "Scanner un produit"
    hint: "Appuyez deux fois pour ouvrir le scanner"

📱 FLUX UTILISATEUR DÉTAILLÉS

FLOW 1 : Premier lancement (Onboarding)

[ÉCRAN 1] —────—— "Bienvenue !" —────—— [ÉCRAN 2]
"Qui êtes-vous ?"                    "Des allergies ?"
                                    
┌──────────────────┐          ┌──────────────────┐
│      Logo        │          │   Grille photos   │
│                  │          │  (pas de texte)   │
│  "Votre prénom"  │          │                   │
│  [Input text]    │          │ [🥜] [🥛] [🍞]  │
│                  │          │ [🦐] [🥚] [🐟]  │
│   [Continuer →]  │          │                   │
│                  │          │  [Continuer →]    │
└──────────────────┘          └──────────────────┘
       │                              │
       ▼                              ▼
[ÉCRAN 3] —────—— "Un objectif ?" —────—— [ÉCRAN 4]
                                    "Scannez !"
┌──────────────────┐          ┌──────────────────┐
│                  │          │    Animation      │
│ ○ Mieux manger   │          │    scanner qui    │
│ ○ Éviter additifs│          │    pulse          │
│ ○ Sans importance│          │                   │
│                  │          │  Pointez l'appareil│
│ [Ignorer] [OK]   │          │  vers un code-    │
│                  │          │  barres            │
│                  │          │                   │
└──────────────────┘          │ [Commencer]       │
       │                      └──────────────────┘
       ▼                              │
       └──────────┬───────────────────┘
                  ▼
          [DASHBOARD]

Règles onboarding :

  • Maximum 4 écrans, 60 secondes cumulées
  • Progression : indicateur en haut (4 points)
  • Skip possible à chaque étape (sauf écran 4 : CTA seul)
  • Pas de permissions demandées avant le scan effectif
  • Stockage local uniquement (pas de compte obligatoire)

FLOW 2 : Scan produit (Parcours principal 80% des usages)

flow_scan_complete:
  
  # ── ÉTAPE 1 : Activation scanner ──
  step_1_open:
    trigger: "Tap sur FAB OU tap sur onglet Scanner"
    animation: "Expansion depuis le centre (Material ContainerTransform)"
    duration: 300ms
    state: |
      ┌──────────────────────────┐
      │ Zone de scan (70% écran) │
      │                          │
      │    ┌──────────────┐      │
      │    │  [reticule]  │      │ Camera preview
      │    │              │      │ + overlay réticule
      │    └──────────────┘      │
      │                          │
      │  "Placez le code-barres  │
      │   dans le cadre"        │
      │                          │
      │   ⚡ Détection auto      │
      ├──────────────────────────┤
      │ [Saisie manuelle] [💡]  │ Bottom bar locale
      └──────────────────────────┘
    
    permissions:
      camera:
        rationale: "Pour scanner les codes-barres"
        on_denied: "Saisie manuelle proposée"
        on_permanently_denied: "Lien vers Réglages + saisie manuelle"
  
  # ── ÉTAPE 2 : Détection et analyse ──
  step_2_processing:
    trigger: "Code-barres ou OCR détecté automatiquement"
    duration_max: 500ms  # Perçues
    
    states:
      detection:
        haptique: "Vibration légère 15ms"
        son: "Déclic discret (optionnel, désactivé par défaut)"
        animation: |
          Le réticule pulse brièvement (scale 1.0 → 1.05 → 1.0, 200ms)
          Puis transition vers l'écran résultat
      
      loading:
        type: "SKELETON SCREEN (jamais de spinner)"
        layout_skeleton: |
          ┌──────────────────────────┐
          │ ████████████  (nom)      │
          │ ██████        (marque)   │
          │ ░░░░░░░░░░░░  (verdict)  │ Background coloré
          │                          │
          │ ████████████             │
          │ ██████████              │
          └──────────────────────────┘
        
        animation: "Shimmer wave, gauche → droite, 1.5s loop"
      
      timeout:
        after_3s: "Afficher 'Analyse en cours...' + option annuler"
        after_8s: "→ FLOW 7 (Gestion d'erreur)"
  
  # ── ÉTAPE 3 : Verdict immédiat ──
  step_3_verdict:
    transition: "Slide up from bottom, 250ms, ease-out"
    
    layout: |
      ┌──────────────────────────────┐
      │  ← Retour scan    ⋮ Options  │ Top bar
      ├──────────────────────────────┤
      │                              │
      │         [Image produit]      │
      │       (120dp, coins ronds)   │
      │                              │
      │   Biscuit Choco Lait        │
      │   Marque X  200g           │
      │                              │
      │ ┌──────────────────────────┐ │
      │ │ ✅ OK pour toute la      │ │ ← Verdict banner
      │ │    famille              │ │   Fond vert #2ECC71
      │ └──────────────────────────┘ │   Icône cercle blanc
      │                              │
      │   [→ Voir détails]           │
      │   [→ Alternatives]           │
      │   [→ Ajouter à la liste]    │
      │                              │
      └──────────────────────────────┘
    
    verdict_variants:
      ok: |
        ✅ "OK pour toute la famille"
        Fond: #E8F8F5 (vert très clair)
        Icône: checkmark.shield.fill, #2ECC71
      
      warning: |
        ⚠️ "Contient : NOISETTES"
        "⚠️ Attention pour Julie"
        Fond: #FEF5E7 (orange très clair)
        Icône: exclamationmark.shield.fill, #E67E22
        Allergène affiché en bold, #E67E22
      
      danger: |
        ❌ "Contient : ARACHIDES"
        "❌ Interdit pour Julie (anaphylaxie)"
        Fond: #FDEDEC (rouge très clair)
        Icône: xmark.shield.fill, #E74C3C
        Allergène affiché en bold, #E74C3C
        + Message explicite : "Ne pas consommer"
    
    actions_disponibles:
      - {id: "details", label: "Voir détails", icon: "info.circle", priority: primary}
      - {id: "alternatives", label: "Voir alternatives", icon: "arrow.triangle.swap", priority: secondary}
      - {id: "add_list", label: "Ajouter à une liste", icon: "plus.circle", priority: secondary}
      - {id: "scan_again", label: "Scanner un autre produit", icon: "camera", priority: tertiary}
    
    accessibility:
      verdict_announcement: "Verdict : {status}. {details}. Actions disponibles : voir détails, voir alternatives, ajouter à la liste."
      # Ex: "Verdict : Attention pour Julie. Contient noisettes. Actions disponibles : ..."

FLOW 3 : Fiche produit détaillée

flow_product_details:
  trigger: "Tap 'Voir détails' depuis le verdict OU depuis l'historique"
  transition: "Slide up (Bottom Sheet) → Plein écran au scroll"
  
  structure_tabs:
    - id: "resume"
      label: "Résumé"
      icon: "list.bullet"
      content: |
        - Verdict sécurité (répété en haut)
        - Nutri-Score visuel (A à E, grandes pastilles colorées)
        - Calories / 100g
        - Taux de sucre, sel, gras (jauges visuelles horizontales)
    
    - id: "allergenes"
      label: "Allergènes"
      icon: "exclamationmark.shield"
      content: |
        - Liste des 14 allergènes réglementaires
        - Chaque allergène : 
          → Présent ❌ / Traces ⚠️ / Absent ✅
        - Les allergènes de la famille sont mis en avant (highlight #FEF5E7)
    
    - id: "additifs"
      label: "Additifs"
      icon: "flask"
      content: |
        - Liste des additifs avec code E
        - Barème couleur : Vert (naturel) / Orange (discutable) / Rouge (éviter)
        - Description courte de chaque additif
        - Lien "En savoir plus" → WebView interne
    
    - id: "alternatives"
      label: "Alternatives"
      icon: "arrow.triangle.swap"
      priority: "high"  # Affiché si verdict != OK
      content: |
        - Carousel horizontal de produits alternatifs
        - Critère : même catégorie, sans l'allergène détecté
        - Chaque carte : photo, nom, verdict mini (couleur fond)
        - Tap → Verdict du produit alternatif

FLOW 4 : Dashboard contextuel

flow_dashboard:
  # Le dashboard n'est plus un onglet mais un overlay intelligent
  # accessible depuis l'onglet "Scanner" (écran principal)
  
  states:
    store_mode: |
      # Détecté via géolocalisation ou heure (8h-20h en semaine)
      ┌──────────────────────────────┐
      │ 🛒 Vous êtes en magasin ?    │
      │                              │
      │  [Scanner rapide] ← Large   │
      │                              │
      │  Votre liste en cours :      │
      │  ┌──────────────────────┐    │
      │  │ 🥛 Lait demi-écrémé  │    │
      │  │ 🍞 Pain complet      │    │
      │  │ 🍎 Pommes x6         │    │
      │  └──────────────────────┘    │
      │                              │
      │  "3 produits restants"       │
      └──────────────────────────────┘
    
    home_mode: |
      # Soirée / weekend
      ┌──────────────────────────────┐
      │ 👋 Bonjour, Sophie           │
      │                              │
      │ 📊 Cette semaine :           │
      │   ████████░░ 78% produits OK │
      │                              │
      │ 🔍 Derniers scans :          │
      │  ✅ Biscuit Choco            │
      │  ⚠️ Sauce Curry             │
      │                              │
      │  [Scanner] [Mes listes]     │
      └──────────────────────────────┘
    
    first_time: |
      # Dashboard vide, guidé
      ┌──────────────────────────────┐
      │   🎉 Prêt à commencer !      │
      │                              │
      │   📷 Scannez votre premier   │
      │      produit                 │
      │                              │
      │      [Commencer →]           │
      └──────────────────────────────┘

FLOW 5 : Listes intelligentes

flow_lists:
  trigger: "Onglet 📋 Listes"
  
  default_view: |
    ┌──────────────────────────────┐
    │ Listes                 + Nouvelle │
    ├──────────────────────────────┤
    │ ┌────────────────────────┐   │
    │ │ 🛒 Courses semaine     │   │
    │ │ 8 produits • 3 achetés │   │
    │ │ ██████░░░░ 37%        │   │
    │ └────────────────────────┘   │
    │ ┌────────────────────────┐   │
    │ │ 🎉 Anniv Lucas         │   │
    │ │ 12 produits • 0 acheté │   │
    │ └────────────────────────┘   │
    └──────────────────────────────┘
  
  list_detail: |
    ┌──────────────────────────────┐
    │ ← Courses semaine    ⋮       │
    ├──────────────────────────────┤
    │ 🔍 Filtrer par rayon         │ ← Chips horizontaux
    │ [Tous] [Frais] [Épicerie]   │
    │                              │
    │ ☐ Lait demi-écrémé     ✅   │
    │ ☐ Pain complet          ⚠️   │ → Attention allergies
    │ ☑ Pommes x6            ✅   │ → Coché = acheté
    │ ☐ Yaourt nature        ✅   │
    │ ☐ Sauce tomate         ❌   │ → Interdit (allergène)
    │                              │
    │ [Tout décocher] [Partager]   │
    └──────────────────────────────┘
    
    swipe_actions:
      swipe_right: "Cocher/décocher (acheté)"
      swipe_left: "Supprimer de la liste (avec undo 5s)"
    
    smart_features:
      auto_categorize: "Détection rayon automatique (frais, épicerie, etc.)"
      allergy_alert: "Icône ⚠️/❌ visible directement sur la ligne"
      merge_lists: "Fusion de plusieurs listes en une"
      share: "Partage via lien ou PDF (sms, email, etc.)"

FLOW 6 : Suivi & Historique

flow_tracking:
  trigger: "Onglet 📊 Suivi"
  
  layout: |
    ┌──────────────────────────────┐
    │ Suivi              Période ▼│
    ├──────────────────────────────┤
    │                              │
    │   ┌────────────────────┐     │
    │   │   78% produits OK   │     │ ← Cercle de progression
    │   │   cette semaine     │     │   (donut chart)
    │   └────────────────────┘     │
    │                              │
    │ 📈 Évolution (graphique)     │
    │   ░░░░░░░░░░░░░░░░░░░░      │ ← Sparkline ou mini graph
    │                              │
    │ 🏷️ Top allergènes détectés  │
    │   🥜 Noisettes : 4 fois     │
    │   🥛 Lactose   : 2 fois     │
    │                              │
    │ 📋 Historique récent         │
    │ ┌────────────────────────┐   │
    │ │ ✅ Biscuit Choco      │   │
    │ │    Il y a 2h          │   │
    │ ├────────────────────────┤   │
    │ │ ⚠️ Sauce Curry       │   │
    │ │    Hier               │   │
    │ └────────────────────────┘   │
    └──────────────────────────────┘
  
  time_filters:
    - {id: "week", label: "Cette semaine"}
    - {id: "month", label: "Ce mois"}
    - {id: "year", label: "Cette année"}
    - {id: "all", label: "Tout"}
  
  empty_state: |
    ┌──────────────────────────────┐
    │         📊                   │
    │   Aucune statistique         │
    │   Scannez vos premiers       │
    │   produits !                 │
    │                              │
    │      [Scanner →]             │
    └──────────────────────────────┘

FLOW 7 : Gestion des erreurs & cas limites

flow_errors:

  # ── CAS 1 : Produit non trouvé en base ──
  product_not_found:
    trigger: "Code-barres scanné, mais absent de la base"
    layout: |
      ┌──────────────────────────────┐
      │         📷                   │
      │                              │
      │  "Produit non reconnu"      │
      │                              │
      │  "Photographiez l'étiquette │
      │   pour nous aider à          │
      │   l'identifier"             │
      │                              │
      │  ┌──────────────────────┐    │
      │  │  [Zone photo étiquette]│   │
      │  └──────────────────────┘    │
      │                              │
      │  [Prendre une photo]         │
      │  [Saisie manuelle]          │
      │  [Annuler]                   │
      └──────────────────────────────┘
    
    actions:
      photo_label:
        camera: "Prendre photo étiquette (ingrédients)"
        crop_guide: "Cadrez la liste d'ingrédients"
        on_success: "Merci ! Nous analysons votre photo. Le produit sera ajouté sous 24h."
      
      manual_entry:
        fields: ["Nom produit", "Marque", "Code-barres (pré-rempli)"]
       on_submit: "Produit enregistré localement. Analyse basique des ingrédients si fournis."

  # ── CAS 2 : Pas de connexion ──
  no_connection:
    detection: "Vérification avant chaque scan"
    behavior: |
      Mode dégradé transparent :
      - Scan fonctionne avec base locale (produits déjà scannés)
      - Nouveau produit : message "Connexion requise pour les nouveaux produits"
      - Listes : synchronisation différée
      - Pas de popup bloquante, un bandeau discret en haut
    banner: |
      ┌──────────────────────────────┐
      │ 📡 Mode hors-ligne           │ ← Bandeau 40dp, fond #FEF5E7
      │ Produits déjà scannés dispo. │   Non bloquant
      └──────────────────────────────┘

  # ── CAS 3 : OCR illisible ──
  ocr_unreadable:
    trigger: "L'OCR n'a pas pu lire les ingrédients sur la photo"
    layout: |
      ┌──────────────────────────────┐
      │         💡                   │
      │                              │
      │  "L'image est trop floue"   │
      │                              │
      │  "Essayez avec plus de       │
      │   lumière ou rapprochez-vous │
      │   de l'étiquette"           │
      │                              │
      │  [Réessayer]                 │
      │  [Saisie manuelle]          │
      │  [Ignorer]                   │
      └──────────────────────────────┘
    
    tips_displayed:
      - "Placez le produit sur une surface plane"
      - "Évitez les reflets"
      - "Utilisez le flash si nécessaire"

  # ── CAS 4 : Permissions refusées ──
  permission_denied:
    camera:
      rationale_dialog: |
        Titre: "Accès à l'appareil photo"
        Message: "L'appareil photo est nécessaire pour scanner les codes-barres."
        Boutons: [Aller aux Réglages] [Pas maintenant]
      fallback: "Saisie manuelle du code-barres toujours disponible"
    
    notification:
      rationale: |
        Titre: "Notifications de sécurité"
        Message: "Recevez les alertes uniquement en cas d'allergène critique détecté."
        Boutons: [Activer] [Plus tard]
      behavior: "Fonctionnalités non-bloquantes sans notification"

FLOW 8 : Profils famille

flow_family:
  trigger: "Onglet 👤 Famille"
  
 layout: |
    ┌──────────────────────────────┐
    │ Ma famille                   │
    ├──────────────────────────────┤
    │                              │
    │ ┌────────────────────────┐   │
    │ │ 👩 Sophie (moi)       │   │ ← Profil principal
    │ │ Allergies: -           │   │
    │ └────────────────────────┘   │
    │ ┌────────────────────────┐   │
    │ │ 👧 Julie, 7 ans       │   │
    │ │ ⚠️ Noisettes, arachides│   │
    │ │ ❌ Anaphylaxie arachide│   │
    │ └────────────────────────┘   │
    │ ┌────────────────────────┐   │
    │ │ 👦 Lucas, 10 ans       │   │
    │ │ ⚠️ Lactose             │   │
    │ └────────────────────────┘   │
    │                              │
    │ [+ Ajouter un membre]        │
    └──────────────────────────────┘
  
  profile_detail: |
    ┌──────────────────────────────┐
    │ ← Julie                      │
    ├──────────────────────────────┤
    │ Photo    [📷]                │
    │ Prénom   [Julie]             │
    │ Âge      [7]                 │
    │                              │
    │ ALLERGIES                    │
    │ ┌────────────────────────┐   │
    │ │ 🥜 Noisettes  [⚠️]    │   │ ← Niveau : traces (⚠️)
    │ │ 🥜 Arachides  [❌]    │   │           ou critique (❌)
    │ └────────────────────────┘   │
    │ [+ Ajouter une allergie]     │
    │                              │
    │ ALERTES                      │
    │ ☑ Alerte critique (anaphyl.)│
    │ ☐ Alerte traces             │
    └──────────────────────────────┘
  
  allergen_selection: |
    # Sélecteur visuel (pas de liste texte)
    Grille 3x5 de :
    ┌───┐ ┌───┐ ┌───┐
    │🥜│ │🥛│ │🍞│
    └───┘ └───┘ └───┘
    ┌───┐ ┌───┐ ┌───┐
    │🦐│ │🥚│ │🐟│
    └───┘ └───┘ └───┘
    ...
    
    Tap once: sélectionne (⚠️ traces/intolérance)
    Tap again: critique (❌ allergie sévère)
    Tap third: désélectionne
    
    Couleur fond change selon sélection (⚠️ #FEF5E7, ❌ #FDEDEC)

FLOW 9 : Notifications

flow_notifications:
  # Règle : seules les notifications de SÉCURITÉ sont push
  # Tout le reste est in-app uniquement
  
  push_notifications:
    security_alert:
      trigger: "Allergène critique détecté pour un membre de la famille"
      title: "⚠️ Attention : {allergène} détecté"
      body: "{nom_produit} contient {allergène}. Interdit pour {membre}."
      tap_action: "Ouvre la fiche produit avec le verdict danger"
    
    new_alternative:
      trigger: "Nouveau produit alternatif disponible"
      title: "💡 Alternative trouvée"
      body: "Un produit similaire sans {allergène} est disponible."
      frequency: "Max 1/semaine"
      user_control: "Désactivable dans Réglages"
  
  in_app_notifications:
    types:
      - "Rappel liste de courses (si produits restants)"
      - "Résumé hebdomadaire (chaque lundi matin)"
      - "Nouvelle fonctionnalité"
    display: "Centre de notifications intégré (cloche dans top bar)"

PERFORMANCE & ANIMATIONS

5.1 Spécifications de performance perçue

Métrique Cible Méthode
Temps d'ouverture scanner < 300ms Pré-initialiser la caméra en arrière-plan
Affichage résultat scan < 500ms Skeleton screen immédiat, données async
Transition entre écrans 200-300ms Courbes ease-out, jamais > 400ms
Scroll FPS 60fps constants RecyclerView + pagination
Taille APK < 25 Mo Optimisation ressources, ProGuard

5.2 Animations standardisées

animations:
  screen_transition:
    push: "Slide from right, 250ms, decelerate"
    pop: "Slide to right, 200ms, accelerate"
  
  fab_morph:
    to_scanner: "ContainerTransform (Material), 300ms"
  
  scan_success:
    reticule_pulse: "scale 1.0 → 1.08 → 1.0, 200ms, ease-in-out"
    haptic: "Vibration 15ms, légère"
  
  verdict_appear:
    type: "Slide up + fade in"
    duration: 250ms
    stagger: "Contenu principal 0ms, actions +50ms chaque"
  
  list_check:
    type: "Strikethrough animé + scale down + coche apparition"
    duration: 300ms
  
  skeleton:
    type: "Shimmer gradient animé"
    gradient: "transparent → rgba(255,255,255,0.4) → transparent"
    duration: "1.5s, boucle infinie"

5.3 Transitions partagées (Shared Elements)

shared_elements:
  product_card_to_detail:
    elements:
      - product_image: "Corner radius animé (8dp → 16dp)"
      - product_name: "Position et taille animées"
    duration: 300ms
    interpolator: "FastOutSlowIn (Material standard)"

ACCESSIBILITÉ (OBLIGATOIRE)

6.1 Checklist WCAG 2.1 AA

accessibility_checklist:
  
  contrast:
    text_normal: "Ratio ≥ 4.5:1"
    text_large: "Ratio ≥ 3:1"
    ui_components: "Ratio ≥ 3:1"
    # Validation : test avec Accessibility Scanner (Android)
  
  color_blindness:
    deuteranopia: "Vert → Orange OK"  # Distinction testée
    protanopia: "Vert → Orange OK"
    tritanopia: "Rare, mais testé"
    # Règle : icônes + formes + couleurs, jamais couleur seule
  
  touch_targets:
    min_size: "48dp x 48dp"  # Obligatoire
    min_spacing: "8dp entre zones tactiles"
  
  screen_reader:
    talkback:
      - "Tout élément interactif a un contentDescription"
      - "Les images décoratives ont contentDescription = null"
      - "Les changements d'état sont annoncés (verdict, chargement)"
      - "Ordre de focus logique (gauche→droite, haut→bas)"
    
    announcements:
      scan_complete: "Produit scanné. {nom_produit}. {verdict}."
      verdict_change: "Attention : verdict modifié. {nouveau_verdict}."
      loading: "Analyse en cours, veuillez patienter."
  
  dynamic_text:
    scale: "Jusqu'à 200% sans perte de contenu"
    implementation: "sp (Android) / Dynamic Type (iOS)"
    testing: "Testé avec la plus grande taille système"
  
  keyboard_navigation:
    android: "DPad et clavier externe Bluetooth"
    focus_indicators: "Visibles (anneau #2ECC71, 2dp)"

6.2 Icônes & formes (Système daltonien)

VERT  (#2ECC71)      ORANGE (#E67E22)      ROUGE (#E74C3C)
─────                ────────               ──────
[CERCLE]             [TRIANGLE]             [LOSANGE]
 ✅ checkmark        ⚠️ exclamation         ❌ croix

Jamais l'un sans l'autre : forme + couleur + icône.

📐 GRILLE & LAYOUT

7.1 Système de grille

grid_system:
  type: "Grille 4 colonnes (mobile)"
  margins: "16dp gauche/droite"
  gutter: "12dp entre colonnes"
  
  responsive:
    phone_portrait: "4 colonnes, margins 16dp"
    phone_landscape: "6 colonnes, margins 24dp"
    tablet: "8 colonnes, margins 32dp, max-width 840dp"

7.2 Composants standardisés

components:
  product_card_compact:
    height: 88dp
    layout: |
      ┌──────────────────────────────┐
      │ [IMG]  Nom produit          │
      │ 48dp   Marque • Poids       │
      │        ┌────────┐            │
      │        │  ✅    │ ← Verdict │
      │        └────────┘   miniature│
      └──────────────────────────────┘
  
  verdict_banner:
    height: 56dp
    padding: 16dp horizontal, 12dp vertical
    radius: 12dp
    icon_size: 24dp
    background: "Dépend du verdict (voir couleur de fond)"
  
  action_button:
    height: 48dp
    radius: 12dp
    padding: 16dp horizontal
    text: "Button style (15sp, weight 600)"
    
    variants:
      primary: "Fond #2D3436, texte blanc"
      secondary: "Fond transparent, bordure #DFE6E9, texte #2D3436"
      danger: "Fond #E74C3C, texte blanc"
  
  chip_filter:
    height: 32dp
    radius: 16dp (pill)
    padding: 8dp horizontal
    selected: "Fond #2D3436, texte blanc"
    unselected: "Fond #F5F5F0, texte #636E72"
  
  input_field:
    height: 56dp
    radius: 12dp
    background: "#FFFFFF"
    border: "1dp #DFE6E9"
    focus_border: "2dp #2ECC71"
    label: "caption style, animé vers le haut au focus"

🗄️ SPÉCIFICATIONS TECHNIQUES

8.1 Architecture recommandée

architecture:
  pattern: "MVVM + Clean Architecture"
  
  layers:
    presentation:
      - "Jetpack Compose (Android) / SwiftUI (iOS)"
      - "ViewModel par écran"
      - "StateFlow / @Published pour état UI"
    
    domain:
      - "UseCases (ScanProductUseCase, GetAlternativesUseCase...)"
      - "Modèles métier (Product, Allergen, FamilyMember...)"
    
    data:
      - "Repository pattern"
      - "Room (base locale) pour historique et listes"
      - "Retrofit (API distante) pour base produits"
      - "DataStore pour préférences utilisateur"
  
  key_dependencies:
    android:
      camera: "CameraX (Google officiel)"
      barcode: "ML Kit Barcode Scanning (Google)"
      ocr: "ML Kit Text Recognition"
      navigation: "Compose Navigation"
      di: "Hilt"
    
    ios:
      camera: "AVFoundation"
      barcode: "Vision Framework"
      ocr: "Vision Framework"
      navigation: "NavigationStack"

8.2 Flux de données Scan

// Flux simplifié pour le scan
sealed class ScanUiState {
    object Idle : ScanUiState()
    data class CameraReady(val isPermissionGranted: Boolean) : ScanUiState()
    data class Scanning(val analyzedFrames: Int) : ScanUiState()
    object Analyzing : ScanUiState()  // → Skeleton screen
    data class Verdict(val product: Product, val verdict: VerdictType) : ScanUiState()
    data class Error(val errorType: ScanError, val recoveryAction: RecoveryAction) : ScanUiState()
}

enum class VerdictType {
    SAFE,       // ✅ OK famille
    WARNING,    // ⚠️ Allergène traces/intolérance
    DANGER      // ❌ Allergène critique
}

🧪 TESTS UX (Checklist validation)

validation_checklist:
  
  user_testing:
    - "5 utilisateurs, scenario 'scanner un produit dangereux'"
    - "Mesurer : temps pour comprendre le verdict"
    - "Objectif : < 2 secondes pour identifier le danger"
    - "Test daltonien : 1 utilisateur daltonien minimum"
  
  edge_cases:
    - "Scan rapide de 10 produits d'affilée"
    - "Rotation écran pendant le scan"
    - "Appel téléphonique interrompt le scan"
    - "Batterie faible (mode économie d'énergie)"
    - "Stockage presque plein"
  
  accessibility_audit:
    - "Test avec TalkBack activé, parcours complet"
    - "Test avec taille de texte maximale"
    - "Contraste vérifié avec Accessibility Scanner"

📋 DÉVELOPPEMENT V1 Scope minimal

v1_mvp_scope:
  
  must_have:
    - module: "Scanner code-barres + verdict immédiat"
    - module: "4 onglets navigation"
    - module: "Profils famille (multi-membres avec allergies)"
    - module: "Listes de courses basiques"
    - module: "Système de couleurs feu tricolore complet"
    - module: "Accessibilité de base (contraste + TalkBack)"
  
  nice_to_have_v1:
    - module: "OCR ingrédients sur photo"
    - module: "Dashboard contextuel (heure/lieu)"
    - module: "Alternatives intelligentes"
    - module: "Suivi statistiques"
  
  v2_planned:
    - "Partage liste en temps réel (collaboratif)"
    - "Scanner rayon (reconnaissance multiple)"
    - "Intégration drive/click&collect"
    - "Badge / scoring produit personnalisé"

🎨 FICHIERS À PRODUIRE

assets/
├── design_system/
│   ├── colors.xml / Colors.xcassets
│   ├── typography.xml / Typography.swift
│   ├── shapes.xml / Shapes.swift
│   └── icons/ (SVG export 24dp, 48dp)
│
├── screens/
│   ├── onboarding_1_welcome.png
│   ├── onboarding_2_allergies.png
│   ├── onboarding_3_goal.png
│   ├── onboarding_4_scan_tuto.png
│   ├── scan_active.png
│   ├── scan_skeleton.png
│   ├── verdict_ok.png
│   ├── verdict_warning.png
│   ├── verdict_danger.png
│   ├── product_detail.png
│   ├── lists_empty.png
│   ├── lists_detail.png
│   ├── tracking.png
│   ├── family_profiles.png
│   └── family_member_detail.png
│
├── components/
│   ├── fab_spec.png
│   ├── bottom_nav_spec.png
│   ├── verdict_banner_variants.png
│   ├── product_card_compact.png
│   └── error_states_all.png
│
└── flows/
    ├── flow_scan_complete.pdf
    ├── flow_onboarding.pdf
    └── flow_error_handling.pdf

Ce document constitue la spécification UX/UI de référence. Toute ambiguïté doit être remontée avant implémentation. Les principes fondateurs (section 1) prévalent sur les détails d'implémentation en cas de conflit.