# 🏗️ 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 = 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) : 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, color: Color = MaterialTheme.colorScheme.primary, modifier: Modifier = Modifier ) // ── AllergenGrid (sélection profils) ── @Composable fun AllergenSelectionGrid( allergens: List, selections: Map, 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 ) : 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.**