16 KiB
Cahier des Charges — Application Utilisateur Mobile & Web
Projet : Laverie Connectée — App Flutter
Version : 1.0
Date : 2026-06-27
Stack principale : Flutter 3.x (Android / iOS / Web)
1. Contexte & Objectifs
1.1 Contexte
Application grand public permettant aux utilisateurs de :
- Localiser les laveries disponibles
- Gérer leur porte-monnaie électronique
- Réserver un créneau machine
- Déclencher un lavage par QR code
- Suivre leurs lavages et consulter leur historique
1.2 Cibles plateformes
| Plateforme | Version minimale | Distribution |
|---|---|---|
| Android | 8.0 (API 26) | Google Play Store |
| iOS | 15.0 | Apple App Store |
| Web | Chrome 100+, Safari 15+, Firefox 100+ | PWA hébergée |
1.3 Contraintes
- L'app doit rester utilisable hors-ligne pour les fonctions de consultation (solde en cache, historique)
- Respect strict du RGPD : collecte minimale, consentements explicites, droit à l'effacement
- L'application doit rester connectée en arrière-plan pour recevoir les notifications push
2. Stack Technique
| Composant | Technologie | Version | Justification |
|---|---|---|---|
| Framework UI | Flutter | 3.x (stable) | Codebase unique Android/iOS/Web |
| Langage | Dart | 3.x | Null-safety, typage fort |
| State management | Riverpod | 2.x | Réactivité, testabilité, pas de BuildContext dependency |
| Navigation | GoRouter | 13.x | Deep links, routing déclaratif |
| HTTP Client | Dio | 5.x | Intercepteurs JWT, retry automatique |
| Cache local | Hive | 2.x | Clé-valeur rapide, offline-first |
| Auth persistence | flutter_secure_storage | 9.x | Stockage sécurisé tokens (Keychain / Keystore) |
| Notifications push | firebase_messaging | 15.x | FCM Android/Web + APNs iOS |
| QR Code scan | mobile_scanner | 5.x | Caméra native, performant |
| Paiement in-app | flutter_stripe | 10.x | Stripe SDK officiel |
| Géolocalisation | geolocator | 13.x | Localisation établissements |
| Cartes | flutter_map + OpenStreetMap | — | Affichage carte sans coût API Google |
| Internationalisation | flutter_localizations | (Flutter) | fr / en au minimum |
| Tests | flutter_test + Mockito | — | Unit + widget + integration tests |
| CI/CD | GitHub Actions + Fastlane | — | Build, test, déploiement stores |
| Monitoring | Firebase Crashlytics | — | Crashes prod |
| Analytics | Firebase Analytics | — | Entonnoirs, rétention |
3. Architecture de l'Application
3.1 Pattern architectural : Clean Architecture + Feature-first
lib/
├── core/
│ ├── api/
│ │ ├── api_client.dart # Instance Dio configurée
│ │ ├── interceptors/
│ │ │ ├── auth_interceptor.dart # Injection Bearer token
│ │ │ └── retry_interceptor.dart # Retry sur 401 avec refresh
│ │ └── api_exception.dart
│ ├── cache/
│ │ └── hive_service.dart # Abstraction cache local
│ ├── router/
│ │ └── app_router.dart # GoRouter centralisé
│ ├── theme/
│ │ ├── app_theme.dart
│ │ └── app_colors.dart
│ └── utils/
│ ├── currency_formatter.dart
│ └── date_formatter.dart
│
├── features/
│ ├── auth/
│ │ ├── data/
│ │ │ ├── auth_repository.dart
│ │ │ └── models/auth_token.dart
│ │ ├── domain/
│ │ │ └── providers/auth_provider.dart
│ │ └── presentation/
│ │ ├── login_screen.dart
│ │ └── register_screen.dart
│ │
│ ├── wallet/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ │ ├── wallet_screen.dart
│ │ ├── transaction_history_screen.dart
│ │ └── top_up_screen.dart
│ │
│ ├── establishments/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ │ ├── map_screen.dart
│ │ ├── establishment_list_screen.dart
│ │ └── establishment_detail_screen.dart
│ │
│ ├── machines/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ │ ├── machine_card_widget.dart
│ │ └── machine_detail_screen.dart
│ │
│ ├── booking/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ │ ├── slot_picker_screen.dart
│ │ ├── booking_confirm_screen.dart
│ │ └── my_bookings_screen.dart
│ │
│ ├── wash/
│ │ ├── data/
│ │ ├── domain/
│ │ └── presentation/
│ │ ├── qr_scanner_screen.dart
│ │ ├── wash_confirm_screen.dart
│ │ └── wash_active_screen.dart
│ │
│ ├── notifications/
│ │ └── notification_service.dart
│ │
│ └── profile/
│ └── presentation/
│ ├── profile_screen.dart
│ └── gdpr_settings_screen.dart
│
└── main.dart
3.2 Flux d'authentification
App start
└── AuthProvider.check()
├── Token valide → HomeScreen
├── Token expiré → RefreshToken()
│ ├── Succès → HomeScreen
│ └── Échec → LoginScreen
└── Pas de token → OnboardingScreen
3.3 Gestion du token JWT
AuthInterceptor(Dio) : injectAuthorization: Bearer <token>sur chaque requête- Sur 401 : appel automatique à
/auth/refresh→ retry de la requête originale - Tokens stockés dans
flutter_secure_storage(Keychain iOS / Keystore Android) - Sur Web : stockage en
sessionStoragechiffré (pas delocalStoragepour les tokens)
4. Écrans & Parcours Utilisateur
4.1 Navigation principale (Bottom Navigation Bar)
┌─────────────────────────────────────────────┐
│ 🗺 Carte │ 💰 Wallet │ 📅 Réservations │ 👤 Profil │
└─────────────────────────────────────────────┘
4.2 Onboarding & Inscription
Écran 1 — Bienvenue
- Logo + baseline
- CTA : "Se connecter" / "Créer un compte"
Écran 2 — Inscription (multi-étapes)
- Étape 1 : Email + mot de passe (+ confirmation)
- Étape 2 : Prénom, nom, téléphone, date de naissance
- Étape 3 : Consentements RGPD (cases à cocher individuelles, non pré-cochées)
- Traitement des données (obligatoire)
- Communications marketing (optionnel)
- Analyse d'utilisation (optionnel)
- Étape 4 : Code parrainage (optionnel)
- Confirmation email requise avant accès complet
Règles de validation :
- Email : format RFC 5322
- Mot de passe : 8 caractères minimum, 1 majuscule, 1 chiffre
- Téléphone : format E.164 (
+33XXXXXXXXX) - Date de naissance : 16 ans minimum
4.3 Carte & Établissements
Écran Carte (MapScreen)
- Carte OpenStreetMap centrée sur la position utilisateur
- Marqueurs pour chaque établissement (couleur selon disponibilité)
- 🟢 Machines disponibles
- 🟡 Toutes occupées, créneaux libres
- 🔴 Toutes occupées, aucun créneau proche
- Tap sur marqueur → sheet inférieure avec résumé
- Bouton liste (switch vue liste / carte)
Écran Détail Établissement
- Nom, adresse, horaires
- Liste des machines avec statut temps réel (polling 30s ou WebSocket si dispo)
- Pour chaque machine : type, statut, prochain créneau libre, tarif actuel
- CTA : "Réserver" ou "Scanner & Lancer"
4.4 Porte-monnaie
Écran Wallet
- Solde affiché en grand (avec animation de mise à jour)
- Bouton "Recharger" bien visible
- Historique des 10 dernières transactions
- Lien "Voir tout"
Écran Historique
- Liste paginée (infinite scroll)
- Filtres : Type (débit/crédit), Période
- Chaque entrée : icône type, montant coloré, source, date/heure, solde après
- Export CSV (optionnel, v2)
Parcours Rechargement
- Saisie du montant (suggestions : 10€, 20€, 50€)
- Sélection du fournisseur de paiement (si plusieurs disponibles)
- Redirection vers Stripe Checkout (WebView sécurisée) ou SDK natif
- Retour sur l'app via deep link
laverie://wallet/topup-result - Confirmation animée + mise à jour du solde
- Notification push de confirmation
Contraintes :
- Montant minimum : 5€, maximum : 150€ par rechargement
- Affichage du solde en cache si hors-ligne
4.5 Réservation d'un Créneau
Écran Sélection de Créneau (SlotPickerScreen)
- Sélecteur de date (calendrier scrollable, 14 jours maximum)
- Pour la date sélectionnée : grille horaire avec créneaux libres/occupés
- Mise en évidence du prix par créneau (heure creuse / heure pleine)
- Information : "Supplément réservation : +1.00€"
Écran Confirmation Réservation
- Récapitulatif : machine, date/heure, durée, prix total
- Solde actuel et solde après débit
- Mention pénalité no-show
- Bouton "Confirmer et débiter X.XX€"
Écran Mes Réservations
- Onglets : À venir / Passées
- Chaque réservation : statut badge, machine, créneau, montant
- Actions possibles : "Annuler", "Déplacer" (si > 2h)
- Countdown pour les réservations imminentes
Règles métier UI :
- Créneau non réservable si solde insuffisant → CTA "Recharger d'abord"
- Annulation : popup de confirmation avec montant remboursé affiché
- Déplacement : ouvre SlotPickerScreen avec le même contexte machine
4.6 Déclenchement d'un Lavage par QR Code
Écran Scanner QR (QrScannerScreen)
- Vue caméra plein écran avec cadre de scan
- Feedback visuel à la détection (vibration + flash vert)
- Fallback : saisie manuelle du code machine
Flux post-scan :
- Identification de la machine via le QR
- Appel API → vérification statut + tarif
- Affichage récap : machine, programme, prix estimé, solde après
- Bouton "Lancer le lavage"
- Animation de confirmation (spinner → ✓)
- Écran "Lavage en cours" avec countdown
Écran Lavage Actif (WashActiveScreen)
- Machine + établissement
- Barre de progression avec temps restant (mise à jour polling API 30s)
- Montant débité
- Notification push à la fin du cycle
4.7 Notifications Push
Configuration :
- Demande de permission notifications à l'inscription (après onboarding)
- Sur iOS :
UNUserNotificationCenter.requestAuthorization - Sur Android 13+ :
POST_NOTIFICATIONSpermission - Token FCM/APNs envoyé à l'API à chaque démarrage de l'app (mise à jour si changé)
Gestion en foreground :
- Affichage d'une bannière in-app (snackbar stylisée)
- Tap → navigation vers l'écran pertinent
Deep links depuis notification :
| Notification | Deep link |
|---|---|
| Rechargement confirmé | laverie://wallet |
| Réservation confirmée | laverie://bookings/{uuid} |
| Rappel créneau | laverie://bookings/{uuid} |
| Lavage terminé | laverie://washes/{uuid} |
| Promo disponible | laverie://establishments/{uuid} |
4.8 Profil & Paramètres
Écran Profil
- Informations personnelles (modifiables)
- Mes statistiques : nombre de lavages, total dépensé, économies promos
- Code parrainage personnel (copiable, partageable)
- Gestion notifications (toggle par type)
- Paramètres RGPD
- Déconnexion
Écran Paramètres RGPD
- Visualisation des consentements donnés (avec date)
- Toggle individuel pour chaque consentement
- Bouton "Télécharger mes données" (export JSON, v2)
- Bouton "Supprimer mon compte" (confirmation en deux étapes + délai 30 jours)
5. Gestion de l'État (Riverpod)
5.1 Providers principaux
// Auth
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>
// Wallet
final walletProvider = FutureProvider<WalletData>
final transactionsProvider = StateNotifierProvider<TransactionsNotifier, TransactionsState>
// Establishments
final establishmentsProvider = FutureProvider.family<Establishment, String> // by uuid
final nearbyEstablishmentsProvider = FutureProvider<List<Establishment>>
// Machines
final machineStatusProvider = StreamProvider.family<Machine, String> // polling 30s
// Bookings
final bookingsProvider = StateNotifierProvider<BookingsNotifier, BookingsState>
final slotAvailabilityProvider = FutureProvider.family<List<TimeSlot>, SlotQuery>
// Active wash
final activeWashProvider = StreamProvider<Wash?> // null si aucun lavage en cours
5.2 Stratégie offline-first (Hive)
| Donnée | Cache | TTL |
|---|---|---|
| Solde wallet | Oui | Jusqu'au prochain refresh |
| Historique transactions | Oui (50 dernières) | 24h |
| Établissements (liste) | Oui | 1h |
| Disponibilités machines | Non | — |
| Réservations | Oui | 30 min |
En cas d'absence de réseau : affichage des données en cache avec badge "Données hors-ligne".
6. Sécurité
6.1 Stockage
- Tokens JWT :
flutter_secure_storage(AES-256 sur Android, Keychain sur iOS) - Aucune donnée sensible dans
SharedPreferences - Sur Web : pas de localStorage, session uniquement
6.2 Réseau
- Certificate pinning en production (via
diocustomHttpClient) - Timeout : 10s connexion, 30s réception
- Toutes les requêtes en HTTPS (TLS 1.3)
6.3 QR Code
- Vérification côté serveur (le QR code seul ne suffit pas à déclencher un lavage)
- Rate limiting API sur
/washes/start: 3 req/min/user
7. Performance & UX
- Splash screen natif (Android 12 Splash API, iOS UILaunchScreen)
- Skeleton loaders sur toutes les listes et cartes pendant le chargement
- Optimistic UI : affichage anticipé des mises à jour (ex: annulation réservation)
- Pull-to-refresh sur toutes les listes
- Pagination infinie sur l'historique des transactions
- Animations : transitions de pages fluides (Hero, Fade), animation solde wallet
8. Tests
| Type | Outil | Portée |
|---|---|---|
| Tests unitaires | flutter_test |
Providers Riverpod, formatters, validators |
| Tests widgets | flutter_test |
Tous les écrans principaux |
| Tests d'intégration | integration_test |
Parcours critiques (inscription, rechargement, scan QR) |
| Tests golden | golden_toolkit |
Composants UI clés (cohérence visuelle) |
Parcours critiques à couvrir en intégration :
- Inscription complète → vérification email → connexion
- Rechargement porte-monnaie (mock Stripe)
- Réservation créneau → annulation → vérification remboursement
- Scan QR → lancement lavage → fin de cycle
9. CI/CD
9.1 Pipeline GitHub Actions
# Sur chaque PR :
- flutter analyze # Linting strict
- flutter test # Tests unitaires + widgets
- flutter build apk --release # Vérification build Android
- flutter build ios --release # Vérification build iOS (macOS runner)
- flutter build web # Vérification build Web
# Sur merge main :
- Fastlane → TestFlight (iOS)
- Fastlane → Play Store Internal Track (Android)
- Deploy Web → hébergement (Firebase Hosting ou VPS)
9.2 Flavors
| Flavor | API URL | Firebase projet | Build suffix |
|---|---|---|---|
| dev | http://localhost:8000 |
laverie-dev | .dev |
| staging | https://api-staging.laverie.app |
laverie-staging | .staging |
| production | https://api.laverie.app |
laverie-prod | — |
10. Livrables attendus
- Code source Flutter (GitHub, repository dédié)
- Flavors configurés (dev / staging / prod)
README.mdavec instructions setup complet- Fichiers de configuration Firebase (
google-services.json,GoogleService-Info.plist) - Fichiers de signature Android (
.keystore) documentés - Screenshots pour les stores (5 par plateforme minimum)
- Suite de tests (couverture > 70% sur la logique métier)
- Build release Android (
.aab) + iOS (.ipa) prêts à soumettre