SafeBite/docs/architecture-ui-ux.md

29 KiB

🏗️ 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
  2. Principes directeurs
  3. Architecture de navigation cible
  4. Design System
  5. Spécifications des écrans — Phase 1
  6. Composants réutilisables
  7. Plan de migration
  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

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

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

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 :

// ── 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

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

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

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

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)

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)

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

// ── 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)

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é

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

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.