1085 lines
41 KiB
Markdown
1085 lines
41 KiB
Markdown
# 📱 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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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)
|
||
|
||
```css
|
||
/* 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
|
||
|
||
```kotlin
|
||
// 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
|
||
|
||
```yaml
|
||
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)
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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)
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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
|
||
|
||
```kotlin
|
||
// 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)
|
||
|
||
```yaml
|
||
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
|
||
|
||
```yaml
|
||
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.** |