Add missing providers (locale, theme, genFilter, caughtCount), all 6 pages, full widget inventory, core/l10n layers, and game mechanics (hints, skips, shiny, bonuses, bilingual input, gen filter). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
106 lines
4.8 KiB
Markdown
106 lines
4.8 KiB
Markdown
# Application Architecture
|
||
|
||
## Overview
|
||
|
||
L'application suit une **Clean Architecture allégée** en trois couches, avec une règle de
|
||
dépendance stricte : les dépendances pointent vers l'intérieur. Le state management est assuré
|
||
par Riverpod (providers manuels).
|
||
|
||
## Couches
|
||
|
||
### core (transversal)
|
||
|
||
- **`AppConstants`** : toutes les constantes métier en un seul endroit (vies, points, shiny odds,
|
||
plages de générations, clés SharedPreferences, URL API).
|
||
- **`logger`** : wrapper minimal autour de `dart:developer`.
|
||
|
||
### domain (Dart pur)
|
||
|
||
- **Entités** : `Pokemon`, immuable, sans dépendance Flutter/DB/API.
|
||
- **Repository (interface)** : `PokemonRepository` définit le contrat d'accès aux données.
|
||
- **Jeu** : `GameState` (état immuable) et `GameEngine` (règles pures, testables).
|
||
- `GameEngine` gère : soumission de réponse, vies, hints, skips, score, Shiny, bonus périodiques.
|
||
- `GameStatus` : `loading | playing | roundWon | gameOver`.
|
||
- `GuessResult` : `correct | wrong | gameOver | invalid`.
|
||
|
||
### data
|
||
|
||
- **DTO** : `PokemonDto` centralise tout le parsing JSON (API Tyradex + SQLite).
|
||
- `fromTyradexJson()`, `fromDb()`, `toDb()`.
|
||
- **Datasources** :
|
||
- `PokemonLocalDataSource` — SQLite via sqflite ; absent (`null`) sur le web.
|
||
- `PokemonRemoteDataSource` — HTTP vers Tyradex + PokéAPI (genus anglais).
|
||
- **Repository (impl)** : `PokemonRepositoryImpl` applique « DB locale d'abord, sinon API + cache ».
|
||
|
||
### l10n
|
||
|
||
Internationalisation complète **FR / EN** via `flutter_localizations` + `intl`.
|
||
Fichiers générés : `app_localizations.dart`, `app_localizations_fr.dart`, `app_localizations_en.dart`.
|
||
La langue active est persistée dans SharedPreferences (`AppConstants.prefsLocale`).
|
||
|
||
### presentation
|
||
|
||
#### Providers (Riverpod)
|
||
|
||
| Provider | Type | Rôle |
|
||
| --- | --- | --- |
|
||
| `pokemonRepositoryProvider` | `Provider` | DI du repository |
|
||
| `pokedexProvider` | `AsyncNotifier<List<Pokemon>>` | Chargement + cache du Pokédex |
|
||
| `gameProvider` | `Notifier<GameState>` | État de la partie en cours |
|
||
| `selectedTabProvider` | `StateProvider<int>` | Onglet de navigation courant |
|
||
| `localeProvider` | `Notifier<Locale>` | Langue active (FR/EN) + persistance |
|
||
| `themeProvider` | `Notifier<AppPalette>` | Palette de couleurs active (5 thèmes) + persistance |
|
||
| `genFilterProvider` | `Notifier<Set<int>>` | Générations sélectionnées pour le jeu + persistance |
|
||
| `caughtCountProvider` | `Provider<int>` | Nombre de Pokémon capturés (dérivé de pokedexProvider) |
|
||
|
||
#### Pages
|
||
|
||
| Fichier | Rôle |
|
||
| --- | --- |
|
||
| `main_page.dart` | Hub de navigation par onglets (Jeu / Pokédex / Système) |
|
||
| `guess_page.dart` | Écran principal du jeu (silhouette, input, lives, score) |
|
||
| `game_over_page.dart` | Écran de fin de partie avec stats de session |
|
||
| `pokemon_list.dart` | Pokédex : liste filtrée + recherche par nom |
|
||
| `pokemon_detail.dart` | Fiche détaillée : stats, type, genus, toggle normal/shiny |
|
||
| `system_page.dart` | Paramètres (langue, palette) et statistiques globales |
|
||
|
||
#### Widgets notables
|
||
|
||
- **Réutilisables** : `PokemonImage`, `PokemonTile`, `PokemonTypeWidget`, `ScanlineOverlay`.
|
||
- **guess/** : `GuessSilhouette`, `GuessInputSection`, `LivesRow`, `ScoreBoard`, `GenFilterSection`.
|
||
- **detail/** : `PokemonDetailTop`, `PokemonStatsPanel`.
|
||
- **game_over/** : `GameOverHeader`, `GameOverStats`, `GameOverActions`, `HingeDivider`.
|
||
- **list/** : `PokedexListHeader`, `PokedexCountBar`.
|
||
- **system/** : `LanguagePicker`, `PalettePicker`, `SectionTitle`, `SystemHeader`, `SystemStats`.
|
||
|
||
#### Thème
|
||
|
||
`type_colors.dart` : couleur et libellé localisé par type Pokémon.
|
||
|
||
## Flux de données
|
||
|
||
```
|
||
UI (Consumer) → Notifier → GameEngine (règles) + Repository (données)
|
||
→ DataSource (SQLite / HTTP) → DTO → Entity
|
||
```
|
||
|
||
Riverpod re-render automatiquement les consommateurs concernés. Plus de bus d'événements global
|
||
ni d'accès inter-pages via l'arbre de widgets.
|
||
|
||
## Mécaniques de jeu (résumé)
|
||
|
||
- **Vies** : 3 au départ ; une vie perdue par mauvaise réponse ; game over à 0.
|
||
- **Hints** : 3 au départ ; révèle partiellement le nom ; +1 tous les 5 bonnes réponses consécutives.
|
||
- **Skips** : 3 au départ ; passe au Pokémon suivant sans pénalité ; +1 tous les 10 bonnes réponses.
|
||
- **Score** : +10 pts normal, +20 pts shiny ; meilleur score persisté via SharedPreferences.
|
||
- **Shiny** : 1 chance sur 10 (`AppConstants.shinyOdds`).
|
||
- **Filtre de génération** : le joueur choisit parmi Gen I–IX ; persisté entre les sessions.
|
||
- **Acceptation bilingue** : le nom FR *ou* EN est accepté quelle que soit la langue de l'UI.
|
||
|
||
## Tests
|
||
|
||
- `test/domain/game_engine_test.dart` : règles du jeu.
|
||
- `test/data/pokemon_dto_test.dart` : parsing JSON ↔ DB.
|
||
- `test/data/pokemon_repository_test.dart` : logique du repository (datasources factices).
|
||
- `test/widget_test.dart` : smoke test de démarrage de l'app.
|