SafeBite/docs/architecture-ui-ux.md

817 lines
29 KiB
Markdown

# 🏗️ ARCHITECTURE UI-UX — SafeBite Android
**Document d'architecture pour la refonte UI/UX • Version 1.0 • 25 avril 2026**
---
## 📋 TABLE DES MATIÈRES
1. [État des lieux](#1-état-des-lieux)
2. [Principes directeurs](#2-principes-directeurs)
3. [Architecture de navigation cible](#3-architecture-de-navigation-cible)
4. [Design System](#4-design-system)
5. [Spécifications des écrans — Phase 1](#5-spécifications-des-écrans--phase-1)
6. [Composants réutilisables](#6-composants-réutilisables)
7. [Plan de migration](#7-plan-de-migration)
8. [Annexes](#8-annexes)
---
## 1. ÉTAT DES LIEUX
### 1.1 Architecture actuelle
L'application suit le pattern **MVVM + Clean Architecture** avec Jetpack Compose :
```
presentation/
├── MainActivity.kt # Point d'entrée
├── common/
│ ├── components/ # Composants UI réutilisables
│ │ ├── AppBars.kt # Barres de navigation
│ │ ├── Buttons.kt # Boutons standardisés
│ │ ├── Cards.kt # Cartes et surfaces
│ │ ├── Components.kt # Chips, banners, avatars
│ │ ├── Feedback.kt # Loading, erreurs
│ │ └── TextFields.kt # Champs de saisie
│ └── util/
│ └── UiState.kt # États UI génériques
├── navigation/
│ ├── NavGraph.kt # Navigation principale
│ └── Screen.kt # Routes
├── screen/
│ ├── home/HomeScreen.kt # Écran d'accueil (dashboard actuel)
│ ├── scanner/ScannerScreen.kt # Scanner code-barres
│ ├── result/ResultScreen.kt # Résultat d'analyse
│ ├── onboarding/ # Onboarding
│ ├── profile/ # Profils famille
│ ├── history/ # Historique
│ ├── settings/ # Paramètres
│ └── ocr/ # Capture OCR
└── theme/
├── Color.kt # Palette de couleurs
├── Type.kt # Typographie
├── Shape.kt # Formes
├── Dimens.kt # Espacements
├── StatusColors.kt # Couleurs de statut
└── Theme.kt # Thème Material 3
```
### 1.2 Écarts identifiés vs spec UX
| Élément | Spec UX (flux-UX.md) | Existant | Écart |
|---------|---------------------|----------|-------|
| **Navigation** | BottomNavigationView 4 onglets + FAB central | Navigation linéaire (Home → Scanner → Result) | 🔴 Majeur |
| **FAB Scanner** | FAB 56dp centré, chevauchant la bottom bar | Bouton dans HomeScreen | 🔴 Majeur |
| **Dashboard contextuel** | 3 modes (store/home/first) | HomeScreen basique | 🟠 Partiel |
| **Verdict immédiat** | Banner tricolore en < 500ms avec skeleton | ResultScreen complet mais sans skeleton | 🟠 Partiel |
| **Skeleton screen** | Shimmer animé pendant le loading | Spinner basique (LoadingIndicator) | 🟡 Mineur |
| **Couleurs** | Feu tricolore strict (#2ECC71, #E67E22, #E74C3C) | Palette Material 3 différente | 🟡 Mineur |
| **Animations** | Transitions standardisées (200-300ms) | Animations de base (fade + slide) | 🟡 Mineur |
| **Accessibilité** | TalkBack, contrastes WCAG AA | Partiel | 🟡 Mineur |
| **Onglet Listes** | 📋 Listes intelligentes | Absent | 🔴 Majeur |
| **Onglet Suivi** | 📊 Statistiques + historique | HistoryScreen basique | 🟠 Partiel |
### 1.3 Points forts de l'existant
- Architecture Clean Architecture bien structurée
- Jetpack Compose avec Material 3
- Thème light/dark fonctionnel
- Composants de base réutilisables
- Domain layer avec UseCases
- Repository pattern avec Room + Retrofit
- Hilt pour l'injection de dépendances
---
## 2. PRINCIPES DIRECTEURS
### 2.1 Règles prioritaires (de flux-UX.md §1)
| # | Principe | Implication technique |
|---|----------|----------------------|
| P1 | **2 taps max** | Le scanner doit être accessible depuis n'importe quel écran en 2 taps |
| P2 | **Verdict immédiat** | Affichage du verdict en < 500ms perçues (skeleton immédiat, données async) |
| P3 | **Feu tricolore** | 3 couleurs sémantiques max jamais de bleu pour les statuts |
| P4 | **Icônes + couleurs** | Aucune info critique basée uniquement sur la couleur |
| P5 | **Guidage positif** | Pas d'erreurs brutes toujours une action de repli |
| P6 | **Mobile-first** | Conception une main, pouce accessible (zone inférieure) |
### 2.2 Contraintes techniques
- **Minimum SDK** : API 26 (Android 8.0)
- **Framework UI** : Jetpack Compose (Material 3)
- **Navigation** : Compose Navigation
- **Caméra** : CameraX + ML Kit Barcode Scanning
- **Base locale** : Room
- **Taille APK cible** : < 25 Mo
---
## 3. ARCHITECTURE DE NAVIGATION CIBLE
### 3.1 Structure globale
```
┌─────────────────────────────────────────────────────┐
│ APPLICATION │
├─────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────┐ │
│ │ Dashboard│ │ Listes │ │ Suivi │ │Famille│ │
│ │ 🏠 │ │ 📋 │ │ 📊 │ │ 👤 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────┘ │
│ ▲ │
│ │ │
│ ┌────┴─────────┐ <-- FAB central (56dp) │
│ │ SCANNER │ Chevauche la bottom bar │
│ └──────────────┘ Disparaît pendant le scan │
└─────────────────────────────────────────────────────┘
```
### 3.2 Nouvelles routes
```kotlin
sealed class Screen(val route: String) {
// ── Onglets principaux (Bottom Navigation) ──
data object Dashboard : Screen("dashboard") // Remplace Home
data object Lists : Screen("lists") // Nouvel onglet
data object Tracking : Screen("tracking") // Remplace History
data object Family : Screen("family") // Remplace ProfileList
// ── Écrans de navigation (non dans bottom nav) ──
data object Scanner : Screen("scanner") // Plein écran
data object Result : Screen("result/{barcode}") // Bottom sheet → plein écran
data object ProductDetail : Screen("product/{id}")// Fiche détaillée avec tabs
data object Onboarding : Screen("onboarding")
data object Settings : Screen("settings")
// ── Sous-écrans ──
data object ListDetail : Screen("list/{id}")
data object ListEdit : Screen("list/edit?id={id}")
data object ProfileEdit : Screen("profile/edit?id={id}")
data object OcrCapture : Screen("ocr/capture")
data object OcrReview : Screen("ocr/review")
}
```
### 3.3 Bottom Navigation — Spécification
```kotlin
data class BottomNavItem(
val id: String,
val iconSelected: ImageVector, // Icône remplie
val iconUnselected: ImageVector, // Icône outline
val label: String,
val contentDescription: String, // TalkBack
val badge: StateFlow<Int> = MutableStateFlow(0) // Notifications non lues
)
val bottomNavItems = listOf(
BottomNavItem(
id = "dashboard",
iconSelected = Icons.Filled.Home,
iconUnselected = Icons.Outlined.Home,
label = "Accueil",
contentDescription = "Tableau de bord"
),
BottomNavItem(
id = "lists",
iconSelected = Icons.Filled.List,
iconUnselected = Icons.Outlined.List,
label = "Listes",
contentDescription = "Mes listes de courses"
),
BottomNavItem(
id = "tracking",
iconSelected = Icons.Filled.BarChart,
iconUnselected = Icons.Outlined.BarChart,
label = "Suivi",
contentDescription = "Statistiques et historique"
),
BottomNavItem(
id = "family",
iconSelected = Icons.Filled.People,
iconUnselected = Icons.Outlined.People,
label = "Famille",
contentDescription = "Profils et réglages"
)
)
```
### 3.4 FAB — Spécification technique
```yaml
fab_scanner:
size: 56dp
icon: Icons.Filled.QrCodeScanner (24dp)
container_color: "#2D3436" # Noir doux
icon_color: "#FFFFFF"
elevation: 6dp
corner_radius: 16dp # Material 3 standard
position:
horizontal: center
vertical: "bottom bar top - 28dp" # Chevauchement
behavior:
visible_on_tabs: ["dashboard", "lists", "tracking", "family"]
hidden_on: ["scanner", "result", "product_detail"]
animation_disappear: "scale(1→0.8) + alpha(1→0), 200ms"
animation_appear: "scale(0.8→1) + alpha(0→1), 200ms"
haptic_feedback: 15ms au tap
accessibility:
content_description: "Scanner un produit"
test_id: "fab_scanner"
```
---
## 4. DESIGN SYSTEM
### 4.1 Palette de couleurs — Alignement spec ↔ existant
La spec UX demande des couleurs spécifiques qui diffèrent de la palette Material 3 actuelle. Voici le mapping :
```kotlin
// ── COULEURS SÉMANTIQUES (Feu tricolore — hors M3) ──
// Ces couleurs restent indépendantes du thème M3 pour cohérence marque
object SemanticColors {
// Light mode
val Safe = Color(0xFF2ECC71) // Vert sécurité
val SafeContainer = Color(0xFFE8F8F5) // Fond très clair
val Warning = Color(0xFFE67E22) // Orange attention
val WarningContainer = Color(0xFFFEF5E7)
val Danger = Color(0xFFE74C3C) // Rouge danger
val DangerContainer = Color(0xFFFDEDEC)
// Dark mode
val SafeDark = Color(0xFF2ECC71)
val SafeContainerDark = Color(0xFF1A3A2A)
val WarningDark = Color(0xFFE67E22)
val WarningContainerDark = Color(0xFF3A2A1A)
val DangerDark = Color(0xFFE74C3C)
val DangerContainerDark = Color(0xFF3A1A1A)
}
// ── NEUTRES ──
object NeutralColors {
val Background = Color(0xFFF5F5F0) // Gris chaud (spec §2.1)
val Surface = Color(0xFFFFFFFF) // Blanc pur pour cartes
val TextPrimary = Color(0xFF2D3436) // Noir doux
val TextSecondary = Color(0xFF636E72) // Gris moyen
val Separator = Color(0xFFDFE6E9) // Gris clair
}
```
### 4.2 Typographie
```kotlin
object SafeBiteTypography {
val Display = TextStyle(
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
lineHeight = 36.sp
)
val Headline = TextStyle(
fontSize = 22.sp,
fontWeight = FontWeight.SemiBold,
lineHeight = 28.sp
)
val Body = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
lineHeight = 24.sp
)
val Caption = TextStyle(
fontSize = 13.sp,
fontWeight = FontWeight.Normal,
lineHeight = 18.sp
)
val Button = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold,
lineHeight = 20.sp
)
}
```
### 4.3 Système d'icônes de statut (daltonien-safe)
```
┌─────────────────────────────────────────────────────────┐
│ VERT (#2ECC71) ORANGE (#E67E22) ROUGE (#E74C3C) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ ⭕ │ │ 🔺 │ │ 🔷 │ │
│ │ ✅ │ │ ⚠️ │ │ ❌ │ │
│ │ CERCLE │ │ TRIANGLE │ │ LOSANGE │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ JAMAIS l'un sans l'autre : forme + couleur + icône │
└─────────────────────────────────────────────────────────┘
```
### 4.4 Élévation & Ombres
```kotlin
object ElevationTokens {
val Card = 2.dp // Ombre: 0 1px 3px rgba(0,0,0,0.08)
val FAB = 6.dp // Ombre: 0 3px 8px rgba(0,0,0,0.16)
val BottomSheet = 16.dp // Ombre: 0 8px 24px rgba(0,0,0,0.20)
val Dialog = 24.dp // Ombre: 0 12px 32px rgba(0,0,0,0.24)
}
```
---
## 5. SPÉCIFICATIONS DES ÉCRANS — PHASE 1
### 5.1 Écran Scanner (refonte)
**Fichier cible** : `app/src/main/java/com/safebite/app/presentation/screen/scanner/ScannerScreen.kt`
```yaml
screen_scanner:
type: "Plein écran (remplace le contenu de l'onglet actif)"
transition_in: "ContainerTransform depuis FAB, 300ms"
transition_out: "Reverse ContainerTransform, 250ms"
layout:
top_bar:
type: "Transparent overlay"
elements:
- {left: "← Retour", action: "popBackStack"}
- {right: "💡 Astuce", action: "showTips"}
camera_zone:
coverage: "70% de l'écran"
reticule:
size: "80% width, 30% height"
border: "4dp blanc, coins arrondis 12dp"
animation: "Ligne laser verte animée (haut → bas, 1.8s loop)"
instruction_text:
content: "Placez le code-barres dans le cadre"
position: "Sous le réticule, centré"
style: "Body, blanc, fond semi-transparent"
bottom_bar:
elements:
- {id: "manual_entry", label: "Saisie manuelle", icon: "keyboard"}
- {id: "torch", label: "Flash", icon: "flash_on/off", toggle: true}
states:
idle: "Camera preview + réticule + instructions"
scanning: "Réticule pulse (scale 1.0 → 1.05 → 1.0, 200ms)"
analyzing: "→ Transition vers skeleton (voir ResultScreen)"
error_camera: "Message + lien Réglages + saisie manuelle"
error_permission: "Dialog rationale + fallback saisie manuelle"
performance:
open_time: "< 300ms"
method: "Pré-initialiser CameraProvider en arrière-plan"
```
**Changements par rapport à l'existant** :
- Réticule animé existe déjà (ScanOverlay)
- Transition ContainerTransform depuis FAB à ajouter
- Top bar transparente overlay à ajouter
- Saisie manuelle du code-barres à ajouter
- Pré-initialisation caméra à implémenter
### 5.2 Écran Verdict (refonte majeure)
**Fichier cible** : `app/src/main/java/com/safebite/app/presentation/screen/result/ResultScreen.kt`
```yaml
screen_verdict:
type: "Bottom Sheet → Plein écran au scroll"
transition_in: "Slide up from bottom, 250ms, ease-out"
# ── PHASE 1 : Skeleton immédiat ──
skeleton:
type: "Shimmer gradient animé"
duration: "1.5s loop"
layout: |
┌──────────────────────────────┐
│ ████████████ (nom produit) │
│ ██████ (marque) │
│ ░░░░░░░░░░░░ (verdict) │ ← Background coloré
│ │
│ ████████████ │
│ ██████████ │
└──────────────────────────────┘
# ── PHASE 2 : Verdict ──
verdict_banner:
height: 56dp minimum
padding: "16dp horizontal, 12dp vertical"
radius: 12dp
icon_size: 24dp
variants:
ok:
text: "✅ OK pour toute la famille"
background: "#E8F8F5"
icon: "checkmark.shield.fill"
icon_color: "#2ECC71"
warning:
text: "⚠️ Contient : NOISETTES"
subtext: "⚠️ Attention pour Julie"
background: "#FEF5E7"
icon: "exclamationmark.shield.fill"
icon_color: "#E67E22"
allergene_style: "bold, #E67E22"
danger:
text: "❌ Contient : ARACHIDES"
subtext: "❌ Interdit pour Julie (anaphylaxie)"
extra: "Ne pas consommer"
background: "#FDEDEC"
icon: "xmark.shield.fill"
icon_color: "#E74C3C"
allergene_style: "bold, #E74C3C"
# ── PHASE 3 : Actions ──
actions:
stagger_animation: "Chaque action +50ms de décalage"
buttons:
- {id: "details", label: "Voir détails", priority: primary, icon: "info"}
- {id: "alternatives", label: "Voir alternatives", priority: secondary, icon: "swap"}
- {id: "add_to_list", label: "Ajouter à la liste", priority: secondary, icon: "plus"}
- {id: "scan_again", label: "Scanner un autre", priority: tertiary, icon: "camera"}
# ── Accessibilité ──
accessibility:
announcement: "Verdict : {status}. {details}. Actions : voir détails, alternatives, ajouter à la liste."
talkback_order: "Verdict banner → Product info → Actions (top to bottom)"
```
**Changements par rapport à l'existant** :
- SafetyStatusBanner existe déjà (à adapter aux nouvelles couleurs)
- Skeleton screen shimmer à ajouter (remplacer LoadingIndicator)
- Bottom sheet transition à ajouter
- Stagger animation sur les actions à ajouter
- Verdict variants (warning/danger) à enrichir avec noms profils
- Bouton "Alternatives" à ajouter
### 5.3 Dashboard contextuel (nouvel écran)
**Fichier cible** : `app/src/main/java/com/safebite/app/presentation/screen/dashboard/DashboardScreen.kt` (nouveau)
```yaml
screen_dashboard:
type: "Écran principal (onglet Accueil)"
replaces: "HomeScreen actuel"
# ── Détection contextuelle ──
context_detection:
store_mode:
trigger: "Géolocalisation magasin OU heure 8h-20h semaine"
confidence: "Score basé sur multiples signaux"
home_mode:
trigger: "Soirée (après 20h) OU weekend"
first_time:
trigger: "Aucun scan dans l'historique"
# ── Mode magasin ──
store_layout: |
┌──────────────────────────────┐
│ 🛒 Vous êtes en magasin ? │
│ │
│ [Scanner rapide] ← Large │
│ │
│ Votre liste en cours : │
│ ┌──────────────────────┐ │
│ │ 🥛 Lait demi-écrémé │ │
│ │ 🍞 Pain complet │ │
│ │ 🍎 Pommes x6 │ │
│ └──────────────────────┘ │
│ │
│ "3 produits restants" │
└──────────────────────────────┘
# ── Mode maison ──
home_layout: |
┌──────────────────────────────┐
│ 👋 Bonjour, Sophie │
│ │
│ 📊 Cette semaine : │
│ ████████░░ 78% produits OK │
│ │
│ 🔍 Derniers scans : │
│ ✅ Biscuit Choco │
│ ⚠️ Sauce Curry │
│ │
│ [Scanner] [Mes listes] │
└──────────────────────────────┘
# ── Premier lancement ──
first_time_layout: |
┌──────────────────────────────┐
│ 🎉 Prêt à commencer ! │
│ │
│ 📷 Scannez votre premier │
│ produit │
│ │
│ [Commencer →] │
└──────────────────────────────┘
viewmodel:
state: "DashboardUiState"
sealed_class: |
sealed class DashboardUiState {
object Loading : DashboardUiState()
data class StoreMode(val activeList: ShoppingList?, val remainingCount: Int) : DashboardUiState()
data class HomeMode(val userName: String, val weeklyStats: WeeklyStats, val recentScans: List<ScanHistory>) : DashboardUiState()
object FirstTime : DashboardUiState()
data class Error(val message: String) : DashboardUiState()
}
```
### 5.4 Fiche produit détaillée (nouvel écran)
**Fichier cible** : `app/src/main/java/com/safebite/app/presentation/screen/product/ProductDetailScreen.kt` (nouveau)
```yaml
screen_product_detail:
type: "Bottom Sheet → Plein écran au scroll"
trigger: "Tap 'Voir détails' depuis verdict OU historique"
tabs:
- id: "resume"
label: "Résumé"
icon: Icons.Filled.List
content:
- "Verdict sécurité (répété)"
- "Nutri-Score visuel (A-E, pastilles)"
- "Calories / 100g"
- "Jauges sucre/sel/gras"
- id: "allergenes"
label: "Allergènes"
icon: Icons.Filled.Warning
content:
- "14 allergènes réglementaires"
- "Statut : Présent ❌ / Traces ⚠️ / Absent ✅"
- "Allergènes famille highlightés"
- id: "additifs"
label: "Additifs"
icon: Icons.Filled.Science
content:
- "Liste additifs code E"
- "Couleur : Vert/Orange/Rouge"
- "Description courte"
- "Lien 'En savoir plus' → WebView"
- id: "alternatives"
label: "Alternatives"
icon: Icons.Filled.SwapHoriz
condition: "Affiché si verdict != OK"
content:
- "Carousel horizontal produits"
- "Critère : même catégorie, sans allergène"
- "Carte : photo + nom + verdict mini"
```
---
## 6. COMPOSANTS RÉUTILISABLES
### 6.1 Nouveaux composants à créer
```kotlin
// ── VerdictBanner ──
@Composable
fun VerdictBanner(
verdict: VerdictType, // SAFE, WARNING, DANGER
message: String,
subMessage: String? = null,
allergenName: String? = null,
profileName: String? = null,
modifier: Modifier = Modifier
)
// ── SkeletonScreen ──
@Composable
fun ProductSkeleton(
modifier: Modifier = Modifier
)
// ── ActionButton (standardisé) ──
@Composable
fun ActionButton(
text: String,
onClick: () -> Unit,
icon: ImageVector? = null,
variant: ButtonVariant = ButtonVariant.Primary, // Primary, Secondary, Danger, Tertiary
modifier: Modifier = Modifier
)
// ── VerdictMiniBadge (pour carrousels) ──
@Composable
fun VerdictMiniBadge(
verdict: VerdictType,
size: Dp = 24.dp
)
// ── ProgressBar circulaire (dashboard) ──
@Composable
fun CircularProgress(
progress: Float, // 0.0 - 1.0
label: String,
color: Color,
size: Dp = 120.dp
)
// ── Sparkline (graphique évolution) ──
@Composable
fun SparklineChart(
data: List<Float>,
color: Color = MaterialTheme.colorScheme.primary,
modifier: Modifier = Modifier
)
// ── AllergenGrid (sélection profils) ──
@Composable
fun AllergenSelectionGrid(
allergens: List<AllergenType>,
selections: Map<AllergenType, AllergenLevel>,
onSelectionChange: (AllergenType, AllergenLevel) -> Unit
)
enum class AllergenLevel {
NONE, // Non sélectionné
TRACE, // ⚠️ Intolérance/traces
SEVERE // ❌ Allergie sévère
}
```
### 6.2 Composants existants à adapter
| Composant | Fichier actuel | Modification nécessaire |
|-----------|---------------|------------------------|
| `SafetyStatusBanner` | `Components.kt:90` | Adapter couleurs spec + formes daltonien |
| `ProductCard` | `Components.kt:118` | Ajouter verdict mini badge |
| `AllergenChip` | `Components.kt:58` | Ajouter 3 états (absent/traces/présent) |
| `PrimaryButton` | `Buttons.kt` | Renommer en `ActionButton` avec variants |
| `OutlinedActionButton` | `Buttons.kt` | Intégrer dans `ActionButton` variant Secondary |
| `LoadingIndicator` | `Feedback.kt` | Remplacer par `ProductSkeleton` |
| `SafeBiteTopAppBar` | `AppBars.kt` | Ajouter support overlay transparent |
---
## 7. PLAN DE MIGRATION
### 7.1 Phase 1 — Scanner, Verdict, Dashboard (priorité haute)
| Étape | Action | Fichiers | Effort |
|-------|--------|----------|--------|
| 1.1 | Créer la Bottom Navigation | `NavGraph.kt`, `Screen.kt`, nouveau `MainScreen.kt` | Moyen |
| 1.2 | Créer le FAB central | `MainScreen.kt`, `components/Buttons.kt` | Moyen |
| 1.3 | Adapter les couleurs du Design System | `Color.kt`, `StatusColors.kt` | Faible |
| 1.4 | Refondre ScannerScreen (transitions, saisie manuelle) | `ScannerScreen.kt` | Moyen |
| 1.5 | Créer le Skeleton Screen | Nouveau `components/Feedback.kt` | Faible |
| 1.6 | Refondre VerdictBanner (3 variantes, accessibilité) | `Components.kt` | Moyen |
| 1.7 | Créer DashboardScreen (3 modes contextuels) | Nouveau `screen/dashboard/` | Élevé |
| 1.8 | Adapter ResultScreen (bottom sheet, stagger actions) | `ResultScreen.kt` | Moyen |
### 7.2 Phase 2 — Listes intelligentes
| Étape | Action | Fichiers |
|-------|--------|----------|
| 2.1 | Créer ListsScreen (liste des listes) | Nouveau `screen/lists/` |
| 2.2 | Créer ListDetailScreen (détail d'une liste) | Nouveau `screen/lists/` |
| 2.3 | Implémenter swipe actions | `ListDetailScreen.kt` |
| 2.4 | Intégrer alertes allergies dans les listes | `ListDetailScreen.kt` |
### 7.3 Phase 3 — Suivi & Statistiques
| Étape | Action | Fichiers |
|-------|--------|----------|
| 3.1 | Créer TrackingScreen (stats + historique) | Nouveau `screen/tracking/` |
| 3.2 | Implémenter CircularProgress | `components/Feedback.kt` |
| 3.3 | Implémenter SparklineChart | Nouveau `components/Charts.kt` |
| 3.4 | Migrer HistoryScreen vers TrackingScreen | `screen/history/` `screen/tracking/` |
### 7.4 Phase 4 — Profils famille (améliorations)
| Étape | Action | Fichiers |
|-------|--------|----------|
| 4.1 | Refondre FamilyScreen (grille profils) | `screen/profile/` |
| 4.2 | Créer AllergenSelectionGrid | `components/` |
| 4.3 | Adapter ProfileEditScreen (3 états allergie) | `screen/profile/ProfileEditScreen.kt` |
---
## 8. ANNEXES
### 8.1 États UI — Scan (mis à jour)
```kotlin
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,
val matchingProfiles: List<ProfileMatch>
) : 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
}
data class ProfileMatch(
val profileName: String,
val matchedAllergen: AllergenType,
val severity: AllergenLevel
)
```
### 8.2 Checklist accessibilité
```yaml
accessibilite:
contrastes:
texte_normal: "Ratio ≥ 4.5:1"
texte_grand: "Ratio ≥ 3:1"
composants_ui: "Ratio ≥ 3:1"
validation: "Accessibility Scanner Android"
daltonisme:
regle: "Jamais couleur seule — toujours forme + icône + couleur"
test: "Utilisateur daltonien minimum"
zones_tactiles:
taille_min: "48dp x 48dp"
espacement_min: "8dp entre zones"
talkback:
content_description: "Tout élément interactif"
images_decoratives: "contentDescription = null"
annonces_etat: "Verdict, chargement, erreurs"
ordre_focus: "Gauche→droite, haut→bas"
texte_dynamique:
scale_max: "200% sans perte de contenu"
implementation: "sp (pas dp pour le texte)"
```
### 8.3 Performance perçue — Objectifs
| Métrique | Cible | Méthode |
|----------|-------|---------|
| Ouverture scanner | < 300ms | Pré-initialisation caméra |
| Affichage verdict | < 500ms | Skeleton immédiat + données async |
| Transition écrans | 200-300ms | ease-out, jamais > 400ms |
| Scroll FPS | 60fps | LazyColumn + pagination |
| Taille APK | < 25 Mo | R8/ProGuard, optimisation ressources |
### 8.4 Diagramme de flux — Scan complet
```mermaid
flowchart TD
A[FAB Scanner] -->|Tap| B[ScannerScreen]
B -->|Code-barres détecté| C[Vibration 15ms]
C -->|Transition| D[Skeleton Screen]
D -->|Données reçues| E{Verdict}
E -->|SAFE| F[VerdictBanner Vert]
E -->|WARNING| G[VerdictBanner Orange]
E -->|DANGER| H[VerdictBanner Rouge]
F --> I[Actions disponibles]
G --> I
H --> I
I -->|Voir détails| J[ProductDetailScreen]
I -->|Alternatives| K[Carousel Alternatives]
I -->|Ajouter liste| L[Dialog Sélection liste]
I -->|Scanner autre| B
D -->|Timeout 3s| M[Analyse en cours...]
M -->|Timeout 8s| N[ErrorScreen]
N -->|Réessayer| B
N -->|Saisie manuelle| O[Dialog Code-barres]
O -->|Valider| D
```
---
**Ce document constitue la référence pour la refonte UI-UX de l'application SafeBite Android. Il sera mis à jour au fur et à mesure de l'implémentation.**