# Storytime — Guide projet & principes d'ingénierie > Lecteur d'histoires audio cadenassé pour le coucher, sur tablette Android. > L'enfant écoute des histoires (podcasts) sans pouvoir sortir de l'application ; > le parent gère les sources, le code parental et les limites. Ce fichier est le contrat d'ingénierie du projet. **Toute contribution doit le respecter.** --- ## 1. Le produit en une page - **Plateforme** : Android uniquement. **Flutter / Dart**. - **Lecture** : streaming (pas de hors-ligne en v1). - **Deux mondes** : - **Espace enfant** (par défaut, verrouillé) : liste de titres d'histoires, gros boutons lecture/pause/suivant. - **Espace parent** (protégé par code 4 chiffres) : gestion des podcasts, limites, réglages. - **Verrouillage** : Screen Pinning (épinglage natif Android). ### Contraintes connues (à ne jamais oublier) - L'épinglage empêche la **sortie facile**, mais la sortie réelle repose sur le **mécanisme natif Android + le PIN de la tablette**, PAS sur notre code parental. C'est un garde-fou enfant, pas un coffre-fort. Ne jamais promettre plus dans l'UI ou les messages. - L'épinglage **ne survit pas à un redémarrage** de la tablette. - Le code parental protège **l'accès aux écrans parent** et la **reprise après limite**, pas le désépinglage système. --- ## 2. Philosophie de développement Le projet suit le **Software Craftsmanship** : code soigné, testé, simple, qui se lit comme une explication du domaine. ### TDD — non négociable Cycle **Red → Green → Refactor** pour toute fonctionnalité ou correction : 1. **Red** : écrire un test qui décrit le comportement attendu. Le lancer, vérifier qu'il échoue **pour la bonne raison**. 2. **Green** : écrire le minimum de code pour le faire passer. 3. **Refactor** : nettoyer sans changer le comportement, tests toujours verts. Ne jamais écrire de code de production sans test qui le justifie. Les seules exceptions : code purement déclaratif (constantes, thèmes) et glue framework triviale — et même là, le comportement observable se teste. ### Clean Code - Noms qui révèlent l'intention ; pas d'abréviations obscures. - Fonctions courtes, une seule raison d'exister. - Pas de commentaire qui paraphrase le code ; un commentaire explique le **pourquoi**, jamais le **quoi**. - Pas de nombre/chaîne magique : constantes nommées. - Gestion d'erreur explicite (voir `Result`/`Either`, §4). - **Boy Scout Rule** : laisser le code un peu plus propre qu'on ne l'a trouvé, sans dérive hors-sujet. ### YAGNI On n'implémente que ce que le jalon courant exige. Pas d'abstraction « au cas où ». --- ## 3. Clean Architecture Dépendances dirigées **vers le domaine**. Le domaine ne connaît ni Flutter, ni HTTP, ni la base de données. ``` Presentation ─────► Application (use cases) ─────► Domain ◄───── Data (infra) (UI/Riverpod) (orchestration) (cœur métier) (impl. dépôts) ``` ### Couches - **Domain** (`domain/`) — entités, value objects, interfaces de dépôts (abstraites), erreurs métier. **Zéro dépendance externe.** Dart pur. - **Application** (`application/`) — *use cases* : une classe = une intention métier (`PlayStoryUseCase`, `AddPodcastUseCase`). Orchestrent le domaine via les interfaces de dépôts. Dart pur. - **Data** (`data/`) — implémentations concrètes des dépôts, *data sources* (HTTP iTunes, parsing RSS, SQLite, secure storage), DTO/mappers. Dépend du domaine, jamais l'inverse. - **Presentation** (`presentation/`) — écrans, widgets, contrôleurs d'état (Riverpod Notifiers). Appelle les use cases. Ne contient pas de logique métier. ### Règle de dépendance Une couche ne dépend que de couches plus internes. **Le domaine n'importe jamais `package:flutter`, `package:http`, `sqflite`, etc.** Toute violation est un bug d'architecture. ### Injection de dépendances **Riverpod** comme conteneur DI + gestion d'état. Les use cases et dépôts sont fournis via des `Provider`. En test, on *override* les providers par des doubles. --- ## 4. Conventions de code - **État/DI** : Riverpod (`Notifier`/`AsyncNotifier`). - **Immutabilité** : modèles immuables (`freezed` ou `copyWith` manuel pour les entités simples). - **Erreurs** : pas d'exception qui traverse les couches en silence. Le domaine renvoie un type `Result` (ou `Either`). Les `Failure` sont des types métier (`NetworkFailure`, `InvalidFeedFailure`, …). Les exceptions techniques sont capturées dans la couche `data` et converties en `Failure`. - **Asynchrone** : `Future`/`Stream` typés ; pas de `dynamic`. - **Lint** : `flutter_lints` + règles strictes activées (`analysis_options.yaml`). Le projet doit rester **0 warning**. - **Format** : `dart format` ; pas de débat de style. ### Arborescence cible ``` lib/ core/ # transverse : Result, erreurs de base, constantes, thème, router features/ locking/ # verrouillage / épinglage domain/ application/ data/ presentation/ playback/ # lecture audio podcasts/ # recherche, RSS, abonnements parental/ # code parental, accès espace parent limits/ # minuterie, compteur d'histoires, avertissements main.dart test/ # miroir de lib/ ; tests unitaires & use cases test/widget/ # tests de widgets integration_test/ # tests d'intégration (épinglage, parcours bout-en-bout) ``` Feature-first, puis découpage en couches dans chaque feature. **Un fichier = une responsabilité.** Quand un fichier grossit, c'est le signal qu'il fait trop de choses : on le scinde. --- ## 5. Stratégie de test | Niveau | Cible | Outils | |--------|-------|--------| | Unitaire | Domain + use cases (la majorité des tests) | `flutter_test`, `mocktail` | | Widget | Écrans/widgets isolés | `flutter_test` | | Intégration | Épinglage réel, parcours enfant/parent | `integration_test` sur appareil/émulateur | - Les tests de domaine/use cases ne touchent **jamais** au réseau, au disque ni à Flutter. - Les dépendances externes (HTTP, RSS, stockage, plugin d'épinglage) sont derrière des interfaces, *mockées* en test. - **Le jalon 1 (épinglage) exige une validation sur appareil réel** : un test/POC manuel documenté en plus des tests automatisés. --- ## 6. Processus & suivi - **`docs/specs/`** : la spec, découpée par **jalon** (un dossier par jalon). Chaque jalon a un `README.md` (objectif + DoD) et ses **étapes en sous-fichiers numérotés**. - **`ROADMAP.md`** : vue d'ensemble de l'avancement. **À mettre à jour à la fin de chaque étape** (cocher la case, dater, noter les écarts). C'est la source de vérité du « où on en est ». - On traite **un jalon à la fois**, **une étape à la fois**, en TDD. - **Definition of Done** d'une étape : code + tests verts + 0 warning lint + `ROADMAP.md` à jour. ### Rituel à chaque étape terminée 1. Tous les tests de l'étape passent. 2. `flutter analyze` ne renvoie aucun warning. 3. Cocher l'étape dans `ROADMAP.md` (avec la date du jour). 4. Si un écart avec la spec est apparu, le noter dans le fichier d'étape concerné. --- ## 7. Décisions techniques figées | Sujet | Choix | Raison | |-------|-------|--------| | Langage/UI | Flutter / Material 3 | Mono-plateforme Android, connu de l'auteur | | État + DI | Riverpod | Testable, override facile en test | | Audio | `just_audio` + `audio_service` | Lecture + contrôle arrière-plan | | Épinglage | `kiosk_mode` (plugin), fallback platform channel Kotlin | API native Screen Pinning | | Recherche podcasts | API iTunes Search (gratuite, sans clé) | Annuaire public | | RSS | `dart_rss` (ou `webfeed`) | Parsing de flux | | Persistance | `sqflite`/`drift` (abonnements) + `flutter_secure_storage` (code haché) | Local, sûr | | Lecture seule réglages | `shared_preferences` | Compteurs/limites | Tout changement de cette table doit être justifié dans le commit et reflété ici.