Compare commits
3
Commits
main
..
3d7f032411
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d7f032411 | ||
|
|
5d8f609e18 | ||
|
|
a59ee26aaa |
@@ -1,6 +1,3 @@
|
|||||||
# Superpowers brainstorming/specs/plans — local only, never commit
|
|
||||||
docs/superpowers/
|
|
||||||
|
|
||||||
# Miscellaneous
|
# Miscellaneous
|
||||||
*.class
|
*.class
|
||||||
*.log
|
*.log
|
||||||
@@ -14,7 +11,6 @@ docs/superpowers/
|
|||||||
.svn/
|
.svn/
|
||||||
.swiftpm/
|
.swiftpm/
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
pubspec.lock
|
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
@@ -48,5 +44,3 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
.claude/
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: "3b62efc2a3da49882f43c372e0bc53daef7295a6"
|
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
@@ -13,11 +13,26 @@ project_type: app
|
|||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
- platform: android
|
- platform: android
|
||||||
create_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
base_revision: 3b62efc2a3da49882f43c372e0bc53daef7295a6
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
- platform: ios
|
||||||
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
- platform: linux
|
||||||
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
- platform: macos
|
||||||
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
- platform: web
|
||||||
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
- platform: windows
|
||||||
|
create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|||||||
@@ -1,82 +1,37 @@
|
|||||||
# Pokéguess
|
# pokedex
|
||||||
|
|
||||||
> Devine le Pokémon à partir de sa silhouette !
|
Projet d'exemple d'application Flutter pour les cours de bootcamp Ynov 2023.
|
||||||
|
Ce projet contient un début de pokédex, une application listant des pokémons et permettant d'en voir les détails.
|
||||||
Application mobile Flutter développée dans le cadre du cours **DEV MOBILE** (Ynov).
|
|
||||||
À l'origine basée sur l'exemple de pokédex du bootcamp, le projet est devenu un jeu
|
|
||||||
complet « Who's that Pokémon ? » adossé à un pokédex.
|
|
||||||
|
|
||||||
## Fonctionnalités
|
|
||||||
|
|
||||||
- **Jeu de devinette** : une silhouette de Pokémon à identifier, avec un système de
|
|
||||||
vies, d'indices et de passes (skips), des Pokémon **shiny** (bonus de points), un
|
|
||||||
score courant et un meilleur score persistant.
|
|
||||||
- **Filtre par génération** : on choisit les générations (I à IX) dont les Pokémon
|
|
||||||
peuvent tomber pendant le jeu.
|
|
||||||
- **Pokédex** : liste nationale des Pokémon avec filtre `ALL` / `CAUGHT` et compteur de
|
|
||||||
progression (découverts / total).
|
|
||||||
- **Fiche détaillée** : statistiques de base, description, types, bascule normal/shiny.
|
|
||||||
- **Page Système** : statistiques globales (attrapés, vus, complétion, meilleur score)
|
|
||||||
et choix de la **palette de couleurs** de l'app.
|
|
||||||
- **Offline-first** : les données sont mises en cache localement (SQLite) ; l'app reste
|
|
||||||
utilisable hors-ligne une fois la synchronisation initiale faite.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Le projet suit une **Clean Architecture allégée** en trois couches, avec **Riverpod**
|
|
||||||
pour la gestion d'état :
|
|
||||||
|
|
||||||
```text
|
|
||||||
lib/
|
|
||||||
├── core/ # constantes, logger (Dart pur)
|
|
||||||
├── domain/ # entités, interface de repository, moteur de jeu (Dart pur, testé)
|
|
||||||
├── data/ # DTO de parsing, datasources SQLite/HTTP, implémentation du repository
|
|
||||||
└── presentation/ # providers Riverpod, pages et widgets (chacun < 150 lignes)
|
|
||||||
```
|
|
||||||
|
|
||||||
La règle de dépendance pointe vers l'intérieur : `domain` ne dépend de rien,
|
|
||||||
`data` et `presentation` dépendent de `domain`. Détails dans
|
|
||||||
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
||||||
|
|
||||||
## Stack technique
|
|
||||||
|
|
||||||
- [Flutter](https://flutter.dev) / Dart
|
|
||||||
- [flutter_riverpod](https://pub.dev/packages/flutter_riverpod) — gestion d'état
|
|
||||||
- [sqflite](https://pub.dev/packages/sqflite) — persistance locale (SQLite)
|
|
||||||
- [http](https://pub.dev/packages/http) — appels réseau
|
|
||||||
- [shared_preferences](https://pub.dev/packages/shared_preferences) — préférences (meilleur score, palette)
|
|
||||||
- [google_fonts](https://pub.dev/packages/google_fonts) — police rétro
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://gitea.losvernos.com/guillaumev/quel-est-ce-pokemon.git
|
git clone https://github.com/Efilown/pokedex-example.git
|
||||||
cd quel-est-ce-pokemon
|
cd pokedex-example
|
||||||
flutter pub get
|
flutter pub get
|
||||||
```
|
```
|
||||||
|
|
||||||
Puis lancer l'application sur le device de votre choix (téléphone, émulateur ou
|
Ensuite, ouvrir le projet avec un IDE (tel que vscode) et le lancer sur le device de votre choix (téléphone, émulateur ou navigateur).
|
||||||
navigateur) :
|
|
||||||
|
|
||||||
```sh
|
|
||||||
flutter run
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
```sh
|
|
||||||
flutter test
|
|
||||||
```
|
|
||||||
|
|
||||||
Les tests couvrent le moteur de jeu (`GameEngine`), le parsing (`PokemonDto`) et le
|
|
||||||
repository.
|
|
||||||
|
|
||||||
## Ressources utilisées
|
## Ressources utilisées
|
||||||
|
|
||||||
- [API Tyradex](https://tyradex.app) — données des Pokémon (français)
|
Pour les images (remplacer par l'id du pokémon désiré):
|
||||||
- Sprites officiels via le dépôt [TyraDex/sprites](https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/)
|
|
||||||
(remplacer `<id>` par le numéro du Pokémon) :
|
|
||||||
- normal : `.../sprites/<id>/regular.png`
|
|
||||||
- shiny : `.../sprites/<id>/shiny.png`
|
|
||||||
- [Cris des Pokémon](https://pokemoncries.com/cries/1.mp3)
|
|
||||||
|
|
||||||
|
- [Sprite normal](https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/6.png)
|
||||||
|
- [Sprite shiny](https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/shiny/6.png)
|
||||||
|
|
||||||
|
Autres liens
|
||||||
|
|
||||||
|
- [l'API pour récupérer les informations](https://pokeapi.co/api/v2/pokemon/6)
|
||||||
|
- [Les cris de bataille](https://pokemoncries.com/cries/1.mp3)
|
||||||
|
- [La documentation du paquet SQFlite](https://pub.dev/packages/sqflite)
|
||||||
|
- [La documentation du paquet HTTP](https://pub.dev/packages/http)
|
||||||
|
|
||||||
|
## Améliorations possible
|
||||||
|
|
||||||
|
- Mettre un lecteur audio pour écouter les cris des pokémons
|
||||||
|
- Rajouter la traduction (en fonction de la langue de l'application ? ☺)
|
||||||
|
- Compléter la vue détail avec des informations tirées de l'API
|
||||||
|
- Faire une vue "mon équipe" contenant 6 pokémons choisis en rajoutant un bouton "ajouter à mon équipe" sur la vue détail.
|
||||||
|
|
||||||
|
Amusez-vous !
|
||||||
|
|||||||
+1
-2
@@ -5,10 +5,9 @@ gradle-wrapper.jar
|
|||||||
/gradlew.bat
|
/gradlew.bat
|
||||||
/local.properties
|
/local.properties
|
||||||
GeneratedPluginRegistrant.java
|
GeneratedPluginRegistrant.java
|
||||||
.cxx/
|
|
||||||
|
|
||||||
# Remember to never publicly share your keystore.
|
# Remember to never publicly share your keystore.
|
||||||
# See https://flutter.dev/to/reference-keystore
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
key.properties
|
key.properties
|
||||||
**/*.keystore
|
**/*.keystore
|
||||||
**/*.jks
|
**/*.jks
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "com.example.pokedex"
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.pokedex"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("com.android.application")
|
|
||||||
id("kotlin-android")
|
|
||||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "com.example.pokeguess"
|
|
||||||
compileSdk = flutter.compileSdkVersion
|
|
||||||
ndkVersion = flutter.ndkVersion
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "com.example.pokeguess"
|
|
||||||
// You can update the following values to match your application needs.
|
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
|
||||||
targetSdk = flutter.targetSdkVersion
|
|
||||||
versionCode = flutter.versionCode
|
|
||||||
versionName = flutter.versionName
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
// TODO: Add your own signing config for the release build.
|
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flutter {
|
|
||||||
source = "../.."
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
|
||||||
<application
|
<application
|
||||||
android:label="pokeguess"
|
android:label="pokeguess"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@@ -9,7 +7,6 @@
|
|||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
@@ -33,15 +30,4 @@
|
|||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
|
||||||
https://developer.android.com/training/package-visibility and
|
|
||||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
|
||||||
|
|
||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
|
||||||
<data android:mimeType="text/plain"/>
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.example.pokedex
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.example.pokeguess
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.7.10'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newBuildDir: Directory =
|
|
||||||
rootProject.layout.buildDirectory
|
|
||||||
.dir("../../build")
|
|
||||||
.get()
|
|
||||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
|
||||||
|
|
||||||
subprojects {
|
|
||||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
|
||||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
|
||||||
}
|
|
||||||
subprojects {
|
|
||||||
project.evaluationDependsOn(":app")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
|
||||||
delete(rootProject.layout.buildDirectory)
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,2 +1,3 @@
|
|||||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
|||||||
+1
-3
@@ -2,6 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||||
org.gradle.caching=true
|
|
||||||
org.gradle.parallel=true
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}
|
||||||
|
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||||
|
|
||||||
|
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
||||||
|
|
||||||
|
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
pluginManagement {
|
|
||||||
val flutterSdkPath =
|
|
||||||
run {
|
|
||||||
val properties = java.util.Properties()
|
|
||||||
file("local.properties").inputStream().use { properties.load(it) }
|
|
||||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
|
||||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
|
||||||
flutterSdkPath
|
|
||||||
}
|
|
||||||
|
|
||||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
|
||||||
id("com.android.application") version "8.11.1" apply false
|
|
||||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
|
||||||
}
|
|
||||||
|
|
||||||
include(":app")
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
description: This file stores settings for Dart & Flutter DevTools.
|
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
|
||||||
extensions:
|
|
||||||
+163
-76
@@ -1,105 +1,192 @@
|
|||||||
# Application Architecture
|
# Architecture du Projet - Pokéguess
|
||||||
|
|
||||||
## Overview
|
## Vue d'ensemble
|
||||||
|
|
||||||
L'application suit une **Clean Architecture allégée** en trois couches, avec une règle de
|
Application Flutter affichant les 151 premiers Pokémon avec noms en français, via l'API Tyradex et cache local SQLite.
|
||||||
dépendance stricte : les dépendances pointent vers l'intérieur. Le state management est assuré
|
|
||||||
par Riverpod (providers manuels).
|
|
||||||
|
|
||||||
## Couches
|
### Informations
|
||||||
|
|
||||||
### core (transversal)
|
- **Version** : 1.0.0+1
|
||||||
|
- **SDK** : Flutter >=3.1.0 <4.0.0
|
||||||
|
- **Plateformes** : Android, iOS, Web, macOS, Linux, Windows
|
||||||
|
|
||||||
- **`AppConstants`** : toutes les constantes métier en un seul endroit (vies, points, shiny odds,
|
## Architecture Générale
|
||||||
plages de générations, clés SharedPreferences, URL API).
|
|
||||||
- **`logger`** : wrapper minimal autour de `dart:developer`.
|
|
||||||
|
|
||||||
### domain (Dart pur)
|
L'application suit une architecture en couches (layered architecture) avec une séparation claire des responsabilités :
|
||||||
|
|
||||||
- **Entités** : `Pokemon`, immuable, sans dépendance Flutter/DB/API.
|
```txt
|
||||||
- **Repository (interface)** : `PokemonRepository` définit le contrat d'accès aux données.
|
┌─────────────────────────────────────────┐
|
||||||
- **Jeu** : `GameState` (état immuable) et `GameEngine` (règles pures, testables).
|
│ Couche Présentation │
|
||||||
- `GameEngine` gère : soumission de réponse, vies, hints, skips, score, Shiny, bonus périodiques.
|
│ (Pages & Components/Widgets) │
|
||||||
- `GameStatus` : `loading | playing | roundWon | gameOver`.
|
├─────────────────────────────────────────┤
|
||||||
- `GuessResult` : `correct | wrong | gameOver | invalid`.
|
│ Couche Métier │
|
||||||
|
│ (Models) │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ Couche Services │
|
||||||
|
│ (API & Database & Utils) │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
### data
|
## Structure du Projet
|
||||||
|
|
||||||
- **DTO** : `PokemonDto` centralise tout le parsing JSON (API Tyradex + SQLite).
|
```md
|
||||||
- `fromTyradexJson()`, `fromDb()`, `toDb()`.
|
lib/
|
||||||
- **Datasources** :
|
├── main.dart # Point d'entrée de l'application
|
||||||
- `PokemonLocalDataSource` — SQLite via sqflite ; absent (`null`) sur le web.
|
├── api/
|
||||||
- `PokemonRemoteDataSource` — HTTP vers Tyradex + PokéAPI (genus anglais).
|
│ └── pokemon_api.dart # Service d'accès à l'API Tyradex
|
||||||
- **Repository (impl)** : `PokemonRepositoryImpl` applique « DB locale d'abord, sinon API + cache ».
|
├── components/
|
||||||
|
│ ├── pokemon_tile.dart # Widget de tuile pour afficher un Pokémon
|
||||||
|
│ └── pokemon_type.dart # Widget pour afficher le type d'un Pokémon
|
||||||
|
├── database/
|
||||||
|
│ └── pokedex_database.dart # Gestion de la base de données SQLite
|
||||||
|
├── models/
|
||||||
|
│ └── pokemon.dart # Modèle de données Pokémon
|
||||||
|
├── pages/
|
||||||
|
│ ├── pokemon_detail.dart # Page de détail d'un Pokémon
|
||||||
|
│ └── pokemon_list.dart # Page de liste des Pokémon
|
||||||
|
└── utils/
|
||||||
|
└── pokemon_type.dart # Utilitaires pour les types de Pokémon
|
||||||
|
```
|
||||||
|
|
||||||
### l10n
|
## Composants Principaux
|
||||||
|
|
||||||
Internationalisation complète **FR / EN** via `flutter_localizations` + `intl`.
|
### 1. Point d'Entrée
|
||||||
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
|
[main.dart](../lib/main.dart) - Initialisation de l'application
|
||||||
|
|
||||||
#### Providers (Riverpod)
|
- Thème Material Design 3
|
||||||
|
- Routes : `/` (liste) et `/pokemon-detail` (détail)
|
||||||
|
|
||||||
| Provider | Type | Rôle |
|
### 2. Couche Présentation
|
||||||
| --- | --- | --- |
|
|
||||||
| `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
|
#### Pages
|
||||||
|
|
||||||
| Fichier | Rôle |
|
##### PokemonListPage ([pokemon_list.dart](../lib/pages/pokemon_list.dart))
|
||||||
| --- | --- |
|
|
||||||
| `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
|
StatefulWidget affichant une grille de 151 Pokémon (2 colonnes mobile, 4 web) via `GridView.builder` et `FutureBuilder`.
|
||||||
|
|
||||||
- **Réutilisables** : `PokemonImage`, `PokemonTile`, `PokemonTypeWidget`, `ScanlineOverlay`.
|
##### PokemonDetailPage ([pokemon_detail.dart](../lib/pages/pokemon_detail.dart))
|
||||||
- **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
|
StatefulWidget affichant les détails d'un Pokémon avec toggle shiny (`_isShiny`) via `GestureDetector`.
|
||||||
|
|
||||||
`type_colors.dart` : couleur et libellé localisé par type Pokémon.
|
#### Components
|
||||||
|
|
||||||
## Flux de données
|
##### PokemonTile ([pokemon_tile.dart](../lib/components/pokemon_tile.dart))
|
||||||
|
|
||||||
```
|
StatefulWidget affichant une carte Pokémon (image + nom) avec navigation vers détail au tap.
|
||||||
UI (Consumer) → Notifier → GameEngine (règles) + Repository (données)
|
|
||||||
→ DataSource (SQLite / HTTP) → DTO → Entity
|
##### PokemonTypeWidget ([pokemon_type.dart](../lib/components/pokemon_type.dart))
|
||||||
|
|
||||||
|
StatelessWidget affichant un badge coloré par type.
|
||||||
|
|
||||||
|
### 3. Couche Modèle
|
||||||
|
|
||||||
|
#### Pokemon ([pokemon.dart](../lib/models/pokemon.dart))
|
||||||
|
|
||||||
|
- **Propriétés** :
|
||||||
|
- `name` : String - Nom du Pokémon
|
||||||
|
- `id` : int - Numéro du Pokémon
|
||||||
|
- `type1` : PokemonType - Type principal
|
||||||
|
- `type2` : PokemonType? - Type secondaire (optionnel)
|
||||||
|
|
||||||
|
- **Propriétés calculées** : `imageUrl`, `shinyImageUrl`, `cryUrl`, `formatedName`, `type1Color`, `type2Color`, `type1Formated`, `type2Formated`
|
||||||
|
- **Méthodes** : `fromJson()`, `toJson()`, `fromID()` (cache + API)
|
||||||
|
- **Enum** : `PokemonType` (20 types)
|
||||||
|
|
||||||
|
### 4. Couche Services
|
||||||
|
|
||||||
|
#### PokemonApi ([pokemon_api.dart](../lib/api/pokemon_api.dart))
|
||||||
|
|
||||||
|
Communication avec Tyradex API (<https://tyradex.vercel.app>)
|
||||||
|
|
||||||
|
- `getPokemon(int id)` : Récupère un Pokémon depuis l'API avec nom en français
|
||||||
|
- Exception levée si code HTTP ≠ 200
|
||||||
|
|
||||||
|
#### PokedexDatabase ([pokedex_database.dart](../lib/database/pokedex_database.dart))
|
||||||
|
|
||||||
|
Gestion SQLite locale (CRUD complet)
|
||||||
|
|
||||||
|
- **Schéma** : `pokemon (id, name, type1, type2)`
|
||||||
|
- **Méthodes** : `initDatabase()`, `getDatabase()`, `insertPokemon()`, `getPokemonList()`, `getPokemon()`, `deletePokemon()`, `deleteAllPokemon()`, `updatePokemon()`
|
||||||
|
- Non disponible sur Web (`kIsWeb`)
|
||||||
|
|
||||||
|
#### Utils ([pokemon_type.dart](../lib/utils/pokemon_type.dart))
|
||||||
|
|
||||||
|
- `frenchTypeToEnum()` : Conversion types français (API) vers enum
|
||||||
|
- `typeToColor()` : Mapping type → couleur
|
||||||
|
- `formatedTypeName()` : Formatage nom de type
|
||||||
|
|
||||||
|
## Flux de Données
|
||||||
|
|
||||||
|
### Récupération d'un Pokémon
|
||||||
|
|
||||||
|
```md
|
||||||
|
1. PokemonListPage demande Pokemon.fromID(id)
|
||||||
|
↓
|
||||||
|
2. Vérification dans PokedexDatabase (si non-web)
|
||||||
|
↓
|
||||||
|
3a. Si trouvé → Retour du Pokémon en cache
|
||||||
|
↓
|
||||||
|
3b. Si non trouvé → Appel à PokemonApi.getPokemon(id)
|
||||||
|
↓
|
||||||
|
4. Parsing JSON et création de l'objet Pokemon
|
||||||
|
↓
|
||||||
|
5. Sauvegarde dans PokedexDatabase (si non-web)
|
||||||
|
↓
|
||||||
|
6. Retour du Pokemon à la vue
|
||||||
```
|
```
|
||||||
|
|
||||||
Riverpod re-render automatiquement les consommateurs concernés. Plus de bus d'événements global
|
### Navigation
|
||||||
ni d'accès inter-pages via l'arbre de widgets.
|
|
||||||
|
|
||||||
## Mécaniques de jeu (résumé)
|
```md
|
||||||
|
PokemonListPage
|
||||||
|
↓ (tap sur PokemonTile)
|
||||||
|
Navigator.pushNamed('/pokemon-detail', arguments: pokemon)
|
||||||
|
↓
|
||||||
|
PokemonDetailPage (récupère le pokemon via ModalRoute)
|
||||||
|
```
|
||||||
|
|
||||||
- **Vies** : 3 au départ ; une vie perdue par mauvaise réponse ; game over à 0.
|
## Patterns et Bonnes Pratiques
|
||||||
- **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
|
**Patterns** : Repository (classe `Pokemon`), Factory (`fromJson()`), FutureBuilder (async)
|
||||||
|
|
||||||
- `test/domain/game_engine_test.dart` : règles du jeu.
|
**Plateformes** : Détection Web via `kIsWeb`, adaptation colonnes grille
|
||||||
- `test/data/pokemon_dto_test.dart` : parsing JSON ↔ DB.
|
|
||||||
- `test/data/pokemon_repository_test.dart` : logique du repository (datasources factices).
|
**Erreurs** : Try/catch + logs, vérification HTTP, messages dans FutureBuilder
|
||||||
- `test/widget_test.dart` : smoke test de démarrage de l'app.
|
|
||||||
|
## Dépendances
|
||||||
|
|
||||||
|
**Production** : `flutter`, `cupertino_icons` (^1.0.2), `sqflite` (^2.3.0), `http` (^1.1.0)
|
||||||
|
|
||||||
|
**Développement** : `flutter_test`, `flutter_lints` (^2.0.0)
|
||||||
|
|
||||||
|
## Points d'Extension
|
||||||
|
|
||||||
|
1. Recherche et filtres (type, génération)
|
||||||
|
2. Mode jeu (deviner à partir de silhouette, score)
|
||||||
|
3. Système de favoris
|
||||||
|
4. Lecture audio des cris
|
||||||
|
5. Détails enrichis (stats, évolutions, capacités)
|
||||||
|
6. Préchargement offline des 151 Pokémon
|
||||||
|
7. Tests unitaires et d'intégration
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
1. Pas de persistance locale sur Web
|
||||||
|
2. Limité à 151 Pokémon (Gen 1)
|
||||||
|
3. Gestion d'erreur minimale
|
||||||
|
4. Pas d'internationalisation
|
||||||
|
5. Pas de tests automatisés
|
||||||
|
|
||||||
|
## Notes Techniques
|
||||||
|
|
||||||
|
- Material Design 3
|
||||||
|
- Images : repository Yarkis01/TyraDex (GitHub)
|
||||||
|
- API : Tyradex API (https://tyradex.vercel.app)
|
||||||
|
- Noms de Pokémon en français
|
||||||
|
- Routage : routes nommées Flutter
|
||||||
|
- Pas de state management externe
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Dernière mise à jour** : 2 février 2026
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,43 +0,0 @@
|
|||||||
# Pokeguess
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Pokeguess is a Flutter mobile application that allows users to discover and collect Pokémon through
|
|
||||||
a silhouette guessing game. The app fetches data from the Tyradex API and stores it locally for
|
|
||||||
offline access.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **National Pokédex**: Browse all 1025 Pokémon from generations I to IX.
|
|
||||||
- **Guess Game**: Identify Pokémon by their silhouette.
|
|
||||||
- **Scoring System**: Earn 10 pts per correct guess, 20 pts for Shiny Pokémon (1 in 10 chance).
|
|
||||||
High scores are saved locally.
|
|
||||||
- **Lives, Hints & Skips**: Start each game with 3 lives, 3 hints, and 3 skips. Earn bonuses
|
|
||||||
every 5 correct guesses (hint) and every 10 correct guesses (skip).
|
|
||||||
- **Generation Filter**: Restrict the game to one or more generations (I–IX). Selection persists
|
|
||||||
across sessions.
|
|
||||||
- **Bilingual Input**: Pokémon names are accepted in French or English regardless of the UI language.
|
|
||||||
- **Collection**: Track caught and seen Pokémon across your Pokédex.
|
|
||||||
- **Search and Filter**: Filter the collection by all / caught status and search by name.
|
|
||||||
- **Pokémon Detail**: View base stats, types, genus, and toggle between normal and shiny sprites.
|
|
||||||
- **Theming**: Choose from 5 color palettes (red, blue, green, yellow, purple).
|
|
||||||
- **Localization**: Full French and English UI support, switchable at any time.
|
|
||||||
- **Statistics**: Global view of caught count, seen count, completion percentage, and best score.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Ensure Flutter SDK is installed.
|
|
||||||
2. Clone the repository.
|
|
||||||
3. Run `flutter pub get` to install dependencies.
|
|
||||||
4. Run `flutter run` to start the application.
|
|
||||||
|
|
||||||
## Technologies
|
|
||||||
|
|
||||||
- **Flutter**: UI Framework.
|
|
||||||
- **Riverpod**: State management (providers + notifiers).
|
|
||||||
- **SQLite (sqflite)**: Local database for offline-first Pokémon storage.
|
|
||||||
- **Tyradex API**: Primary Pokémon data source (FR/EN names, sprites, types, stats).
|
|
||||||
- **PokéAPI**: Secondary source for English genus data.
|
|
||||||
- **Shared Preferences**: Persistence for best score, generation filter, language, and palette.
|
|
||||||
- **Google Fonts**: Custom typography (VT323).
|
|
||||||
- **flutter_localizations / intl**: FR and EN localization.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 775 KiB |
@@ -20,5 +20,7 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
+1
-4
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '15.0'
|
# platform :ios, '12.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
@@ -39,8 +39,5 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
target.build_configurations.each do |config|
|
|
||||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+15
-46
@@ -1,64 +1,33 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- shared_preferences_foundation (0.0.1):
|
- FMDB (2.7.12):
|
||||||
|
- FMDB/standard (= 2.7.12)
|
||||||
|
- FMDB/Core (2.7.12)
|
||||||
|
- FMDB/standard (2.7.12):
|
||||||
|
- FMDB/Core
|
||||||
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FMDB (>= 2.7.5)
|
||||||
- sqflite_darwin (0.0.4):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- sqlite3 (3.52.0):
|
|
||||||
- sqlite3/common (= 3.52.0)
|
|
||||||
- sqlite3/common (3.52.0)
|
|
||||||
- sqlite3/dbstatvtab (3.52.0):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/fts5 (3.52.0):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/math (3.52.0):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/perf-threadsafe (3.52.0):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/rtree (3.52.0):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/session (3.52.0):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- sqlite3 (~> 3.52.0)
|
|
||||||
- sqlite3/dbstatvtab
|
|
||||||
- sqlite3/fts5
|
|
||||||
- sqlite3/math
|
|
||||||
- sqlite3/perf-threadsafe
|
|
||||||
- sqlite3/rtree
|
|
||||||
- sqlite3/session
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
|
||||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- sqlite3
|
- FMDB
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
shared_preferences_foundation:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
sqflite_darwin:
|
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
|
||||||
sqlite3_flutter_libs:
|
|
||||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
FMDB: 728731dd336af3936ce00f91d9d8495f5718a0e6
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite: 5b24d06a453c198c13b305ceea4b4286cc07cfe4
|
||||||
sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
|
|
||||||
sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 4b015915ec662986b54bf30ab778da63f7dda016
|
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -114,7 +114,6 @@
|
|||||||
379611D51312FF940696FCAB /* Pods-RunnerTests.release.xcconfig */,
|
379611D51312FF940696FCAB /* Pods-RunnerTests.release.xcconfig */,
|
||||||
0572F0648954163DB68882FD /* Pods-RunnerTests.profile.xcconfig */,
|
0572F0648954163DB68882FD /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -453,7 +452,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -469,13 +468,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 45K9ZX66MZ;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.pokedex;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.pokeguess;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -580,7 +580,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -629,7 +629,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -647,13 +647,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 45K9ZX66MZ;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.pokedex;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.pokeguess;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -669,13 +670,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = 45K9ZX66MZ;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.pokedex;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.pokeguess;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
@@ -55,7 +54,6 @@
|
|||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import Flutter
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
|
||||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-21
@@ -2,6 +2,15 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>This app uses the local network to discover and connect to nearby devices on your network.</string>
|
||||||
|
|
||||||
|
<key>NSBonjourServices</key>
|
||||||
|
<array>
|
||||||
|
<!-- Replace with the actual service types your app uses -->
|
||||||
|
<string>_http._tcp.</string>
|
||||||
|
<!-- Example: <string>_yourservice._tcp.</string> -->
|
||||||
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
@@ -26,27 +35,6 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSceneManifest</key>
|
|
||||||
<dict>
|
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
|
||||||
<false/>
|
|
||||||
<key>UISceneConfigurations</key>
|
|
||||||
<dict>
|
|
||||||
<key>UIWindowSceneSessionRoleApplication</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>UISceneClassName</key>
|
|
||||||
<string>UIWindowScene</string>
|
|
||||||
<key>UISceneConfigurationName</key>
|
|
||||||
<string>flutter</string>
|
|
||||||
<key>UISceneDelegateClassName</key>
|
|
||||||
<string>FlutterSceneDelegate</string>
|
|
||||||
<key>UISceneStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
arb-dir: lib/l10n
|
|
||||||
template-arb-file: app_en.arb
|
|
||||||
output-localization-file: app_localizations.dart
|
|
||||||
output-dir: lib/l10n
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import '../models/pokemon.dart';
|
||||||
|
import '../utils/pokemon_type.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
// Classe qui permet de récupérer les données des pokémons depuis l'API Tyradex
|
||||||
|
// On utilise la librairie http pour effectuer les requêtes
|
||||||
|
// On utilise la librairie dart:convert pour convertir les données JSON en objet Dart
|
||||||
|
class PokemonApi {
|
||||||
|
static const String baseUrl = 'tyradex.vercel.app';
|
||||||
|
static const String pokemonUrl = 'api/v1/pokemon';
|
||||||
|
|
||||||
|
static Future<Pokemon> getPokemon(int id) async {
|
||||||
|
// On utilise la méthode get de la classe http pour effectuer une requête GET
|
||||||
|
// On utilise Uri.https pour construire l'URL de la requête
|
||||||
|
var response = await http.get(Uri.https(baseUrl, "$pokemonUrl/$id"));
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
// Si le code de retour de la requête n'est pas 200, on lève une exception
|
||||||
|
throw Exception('Erreur lors de la récupération du pokémon $id, code de retour ${response.statusCode}');
|
||||||
|
}
|
||||||
|
// On utilise la méthode jsonDecode de la librairie dart:convert pour convertir le corps de la réponse en fichier JSON
|
||||||
|
var json = jsonDecode(response.body);
|
||||||
|
// Récupération du nom en français
|
||||||
|
String name = json['name']['fr'] ?? json['name']['en'] ?? 'unknown';
|
||||||
|
|
||||||
|
// Récupération des types (en français dans l'API Tyradex)
|
||||||
|
List types = json['types'] ?? [];
|
||||||
|
PokemonType type1 = types.isNotEmpty
|
||||||
|
? frenchTypeToEnum(types[0]['name'])
|
||||||
|
: PokemonType.unknown;
|
||||||
|
PokemonType? type2 = types.length > 1
|
||||||
|
? frenchTypeToEnum(types[1]['name'])
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// On crée un objet Pokemon à partir du fichier JSON
|
||||||
|
return Pokemon(
|
||||||
|
name: name,
|
||||||
|
id: id,
|
||||||
|
type1: type1,
|
||||||
|
type2: type2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/pokemon.dart';
|
||||||
|
|
||||||
|
// Widget qui permet d'afficher un pokémon
|
||||||
|
// Elle prend en paramètre un pokémon
|
||||||
|
// Elle affiche l'image du pokémon, son nom et son numéro
|
||||||
|
// Elle permet également de naviguer vers la page de détail du pokémon
|
||||||
|
class PokemonTile extends StatefulWidget {
|
||||||
|
const PokemonTile(this.pokemon, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
final Pokemon pokemon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PokemonTile> createState() => _PokemonTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PokemonTileState extends State<PokemonTile> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// Lorsqu'on tap sur le widget, on navigue vers la page de détail du pokémon
|
||||||
|
// On utilise la méthode Navigator.pushNamed pour naviguer vers la page de détail
|
||||||
|
// On passe en paramètre du Navigator le contexte et la route de la page de détail
|
||||||
|
// on utilise "widget.pokemon" pour accéder au pokémon passé en paramètre; widget représente l'instance de la classe PokemonTile
|
||||||
|
Navigator.pushNamed(context, "/pokemon-detail", arguments: widget.pokemon);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 150,
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.black),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.network(widget.pokemon.imageUrl, height: 100),
|
||||||
|
Text(widget.pokemon.formatedName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../../domain/entities/pokemon.dart';
|
import '../models/pokemon.dart';
|
||||||
import '../theme/type_colors.dart';
|
import '../utils/pokemon_type.dart';
|
||||||
|
|
||||||
/// Pastille colorée affichant le nom d'un type de Pokémon (couleur de fond selon le type).
|
// Widget qui permet d'afficher un type de Pokémon
|
||||||
|
// Elle prend en paramètre un type de Pokémon
|
||||||
|
// Elle affiche le nom du type avec une couleur de fond correspondant au type
|
||||||
class PokemonTypeWidget extends StatelessWidget {
|
class PokemonTypeWidget extends StatelessWidget {
|
||||||
const PokemonTypeWidget(this.type, {Key? key}) : super(key: key);
|
const PokemonTypeWidget(this.type, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@@ -10,13 +12,12 @@ class PokemonTypeWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final lang = Localizations.localeOf(context).languageCode;
|
String typeName = formatedTypeName(type);
|
||||||
String typeName = localizedTypeName(type, lang);
|
|
||||||
Color typeColor = typeToColor(type);
|
Color typeColor = typeToColor(type);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.all(15),
|
||||||
margin: const EdgeInsets.only(right: 6),
|
margin: const EdgeInsets.only(right: 10),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: typeColor,
|
color: typeColor,
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/// Constantes globales de l'application, centralisées pour éviter les valeurs en dur.
|
|
||||||
class AppConstants {
|
|
||||||
AppConstants._();
|
|
||||||
|
|
||||||
/// Nombre total de Pokémon gérés (jusqu'à la Gen 9).
|
|
||||||
static const int totalPokemon = 1025;
|
|
||||||
|
|
||||||
/// Points gagnés pour une bonne réponse normale.
|
|
||||||
static const int pointsNormal = 10;
|
|
||||||
|
|
||||||
/// Points gagnés pour une bonne réponse sur un Pokémon shiny.
|
|
||||||
static const int pointsShiny = 20;
|
|
||||||
|
|
||||||
/// Nombre de vies au début d'une partie.
|
|
||||||
static const int startingLives = 3;
|
|
||||||
|
|
||||||
/// Nombre de skips au début d'une partie.
|
|
||||||
static const int startingSkips = 3;
|
|
||||||
|
|
||||||
/// Nombre d'indices au début d'une partie.
|
|
||||||
static const int startingHints = 3;
|
|
||||||
|
|
||||||
/// Une bonne réponse tous les N donne un indice bonus.
|
|
||||||
static const int hintBonusEvery = 5;
|
|
||||||
|
|
||||||
/// Une bonne réponse tous les N donne un skip bonus.
|
|
||||||
static const int skipBonusEvery = 10;
|
|
||||||
|
|
||||||
/// Probabilité d'apparition d'un shiny : 1 chance sur N.
|
|
||||||
static const int shinyOdds = 10;
|
|
||||||
|
|
||||||
/// Hôte de l'API Tyradex.
|
|
||||||
static const String apiBaseUrl = 'tyradex.app';
|
|
||||||
|
|
||||||
/// Chemin de l'endpoint Pokémon.
|
|
||||||
static const String apiPokemonPath = 'api/v1/pokemon';
|
|
||||||
|
|
||||||
/// Clé SharedPreferences pour le meilleur score.
|
|
||||||
static const String prefsBestScore = 'best_score';
|
|
||||||
|
|
||||||
/// Clé SharedPreferences pour le filtre de générations.
|
|
||||||
static const String prefsGenFilter = 'gen_filter';
|
|
||||||
|
|
||||||
/// Clé SharedPreferences pour la langue de l'application.
|
|
||||||
static const String prefsLocale = 'locale_code';
|
|
||||||
|
|
||||||
/// Plages d'IDs Pokémon par génération [min, max] (inclusif).
|
|
||||||
static const List<(int, int)> genRanges = [
|
|
||||||
(1, 151), // Gen 1
|
|
||||||
(152, 251), // Gen 2
|
|
||||||
(252, 386), // Gen 3
|
|
||||||
(387, 493), // Gen 4
|
|
||||||
(494, 649), // Gen 5
|
|
||||||
(650, 721), // Gen 6
|
|
||||||
(722, 809), // Gen 7
|
|
||||||
(810, 905), // Gen 8
|
|
||||||
(906, 1025), // Gen 9
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import 'dart:developer' as dev;
|
|
||||||
|
|
||||||
/// Logger minimal de l'application. Remplace les appels directs à print().
|
|
||||||
/// Silencieux en release (les blocs assert sont retirés du build release).
|
|
||||||
class AppLogger {
|
|
||||||
AppLogger._();
|
|
||||||
|
|
||||||
static void info(String message) {
|
|
||||||
assert(() {
|
|
||||||
dev.log(message, name: 'INFO');
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void error(String message, [Object? error, StackTrace? stackTrace]) {
|
|
||||||
assert(() {
|
|
||||||
dev.log(message, name: 'ERROR', error: error, stackTrace: stackTrace);
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import 'package:sqflite_common/sqflite.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import '../dto/pokemon_dto.dart';
|
|
||||||
|
|
||||||
const _createSql = '''
|
|
||||||
CREATE TABLE IF NOT EXISTS pokemon (
|
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
name_fr TEXT,
|
|
||||||
name_en TEXT,
|
|
||||||
type1 TEXT NOT NULL,
|
|
||||||
type2 TEXT,
|
|
||||||
hp INTEGER NOT NULL,
|
|
||||||
atk INTEGER NOT NULL,
|
|
||||||
def INTEGER NOT NULL,
|
|
||||||
spd INTEGER NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
description_en TEXT,
|
|
||||||
isCaught INTEGER NOT NULL DEFAULT 0,
|
|
||||||
isSeen INTEGER NOT NULL DEFAULT 0
|
|
||||||
)
|
|
||||||
''';
|
|
||||||
|
|
||||||
class PokemonLocalDataSource {
|
|
||||||
final String languageCode;
|
|
||||||
|
|
||||||
PokemonLocalDataSource({this.languageCode = 'fr'});
|
|
||||||
|
|
||||||
Future<Database>? _dbFuture;
|
|
||||||
|
|
||||||
Future<Database> _getDb() {
|
|
||||||
return _dbFuture ??= openDatabase(
|
|
||||||
'pokedex.db',
|
|
||||||
version: 5,
|
|
||||||
onUpgrade: (db, oldVersion, newVersion) async {
|
|
||||||
// Drop and recreate on any upgrade to pick up new columns cleanly.
|
|
||||||
await db.execute('DROP TABLE IF EXISTS pokemon');
|
|
||||||
await db.execute(_createSql);
|
|
||||||
},
|
|
||||||
onCreate: (db, version) async {
|
|
||||||
await db.execute(_createSql);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Pokemon>> getAll() async {
|
|
||||||
final db = await _getDb();
|
|
||||||
final rows = await db.query('pokemon');
|
|
||||||
return rows.map((r) => PokemonDto.fromDb(r, languageCode: languageCode)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Pokemon?> getById(int id) async {
|
|
||||||
final db = await _getDb();
|
|
||||||
final rows = await db.query('pokemon', where: 'id = ?', whereArgs: [id]);
|
|
||||||
if (rows.isEmpty) return null;
|
|
||||||
return PokemonDto.fromDb(rows.first, languageCode: languageCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveAll(List<Pokemon> pokemons) async {
|
|
||||||
final db = await _getDb();
|
|
||||||
final batch = db.batch();
|
|
||||||
for (final p in pokemons) {
|
|
||||||
batch.insert('pokemon', PokemonDto.toDb(p),
|
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
|
||||||
}
|
|
||||||
await batch.commit(noResult: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> update(Pokemon pokemon) async {
|
|
||||||
final db = await _getDb();
|
|
||||||
await db.update('pokemon', PokemonDto.toDb(pokemon),
|
|
||||||
where: 'id = ?', whereArgs: [pokemon.id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> caughtCount() async {
|
|
||||||
final db = await _getDb();
|
|
||||||
final result =
|
|
||||||
await db.rawQuery('SELECT COUNT(*) FROM pokemon WHERE isCaught = 1');
|
|
||||||
return result.isNotEmpty ? (result.first.values.first as int? ?? 0) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> seenCount() async {
|
|
||||||
final db = await _getDb();
|
|
||||||
final result =
|
|
||||||
await db.rawQuery('SELECT COUNT(*) FROM pokemon WHERE isSeen = 1');
|
|
||||||
return result.isNotEmpty ? (result.first.values.first as int? ?? 0) : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../../core/logger.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import '../dto/pokemon_dto.dart';
|
|
||||||
|
|
||||||
/// Accès distant à l'API Tyradex.
|
|
||||||
class PokemonRemoteDataSource {
|
|
||||||
final http.Client _client;
|
|
||||||
final String languageCode;
|
|
||||||
|
|
||||||
PokemonRemoteDataSource({http.Client? client, this.languageCode = 'fr'})
|
|
||||||
: _client = client ?? http.Client();
|
|
||||||
|
|
||||||
Future<Pokemon> getById(int id) async {
|
|
||||||
AppLogger.info('API: fetching Pokémon $id (lang: $languageCode)');
|
|
||||||
final response = await _client
|
|
||||||
.get(Uri.https(AppConstants.apiBaseUrl, '${AppConstants.apiPokemonPath}/$id'));
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw Exception(
|
|
||||||
'Erreur récupération du pokémon $id, code ${response.statusCode}');
|
|
||||||
}
|
|
||||||
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
return PokemonDto.fromTyradexJson(json,
|
|
||||||
fallbackId: id, languageCode: languageCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches the English genus (category) from PokéAPI for a given Pokémon id.
|
|
||||||
/// Returns null on any error (network, missing entry, etc.).
|
|
||||||
Future<String?> getEnglishGenus(int id) async {
|
|
||||||
try {
|
|
||||||
final response = await _client
|
|
||||||
.get(Uri.https('pokeapi.co', '/api/v2/pokemon-species/$id/'));
|
|
||||||
if (response.statusCode != 200) return null;
|
|
||||||
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
|
||||||
final genera = json['genera'] as List<dynamic>? ?? [];
|
|
||||||
for (final g in genera) {
|
|
||||||
final lang = (g['language'] as Map<String, dynamic>?)?['name'];
|
|
||||||
if (lang == 'en') return g['genus'] as String?;
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Pokemon>> getAll() async {
|
|
||||||
AppLogger.info('API: fetching ALL Pokémon (lang: $languageCode)');
|
|
||||||
final response = await _client
|
|
||||||
.get(Uri.https(AppConstants.apiBaseUrl, AppConstants.apiPokemonPath));
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw Exception('Failed to load pokemon (code ${response.statusCode})');
|
|
||||||
}
|
|
||||||
final List<dynamic> jsonList = jsonDecode(response.body);
|
|
||||||
final result = <Pokemon>[];
|
|
||||||
for (final json in jsonList) {
|
|
||||||
if (json['pokedex_id'] == 0) continue;
|
|
||||||
try {
|
|
||||||
result.add(PokemonDto.fromTyradexJson(
|
|
||||||
json as Map<String, dynamic>,
|
|
||||||
languageCode: languageCode,
|
|
||||||
));
|
|
||||||
} catch (e, st) {
|
|
||||||
AppLogger.error('Parsing pokemon échoué: ${json['name']}', e, st);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
|
|
||||||
/// Conversion JSON <-> entité Pokemon. Unique endroit de parsing.
|
|
||||||
class PokemonDto {
|
|
||||||
PokemonDto._();
|
|
||||||
|
|
||||||
static PokemonType _frenchTypeToEnum(String type) {
|
|
||||||
const map = {
|
|
||||||
'Normal': PokemonType.normal,
|
|
||||||
'Combat': PokemonType.fighting,
|
|
||||||
'Vol': PokemonType.flying,
|
|
||||||
'Poison': PokemonType.poison,
|
|
||||||
'Sol': PokemonType.ground,
|
|
||||||
'Roche': PokemonType.rock,
|
|
||||||
'Insecte': PokemonType.bug,
|
|
||||||
'Spectre': PokemonType.ghost,
|
|
||||||
'Acier': PokemonType.steel,
|
|
||||||
'Feu': PokemonType.fire,
|
|
||||||
'Eau': PokemonType.water,
|
|
||||||
'Plante': PokemonType.grass,
|
|
||||||
'Électrik': PokemonType.electric,
|
|
||||||
'Psy': PokemonType.psychic,
|
|
||||||
'Glace': PokemonType.ice,
|
|
||||||
'Dragon': PokemonType.dragon,
|
|
||||||
'Ténèbres': PokemonType.dark,
|
|
||||||
'Fée': PokemonType.fairy,
|
|
||||||
};
|
|
||||||
return map[type] ?? PokemonType.unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tyradex always returns type names in French regardless of endpoint language.
|
|
||||||
static PokemonType _typeToEnum(String typeName, String languageCode) {
|
|
||||||
return _frenchTypeToEnum(typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construit une entité depuis la réponse Tyradex.
|
|
||||||
/// [languageCode] détermine quelle langue utiliser pour [name] ('fr' ou 'en').
|
|
||||||
static Pokemon fromTyradexJson(
|
|
||||||
Map<String, dynamic> json, {
|
|
||||||
int? fallbackId,
|
|
||||||
String languageCode = 'fr',
|
|
||||||
}) {
|
|
||||||
final id = (json['pokedex_id'] as int?) ??
|
|
||||||
fallbackId ??
|
|
||||||
(throw ArgumentError('pokedex_id absent et fallbackId non fourni'));
|
|
||||||
|
|
||||||
final nameMap = json['name'] as Map<String, dynamic>?;
|
|
||||||
final nameFr = nameMap?['fr'] as String? ?? 'unknown';
|
|
||||||
final nameEn = nameMap?['en'] as String? ?? nameFr;
|
|
||||||
final name = languageCode == 'en' ? nameEn : nameFr;
|
|
||||||
|
|
||||||
final List types = json['types'] ?? [];
|
|
||||||
// Tyradex retourne toujours les types dans la langue du endpoint (fr ou en).
|
|
||||||
final type1 = types.isNotEmpty
|
|
||||||
? _typeToEnum(types[0]['name'] as String, languageCode)
|
|
||||||
: PokemonType.unknown;
|
|
||||||
final type2 = types.length > 1
|
|
||||||
? _typeToEnum(types[1]['name'] as String, languageCode)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final Map<String, dynamic>? stats = json['stats'];
|
|
||||||
// Tyradex expose 'category' (FR) et parfois 'category_en' (EN).
|
|
||||||
final descFr = json['category'] as String?;
|
|
||||||
final descEn = (json['category_en'] ?? json['category']) as String?;
|
|
||||||
return Pokemon(
|
|
||||||
name: name,
|
|
||||||
nameFr: nameFr,
|
|
||||||
nameEn: nameEn,
|
|
||||||
id: id,
|
|
||||||
type1: type1,
|
|
||||||
type2: type2,
|
|
||||||
hp: stats?['hp'] ?? 0,
|
|
||||||
atk: stats?['atk'] ?? 0,
|
|
||||||
def: stats?['def'] ?? 0,
|
|
||||||
spd: stats?['vit'] ?? 0,
|
|
||||||
description: descFr,
|
|
||||||
descriptionEn: descEn,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sérialise pour SQLite (stocke les deux noms et descriptions).
|
|
||||||
static Map<String, dynamic> toDb(Pokemon p) {
|
|
||||||
return {
|
|
||||||
'name': p.name,
|
|
||||||
'name_fr': p.nameFr ?? p.name,
|
|
||||||
'name_en': p.nameEn ?? p.name,
|
|
||||||
'id': p.id,
|
|
||||||
'type1': p.type1.name,
|
|
||||||
'type2': p.type2?.name,
|
|
||||||
'hp': p.hp,
|
|
||||||
'atk': p.atk,
|
|
||||||
'def': p.def,
|
|
||||||
'spd': p.spd,
|
|
||||||
'description': p.description,
|
|
||||||
'description_en': p.descriptionEn,
|
|
||||||
'isCaught': p.isCaught ? 1 : 0,
|
|
||||||
'isSeen': p.isSeen ? 1 : 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reconstruit depuis une ligne SQLite.
|
|
||||||
/// [languageCode] détermine quel nom afficher.
|
|
||||||
static Pokemon fromDb(Map<String, dynamic> row, {String languageCode = 'fr'}) {
|
|
||||||
final nameFr = row['name_fr'] as String? ?? row['name'] as String;
|
|
||||||
final nameEn = row['name_en'] as String? ?? nameFr;
|
|
||||||
final name = languageCode == 'en' ? nameEn : nameFr;
|
|
||||||
|
|
||||||
return Pokemon(
|
|
||||||
name: name,
|
|
||||||
nameFr: nameFr,
|
|
||||||
nameEn: nameEn,
|
|
||||||
id: row['id'],
|
|
||||||
type1: PokemonType.values.firstWhere(
|
|
||||||
(e) => e.name == row['type1'],
|
|
||||||
orElse: () => PokemonType.unknown,
|
|
||||||
),
|
|
||||||
type2: row['type2'] != null
|
|
||||||
? PokemonType.values.firstWhere(
|
|
||||||
(e) => e.name == row['type2'],
|
|
||||||
orElse: () => PokemonType.unknown,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
hp: row['hp'] ?? 0,
|
|
||||||
atk: row['atk'] ?? 0,
|
|
||||||
def: row['def'] ?? 0,
|
|
||||||
spd: row['spd'] ?? 0,
|
|
||||||
description: row['description'],
|
|
||||||
descriptionEn: row['description_en'] as String?,
|
|
||||||
isCaught: row['isCaught'] == 1 || row['isCaught'] == true,
|
|
||||||
isSeen: row['isSeen'] == 1 || row['isSeen'] == true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../../core/logger.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import '../../domain/repositories/pokemon_repository.dart';
|
|
||||||
import '../datasources/pokemon_local_datasource.dart';
|
|
||||||
import '../datasources/pokemon_remote_datasource.dart';
|
|
||||||
|
|
||||||
/// Implémentation : DB locale d'abord, complétée/repliée sur l'API.
|
|
||||||
/// [local] vaut `null` sur le web (pas de SQLite).
|
|
||||||
class PokemonRepositoryImpl implements PokemonRepository {
|
|
||||||
final PokemonRemoteDataSource remote;
|
|
||||||
final PokemonLocalDataSource? local;
|
|
||||||
|
|
||||||
PokemonRepositoryImpl({required this.remote, this.local});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Pokemon>> getAll() async {
|
|
||||||
final localDs = local;
|
|
||||||
if (localDs == null) return remote.getAll();
|
|
||||||
|
|
||||||
final cached = await localDs.getAll();
|
|
||||||
if (cached.length >= AppConstants.totalPokemon) return cached;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final remoteList = await remote.getAll();
|
|
||||||
await localDs.saveAll(remoteList);
|
|
||||||
return localDs.getAll();
|
|
||||||
} catch (e, st) {
|
|
||||||
AppLogger.error('Sync getAll échouée', e, st);
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Pokemon?> getById(int id) async {
|
|
||||||
final localDs = local;
|
|
||||||
if (localDs != null) {
|
|
||||||
final cached = await localDs.getById(id);
|
|
||||||
if (cached != null) return cached;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final fetched = await remote.getById(id);
|
|
||||||
if (localDs != null) await localDs.saveAll([fetched]);
|
|
||||||
return fetched;
|
|
||||||
} catch (e, st) {
|
|
||||||
AppLogger.error('getById($id) échoué', e, st);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> saveAll(List<Pokemon> pokemons) async =>
|
|
||||||
local?.saveAll(pokemons);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> update(Pokemon pokemon) async => local?.update(pokemon);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> caughtCount() async => (await local?.caughtCount()) ?? 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> seenCount() async => (await local?.seenCount()) ?? 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String?> getEnglishGenus(int id) => remote.getEnglishGenus(id);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
import '../models/pokemon.dart';
|
||||||
|
|
||||||
|
// Permet de gérer la base de données
|
||||||
|
class PokedexDatabase {
|
||||||
|
static Database? database;
|
||||||
|
static Future<void> initDatabase() async {
|
||||||
|
database = await openDatabase(
|
||||||
|
"pokedex.db", // Nom de la base de données
|
||||||
|
version: 1, // Version de la base de données, permet de gérer les migrations
|
||||||
|
onCreate: (db, version) async { // Fonction qui sera appelée lors de la création de la base de données
|
||||||
|
// Création de la table pokemon avec les colonnes id, name, type1 et type2
|
||||||
|
await db.execute("CREATE TABLE IF NOT EXISTS pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT)");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de récupérer la base de données
|
||||||
|
static Future<Database> getDatabase() async {
|
||||||
|
if (database == null) {
|
||||||
|
await initDatabase(); // On initialise la base de données si elle n'est pas encore initialisée
|
||||||
|
}
|
||||||
|
return database!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet d'insérer un Pokémon dans la base de données
|
||||||
|
static Future<void> insertPokemon(Pokemon pokemon) async {
|
||||||
|
Database database = await getDatabase();
|
||||||
|
await database.insert("pokemon", pokemon.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de récupérer la liste des pokémons dans la base de données
|
||||||
|
static Future<List<Pokemon>> getPokemonList() async {
|
||||||
|
Database database = await getDatabase();
|
||||||
|
var response = await database.query("pokemon");
|
||||||
|
return response.map((pokemon) => Pokemon.fromJson(pokemon)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de supprimer un Pokémon de la base de données
|
||||||
|
static Future<void> deletePokemon(int id) async {
|
||||||
|
Database database = await getDatabase();
|
||||||
|
await database.delete("pokemon", where: "id = ?", whereArgs: [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de supprimer tous les pokémons de la base de données
|
||||||
|
static Future<void> deleteAllPokemon() async {
|
||||||
|
Database database = await getDatabase();
|
||||||
|
await database.delete("pokemon");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de mettre à jour un Pokémon dans la base de données
|
||||||
|
static Future<void> updatePokemon(Pokemon pokemon) async {
|
||||||
|
Database database = await getDatabase();
|
||||||
|
await database.update("pokemon", pokemon.toJson(), where: "id = ?", whereArgs: [pokemon.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de récupérer un Pokémon dans la base de données à partir de son ID
|
||||||
|
static Future<Pokemon?> getPokemon(int id) async {
|
||||||
|
Database database = await getDatabase();
|
||||||
|
List<Map<String, dynamic>> pokemonList = await database.query("pokemon", where: "id = ?", whereArgs: [id]);
|
||||||
|
if (pokemonList.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Pokemon.fromJson(pokemonList.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/// Entité métier représentant un Pokémon. Pure : aucun import Flutter / DB / API.
|
|
||||||
class Pokemon {
|
|
||||||
final String name;
|
|
||||||
final String? nameFr;
|
|
||||||
final String? nameEn;
|
|
||||||
final int id;
|
|
||||||
final PokemonType type1;
|
|
||||||
final PokemonType? type2;
|
|
||||||
final int hp;
|
|
||||||
final int atk;
|
|
||||||
final int def;
|
|
||||||
final int spd;
|
|
||||||
final String? description;
|
|
||||||
final String? descriptionEn;
|
|
||||||
final bool isCaught;
|
|
||||||
final bool isSeen;
|
|
||||||
|
|
||||||
const Pokemon({
|
|
||||||
required this.name,
|
|
||||||
this.nameFr,
|
|
||||||
this.nameEn,
|
|
||||||
required this.id,
|
|
||||||
required this.type1,
|
|
||||||
this.type2,
|
|
||||||
required this.hp,
|
|
||||||
required this.atk,
|
|
||||||
required this.def,
|
|
||||||
required this.spd,
|
|
||||||
this.description,
|
|
||||||
this.descriptionEn,
|
|
||||||
this.isCaught = false,
|
|
||||||
this.isSeen = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
String get imageUrl =>
|
|
||||||
'https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/$id/regular.png';
|
|
||||||
String get shinyImageUrl =>
|
|
||||||
'https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/$id/shiny.png';
|
|
||||||
String get cryUrl => 'https://pokemoncries.com/cries/$id.mp3';
|
|
||||||
|
|
||||||
String get formatedName =>
|
|
||||||
name.isEmpty ? name : name[0].toUpperCase() + name.substring(1);
|
|
||||||
|
|
||||||
Pokemon copyWith({
|
|
||||||
String? name,
|
|
||||||
String? nameFr,
|
|
||||||
String? nameEn,
|
|
||||||
int? id,
|
|
||||||
PokemonType? type1,
|
|
||||||
PokemonType? type2,
|
|
||||||
int? hp,
|
|
||||||
int? atk,
|
|
||||||
int? def,
|
|
||||||
int? spd,
|
|
||||||
String? description,
|
|
||||||
String? descriptionEn,
|
|
||||||
bool? isCaught,
|
|
||||||
bool? isSeen,
|
|
||||||
}) {
|
|
||||||
return Pokemon(
|
|
||||||
name: name ?? this.name,
|
|
||||||
nameFr: nameFr ?? this.nameFr,
|
|
||||||
nameEn: nameEn ?? this.nameEn,
|
|
||||||
id: id ?? this.id,
|
|
||||||
type1: type1 ?? this.type1,
|
|
||||||
type2: type2 ?? this.type2,
|
|
||||||
hp: hp ?? this.hp,
|
|
||||||
atk: atk ?? this.atk,
|
|
||||||
def: def ?? this.def,
|
|
||||||
spd: spd ?? this.spd,
|
|
||||||
description: description ?? this.description,
|
|
||||||
descriptionEn: descriptionEn ?? this.descriptionEn,
|
|
||||||
isCaught: isCaught ?? this.isCaught,
|
|
||||||
isSeen: isSeen ?? this.isSeen,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Les différents types de Pokémon.
|
|
||||||
enum PokemonType {
|
|
||||||
normal, fighting, flying, poison, ground, rock, bug, ghost, steel, fire,
|
|
||||||
water, grass, electric, psychic, ice, dragon, dark, fairy, unknown, shadow
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../entities/pokemon.dart';
|
|
||||||
import 'game_state.dart';
|
|
||||||
|
|
||||||
/// Règles du jeu, pures (aucune IO, aucun widget). Entièrement testable.
|
|
||||||
class GameEngine {
|
|
||||||
const GameEngine();
|
|
||||||
|
|
||||||
/// Nouvelle partie : réinitialise tout sauf le bestScore.
|
|
||||||
GameState newGame(GameState s) {
|
|
||||||
return s.copyWith(
|
|
||||||
lives: AppConstants.startingLives,
|
|
||||||
skips: AppConstants.startingSkips,
|
|
||||||
hints: AppConstants.startingHints,
|
|
||||||
sessionCorrectCount: 0,
|
|
||||||
currentScore: 0,
|
|
||||||
status: GameStatus.loading,
|
|
||||||
isHintUsed: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Démarre une manche avec un nouveau Pokémon.
|
|
||||||
GameState startRound(GameState s, Pokemon p, {required bool isShiny}) {
|
|
||||||
return s.copyWith(
|
|
||||||
currentPokemon: p,
|
|
||||||
isShiny: isShiny,
|
|
||||||
isHintUsed: false,
|
|
||||||
status: GameStatus.playing,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Soumet une réponse. Renvoie le nouvel état + la nature du résultat.
|
|
||||||
GuessOutcome submitGuess(GameState s, String guess) {
|
|
||||||
final pokemon = s.currentPokemon;
|
|
||||||
if (pokemon == null || s.status != GameStatus.playing) {
|
|
||||||
return GuessOutcome(s, GuessResult.invalid);
|
|
||||||
}
|
|
||||||
|
|
||||||
final normalizedGuess = _normalize(guess.trim().toLowerCase());
|
|
||||||
|
|
||||||
// Accept the name in either language so a language switch mid-game never blocks the player.
|
|
||||||
final acceptedNames = <String>{
|
|
||||||
pokemon.name,
|
|
||||||
if (pokemon.nameFr != null) pokemon.nameFr!,
|
|
||||||
if (pokemon.nameEn != null) pokemon.nameEn!,
|
|
||||||
}.map((n) => _normalize(n.toLowerCase())).toSet();
|
|
||||||
|
|
||||||
if (acceptedNames.contains(normalizedGuess)) {
|
|
||||||
final newSession = s.sessionCorrectCount + 1;
|
|
||||||
final gained = s.isShiny ? AppConstants.pointsShiny : AppConstants.pointsNormal;
|
|
||||||
final newScore = s.currentScore + gained;
|
|
||||||
final hintBonus =
|
|
||||||
newSession % AppConstants.hintBonusEvery == 0 ? 1 : 0;
|
|
||||||
final skipBonus =
|
|
||||||
newSession % AppConstants.skipBonusEvery == 0 ? 1 : 0;
|
|
||||||
|
|
||||||
final newState = s.copyWith(
|
|
||||||
currentPokemon: pokemon.copyWith(isCaught: true, isSeen: true),
|
|
||||||
currentScore: newScore,
|
|
||||||
bestScore: newScore > s.bestScore ? newScore : s.bestScore,
|
|
||||||
sessionCorrectCount: newSession,
|
|
||||||
hints: s.hints + hintBonus,
|
|
||||||
skips: s.skips + skipBonus,
|
|
||||||
status: GameStatus.roundWon,
|
|
||||||
);
|
|
||||||
return GuessOutcome(newState, GuessResult.correct);
|
|
||||||
}
|
|
||||||
|
|
||||||
final remainingLives = s.lives - 1;
|
|
||||||
if (remainingLives <= 0) {
|
|
||||||
return GuessOutcome(
|
|
||||||
s.copyWith(lives: 0, status: GameStatus.gameOver),
|
|
||||||
GuessResult.gameOver,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return GuessOutcome(
|
|
||||||
s.copyWith(lives: remainingLives),
|
|
||||||
GuessResult.wrong,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consomme un indice si disponible et non déjà utilisé.
|
|
||||||
GameState useHint(GameState s) {
|
|
||||||
if (s.isHintUsed || s.hints <= 0) return s;
|
|
||||||
return s.copyWith(isHintUsed: true, hints: s.hints - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consomme un skip si disponible.
|
|
||||||
GameState useSkip(GameState s) {
|
|
||||||
if (s.skips <= 0) return s;
|
|
||||||
return s.copyWith(skips: s.skips - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _normalize(String input) {
|
|
||||||
const withDia = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÖØòóôõöøÈÉÊËèéêëÇçÌÍÎÏìíîïÙÚÛÜùúûüÿÑñ';
|
|
||||||
const withoutDia = 'AAAAAAaaaaaaOOOOOOooooooEEEEeeeeCcIIIIiiiiUUUUuuuuyNn';
|
|
||||||
var output = input;
|
|
||||||
for (var i = 0; i < withDia.length; i++) {
|
|
||||||
output = output.replaceAll(withDia[i], withoutDia[i]);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../entities/pokemon.dart';
|
|
||||||
|
|
||||||
/// Phase courante de la partie.
|
|
||||||
enum GameStatus { loading, playing, roundWon, gameOver, error }
|
|
||||||
|
|
||||||
/// Résultat d'une soumission de réponse.
|
|
||||||
enum GuessResult { correct, wrong, gameOver, invalid }
|
|
||||||
|
|
||||||
/// État immuable d'une partie.
|
|
||||||
class GameState {
|
|
||||||
final Pokemon? currentPokemon;
|
|
||||||
final int lives;
|
|
||||||
final int skips;
|
|
||||||
final int hints;
|
|
||||||
final int sessionCorrectCount;
|
|
||||||
final int currentScore;
|
|
||||||
final int bestScore;
|
|
||||||
final bool isShiny;
|
|
||||||
final bool isHintUsed;
|
|
||||||
final GameStatus status;
|
|
||||||
|
|
||||||
const GameState({
|
|
||||||
this.currentPokemon,
|
|
||||||
this.lives = AppConstants.startingLives,
|
|
||||||
this.skips = AppConstants.startingSkips,
|
|
||||||
this.hints = AppConstants.startingHints,
|
|
||||||
this.sessionCorrectCount = 0,
|
|
||||||
this.currentScore = 0,
|
|
||||||
this.bestScore = 0,
|
|
||||||
this.isShiny = false,
|
|
||||||
this.isHintUsed = false,
|
|
||||||
this.status = GameStatus.loading,
|
|
||||||
});
|
|
||||||
|
|
||||||
GameState copyWith({
|
|
||||||
Pokemon? currentPokemon,
|
|
||||||
int? lives,
|
|
||||||
int? skips,
|
|
||||||
int? hints,
|
|
||||||
int? sessionCorrectCount,
|
|
||||||
int? currentScore,
|
|
||||||
int? bestScore,
|
|
||||||
bool? isShiny,
|
|
||||||
bool? isHintUsed,
|
|
||||||
GameStatus? status,
|
|
||||||
}) {
|
|
||||||
return GameState(
|
|
||||||
currentPokemon: currentPokemon ?? this.currentPokemon,
|
|
||||||
lives: lives ?? this.lives,
|
|
||||||
skips: skips ?? this.skips,
|
|
||||||
hints: hints ?? this.hints,
|
|
||||||
sessionCorrectCount: sessionCorrectCount ?? this.sessionCorrectCount,
|
|
||||||
currentScore: currentScore ?? this.currentScore,
|
|
||||||
bestScore: bestScore ?? this.bestScore,
|
|
||||||
isShiny: isShiny ?? this.isShiny,
|
|
||||||
isHintUsed: isHintUsed ?? this.isHintUsed,
|
|
||||||
status: status ?? this.status,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Issue d'un appel à GameEngine.submitGuess : nouvel état + nature du résultat.
|
|
||||||
class GuessOutcome {
|
|
||||||
final GameState state;
|
|
||||||
final GuessResult result;
|
|
||||||
const GuessOutcome(this.state, this.result);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import '../entities/pokemon.dart';
|
|
||||||
|
|
||||||
/// Contrat d'accès aux données Pokémon. L'UI et le domaine ne connaissent que cette interface.
|
|
||||||
abstract interface class PokemonRepository {
|
|
||||||
/// Tous les Pokémon (DB locale d'abord, complétée par l'API si nécessaire).
|
|
||||||
Future<List<Pokemon>> getAll();
|
|
||||||
|
|
||||||
/// Un Pokémon par id (DB d'abord, sinon API + mise en cache). `null` si introuvable.
|
|
||||||
Future<Pokemon?> getById(int id);
|
|
||||||
|
|
||||||
/// Insère/remplace une liste de Pokémon.
|
|
||||||
Future<void> saveAll(List<Pokemon> pokemons);
|
|
||||||
|
|
||||||
/// Met à jour un Pokémon existant.
|
|
||||||
Future<void> update(Pokemon pokemon);
|
|
||||||
|
|
||||||
/// Nombre de Pokémon attrapés.
|
|
||||||
Future<int> caughtCount();
|
|
||||||
|
|
||||||
/// Nombre de Pokémon vus.
|
|
||||||
Future<int> seenCount();
|
|
||||||
|
|
||||||
/// Catégorie anglaise d'un Pokémon (ex. "Seed Pokémon") depuis PokéAPI. Null si indisponible.
|
|
||||||
Future<String?> getEnglishGenus(int id);
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"@@locale": "en",
|
|
||||||
"appTitle": "Pokéguess",
|
|
||||||
|
|
||||||
"whosThatPokemon": "WHO'S THAT POKÉMON?",
|
|
||||||
"shinyDetected": "✨ SHINY POKÉMON DETECTED! ✨",
|
|
||||||
"genFilter": "GEN FILTER",
|
|
||||||
"identificationInput": "IDENTIFICATION INPUT",
|
|
||||||
"hintPrefix": "HINT",
|
|
||||||
"enterPokemonName": "Enter Pokémon name...",
|
|
||||||
"continueButton": "CONTINUE",
|
|
||||||
"guessButton": "GUESS!",
|
|
||||||
"hintButton": "HINT ({count})",
|
|
||||||
"@hintButton": { "placeholders": { "count": { "type": "int" } } },
|
|
||||||
"skipButton": "SKIP ({count})",
|
|
||||||
"@skipButton": { "placeholders": { "count": { "type": "int" } } },
|
|
||||||
"currentScore": "CURRENT SCORE",
|
|
||||||
"personalBest": "PERSONAL BEST: {score}",
|
|
||||||
"@personalBest": { "placeholders": { "score": { "type": "int" } } },
|
|
||||||
"caughtShiny": "✨ SHINY! You caught {name}! (+20 pts) ✨",
|
|
||||||
"@caughtShiny": { "placeholders": { "name": { "type": "String" } } },
|
|
||||||
"caughtNormal": "Correct! You caught {name}!",
|
|
||||||
"@caughtNormal": { "placeholders": { "name": { "type": "String" } } },
|
|
||||||
"wrongGuess": "Wrong guess! Try again.",
|
|
||||||
"errorLoadingPokemon": "Error loading Pokémon",
|
|
||||||
|
|
||||||
"listNational": "LIST - NATIONAL",
|
|
||||||
"tabAll": "ALL",
|
|
||||||
"tabCaught": "CAUGHT",
|
|
||||||
"pokemonDiscovered": "POKEMON DISCOVERED",
|
|
||||||
"noPokemonFound": "NO POKEMON FOUND IN {filter}",
|
|
||||||
"@noPokemonFound": { "placeholders": { "filter": { "type": "String" } } },
|
|
||||||
"pokedexFooter": "NATIONAL POKEDEX V2.0",
|
|
||||||
"loadingError": "Loading error",
|
|
||||||
|
|
||||||
"baseStats": "BASE STATS",
|
|
||||||
"noDescription": "No description available for this Pokémon.",
|
|
||||||
|
|
||||||
"gameOver": "GAME OVER",
|
|
||||||
"itWas": "It was {name}!",
|
|
||||||
"@itWas": { "placeholders": { "name": { "type": "String" } } },
|
|
||||||
"gameOverMessage": "\"Looks like your journey ends here. You've run out of energy!\"",
|
|
||||||
"statStreak": "STREAK",
|
|
||||||
"statSeen": "SEEN",
|
|
||||||
"statScore": "SCORE",
|
|
||||||
"tryAgain": "TRY AGAIN",
|
|
||||||
"backToPokedex": "BACK TO POKEDEX",
|
|
||||||
|
|
||||||
"system": "SYSTEM",
|
|
||||||
"statistics": "STATISTICS",
|
|
||||||
"colorPalette": "COLOR PALETTE",
|
|
||||||
"language": "LANGUAGE",
|
|
||||||
"statBestScore": "Best score",
|
|
||||||
"statCaught": "Caught",
|
|
||||||
"statSeenLabel": "Seen",
|
|
||||||
"statCompletion": "Completion",
|
|
||||||
|
|
||||||
"navList": "LIST",
|
|
||||||
"navGuess": "GUESS",
|
|
||||||
"navSystem": "SYSTEM",
|
|
||||||
|
|
||||||
"langEnglish": "English",
|
|
||||||
"langFrench": "French",
|
|
||||||
|
|
||||||
"palette0": "Pokédex Red",
|
|
||||||
"palette1": "Blue Ocean",
|
|
||||||
"palette2": "Green Forest",
|
|
||||||
"palette3": "Lightning Yellow",
|
|
||||||
"palette4": "Purple Shadow"
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"@@locale": "fr",
|
|
||||||
"appTitle": "Pokéguess",
|
|
||||||
|
|
||||||
"whosThatPokemon": "QUI EST CE POKÉMON ?",
|
|
||||||
"shinyDetected": "✨ POKÉMON SHINY DÉTECTÉ ! ✨",
|
|
||||||
"genFilter": "FILTRE GÉN.",
|
|
||||||
"identificationInput": "SAISIE D'IDENTIFICATION",
|
|
||||||
"hintPrefix": "INDICE",
|
|
||||||
"enterPokemonName": "Entrez le nom du Pokémon...",
|
|
||||||
"continueButton": "CONTINUER",
|
|
||||||
"guessButton": "DEVINER !",
|
|
||||||
"hintButton": "INDICE ({count})",
|
|
||||||
"skipButton": "PASSER ({count})",
|
|
||||||
"currentScore": "SCORE ACTUEL",
|
|
||||||
"personalBest": "MEILLEUR SCORE : {score}",
|
|
||||||
"caughtShiny": "✨ SHINY ! Tu as attrapé {name} ! (+20 pts) ✨",
|
|
||||||
"caughtNormal": "Correct ! Tu as attrapé {name} !",
|
|
||||||
"wrongGuess": "Mauvaise réponse ! Réessaie.",
|
|
||||||
"errorLoadingPokemon": "Erreur de chargement du Pokémon",
|
|
||||||
|
|
||||||
"listNational": "LISTE - NATIONALE",
|
|
||||||
"tabAll": "TOUS",
|
|
||||||
"tabCaught": "ATTRAPÉS",
|
|
||||||
"pokemonDiscovered": "POKÉMON DÉCOUVERTS",
|
|
||||||
"noPokemonFound": "AUCUN POKÉMON DANS {filter}",
|
|
||||||
"pokedexFooter": "POKÉDEX NATIONAL V2.0",
|
|
||||||
"loadingError": "Erreur de chargement",
|
|
||||||
|
|
||||||
"baseStats": "STATS DE BASE",
|
|
||||||
"noDescription": "Aucune description disponible pour ce Pokémon.",
|
|
||||||
|
|
||||||
"gameOver": "PARTIE TERMINÉE",
|
|
||||||
"itWas": "C'était {name} !",
|
|
||||||
"gameOverMessage": "« On dirait que ton aventure s'arrête ici. Tu n'as plus d'énergie ! »",
|
|
||||||
"statStreak": "SÉRIE",
|
|
||||||
"statSeen": "VUS",
|
|
||||||
"statScore": "SCORE",
|
|
||||||
"tryAgain": "REJOUER",
|
|
||||||
"backToPokedex": "RETOUR AU POKÉDEX",
|
|
||||||
|
|
||||||
"system": "SYSTÈME",
|
|
||||||
"statistics": "STATISTIQUES",
|
|
||||||
"colorPalette": "PALETTE DE COULEURS",
|
|
||||||
"language": "LANGUE",
|
|
||||||
"statBestScore": "Meilleur score",
|
|
||||||
"statCaught": "Attrapés",
|
|
||||||
"statSeenLabel": "Vus",
|
|
||||||
"statCompletion": "Complétion",
|
|
||||||
|
|
||||||
"navList": "LISTE",
|
|
||||||
"navGuess": "DEVINER",
|
|
||||||
"navSystem": "SYSTÈME",
|
|
||||||
|
|
||||||
"langEnglish": "Anglais",
|
|
||||||
"langFrench": "Français",
|
|
||||||
|
|
||||||
"palette0": "Pokédex Rouge",
|
|
||||||
"palette1": "Océan Bleu",
|
|
||||||
"palette2": "Forêt Verte",
|
|
||||||
"palette3": "Foudre Jaune",
|
|
||||||
"palette4": "Ombre Violette"
|
|
||||||
}
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
||||||
import 'package:intl/intl.dart' as intl;
|
|
||||||
|
|
||||||
import 'app_localizations_en.dart';
|
|
||||||
import 'app_localizations_fr.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
|
|
||||||
/// Callers can lookup localized strings with an instance of AppLocalizations
|
|
||||||
/// returned by `AppLocalizations.of(context)`.
|
|
||||||
///
|
|
||||||
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
|
||||||
/// `localizationDelegates` list, and the locales they support in the app's
|
|
||||||
/// `supportedLocales` list. For example:
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// import 'l10n/app_localizations.dart';
|
|
||||||
///
|
|
||||||
/// return MaterialApp(
|
|
||||||
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
||||||
/// supportedLocales: AppLocalizations.supportedLocales,
|
|
||||||
/// home: MyApplicationHome(),
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## Update pubspec.yaml
|
|
||||||
///
|
|
||||||
/// Please make sure to update your pubspec.yaml to include the following
|
|
||||||
/// packages:
|
|
||||||
///
|
|
||||||
/// ```yaml
|
|
||||||
/// dependencies:
|
|
||||||
/// # Internationalization support.
|
|
||||||
/// flutter_localizations:
|
|
||||||
/// sdk: flutter
|
|
||||||
/// intl: any # Use the pinned version from flutter_localizations
|
|
||||||
///
|
|
||||||
/// # Rest of dependencies
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ## iOS Applications
|
|
||||||
///
|
|
||||||
/// iOS applications define key application metadata, including supported
|
|
||||||
/// locales, in an Info.plist file that is built into the application bundle.
|
|
||||||
/// To configure the locales supported by your app, you’ll need to edit this
|
|
||||||
/// file.
|
|
||||||
///
|
|
||||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
|
||||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
|
||||||
/// project’s Runner folder.
|
|
||||||
///
|
|
||||||
/// Next, select the Information Property List item, select Add Item from the
|
|
||||||
/// Editor menu, then select Localizations from the pop-up menu.
|
|
||||||
///
|
|
||||||
/// Select and expand the newly-created Localizations item then, for each
|
|
||||||
/// locale your application supports, add a new item and select the locale
|
|
||||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
|
||||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
|
||||||
/// property.
|
|
||||||
abstract class AppLocalizations {
|
|
||||||
AppLocalizations(String locale)
|
|
||||||
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
|
||||||
|
|
||||||
final String localeName;
|
|
||||||
|
|
||||||
static AppLocalizations? of(BuildContext context) {
|
|
||||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const LocalizationsDelegate<AppLocalizations> delegate =
|
|
||||||
_AppLocalizationsDelegate();
|
|
||||||
|
|
||||||
/// A list of this localizations delegate along with the default localizations
|
|
||||||
/// delegates.
|
|
||||||
///
|
|
||||||
/// Returns a list of localizations delegates containing this delegate along with
|
|
||||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
|
||||||
/// and GlobalWidgetsLocalizations.delegate.
|
|
||||||
///
|
|
||||||
/// Additional delegates can be added by appending to this list in
|
|
||||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
|
||||||
/// of delegates is preferred or required.
|
|
||||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
|
||||||
<LocalizationsDelegate<dynamic>>[
|
|
||||||
delegate,
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// A list of this localizations delegate's supported locales.
|
|
||||||
static const List<Locale> supportedLocales = <Locale>[
|
|
||||||
Locale('en'),
|
|
||||||
Locale('fr')
|
|
||||||
];
|
|
||||||
|
|
||||||
/// No description provided for @appTitle.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Pokéguess'**
|
|
||||||
String get appTitle;
|
|
||||||
|
|
||||||
/// No description provided for @whosThatPokemon.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'WHO\'S THAT POKÉMON?'**
|
|
||||||
String get whosThatPokemon;
|
|
||||||
|
|
||||||
/// No description provided for @shinyDetected.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'✨ SHINY POKÉMON DETECTED! ✨'**
|
|
||||||
String get shinyDetected;
|
|
||||||
|
|
||||||
/// No description provided for @genFilter.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'GEN FILTER'**
|
|
||||||
String get genFilter;
|
|
||||||
|
|
||||||
/// No description provided for @identificationInput.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'IDENTIFICATION INPUT'**
|
|
||||||
String get identificationInput;
|
|
||||||
|
|
||||||
/// No description provided for @hintPrefix.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'HINT'**
|
|
||||||
String get hintPrefix;
|
|
||||||
|
|
||||||
/// No description provided for @enterPokemonName.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Enter Pokémon name...'**
|
|
||||||
String get enterPokemonName;
|
|
||||||
|
|
||||||
/// No description provided for @continueButton.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'CONTINUE'**
|
|
||||||
String get continueButton;
|
|
||||||
|
|
||||||
/// No description provided for @guessButton.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'GUESS!'**
|
|
||||||
String get guessButton;
|
|
||||||
|
|
||||||
/// No description provided for @hintButton.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'HINT ({count})'**
|
|
||||||
String hintButton(int count);
|
|
||||||
|
|
||||||
/// No description provided for @skipButton.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'SKIP ({count})'**
|
|
||||||
String skipButton(int count);
|
|
||||||
|
|
||||||
/// No description provided for @currentScore.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'CURRENT SCORE'**
|
|
||||||
String get currentScore;
|
|
||||||
|
|
||||||
/// No description provided for @personalBest.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'PERSONAL BEST: {score}'**
|
|
||||||
String personalBest(int score);
|
|
||||||
|
|
||||||
/// No description provided for @caughtShiny.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'✨ SHINY! You caught {name}! (+20 pts) ✨'**
|
|
||||||
String caughtShiny(String name);
|
|
||||||
|
|
||||||
/// No description provided for @caughtNormal.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Correct! You caught {name}!'**
|
|
||||||
String caughtNormal(String name);
|
|
||||||
|
|
||||||
/// No description provided for @wrongGuess.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Wrong guess! Try again.'**
|
|
||||||
String get wrongGuess;
|
|
||||||
|
|
||||||
/// No description provided for @errorLoadingPokemon.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Error loading Pokémon'**
|
|
||||||
String get errorLoadingPokemon;
|
|
||||||
|
|
||||||
/// No description provided for @listNational.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'LIST - NATIONAL'**
|
|
||||||
String get listNational;
|
|
||||||
|
|
||||||
/// No description provided for @tabAll.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'ALL'**
|
|
||||||
String get tabAll;
|
|
||||||
|
|
||||||
/// No description provided for @tabCaught.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'CAUGHT'**
|
|
||||||
String get tabCaught;
|
|
||||||
|
|
||||||
/// No description provided for @pokemonDiscovered.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'POKEMON DISCOVERED'**
|
|
||||||
String get pokemonDiscovered;
|
|
||||||
|
|
||||||
/// No description provided for @noPokemonFound.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'NO POKEMON FOUND IN {filter}'**
|
|
||||||
String noPokemonFound(String filter);
|
|
||||||
|
|
||||||
/// No description provided for @pokedexFooter.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'NATIONAL POKEDEX V2.0'**
|
|
||||||
String get pokedexFooter;
|
|
||||||
|
|
||||||
/// No description provided for @loadingError.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Loading error'**
|
|
||||||
String get loadingError;
|
|
||||||
|
|
||||||
/// No description provided for @baseStats.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'BASE STATS'**
|
|
||||||
String get baseStats;
|
|
||||||
|
|
||||||
/// No description provided for @noDescription.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'No description available for this Pokémon.'**
|
|
||||||
String get noDescription;
|
|
||||||
|
|
||||||
/// No description provided for @gameOver.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'GAME OVER'**
|
|
||||||
String get gameOver;
|
|
||||||
|
|
||||||
/// No description provided for @itWas.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'It was {name}!'**
|
|
||||||
String itWas(String name);
|
|
||||||
|
|
||||||
/// No description provided for @gameOverMessage.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'\"Looks like your journey ends here. You\'ve run out of energy!\"'**
|
|
||||||
String get gameOverMessage;
|
|
||||||
|
|
||||||
/// No description provided for @statStreak.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'STREAK'**
|
|
||||||
String get statStreak;
|
|
||||||
|
|
||||||
/// No description provided for @statSeen.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'SEEN'**
|
|
||||||
String get statSeen;
|
|
||||||
|
|
||||||
/// No description provided for @statScore.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'SCORE'**
|
|
||||||
String get statScore;
|
|
||||||
|
|
||||||
/// No description provided for @tryAgain.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'TRY AGAIN'**
|
|
||||||
String get tryAgain;
|
|
||||||
|
|
||||||
/// No description provided for @backToPokedex.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'BACK TO POKEDEX'**
|
|
||||||
String get backToPokedex;
|
|
||||||
|
|
||||||
/// No description provided for @system.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'SYSTEM'**
|
|
||||||
String get system;
|
|
||||||
|
|
||||||
/// No description provided for @statistics.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'STATISTICS'**
|
|
||||||
String get statistics;
|
|
||||||
|
|
||||||
/// No description provided for @colorPalette.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'COLOR PALETTE'**
|
|
||||||
String get colorPalette;
|
|
||||||
|
|
||||||
/// No description provided for @language.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'LANGUAGE'**
|
|
||||||
String get language;
|
|
||||||
|
|
||||||
/// No description provided for @statBestScore.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Best score'**
|
|
||||||
String get statBestScore;
|
|
||||||
|
|
||||||
/// No description provided for @statCaught.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Caught'**
|
|
||||||
String get statCaught;
|
|
||||||
|
|
||||||
/// No description provided for @statSeenLabel.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Seen'**
|
|
||||||
String get statSeenLabel;
|
|
||||||
|
|
||||||
/// No description provided for @statCompletion.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Completion'**
|
|
||||||
String get statCompletion;
|
|
||||||
|
|
||||||
/// No description provided for @navList.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'LIST'**
|
|
||||||
String get navList;
|
|
||||||
|
|
||||||
/// No description provided for @navGuess.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'GUESS'**
|
|
||||||
String get navGuess;
|
|
||||||
|
|
||||||
/// No description provided for @navSystem.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'SYSTEM'**
|
|
||||||
String get navSystem;
|
|
||||||
|
|
||||||
/// No description provided for @langEnglish.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'English'**
|
|
||||||
String get langEnglish;
|
|
||||||
|
|
||||||
/// No description provided for @langFrench.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'French'**
|
|
||||||
String get langFrench;
|
|
||||||
|
|
||||||
/// No description provided for @palette0.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Pokédex Red'**
|
|
||||||
String get palette0;
|
|
||||||
|
|
||||||
/// No description provided for @palette1.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Blue Ocean'**
|
|
||||||
String get palette1;
|
|
||||||
|
|
||||||
/// No description provided for @palette2.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Green Forest'**
|
|
||||||
String get palette2;
|
|
||||||
|
|
||||||
/// No description provided for @palette3.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Lightning Yellow'**
|
|
||||||
String get palette3;
|
|
||||||
|
|
||||||
/// No description provided for @palette4.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Purple Shadow'**
|
|
||||||
String get palette4;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
|
||||||
extends LocalizationsDelegate<AppLocalizations> {
|
|
||||||
const _AppLocalizationsDelegate();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<AppLocalizations> load(Locale locale) {
|
|
||||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool isSupported(Locale locale) =>
|
|
||||||
<String>['en', 'fr'].contains(locale.languageCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
|
||||||
// Lookup logic when only language code is specified.
|
|
||||||
switch (locale.languageCode) {
|
|
||||||
case 'en':
|
|
||||||
return AppLocalizationsEn();
|
|
||||||
case 'fr':
|
|
||||||
return AppLocalizationsFr();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw FlutterError(
|
|
||||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
|
||||||
'an issue with the localizations generation tool. Please file an issue '
|
|
||||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
|
||||||
'that was used.');
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
// ignore: unused_import
|
|
||||||
import 'package:intl/intl.dart' as intl;
|
|
||||||
import 'app_localizations.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
|
|
||||||
/// The translations for English (`en`).
|
|
||||||
class AppLocalizationsEn extends AppLocalizations {
|
|
||||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get appTitle => 'Pokéguess';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get whosThatPokemon => 'WHO\'S THAT POKÉMON?';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get shinyDetected => '✨ SHINY POKÉMON DETECTED! ✨';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get genFilter => 'GEN FILTER';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get identificationInput => 'IDENTIFICATION INPUT';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get hintPrefix => 'HINT';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get enterPokemonName => 'Enter Pokémon name...';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get continueButton => 'CONTINUE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get guessButton => 'GUESS!';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String hintButton(int count) {
|
|
||||||
return 'HINT ($count)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String skipButton(int count) {
|
|
||||||
return 'SKIP ($count)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get currentScore => 'CURRENT SCORE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String personalBest(int score) {
|
|
||||||
return 'PERSONAL BEST: $score';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String caughtShiny(String name) {
|
|
||||||
return '✨ SHINY! You caught $name! (+20 pts) ✨';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String caughtNormal(String name) {
|
|
||||||
return 'Correct! You caught $name!';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get wrongGuess => 'Wrong guess! Try again.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get errorLoadingPokemon => 'Error loading Pokémon';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get listNational => 'LIST - NATIONAL';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tabAll => 'ALL';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tabCaught => 'CAUGHT';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get pokemonDiscovered => 'POKEMON DISCOVERED';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String noPokemonFound(String filter) {
|
|
||||||
return 'NO POKEMON FOUND IN $filter';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get pokedexFooter => 'NATIONAL POKEDEX V2.0';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get loadingError => 'Loading error';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseStats => 'BASE STATS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get noDescription => 'No description available for this Pokémon.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get gameOver => 'GAME OVER';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String itWas(String name) {
|
|
||||||
return 'It was $name!';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get gameOverMessage =>
|
|
||||||
'\"Looks like your journey ends here. You\'ve run out of energy!\"';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statStreak => 'STREAK';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statSeen => 'SEEN';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statScore => 'SCORE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tryAgain => 'TRY AGAIN';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get backToPokedex => 'BACK TO POKEDEX';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get system => 'SYSTEM';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statistics => 'STATISTICS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get colorPalette => 'COLOR PALETTE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get language => 'LANGUAGE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statBestScore => 'Best score';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statCaught => 'Caught';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statSeenLabel => 'Seen';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statCompletion => 'Completion';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get navList => 'LIST';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get navGuess => 'GUESS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get navSystem => 'SYSTEM';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get langEnglish => 'English';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get langFrench => 'French';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette0 => 'Pokédex Red';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette1 => 'Blue Ocean';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette2 => 'Green Forest';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette3 => 'Lightning Yellow';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette4 => 'Purple Shadow';
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
// ignore: unused_import
|
|
||||||
import 'package:intl/intl.dart' as intl;
|
|
||||||
import 'app_localizations.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
|
|
||||||
/// The translations for French (`fr`).
|
|
||||||
class AppLocalizationsFr extends AppLocalizations {
|
|
||||||
AppLocalizationsFr([String locale = 'fr']) : super(locale);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get appTitle => 'Pokéguess';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get whosThatPokemon => 'QUI EST CE POKÉMON ?';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get shinyDetected => '✨ POKÉMON SHINY DÉTECTÉ ! ✨';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get genFilter => 'FILTRE GÉN.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get identificationInput => 'SAISIE D\'IDENTIFICATION';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get hintPrefix => 'INDICE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get enterPokemonName => 'Entrez le nom du Pokémon...';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get continueButton => 'CONTINUER';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get guessButton => 'DEVINER !';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String hintButton(int count) {
|
|
||||||
return 'INDICE ($count)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String skipButton(int count) {
|
|
||||||
return 'PASSER ($count)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get currentScore => 'SCORE ACTUEL';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String personalBest(int score) {
|
|
||||||
return 'MEILLEUR SCORE : $score';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String caughtShiny(String name) {
|
|
||||||
return '✨ SHINY ! Tu as attrapé $name ! (+20 pts) ✨';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String caughtNormal(String name) {
|
|
||||||
return 'Correct ! Tu as attrapé $name !';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get wrongGuess => 'Mauvaise réponse ! Réessaie.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get errorLoadingPokemon => 'Erreur de chargement du Pokémon';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get listNational => 'LISTE - NATIONALE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tabAll => 'TOUS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tabCaught => 'ATTRAPÉS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get pokemonDiscovered => 'POKÉMON DÉCOUVERTS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String noPokemonFound(String filter) {
|
|
||||||
return 'AUCUN POKÉMON DANS $filter';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get pokedexFooter => 'POKÉDEX NATIONAL V2.0';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get loadingError => 'Erreur de chargement';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseStats => 'STATS DE BASE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get noDescription => 'Aucune description disponible pour ce Pokémon.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get gameOver => 'PARTIE TERMINÉE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String itWas(String name) {
|
|
||||||
return 'C\'était $name !';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get gameOverMessage =>
|
|
||||||
'« On dirait que ton aventure s\'arrête ici. Tu n\'as plus d\'énergie ! »';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statStreak => 'SÉRIE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statSeen => 'VUS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statScore => 'SCORE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tryAgain => 'REJOUER';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get backToPokedex => 'RETOUR AU POKÉDEX';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get system => 'SYSTÈME';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statistics => 'STATISTIQUES';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get colorPalette => 'PALETTE DE COULEURS';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get language => 'LANGUE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statBestScore => 'Meilleur score';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statCaught => 'Attrapés';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statSeenLabel => 'Vus';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get statCompletion => 'Complétion';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get navList => 'LISTE';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get navGuess => 'DEVINER';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get navSystem => 'SYSTÈME';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get langEnglish => 'Anglais';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get langFrench => 'Français';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette0 => 'Pokédex Rouge';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette1 => 'Océan Bleu';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette2 => 'Forêt Verte';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette3 => 'Foudre Jaune';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get palette4 => 'Ombre Violette';
|
|
||||||
}
|
|
||||||
+14
-40
@@ -1,57 +1,31 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'pages/home_page.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'pages/game_page.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'pages/pokemon_list.dart';
|
||||||
import 'l10n/app_localizations.dart';
|
import 'pages/pokemon_detail.dart';
|
||||||
import 'presentation/pages/main_page.dart';
|
|
||||||
import 'presentation/pages/pokemon_detail.dart';
|
|
||||||
import 'presentation/pages/game_over_page.dart';
|
|
||||||
import 'presentation/providers/theme_provider.dart';
|
|
||||||
import 'presentation/providers/locale_provider.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
runApp(const MyApp());
|
||||||
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
|
||||||
sqfliteFfiInit();
|
|
||||||
databaseFactory = databaseFactoryFfi;
|
|
||||||
}
|
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Widget racine : construit le MaterialApp (thème dérivé de la palette, routes).
|
class MyApp extends StatelessWidget {
|
||||||
class MyApp extends ConsumerWidget {
|
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
final paletteIndex = ref.watch(themeProvider);
|
|
||||||
final palette = appPalettes[paletteIndex];
|
|
||||||
final locale = ref.watch(localeProvider);
|
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
|
title: 'Pokéguess', // Titre de l'application
|
||||||
locale: locale,
|
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
seedColor: palette.primary,
|
|
||||||
surface: palette.surface,
|
|
||||||
),
|
|
||||||
textTheme: GoogleFonts.vt323TextTheme(
|
|
||||||
Theme.of(context).textTheme,
|
|
||||||
).apply(
|
|
||||||
bodyColor: Colors.black87,
|
|
||||||
displayColor: Colors.black87,
|
|
||||||
),
|
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner:
|
||||||
|
false, // Permet de masquer la bannière "Debug"
|
||||||
routes: {
|
routes: {
|
||||||
'/': (context) => const MainPage(),
|
'/': (context) => const HomePage(), // Menu principal
|
||||||
|
'/game': (context) => const GamePage(), // Page de jeu
|
||||||
|
'/pokedex': (context) => const PokemonListPage(), // Liste des Pokémon
|
||||||
'/pokemon-detail': (context) => const PokemonDetailPage(),
|
'/pokemon-detail': (context) => const PokemonDetailPage(),
|
||||||
'/game-over': (context) => const GameOverPage(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/foundation.dart' show kIsWeb; // Platform is not supported on web
|
||||||
|
import '../database/pokedex_database.dart';
|
||||||
|
import '../api/pokemon_api.dart';
|
||||||
|
import '../utils/pokemon_type.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Classe représentant un Pokémon. Elle contient le nom, le numéro et les types du Pokémon.
|
||||||
|
// Elle contient aussi des propriétés calculées pour récupérer l'url de l'image du Pokémon, l'url de l'image shiny du Pokémon et l'url du cri du Pokémon.
|
||||||
|
class Pokemon {
|
||||||
|
String name;
|
||||||
|
int id;
|
||||||
|
PokemonType type1;
|
||||||
|
PokemonType? type2;
|
||||||
|
|
||||||
|
String get imageUrl => 'https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/$id/regular.png';
|
||||||
|
String get shinyImageUrl => 'https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/$id/shiny.png';
|
||||||
|
String get cryUrl => 'https://pokemoncries.com/cries/$id.mp3';
|
||||||
|
|
||||||
|
String get formatedName {
|
||||||
|
return name[0].toUpperCase() + name.substring(1);
|
||||||
|
}
|
||||||
|
Color get type1Color => typeToColor(type1);
|
||||||
|
Color get type2Color => typeToColor(type2 ?? PokemonType.unknown);
|
||||||
|
String get type1Formated => formatedTypeName(type1);
|
||||||
|
String get type2Formated => formatedTypeName(type2 ?? PokemonType.unknown);
|
||||||
|
|
||||||
|
Pokemon({
|
||||||
|
required this.name,
|
||||||
|
required this.id,
|
||||||
|
required this.type1,
|
||||||
|
this.type2, // Le type 2 n'est pas toujours présent
|
||||||
|
});
|
||||||
|
|
||||||
|
// Constructeur qui permet de créer un Pokémon à partir d'un fichier JSON récupéré depuis l'API.
|
||||||
|
// Sera aussi utilisé pour la récupération depuis la base de données
|
||||||
|
factory Pokemon.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Pokemon(
|
||||||
|
name: json['name'],
|
||||||
|
id: json['id'],
|
||||||
|
// Parcours des valeurs de l'enum PokemonType et récupération de la première valeur qui correspond à la string 'PokemonType.${json['type1']}'
|
||||||
|
type1: PokemonType.values.firstWhere((element) => element.toString() == 'PokemonType.${json['type1']}'),
|
||||||
|
type2: json['type2'] != null ? PokemonType.values.firstWhere((element) => element.toString() == 'PokemonType.${json['type2']}') : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de convertir un Pokémon en fichier JSON. Sera aussi utilisé pour l'insertion dans la base de données
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'id': id,
|
||||||
|
'type1': type1.toString().split('.').last, // On récupère la valeur de l'enum PokemonType sans le préfixe 'PokemonType.'
|
||||||
|
'type2': type2?.toString().split('.').last,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthode qui permet de récupérer un Pokémon à partir de son ID
|
||||||
|
// Si le Pokémon n'est pas présent dans la base de données, on le récupère depuis l'API
|
||||||
|
static Future<Pokemon?> fromID(int id) async {
|
||||||
|
Pokemon? pokemon;
|
||||||
|
if (!kIsWeb) {
|
||||||
|
// La base de données n'est pas disponible sur le web
|
||||||
|
pokemon = await PokedexDatabase.getPokemon(id);
|
||||||
|
}
|
||||||
|
if (pokemon == null) {
|
||||||
|
try {
|
||||||
|
pokemon = await PokemonApi.getPokemon(id);
|
||||||
|
if (!kIsWeb) {
|
||||||
|
// On insère le Pokémon dans la base de données
|
||||||
|
await PokedexDatabase.insertPokemon(pokemon);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pokemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum qui représente les différents types de Pokémon
|
||||||
|
enum PokemonType {
|
||||||
|
normal, fighting, flying, poison, ground, rock, bug, ghost, steel, fire, water, grass, electric, psychic, ice, dragon, dark, fairy, unknown, shadow
|
||||||
|
}
|
||||||
@@ -0,0 +1,519 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/pokemon.dart';
|
||||||
|
|
||||||
|
/// GamePage - Main quiz game where users guess Pokémon from silhouettes
|
||||||
|
class GamePage extends StatefulWidget {
|
||||||
|
const GamePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GamePage> createState() => _GamePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GamePageState extends State<GamePage> {
|
||||||
|
// Game state
|
||||||
|
int score = 0;
|
||||||
|
int lives = 3;
|
||||||
|
Pokemon? currentPokemon;
|
||||||
|
bool isShiny = false;
|
||||||
|
bool isRevealed = false;
|
||||||
|
bool showHint = false;
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
final Random _random = Random();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadNewPokemon();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a new random Pokémon (1-151)
|
||||||
|
Future<void> _loadNewPokemon() async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
isRevealed = false;
|
||||||
|
showHint = false;
|
||||||
|
_controller.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Random ID between 1 and 151
|
||||||
|
final int randomId = _random.nextInt(151) + 1;
|
||||||
|
|
||||||
|
// Shiny chance: 1/20 (5%)
|
||||||
|
final bool shiny = _random.nextInt(20) == 0;
|
||||||
|
|
||||||
|
// Fetch Pokémon using existing model (uses cache/API layer)
|
||||||
|
final Pokemon? pokemon = await Pokemon.fromID(randomId);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
currentPokemon = pokemon;
|
||||||
|
isShiny = shiny;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate user's guess
|
||||||
|
void _validateGuess() {
|
||||||
|
// Hide keyboard
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
|
||||||
|
if (currentPokemon == null) return;
|
||||||
|
|
||||||
|
final String userInput = _controller.text.trim().toLowerCase();
|
||||||
|
final String correctName = currentPokemon!.name.toLowerCase();
|
||||||
|
|
||||||
|
if (userInput == correctName) {
|
||||||
|
_handleCorrectGuess();
|
||||||
|
} else {
|
||||||
|
_handleWrongGuess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle correct guess
|
||||||
|
void _handleCorrectGuess() {
|
||||||
|
final int points = isShiny ? 20 : 10;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isRevealed = true;
|
||||||
|
score += points;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
isShiny ? '✨ SHINY! +$points points!' : '✅ Correct! +$points points!',
|
||||||
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
backgroundColor: isShiny ? Colors.amber : Colors.green,
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait 2 seconds, then load new Pokémon
|
||||||
|
Timer(const Duration(seconds: 2), () {
|
||||||
|
if (mounted) {
|
||||||
|
_loadNewPokemon();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle wrong guess
|
||||||
|
void _handleWrongGuess() {
|
||||||
|
setState(() {
|
||||||
|
lives--;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lives <= 0) {
|
||||||
|
_showGameOverDialog();
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'❌ Incorrect! Il reste $lives vie${lives > 1 ? 's' : ''}',
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show game over dialog
|
||||||
|
void _showGameOverDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: const Color(0xFF1A1A2E),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||||
|
title: const Text(
|
||||||
|
'GAME OVER',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Le Pokémon était:',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
currentPokemon?.formatedName ?? '???',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Text(
|
||||||
|
'Score final',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'$score',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFE94560),
|
||||||
|
fontSize: 48,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_restartGame();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFE94560),
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 40, vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'REJOUER',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restart the game
|
||||||
|
void _restartGame() {
|
||||||
|
setState(() {
|
||||||
|
score = 0;
|
||||||
|
lives = 3;
|
||||||
|
});
|
||||||
|
_loadNewPokemon();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use hint (costs 5 points)
|
||||||
|
void _useHint() {
|
||||||
|
if (score >= 5 && !showHint) {
|
||||||
|
setState(() {
|
||||||
|
score -= 5;
|
||||||
|
showHint = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF1A1A2E),
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'POKÉGUESS',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Score and Lives row
|
||||||
|
_buildScoreAndLives(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Pokémon silhouette
|
||||||
|
Expanded(
|
||||||
|
child: _buildPokemonDisplay(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Hint display
|
||||||
|
if (showHint && currentPokemon != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: currentPokemon!.type1Color.withValues(alpha: 0.3),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
border: Border.all(color: currentPokemon!.type1Color),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Type: ${currentPokemon!.type1Formated}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Input and buttons
|
||||||
|
_buildInputSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build score and lives display
|
||||||
|
Widget _buildScoreAndLives() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// Score
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.star, color: Colors.amber, size: 24),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'$score',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Lives (hearts)
|
||||||
|
Row(
|
||||||
|
children: List.generate(3, (index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: Icon(
|
||||||
|
index < lives ? Icons.favorite : Icons.favorite_border,
|
||||||
|
color: const Color(0xFFE94560),
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build Pokémon silhouette or revealed image
|
||||||
|
Widget _buildPokemonDisplay() {
|
||||||
|
if (isLoading) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Color(0xFFE94560),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPokemon == null) {
|
||||||
|
return const Center(
|
||||||
|
child: Text(
|
||||||
|
'Erreur de chargement',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String imageUrl =
|
||||||
|
isShiny ? currentPokemon!.shinyImageUrl : currentPokemon!.imageUrl;
|
||||||
|
|
||||||
|
Widget image = Image.network(
|
||||||
|
imageUrl,
|
||||||
|
width: 250,
|
||||||
|
height: 250,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return const SizedBox(
|
||||||
|
width: 250,
|
||||||
|
height: 250,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(color: Color(0xFFE94560)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return const SizedBox(
|
||||||
|
width: 250,
|
||||||
|
height: 250,
|
||||||
|
child: Center(
|
||||||
|
child: Icon(Icons.error, color: Colors.red, size: 50),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply silhouette filter if not revealed
|
||||||
|
if (!isRevealed) {
|
||||||
|
image = ColorFiltered(
|
||||||
|
colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
||||||
|
child: image,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add shiny sparkle effect if revealed and shiny
|
||||||
|
return Center(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// Glow effect when revealed
|
||||||
|
if (isRevealed)
|
||||||
|
Container(
|
||||||
|
width: 280,
|
||||||
|
height: 280,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: isShiny
|
||||||
|
? Colors.amber.withValues(alpha: 0.4)
|
||||||
|
: Colors.blueAccent.withValues(alpha: 0.3),
|
||||||
|
blurRadius: 40,
|
||||||
|
spreadRadius: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
image,
|
||||||
|
// Shiny indicator
|
||||||
|
if (isShiny && isRevealed)
|
||||||
|
const Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 50,
|
||||||
|
child: Text(
|
||||||
|
'✨',
|
||||||
|
style: TextStyle(fontSize: 32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build input section with text field and buttons
|
||||||
|
Widget _buildInputSection() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// Text input
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: _controller,
|
||||||
|
enabled: !isRevealed,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 18),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textCapitalization: TextCapitalization.words,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Nom du Pokémon...',
|
||||||
|
hintStyle: TextStyle(color: Colors.white38),
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => _validateGuess(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Buttons row
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// Hint button
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed:
|
||||||
|
score >= 5 && !showHint && !isRevealed ? _useHint : null,
|
||||||
|
icon: const Icon(Icons.lightbulb_outline, size: 20),
|
||||||
|
label: const Text('Indice (5 pts)'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.amber.withValues(alpha: 0.8),
|
||||||
|
foregroundColor: Colors.black87,
|
||||||
|
disabledBackgroundColor: Colors.grey.withValues(alpha: 0.3),
|
||||||
|
disabledForegroundColor: Colors.white38,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
|
||||||
|
// Validate button
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: !isRevealed ? _validateGuess : null,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFE94560),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
disabledBackgroundColor: Colors.grey.withValues(alpha: 0.3),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'VALIDER',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// HomePage - Main menu for Pokéguess
|
||||||
|
/// Contains PLAY and POKEDEX navigation buttons
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
const HomePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Color(0xFF1A1A2E),
|
||||||
|
Color(0xFF16213E),
|
||||||
|
Color(0xFF0F3460),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Logo / Title
|
||||||
|
const Text(
|
||||||
|
'POKÉGUESS',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 48,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 4,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
blurRadius: 20,
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
offset: Offset(0, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Devine le Pokémon !',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Colors.white70,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 80),
|
||||||
|
|
||||||
|
// PLAY Button
|
||||||
|
_MenuButton(
|
||||||
|
label: 'JOUER',
|
||||||
|
icon: Icons.play_arrow_rounded,
|
||||||
|
color: const Color(0xFFE94560),
|
||||||
|
onPressed: () => Navigator.pushNamed(context, '/game'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// POKEDEX Button
|
||||||
|
_MenuButton(
|
||||||
|
label: 'POKÉDEX',
|
||||||
|
icon: Icons.catching_pokemon,
|
||||||
|
color: const Color(0xFF0F3460),
|
||||||
|
borderColor: Colors.white38,
|
||||||
|
onPressed: () => Navigator.pushNamed(context, '/pokedex'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reusable menu button with icon and gradient effect
|
||||||
|
class _MenuButton extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final IconData icon;
|
||||||
|
final Color color;
|
||||||
|
final Color? borderColor;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
const _MenuButton({
|
||||||
|
required this.label,
|
||||||
|
required this.icon,
|
||||||
|
required this.color,
|
||||||
|
required this.onPressed,
|
||||||
|
this.borderColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 220,
|
||||||
|
height: 60,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: color,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 8,
|
||||||
|
shadowColor: color.withValues(alpha: 0.5),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
side: borderColor != null
|
||||||
|
? BorderSide(color: borderColor!, width: 2)
|
||||||
|
: BorderSide.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 28),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/pokemon.dart';
|
||||||
|
import '../components/pokemon_type.dart';
|
||||||
|
|
||||||
|
// Vue détail d'un Pokémon. Elle est appelée par la route "/pokemon-detail". Elle prend en paramètre un Pokémon.
|
||||||
|
// Elle affiche l'image du Pokémon, son nom, son numéro et ses types. Elle permet également de passer en mode shiny.
|
||||||
|
// Elle hérite de la classe StatefulWidget car elle a besoin de gérer un état (le mode shiny).
|
||||||
|
class PokemonDetailPage extends StatefulWidget {
|
||||||
|
const PokemonDetailPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PokemonDetailPage> createState() => _PokemonDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// La classe _PokemonDetailPageState hérite de la classe State. Elle permet de gérer l'état de la page.
|
||||||
|
// Elle contient une variable _isShiny qui permet de savoir si le mode shiny est activé ou non.
|
||||||
|
class _PokemonDetailPageState extends State<PokemonDetailPage> {
|
||||||
|
// Variable qui permet de savoir si le mode shiny est activé ou non
|
||||||
|
bool _isShiny = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// On récupère le Pokémon passé en paramètre de la route
|
||||||
|
final Pokemon pokemon = ModalRoute.of(context)!.settings.arguments as Pokemon;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Le GestureDetector va permettre de détecter un tap sur l'image du Pokémon
|
||||||
|
GestureDetector(
|
||||||
|
// L'image du Pokémon est une image en ligne. On utilise donc Image.network
|
||||||
|
// On utilise la variable _isShiny pour savoir si on affiche l'image normale ou l'image shiny
|
||||||
|
child: Image.network(_isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl, width: 200),
|
||||||
|
onTap:() {
|
||||||
|
// Lorsqu'on tap sur l'image, on change la valeur de la variable _isShiny
|
||||||
|
// Cela va permettre de changer l'image affichée
|
||||||
|
// On utilise la méthode setState pour dire à Flutter que la valeur de la variable a changé
|
||||||
|
setState(() {
|
||||||
|
_isShiny = !_isShiny;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text: pokemon.formatedName, // formatedName est une propriété calculée du modèle Pokemon
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: " #${pokemon.id.toString().padLeft(4, "0")}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
PokemonTypeWidget(pokemon.type1),
|
||||||
|
pokemon.type2 != null ? PokemonTypeWidget(pokemon.type2!) : Container(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/pokemon.dart';
|
||||||
|
import '../components/pokemon_tile.dart';
|
||||||
|
import 'package:flutter/foundation.dart'
|
||||||
|
show kIsWeb; // Platform is not supported on web
|
||||||
|
|
||||||
|
// Page de la liste des pokémons. Elle est appelée par la route "/". Elle affiche la liste des 151 premiers pokémons.
|
||||||
|
// Elle hérite de la classe StatefulWidget car elle a besoin de gérer un état (la liste des pokémons).
|
||||||
|
class PokemonListPage extends StatefulWidget {
|
||||||
|
const PokemonListPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PokemonListPage> createState() => _PokemonListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PokemonListPageState extends State<PokemonListPage> {
|
||||||
|
Widget _buildPokemonTile(BuildContext context, int index) {
|
||||||
|
// On utilise un FutureBuilder pour afficher un pokémon à partir de son ID. L'index commençant à 0, on ajoute 1 pour le numéro du pokémon.
|
||||||
|
// Le FutureBuilder va permettre d'afficher un widget en fonction de l'état du Future
|
||||||
|
// Le FutureBuilder prend en paramètre un Future (ici Pokemon.fromID(index + 1))
|
||||||
|
// Il prend aussi en paramètre une fonction qui va permettre de construire le widget en fonction de l'état du Future : builder: (context, snapshot) {}
|
||||||
|
return FutureBuilder(
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
// Si le Future a réussi à récupérer les données, on affiche le widget PokemonTile
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return Text('Error while fetching pokemon #${index + 1}');
|
||||||
|
}
|
||||||
|
return PokemonTile(snapshot.data as Pokemon);
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
// Si le Future a échoué à récupérer les données, on affiche un message d'erreur
|
||||||
|
print(snapshot.error);
|
||||||
|
return const Text('Erreur : ');
|
||||||
|
} else {
|
||||||
|
// Si le Future n'a pas encore récupéré les données, on affiche un widget de chargement
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
future: Pokemon.fromID(index + 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Liste des pokémons'),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: GridView.builder(
|
||||||
|
// Le GridView permet d'afficher une liste de widgets sous forme de grille
|
||||||
|
// On utilise le constructeur GridView.builder pour construire la grille
|
||||||
|
// Le GridView.builder prend en paramètre un itemCount qui correspond au nombre d'éléments à afficher
|
||||||
|
// Il prend aussi en paramètre un itemBuilder qui va permettre de construire chaque élément de la grille
|
||||||
|
// Le GridView.builder prend aussi en paramètre un gridDelegate qui va permettre de définir le nombre de colonnes de la grille
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: kIsWeb
|
||||||
|
? 4
|
||||||
|
: 2, // On affiche 4 colonnes sur le web et 2 colonnes sur mobile
|
||||||
|
),
|
||||||
|
itemCount:
|
||||||
|
151, // On pourrait en mettre plus mais on va se limiter aux 151 premiers pokémons
|
||||||
|
itemBuilder: _buildPokemonTile,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
|
||||||
import '../providers/repository_provider.dart';
|
|
||||||
import '../providers/theme_provider.dart';
|
|
||||||
import '../widgets/game_over/game_over_header.dart';
|
|
||||||
import '../widgets/game_over/game_over_stats.dart';
|
|
||||||
import '../widgets/game_over/game_over_actions.dart';
|
|
||||||
import '../widgets/game_over/hinge_divider.dart';
|
|
||||||
|
|
||||||
/// Écran de fin de partie. Affiche le Pokémon manqué, les stats, et propose de rejouer.
|
|
||||||
class GameOverPage extends ConsumerStatefulWidget {
|
|
||||||
const GameOverPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<GameOverPage> createState() => _GameOverPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GameOverPageState extends ConsumerState<GameOverPage> {
|
|
||||||
int _seenCount = 0;
|
|
||||||
bool _isLoading = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadSeenCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadSeenCount() async {
|
|
||||||
final count = await ref.read(pokemonRepositoryProvider).seenCount();
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_seenCount = count;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
|
|
||||||
final pokemonImage = args?['pokemonImage'] ?? '';
|
|
||||||
final pokemonName = args?['pokemonName'] ?? 'Unknown';
|
|
||||||
final int streak = args?['streak'] ?? 0;
|
|
||||||
final int score = args?['score'] ?? 0;
|
|
||||||
|
|
||||||
final palette = appPalettes[ref.watch(themeProvider)];
|
|
||||||
final primaryDark = HSLColor.fromColor(palette.primary)
|
|
||||||
.withLightness((HSLColor.fromColor(palette.primary).lightness - 0.12).clamp(0.0, 1.0))
|
|
||||||
.toColor();
|
|
||||||
const silverBg = Color(0xFFC8D1D8);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: palette.primary,
|
|
||||||
body: _isLoading
|
|
||||||
? const Center(child: CircularProgressIndicator(color: Colors.white))
|
|
||||||
: SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
GameOverHeader(
|
|
||||||
pokemonImage: pokemonImage,
|
|
||||||
pokemonName: pokemonName,
|
|
||||||
primaryDark: primaryDark,
|
|
||||||
surfaceColor: palette.surface,
|
|
||||||
),
|
|
||||||
HingeDivider(color: primaryDark),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(color: primaryDark, border: Border.all(color: primaryDark, width: 4)),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
color: silverBg,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
color: palette.surface,
|
|
||||||
padding: const EdgeInsets.all(24),
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.gameOverMessage,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 18, height: 1.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GameOverStats(
|
|
||||||
streak: streak.toString().padLeft(3, '0'),
|
|
||||||
seen: "$_seenCount/${AppConstants.totalPokemon}",
|
|
||||||
score: "$score",
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GameOverActions(
|
|
||||||
primaryColor: palette.primary,
|
|
||||||
primaryDark: primaryDark,
|
|
||||||
onTryAgain: () => Navigator.pop(context, true),
|
|
||||||
onBack: () => Navigator.pop(context, false),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../domain/game/game_state.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
|
||||||
import '../providers/gen_filter_provider.dart';
|
|
||||||
import '../providers/game_provider.dart';
|
|
||||||
import '../providers/navigation_provider.dart';
|
|
||||||
import '../providers/theme_provider.dart';
|
|
||||||
import '../widgets/scanline_overlay.dart';
|
|
||||||
import '../widgets/guess/guess_silhouette.dart';
|
|
||||||
import '../widgets/guess/gen_filter_section.dart';
|
|
||||||
import '../widgets/guess/lives_row.dart';
|
|
||||||
import '../widgets/guess/guess_input_section.dart';
|
|
||||||
import '../widgets/guess/score_board.dart';
|
|
||||||
|
|
||||||
/// Page de jeu "devine le Pokémon" : orchestre gameProvider et compose les sous-widgets.
|
|
||||||
class GuessPage extends ConsumerStatefulWidget {
|
|
||||||
const GuessPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<GuessPage> createState() => _GuessPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GuessPageState extends ConsumerState<GuessPage> {
|
|
||||||
final TextEditingController _guessController = TextEditingController();
|
|
||||||
bool _started = false;
|
|
||||||
bool _genFilterOpen = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (!_started) {
|
|
||||||
_started = true;
|
|
||||||
ref.read(gameProvider.notifier).startNewGame();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_guessController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onGuess() async {
|
|
||||||
final result = await ref.read(gameProvider.notifier).submitGuess(_guessController.text);
|
|
||||||
if (!mounted) return;
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final state = ref.read(gameProvider);
|
|
||||||
switch (result) {
|
|
||||||
case GuessResult.correct:
|
|
||||||
final name = state.currentPokemon!.formatedName;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(state.isShiny ? l.caughtShiny(name) : l.caughtNormal(name)),
|
|
||||||
backgroundColor: state.isShiny ? Colors.amber[800] : Colors.green,
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
case GuessResult.wrong:
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(l.wrongGuess), backgroundColor: Colors.orange));
|
|
||||||
break;
|
|
||||||
case GuessResult.gameOver:
|
|
||||||
await _showGameOver();
|
|
||||||
break;
|
|
||||||
case GuessResult.invalid:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_guessController.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showGameOver() async {
|
|
||||||
final state = ref.read(gameProvider);
|
|
||||||
final playAgain = await Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
'/game-over',
|
|
||||||
arguments: {
|
|
||||||
'pokemonName': state.currentPokemon!.formatedName,
|
|
||||||
'score': state.currentScore,
|
|
||||||
'streak': state.sessionCorrectCount,
|
|
||||||
'pokemonImage': state.currentPokemon!.imageUrl,
|
|
||||||
},
|
|
||||||
) as bool?;
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
if (playAgain == false) {
|
|
||||||
ref.read(selectedTabProvider.notifier).set(0); // onglet LIST
|
|
||||||
}
|
|
||||||
// true / false / null (geste retour) relancent tous une partie pour ne pas rester bloqué.
|
|
||||||
await ref.read(gameProvider.notifier).startNewGame();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final state = ref.watch(gameProvider);
|
|
||||||
final palette = appPalettes[ref.watch(themeProvider)];
|
|
||||||
|
|
||||||
if (state.status == GameStatus.loading) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
if (state.currentPokemon == null || state.status == GameStatus.error) {
|
|
||||||
return Center(child: Text(AppLocalizations.of(context)!.errorLoadingPokemon));
|
|
||||||
}
|
|
||||||
|
|
||||||
final pokemon = state.currentPokemon!;
|
|
||||||
final isGuessed = state.status == GameStatus.roundWon;
|
|
||||||
final notifier = ref.read(gameProvider.notifier);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: const BoxDecoration(color: Color(0xFFC8D1D8)),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
const ScanlineOverlay(),
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
GuessSilhouette(pokemon: pokemon, isShiny: state.isShiny, isGuessed: isGuessed, surfaceColor: palette.surface),
|
|
||||||
GenFilterSection(
|
|
||||||
isOpen: _genFilterOpen,
|
|
||||||
onToggleOpen: () => setState(() => _genFilterOpen = !_genFilterOpen),
|
|
||||||
selectedGens: ref.watch(genFilterProvider),
|
|
||||||
onToggle: (i) => ref.read(genFilterProvider.notifier).toggle(i),
|
|
||||||
surfaceColor: palette.surface,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
LivesRow(lives: state.lives),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GuessInputSection(
|
|
||||||
controller: _guessController,
|
|
||||||
pokemonName: pokemon.formatedName,
|
|
||||||
isHintUsed: state.isHintUsed,
|
|
||||||
isGuessed: isGuessed,
|
|
||||||
hints: state.hints,
|
|
||||||
skips: state.skips,
|
|
||||||
onGuess: _onGuess,
|
|
||||||
onContinue: () => notifier.loadNextPokemon(),
|
|
||||||
onHint: (state.isHintUsed || state.hints <= 0) ? null : () => notifier.useHint(),
|
|
||||||
onSkip: state.skips > 0 ? () => notifier.useSkip() : null,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
||||||
child: ScoreBoard(currentScore: state.currentScore, bestScore: state.bestScore),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
|
||||||
import '../providers/navigation_provider.dart';
|
|
||||||
import '../providers/theme_provider.dart';
|
|
||||||
import 'pokemon_list.dart';
|
|
||||||
import 'guess_page.dart';
|
|
||||||
import 'system_page.dart';
|
|
||||||
|
|
||||||
/// Coquille principale : navigation par onglets (liste / jeu / système) via un IndexedStack.
|
|
||||||
class MainPage extends ConsumerWidget {
|
|
||||||
const MainPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
static const List<Widget> _pages = [
|
|
||||||
PokemonListPage(),
|
|
||||||
GuessPage(),
|
|
||||||
SystemPage(),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final currentIndex = ref.watch(selectedTabProvider);
|
|
||||||
final palette = appPalettes[ref.watch(themeProvider)];
|
|
||||||
final primaryDark = HSLColor.fromColor(palette.primary)
|
|
||||||
.withLightness((HSLColor.fromColor(palette.primary).lightness - 0.1).clamp(0.0, 1.0))
|
|
||||||
.toColor();
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: palette.surface,
|
|
||||||
body: SafeArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: palette.primary,
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
border: Border.all(color: primaryDark, width: 4),
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(26),
|
|
||||||
child: IndexedStack(
|
|
||||||
index: currentIndex,
|
|
||||||
children: _pages,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: Theme(
|
|
||||||
data: Theme.of(context).copyWith(
|
|
||||||
splashColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: BottomNavigationBar(
|
|
||||||
currentIndex: currentIndex,
|
|
||||||
onTap: (index) => ref.read(selectedTabProvider.notifier).set(index),
|
|
||||||
type: BottomNavigationBarType.fixed,
|
|
||||||
selectedItemColor: palette.primary,
|
|
||||||
unselectedItemColor: Colors.grey,
|
|
||||||
items: [
|
|
||||||
BottomNavigationBarItem(icon: const Icon(Icons.grid_view), label: AppLocalizations.of(context)!.navList),
|
|
||||||
BottomNavigationBarItem(icon: const Icon(Icons.games), label: AppLocalizations.of(context)!.navGuess),
|
|
||||||
BottomNavigationBarItem(icon: const Icon(Icons.settings), label: AppLocalizations.of(context)!.navSystem),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import '../providers/locale_provider.dart';
|
|
||||||
import '../providers/repository_provider.dart';
|
|
||||||
import '../providers/theme_provider.dart';
|
|
||||||
import '../widgets/detail/pokemon_detail_top.dart';
|
|
||||||
import '../widgets/detail/pokemon_stats_panel.dart';
|
|
||||||
|
|
||||||
/// Fiche détaillée d'un Pokémon (reçu via les arguments de route).
|
|
||||||
class PokemonDetailPage extends ConsumerStatefulWidget {
|
|
||||||
const PokemonDetailPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<PokemonDetailPage> createState() => _PokemonDetailPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PokemonDetailPageState extends ConsumerState<PokemonDetailPage> {
|
|
||||||
bool _isShiny = false;
|
|
||||||
String? _englishGenus;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
_fetchEnglishGenusIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _fetchEnglishGenusIfNeeded() async {
|
|
||||||
final lang = ref.read(localeProvider).languageCode;
|
|
||||||
if (lang != 'en') return;
|
|
||||||
final pokemon = ModalRoute.of(context)!.settings.arguments as Pokemon;
|
|
||||||
final genus = await ref.read(pokemonRepositoryProvider).getEnglishGenus(pokemon.id);
|
|
||||||
if (mounted && genus != null) setState(() => _englishGenus = genus);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final pokemon = ModalRoute.of(context)!.settings.arguments as Pokemon;
|
|
||||||
final palette = appPalettes[ref.watch(themeProvider)];
|
|
||||||
final primaryDark = HSLColor.fromColor(palette.primary)
|
|
||||||
.withLightness(
|
|
||||||
(HSLColor.fromColor(palette.primary).lightness - 0.12).clamp(0.0, 1.0))
|
|
||||||
.toColor();
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: palette.surface,
|
|
||||||
body: SafeArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: palette.primary,
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
border: Border.all(color: primaryDark, width: 4),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_backBar(context, primaryDark),
|
|
||||||
PokemonDetailTop(
|
|
||||||
pokemon: pokemon,
|
|
||||||
isShiny: _isShiny,
|
|
||||||
onToggleShiny: () => setState(() => _isShiny = !_isShiny),
|
|
||||||
primaryColor: palette.primary,
|
|
||||||
primaryDark: primaryDark,
|
|
||||||
surfaceColor: palette.surface,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_hinge(primaryDark),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
PokemonStatsPanel(
|
|
||||||
pokemon: pokemon,
|
|
||||||
surfaceColor: palette.surface,
|
|
||||||
descriptionOverride: _englishGenus,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
_bottomDots(primaryDark),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _backBar(BuildContext context, Color primaryDark) {
|
|
||||||
return Container(
|
|
||||||
height: 50,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () => Navigator.pop(context),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(4),
|
|
||||||
decoration: BoxDecoration(color: primaryDark, shape: BoxShape.circle),
|
|
||||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _hinge(Color primaryDark) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Container(height: 6, width: 40, color: primaryDark),
|
|
||||||
Container(height: 6, width: 40, color: primaryDark),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _bottomDots(Color primaryDark) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: List.generate(
|
|
||||||
4,
|
|
||||||
(i) => Container(
|
|
||||||
width: 6,
|
|
||||||
height: 6,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
|
||||||
decoration: BoxDecoration(color: primaryDark, shape: BoxShape.circle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
|
||||||
import '../providers/pokedex_provider.dart';
|
|
||||||
import '../providers/theme_provider.dart';
|
|
||||||
import '../widgets/pokemon_tile.dart';
|
|
||||||
import '../widgets/scanline_overlay.dart';
|
|
||||||
import '../widgets/list/pokedex_list_header.dart';
|
|
||||||
import '../widgets/list/pokedex_count_bar.dart';
|
|
||||||
|
|
||||||
/// Liste du Pokédex national avec filtre ALL / CAUGHT.
|
|
||||||
class PokemonListPage extends ConsumerStatefulWidget {
|
|
||||||
const PokemonListPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<PokemonListPage> createState() => _PokemonListPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PokemonListPageState extends ConsumerState<PokemonListPage> {
|
|
||||||
String _filter = 'ALL'; // ALL, CAUGHT
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Pokemon> _applyFilter(List<Pokemon> all) {
|
|
||||||
if (_filter == 'CAUGHT') return all.where((p) => p.isCaught).toList();
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final palette = appPalettes[ref.watch(themeProvider)];
|
|
||||||
final pokedexAsync = ref.watch(pokedexProvider);
|
|
||||||
final caughtCount = ref.watch(caughtCountProvider);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: const BoxDecoration(color: Color(0xFFC8D1D8)),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const PokedexListHeader(),
|
|
||||||
Container(
|
|
||||||
color: const Color(0xFF90A4AE),
|
|
||||||
height: 40,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
_buildTab('ALL', l.tabAll, _filter == 'ALL', palette.primary),
|
|
||||||
_buildTab('CAUGHT', l.tabCaught, _filter == 'CAUGHT', palette.primary),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PokedexCountBar(caught: caughtCount, total: pokedexAsync.valueOrNull?.length ?? 0),
|
|
||||||
Expanded(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
const ScanlineOverlay(),
|
|
||||||
pokedexAsync.when(
|
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
|
||||||
error: (e, _) => Center(
|
|
||||||
child: Text('${l.loadingError}\n$e',
|
|
||||||
textAlign: TextAlign.center, style: const TextStyle(color: Colors.black54)),
|
|
||||||
),
|
|
||||||
data: (all) {
|
|
||||||
final filtered = _applyFilter(all);
|
|
||||||
if (filtered.isEmpty) return _emptyState();
|
|
||||||
return ListView.builder(
|
|
||||||
controller: _scrollController,
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
itemCount: filtered.length,
|
|
||||||
itemBuilder: (context, index) => PokemonTile(filtered[index]),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 24,
|
|
||||||
color: palette.surface,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(l.pokedexFooter,
|
|
||||||
style: const TextStyle(color: Colors.white70, fontSize: 12, letterSpacing: 1)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _emptyState() {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final filterLabel = _filter == 'CAUGHT' ? l.tabCaught : l.tabAll;
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.search_off, size: 64, color: Colors.black26),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(l.noPokemonFound(filterLabel),
|
|
||||||
style: const TextStyle(color: Colors.black45, fontSize: 18, fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTab(String key, String label, bool isSelected, Color primaryColor) {
|
|
||||||
return Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (_filter != key) {
|
|
||||||
setState(() => _filter = key);
|
|
||||||
if (_scrollController.hasClients) _scrollController.jumpTo(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? const Color(0xFFB0BEC5) : Colors.transparent,
|
|
||||||
border: isSelected
|
|
||||||
? Border(bottom: BorderSide(color: primaryColor, width: 3))
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(label,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18, fontWeight: FontWeight.bold, color: isSelected ? Colors.black : Colors.black54)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../../l10n/app_localizations.dart';
|
|
||||||
import '../providers/pokedex_provider.dart';
|
|
||||||
import '../providers/theme_provider.dart';
|
|
||||||
import '../providers/locale_provider.dart';
|
|
||||||
import '../widgets/system/system_header.dart';
|
|
||||||
import '../widgets/system/section_title.dart';
|
|
||||||
import '../widgets/system/system_stats.dart';
|
|
||||||
import '../widgets/system/palette_picker.dart';
|
|
||||||
import '../widgets/system/language_picker.dart';
|
|
||||||
|
|
||||||
/// Page "Système" : statistiques de jeu, langue et palette de couleurs.
|
|
||||||
class SystemPage extends ConsumerWidget {
|
|
||||||
const SystemPage({super.key});
|
|
||||||
|
|
||||||
Future<int> _loadBestScore() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
return prefs.getInt(AppConstants.prefsBestScore) ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final paletteIndex = ref.watch(themeProvider);
|
|
||||||
final palette = appPalettes[paletteIndex];
|
|
||||||
final pokedexAsync = ref.watch(pokedexProvider);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
color: const Color(0xFFC8D1D8),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SystemHeader(primaryColor: palette.primary, title: l.system),
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
SectionTitle(text: l.statistics, color: palette.primary),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
pokedexAsync.when(
|
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
|
||||||
error: (_, __) => Text(l.loadingError),
|
|
||||||
data: (pokemons) {
|
|
||||||
final total = pokemons.length;
|
|
||||||
final caught = pokemons.where((p) => p.isCaught).length;
|
|
||||||
final seen = pokemons.where((p) => p.isSeen).length;
|
|
||||||
final pct = total > 0 ? (caught / total * 100).toStringAsFixed(1) : '0.0';
|
|
||||||
return FutureBuilder<int>(
|
|
||||||
future: _loadBestScore(),
|
|
||||||
builder: (context, snap) {
|
|
||||||
final best = snap.data ?? 0;
|
|
||||||
return StatsGrid(
|
|
||||||
primaryColor: palette.primary,
|
|
||||||
items: [
|
|
||||||
StatItem(label: l.statBestScore, value: '$best', icon: Icons.emoji_events),
|
|
||||||
StatItem(label: l.statCaught, value: '$caught / $total', icon: Icons.catching_pokemon),
|
|
||||||
StatItem(label: l.statSeenLabel, value: '$seen / $total', icon: Icons.visibility),
|
|
||||||
StatItem(label: l.statCompletion, value: '$pct%', icon: Icons.pie_chart),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
SectionTitle(text: l.language, color: palette.primary),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
LanguagePicker(
|
|
||||||
selected: ref.watch(localeProvider),
|
|
||||||
primaryColor: palette.primary,
|
|
||||||
onSelect: (loc) => ref.read(localeProvider.notifier).setLocale(loc),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
SectionTitle(text: l.colorPalette, color: palette.primary),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
PalettePicker(
|
|
||||||
selectedIndex: paletteIndex,
|
|
||||||
onSelect: (i) => ref.read(themeProvider.notifier).setPalette(i),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import '../../core/config/app_constants.dart';
|
|
||||||
import '../../domain/game/game_engine.dart';
|
|
||||||
import '../../domain/game/game_state.dart';
|
|
||||||
import '../../core/logger.dart';
|
|
||||||
import 'gen_filter_provider.dart';
|
|
||||||
import 'locale_provider.dart';
|
|
||||||
import 'pokedex_provider.dart';
|
|
||||||
import 'repository_provider.dart';
|
|
||||||
|
|
||||||
/// Orchestration de la partie : relie GameEngine (règles), le repository (données)
|
|
||||||
/// et SharedPreferences (best score).
|
|
||||||
class GameNotifier extends Notifier<GameState> {
|
|
||||||
static const _engine = GameEngine();
|
|
||||||
final _random = Random();
|
|
||||||
|
|
||||||
@override
|
|
||||||
GameState build() {
|
|
||||||
// Re-fetch the current Pokémon whenever the locale changes so the name
|
|
||||||
// (used for hints and the guess comparison) switches language instantly.
|
|
||||||
ref.listen(localeProvider, (_, __) => _reloadCurrentPokemon());
|
|
||||||
return const GameState();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _reloadCurrentPokemon() async {
|
|
||||||
final current = state.currentPokemon;
|
|
||||||
if (current == null || state.status == GameStatus.loading) return;
|
|
||||||
try {
|
|
||||||
final updated = await ref.read(pokemonRepositoryProvider).getById(current.id);
|
|
||||||
if (updated != null) {
|
|
||||||
state = state.copyWith(
|
|
||||||
currentPokemon: updated.copyWith(
|
|
||||||
isCaught: current.isCaught,
|
|
||||||
isSeen: current.isSeen,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> startNewGame() async {
|
|
||||||
state = _engine.newGame(state);
|
|
||||||
await _loadBestScore();
|
|
||||||
await loadNextPokemon();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadBestScore() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
state = state.copyWith(bestScore: prefs.getInt(AppConstants.prefsBestScore) ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadNextPokemon() async {
|
|
||||||
state = state.copyWith(status: GameStatus.loading);
|
|
||||||
final repo = ref.read(pokemonRepositoryProvider);
|
|
||||||
final isShiny = _random.nextInt(AppConstants.shinyOdds) == 0;
|
|
||||||
|
|
||||||
final genFilter = ref.read(genFilterProvider.notifier);
|
|
||||||
await genFilter.ready;
|
|
||||||
final id = genFilter.randomId(_random);
|
|
||||||
try {
|
|
||||||
final pokemon = await repo.getById(id);
|
|
||||||
if (pokemon == null) {
|
|
||||||
state = state.copyWith(status: GameStatus.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state = _engine.startRound(state, pokemon, isShiny: isShiny);
|
|
||||||
} catch (e, st) {
|
|
||||||
AppLogger.error('loadNextPokemon a échoué', e, st);
|
|
||||||
state = state.copyWith(status: GameStatus.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<GuessResult> submitGuess(String guess) async {
|
|
||||||
final outcome = _engine.submitGuess(state, guess);
|
|
||||||
state = outcome.state;
|
|
||||||
|
|
||||||
if (outcome.result == GuessResult.correct) {
|
|
||||||
final repo = ref.read(pokemonRepositoryProvider);
|
|
||||||
final caught = state.currentPokemon;
|
|
||||||
if (caught != null) await repo.update(caught);
|
|
||||||
await _persistBestScore();
|
|
||||||
if (!kIsWeb) ref.invalidate(pokedexProvider); // rafraîchit la liste (pas de persistance sur web)
|
|
||||||
}
|
|
||||||
return outcome.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _persistBestScore() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
final stored = prefs.getInt(AppConstants.prefsBestScore) ?? 0;
|
|
||||||
if (state.bestScore > stored) {
|
|
||||||
await prefs.setInt(AppConstants.prefsBestScore, state.bestScore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void useHint() => state = _engine.useHint(state);
|
|
||||||
|
|
||||||
Future<void> useSkip() async {
|
|
||||||
final updated = _engine.useSkip(state);
|
|
||||||
if (updated.skips == state.skips) return; // pas de skip dispo
|
|
||||||
state = updated;
|
|
||||||
await loadNextPokemon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final gameProvider = NotifierProvider<GameNotifier, GameState>(GameNotifier.new);
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import '../../core/config/app_constants.dart';
|
|
||||||
|
|
||||||
/// Gère les générations activées pour le tirage et fournit un id aléatoire dans celles-ci.
|
|
||||||
/// La sélection est persistée dans les préférences.
|
|
||||||
class GenFilterNotifier extends Notifier<Set<int>> {
|
|
||||||
late final Future<void> _ready;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<int> build() {
|
|
||||||
_ready = _load();
|
|
||||||
// Default: all gens enabled — overwritten by _load() once prefs are read.
|
|
||||||
return Set.from(List.generate(AppConstants.genRanges.length, (i) => i));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _load() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
final saved = prefs.getStringList(AppConstants.prefsGenFilter);
|
|
||||||
if (saved != null && saved.isNotEmpty) {
|
|
||||||
state = saved.map(int.parse).toSet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves once the persisted filter has been loaded from SharedPreferences.
|
|
||||||
Future<void> get ready => _ready;
|
|
||||||
|
|
||||||
Future<void> toggle(int genIndex) async {
|
|
||||||
final next = Set<int>.from(state);
|
|
||||||
if (next.contains(genIndex)) {
|
|
||||||
// Prevent deselecting the last gen
|
|
||||||
if (next.length == 1) return;
|
|
||||||
next.remove(genIndex);
|
|
||||||
} else {
|
|
||||||
next.add(genIndex);
|
|
||||||
}
|
|
||||||
state = next;
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.setStringList(
|
|
||||||
AppConstants.prefsGenFilter,
|
|
||||||
state.map((i) => i.toString()).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a random Pokémon ID within the currently enabled gens.
|
|
||||||
int randomId(Random random) {
|
|
||||||
final ranges = state.map((i) => AppConstants.genRanges[i]).toList();
|
|
||||||
final totalPool = ranges.fold(0, (sum, r) => sum + (r.$2 - r.$1 + 1));
|
|
||||||
var pick = random.nextInt(totalPool);
|
|
||||||
for (final r in ranges) {
|
|
||||||
final size = r.$2 - r.$1 + 1;
|
|
||||||
if (pick < size) return r.$1 + pick;
|
|
||||||
pick -= size;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensemble des index de générations actuellement activées pour le jeu.
|
|
||||||
final genFilterProvider =
|
|
||||||
NotifierProvider<GenFilterNotifier, Set<int>>(GenFilterNotifier.new);
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import '../../core/config/app_constants.dart';
|
|
||||||
|
|
||||||
/// Langues prises en charge par l'application.
|
|
||||||
const List<Locale> supportedLocales = [Locale('en'), Locale('fr')];
|
|
||||||
|
|
||||||
/// Gère la langue sélectionnée, persistée dans les préférences.
|
|
||||||
class LocaleNotifier extends Notifier<Locale> {
|
|
||||||
@override
|
|
||||||
Locale build() {
|
|
||||||
_loadSaved();
|
|
||||||
return const Locale('fr');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadSaved() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
final code = prefs.getString(AppConstants.prefsLocale);
|
|
||||||
if (code != null && supportedLocales.any((l) => l.languageCode == code)) {
|
|
||||||
state = Locale(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setLocale(Locale locale) async {
|
|
||||||
state = locale;
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.setString(AppConstants.prefsLocale, locale.languageCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Langue active de l'application.
|
|
||||||
final localeProvider = NotifierProvider<LocaleNotifier, Locale>(LocaleNotifier.new);
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
/// Index de l'onglet sélectionné dans la navigation principale.
|
|
||||||
/// Remplace le hack findAncestorStateOfType<MainPageState>.
|
|
||||||
class SelectedTabNotifier extends Notifier<int> {
|
|
||||||
@override
|
|
||||||
int build() => 0;
|
|
||||||
|
|
||||||
void set(int index) => state = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
final selectedTabProvider =
|
|
||||||
NotifierProvider<SelectedTabNotifier, int>(SelectedTabNotifier.new);
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import 'repository_provider.dart';
|
|
||||||
|
|
||||||
/// Liste complète du Pokédex (triée par id), avec synchro initiale gérée par le repository.
|
|
||||||
class PokedexNotifier extends AsyncNotifier<List<Pokemon>> {
|
|
||||||
Future<List<Pokemon>> _load() async {
|
|
||||||
final repo = ref.watch(pokemonRepositoryProvider);
|
|
||||||
final list = await repo.getAll();
|
|
||||||
list.sort((a, b) => a.id.compareTo(b.id));
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Pokemon>> build() => _load();
|
|
||||||
|
|
||||||
/// Recharge la liste (ex. après avoir attrapé un Pokémon).
|
|
||||||
Future<void> refresh() async {
|
|
||||||
state = const AsyncLoading();
|
|
||||||
state = await AsyncValue.guard(_load);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final pokedexProvider =
|
|
||||||
AsyncNotifierProvider<PokedexNotifier, List<Pokemon>>(PokedexNotifier.new);
|
|
||||||
|
|
||||||
/// Nombre de Pokémon attrapés, dérivé de la liste.
|
|
||||||
final caughtCountProvider = Provider<int>((ref) {
|
|
||||||
final async = ref.watch(pokedexProvider);
|
|
||||||
return async.maybeWhen(
|
|
||||||
data: (list) => list.where((p) => p.isCaught).length,
|
|
||||||
orElse: () => 0,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../data/datasources/pokemon_local_datasource.dart';
|
|
||||||
import '../../data/datasources/pokemon_remote_datasource.dart';
|
|
||||||
import '../../data/repositories/pokemon_repository_impl.dart';
|
|
||||||
import '../../domain/repositories/pokemon_repository.dart';
|
|
||||||
import 'locale_provider.dart';
|
|
||||||
|
|
||||||
/// Point d'injection unique du repository.
|
|
||||||
/// Se reconstruit automatiquement quand la langue change,
|
|
||||||
/// ce qui invalide [pokedexProvider] en cascade.
|
|
||||||
final pokemonRepositoryProvider = Provider<PokemonRepository>((ref) {
|
|
||||||
final lang = ref.watch(localeProvider).languageCode;
|
|
||||||
final remote = PokemonRemoteDataSource(languageCode: lang);
|
|
||||||
final local = kIsWeb ? null : PokemonLocalDataSource(languageCode: lang);
|
|
||||||
return PokemonRepositoryImpl(remote: remote, local: local);
|
|
||||||
});
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
/// Palette de couleurs de l'application (couleur primaire + surface de fond).
|
|
||||||
/// Le nom affiché est géré via AppLocalizations (palette0…palette4).
|
|
||||||
class AppPalette {
|
|
||||||
final Color primary;
|
|
||||||
final Color surface;
|
|
||||||
|
|
||||||
const AppPalette({required this.primary, required this.surface});
|
|
||||||
}
|
|
||||||
|
|
||||||
const List<AppPalette> appPalettes = [
|
|
||||||
AppPalette(primary: Color(0xFFD32F2F), surface: Color(0xFF1B2333)),
|
|
||||||
AppPalette(primary: Color(0xFF1565C0), surface: Color(0xFF0D1B2A)),
|
|
||||||
AppPalette(primary: Color(0xFF2E7D32), surface: Color(0xFF1A2B1A)),
|
|
||||||
AppPalette(primary: Color(0xFFF9A825), surface: Color(0xFF1C1A00)),
|
|
||||||
AppPalette(primary: Color(0xFF6A1B9A), surface: Color(0xFF1A0A2B)),
|
|
||||||
];
|
|
||||||
|
|
||||||
const String _prefsPaletteIndex = 'palette_index';
|
|
||||||
|
|
||||||
/// Gère l'index de la palette sélectionnée, persistée dans les préférences.
|
|
||||||
class ThemeNotifier extends Notifier<int> {
|
|
||||||
@override
|
|
||||||
int build() {
|
|
||||||
_loadSaved();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadSaved() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
final idx = prefs.getInt(_prefsPaletteIndex) ?? 0;
|
|
||||||
state = idx.clamp(0, appPalettes.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setPalette(int index) async {
|
|
||||||
state = index.clamp(0, appPalettes.length - 1);
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.setInt(_prefsPaletteIndex, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppPalette get current => appPalettes[state];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Index de la palette de couleurs active.
|
|
||||||
final themeProvider = NotifierProvider<ThemeNotifier, int>(ThemeNotifier.new);
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
|
|
||||||
/// Couleur d'affichage associée à un type de Pokémon.
|
|
||||||
Color typeToColor(PokemonType type) {
|
|
||||||
final Map<PokemonType, Color> map = {
|
|
||||||
PokemonType.normal: Colors.white,
|
|
||||||
PokemonType.fire: Colors.red,
|
|
||||||
PokemonType.water: Colors.blue,
|
|
||||||
PokemonType.electric: Colors.yellow,
|
|
||||||
PokemonType.grass: Colors.green,
|
|
||||||
PokemonType.ice: Colors.cyan,
|
|
||||||
PokemonType.fighting: Colors.orange,
|
|
||||||
PokemonType.poison: Colors.purple,
|
|
||||||
PokemonType.ground: Colors.brown,
|
|
||||||
PokemonType.flying: Colors.indigo,
|
|
||||||
PokemonType.psychic: Colors.pink,
|
|
||||||
PokemonType.bug: Colors.lightGreen,
|
|
||||||
PokemonType.rock: Colors.grey,
|
|
||||||
PokemonType.ghost: Colors.indigo,
|
|
||||||
PokemonType.dragon: Colors.indigo,
|
|
||||||
PokemonType.dark: Colors.black45,
|
|
||||||
PokemonType.steel: Colors.grey.shade600,
|
|
||||||
PokemonType.fairy: Colors.pinkAccent,
|
|
||||||
PokemonType.unknown: Colors.transparent,
|
|
||||||
PokemonType.shadow: Colors.transparent,
|
|
||||||
};
|
|
||||||
return map[type] ?? Colors.transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Nom du type localisé (EN par défaut, FR si languageCode == 'fr').
|
|
||||||
String localizedTypeName(PokemonType type, String languageCode) {
|
|
||||||
if (languageCode == 'fr') {
|
|
||||||
const fr = {
|
|
||||||
PokemonType.normal: 'Normal',
|
|
||||||
PokemonType.fighting: 'Combat',
|
|
||||||
PokemonType.flying: 'Vol',
|
|
||||||
PokemonType.poison: 'Poison',
|
|
||||||
PokemonType.ground: 'Sol',
|
|
||||||
PokemonType.rock: 'Roche',
|
|
||||||
PokemonType.bug: 'Insecte',
|
|
||||||
PokemonType.ghost: 'Spectre',
|
|
||||||
PokemonType.steel: 'Acier',
|
|
||||||
PokemonType.fire: 'Feu',
|
|
||||||
PokemonType.water: 'Eau',
|
|
||||||
PokemonType.grass: 'Plante',
|
|
||||||
PokemonType.electric: 'Électrik',
|
|
||||||
PokemonType.psychic: 'Psy',
|
|
||||||
PokemonType.ice: 'Glace',
|
|
||||||
PokemonType.dragon: 'Dragon',
|
|
||||||
PokemonType.dark: 'Ténèbres',
|
|
||||||
PokemonType.fairy: 'Fée',
|
|
||||||
PokemonType.unknown: '?',
|
|
||||||
PokemonType.shadow: '?',
|
|
||||||
};
|
|
||||||
return fr[type] ?? '?';
|
|
||||||
}
|
|
||||||
// English: capitalise the enum name
|
|
||||||
final name = type.name;
|
|
||||||
return name[0].toUpperCase() + name.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Nom du type avec une majuscule initiale (anglais, conservé pour compatibilité).
|
|
||||||
String formatedTypeName(PokemonType type) => localizedTypeName(type, 'en');
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../domain/entities/pokemon.dart';
|
|
||||||
import '../pokemon_type.dart';
|
|
||||||
import '../pokemon_image.dart';
|
|
||||||
|
|
||||||
/// Écran supérieur du détail : numéro, sprite (tap pour basculer shiny), nom et types.
|
|
||||||
class PokemonDetailTop extends StatelessWidget {
|
|
||||||
final Pokemon pokemon;
|
|
||||||
final bool isShiny;
|
|
||||||
final VoidCallback onToggleShiny;
|
|
||||||
final Color primaryColor;
|
|
||||||
final Color primaryDark;
|
|
||||||
final Color surfaceColor;
|
|
||||||
|
|
||||||
const PokemonDetailTop({
|
|
||||||
super.key,
|
|
||||||
required this.pokemon,
|
|
||||||
required this.isShiny,
|
|
||||||
required this.onToggleShiny,
|
|
||||||
required this.primaryColor,
|
|
||||||
required this.primaryDark,
|
|
||||||
required this.surfaceColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: surfaceColor,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(color: surfaceColor, width: 8),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
color: const Color(0xFF90A4AE),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
color: surfaceColor,
|
|
||||||
child: Text(
|
|
||||||
"NO. ${pokemon.id.toString().padLeft(3, '0')}",
|
|
||||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: onToggleShiny,
|
|
||||||
child: Container(
|
|
||||||
height: 180,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
color: const Color(0xFF81CCA5).withAlpha(153),
|
|
||||||
child: PokemonImage(
|
|
||||||
imageUrl: isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl,
|
|
||||||
fallbackUrl: isShiny ? pokemon.imageUrl : null,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
color: const Color(0xFF37474F),
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
pokemon.formatedName.toUpperCase(),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 2),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
PokemonTypeWidget(pokemon.type1),
|
|
||||||
if (pokemon.type2 != null) const SizedBox(width: 4),
|
|
||||||
if (pokemon.type2 != null) PokemonTypeWidget(pokemon.type2!),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import '../../../domain/entities/pokemon.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
import '../../providers/locale_provider.dart';
|
|
||||||
|
|
||||||
/// Écran inférieur du détail : stats de base, description localisée et éléments décoratifs.
|
|
||||||
class PokemonStatsPanel extends ConsumerWidget {
|
|
||||||
final Pokemon pokemon;
|
|
||||||
final Color surfaceColor;
|
|
||||||
final String? descriptionOverride;
|
|
||||||
|
|
||||||
const PokemonStatsPanel({
|
|
||||||
super.key,
|
|
||||||
required this.pokemon,
|
|
||||||
required this.surfaceColor,
|
|
||||||
this.descriptionOverride,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
ref.watch(localeProvider); // watch locale so widget rebuilds on language change
|
|
||||||
final description = descriptionOverride ?? pokemon.description;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: surfaceColor,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
color: const Color(0xFFC8D1D8),
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(l.baseStats,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 2)),
|
|
||||||
Text("MODEL: DS-01",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey[700],
|
|
||||||
fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Divider(color: Colors.black38, thickness: 2, height: 20),
|
|
||||||
_StatBar(label: "HP", value: pokemon.hp, color: const Color(0xFFE53935)),
|
|
||||||
_StatBar(label: "ATK", value: pokemon.atk, color: const Color(0xFFFB8C00)),
|
|
||||||
_StatBar(label: "DEF", value: pokemon.def, color: const Color(0xFFFDD835)),
|
|
||||||
_StatBar(label: "SPD", value: pokemon.spd, color: const Color(0xFF1E88E5)),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFE2EBF0),
|
|
||||||
border: Border.all(color: Colors.grey[400]!),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
description != null && description.isNotEmpty
|
|
||||||
? '"$description"'
|
|
||||||
: '"${l.noDescription}"',
|
|
||||||
style: const TextStyle(fontSize: 16, height: 1.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_DecorativeLights(accentColor: surfaceColor),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StatBar extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
final int value;
|
|
||||||
final Color color;
|
|
||||||
const _StatBar({required this.label, required this.value, required this.color});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final ratio = (value / 255).clamp(0.0, 1.0);
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 50,
|
|
||||||
child: Text(label,
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18))),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 14,
|
|
||||||
decoration: BoxDecoration(color: Colors.grey[400]),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: (ratio * 100).toInt(),
|
|
||||||
child: Container(color: color)),
|
|
||||||
Expanded(
|
|
||||||
flex: 100 - (ratio * 100).toInt(),
|
|
||||||
child: Container()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
SizedBox(
|
|
||||||
width: 40,
|
|
||||||
child: Text(value.toString(),
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
|
||||||
textAlign: TextAlign.right),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DecorativeLights extends StatelessWidget {
|
|
||||||
final Color accentColor;
|
|
||||||
const _DecorativeLights({required this.accentColor});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final dark = HSLColor.fromColor(accentColor)
|
|
||||||
.withLightness(
|
|
||||||
(HSLColor.fromColor(accentColor).lightness - 0.1).clamp(0.0, 1.0))
|
|
||||||
.toColor();
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: accentColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: dark, width: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Container(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFFFB300),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: const Color(0xFFF57C00), width: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 6,
|
|
||||||
width: 30,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[500],
|
|
||||||
borderRadius: BorderRadius.circular(3))),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Container(
|
|
||||||
height: 6,
|
|
||||||
width: 30,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[500],
|
|
||||||
borderRadius: BorderRadius.circular(3))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Boutons d'action de l'écran game over : rejouer ou retourner au Pokédex.
|
|
||||||
class GameOverActions extends StatelessWidget {
|
|
||||||
final Color primaryColor;
|
|
||||||
final Color primaryDark;
|
|
||||||
final VoidCallback onTryAgain;
|
|
||||||
final VoidCallback onBack;
|
|
||||||
|
|
||||||
const GameOverActions({
|
|
||||||
super.key,
|
|
||||||
required this.primaryColor,
|
|
||||||
required this.primaryDark,
|
|
||||||
required this.onTryAgain,
|
|
||||||
required this.onBack,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
_button(label: l.tryAgain, icon: Icons.refresh, color: primaryColor, onPressed: onTryAgain),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_button(label: l.backToPokedex, icon: Icons.menu_book, color: primaryDark, onPressed: onBack),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _button({
|
|
||||||
required String label,
|
|
||||||
required IconData icon,
|
|
||||||
required Color color,
|
|
||||||
required VoidCallback onPressed,
|
|
||||||
}) {
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 60,
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
onPressed: onPressed,
|
|
||||||
icon: Icon(icon, color: Colors.white, size: 24),
|
|
||||||
label: Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 2),
|
|
||||||
),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: color,
|
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
import '../pokemon_image.dart';
|
|
||||||
|
|
||||||
/// Bloc supérieur de l'écran game over : bannière "GAME OVER", image et nom du Pokémon.
|
|
||||||
class GameOverHeader extends StatelessWidget {
|
|
||||||
final String pokemonImage;
|
|
||||||
final String pokemonName;
|
|
||||||
final Color primaryDark;
|
|
||||||
final Color surfaceColor;
|
|
||||||
|
|
||||||
const GameOverHeader({
|
|
||||||
super.key,
|
|
||||||
required this.pokemonImage,
|
|
||||||
required this.pokemonName,
|
|
||||||
required this.primaryDark,
|
|
||||||
required this.surfaceColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const _silverBg = Color(0xFFC8D1D8);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(color: primaryDark, border: Border.all(color: primaryDark, width: 4)),
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16),
|
|
||||||
color: _silverBg,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
color: primaryDark,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
|
||||||
child: Text(
|
|
||||||
l.gameOver,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 26,
|
|
||||||
color: Colors.yellow,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 2,
|
|
||||||
shadows: [Shadow(offset: Offset(1.5, 1.5), color: Colors.black)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
if (pokemonImage.isNotEmpty)
|
|
||||||
SizedBox(height: 140, child: PokemonImage(imageUrl: pokemonImage, fit: BoxFit.contain)),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
l.itWas(pokemonName),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: surfaceColor,
|
|
||||||
letterSpacing: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Rangée de statistiques de fin de partie (STREAK / SEEN / SCORE).
|
|
||||||
class GameOverStats extends StatelessWidget {
|
|
||||||
final String streak;
|
|
||||||
final String seen;
|
|
||||||
final String score;
|
|
||||||
|
|
||||||
const GameOverStats({super.key, required this.streak, required this.seen, required this.score});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: _StatBox(label: l.statStreak, value: streak)),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(child: _StatBox(label: l.statSeen, value: seen)),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(child: _StatBox(label: l.statScore, value: score)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Une case de statistique : un libellé rouge au-dessus d'une valeur.
|
|
||||||
class _StatBox extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
const _StatBox({required this.label, required this.value});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
color: const Color(0xFFD9E0E5),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(color: Colors.red, fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: 1),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Séparateur décoratif (deux traits + trois points) entre les blocs de l'écran game over.
|
|
||||||
class HingeDivider extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
const HingeDivider({super.key, required this.color});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: Container(height: 2, color: color)),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: List.generate(
|
|
||||||
3,
|
|
||||||
(index) => Container(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
|
||||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(child: Container(height: 2, color: color)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../core/config/app_constants.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Bouton "GEN FILTER" et son panneau dépliable de sélection des générations.
|
|
||||||
class GenFilterSection extends StatelessWidget {
|
|
||||||
final bool isOpen;
|
|
||||||
final VoidCallback onToggleOpen;
|
|
||||||
final Set<int> selectedGens;
|
|
||||||
final void Function(int) onToggle;
|
|
||||||
final Color surfaceColor;
|
|
||||||
|
|
||||||
const GenFilterSection({
|
|
||||||
super.key,
|
|
||||||
required this.isOpen,
|
|
||||||
required this.onToggleOpen,
|
|
||||||
required this.selectedGens,
|
|
||||||
required this.onToggle,
|
|
||||||
required this.surfaceColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: onToggleOpen,
|
|
||||||
icon: Icon(
|
|
||||||
isOpen ? Icons.expand_less : Icons.filter_list,
|
|
||||||
color: surfaceColor,
|
|
||||||
),
|
|
||||||
label: Text(
|
|
||||||
AppLocalizations.of(context)!.genFilter,
|
|
||||||
style: TextStyle(
|
|
||||||
color: surfaceColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
letterSpacing: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
side: BorderSide(color: surfaceColor, width: 2),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedSize(
|
|
||||||
duration: const Duration(milliseconds: 250),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
child: isOpen
|
|
||||||
? _GenFilterPanel(selectedGens: selectedGens, onToggle: onToggle, surfaceColor: surfaceColor)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GenFilterPanel extends StatelessWidget {
|
|
||||||
final Set<int> selectedGens;
|
|
||||||
final void Function(int) onToggle;
|
|
||||||
final Color surfaceColor;
|
|
||||||
|
|
||||||
const _GenFilterPanel({required this.selectedGens, required this.onToggle, required this.surfaceColor});
|
|
||||||
|
|
||||||
static const _genNames = [
|
|
||||||
'Gen I', 'Gen II', 'Gen III', 'Gen IV', 'Gen V', 'Gen VI', 'Gen VII', 'Gen VIII', 'Gen IX'
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(top: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
border: Border.all(color: surfaceColor, width: 2),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: List.generate(AppConstants.genRanges.length, (i) {
|
|
||||||
final range = AppConstants.genRanges[i];
|
|
||||||
final isSelected = selectedGens.contains(i);
|
|
||||||
final isLast = i == AppConstants.genRanges.length - 1;
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => onToggle(i),
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? const Color(0xFF1B2333).withAlpha(12) : Colors.transparent,
|
|
||||||
border: isLast ? null : Border(bottom: BorderSide(color: Colors.grey.shade200)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 150),
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? const Color(0xFF3B6EE3) : Colors.transparent,
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? const Color(0xFF3B6EE3) : Colors.grey.shade400,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(3),
|
|
||||||
),
|
|
||||||
child: isSelected ? const Icon(Icons.check, size: 13, color: Colors.white) : null,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_genNames[i],
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
|
||||||
color: isSelected ? const Color(0xFF1B2333) : Colors.black54,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'#${range.$1}–#${range.$2}',
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Section de saisie : indice optionnel, champ de réponse et boutons d'action
|
|
||||||
/// (Guess / Continue / Hint / Skip). Purement présentationnelle : tout passe par les callbacks.
|
|
||||||
class GuessInputSection extends StatelessWidget {
|
|
||||||
final TextEditingController controller;
|
|
||||||
final String pokemonName;
|
|
||||||
final bool isHintUsed;
|
|
||||||
final bool isGuessed;
|
|
||||||
final int hints;
|
|
||||||
final int skips;
|
|
||||||
final VoidCallback onGuess;
|
|
||||||
final VoidCallback onContinue;
|
|
||||||
final VoidCallback? onHint;
|
|
||||||
final VoidCallback? onSkip;
|
|
||||||
|
|
||||||
const GuessInputSection({
|
|
||||||
super.key,
|
|
||||||
required this.controller,
|
|
||||||
required this.pokemonName,
|
|
||||||
required this.isHintUsed,
|
|
||||||
required this.isGuessed,
|
|
||||||
required this.hints,
|
|
||||||
required this.skips,
|
|
||||||
required this.onGuess,
|
|
||||||
required this.onContinue,
|
|
||||||
required this.onHint,
|
|
||||||
required this.onSkip,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Masque le nom en ne laissant visibles que la première et la dernière lettre.
|
|
||||||
String _maskedName(String name) {
|
|
||||||
if (name.length <= 2) return name; // trop court pour masquer utilement
|
|
||||||
return '${name[0]}${List.filled(name.length - 2, '_').join()}${name[name.length - 1]}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
l.identificationInput,
|
|
||||||
style: const TextStyle(color: Colors.black54, fontSize: 12, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
if (isHintUsed)
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.amber[100],
|
|
||||||
border: Border.all(color: Colors.amber[600]!, width: 2),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
"${l.hintPrefix}: ${_maskedName(pokemonName)}",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.amber[900], letterSpacing: 4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: Colors.grey[400]!)),
|
|
||||||
child: TextField(
|
|
||||||
controller: controller,
|
|
||||||
style: const TextStyle(fontSize: 24, letterSpacing: 1.5),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: l.enterPokemonName,
|
|
||||||
),
|
|
||||||
onSubmitted: (_) => onGuess(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
if (isGuessed)
|
|
||||||
_bigButton(label: l.continueButton, color: Colors.green, onPressed: onContinue)
|
|
||||||
else ...[
|
|
||||||
_bigButton(label: l.guessButton, color: const Color(0xFF3B6EE3), onPressed: onGuess),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _actionButton(icon: Icons.lightbulb, label: l.hintButton(hints), color: Colors.amber, onPressed: onHint),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: _actionButton(icon: Icons.skip_next, label: l.skipButton(skips), color: Colors.grey[400]!, onPressed: onSkip),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _bigButton({required String label, required Color color, required VoidCallback onPressed}) {
|
|
||||||
return SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 60,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: onPressed,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: color,
|
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _actionButton({
|
|
||||||
required IconData icon,
|
|
||||||
required String label,
|
|
||||||
required Color color,
|
|
||||||
required VoidCallback? onPressed,
|
|
||||||
}) {
|
|
||||||
return ElevatedButton.icon(
|
|
||||||
onPressed: onPressed,
|
|
||||||
icon: Icon(icon, color: Colors.black87),
|
|
||||||
label: Text(label, style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: color,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../domain/entities/pokemon.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
import '../pokemon_image.dart';
|
|
||||||
|
|
||||||
/// Écran bleu affichant la silhouette (jeu en cours) ou l'image révélée (manche gagnée).
|
|
||||||
class GuessSilhouette extends StatelessWidget {
|
|
||||||
final Pokemon pokemon;
|
|
||||||
final bool isShiny;
|
|
||||||
final bool isGuessed;
|
|
||||||
final Color surfaceColor;
|
|
||||||
|
|
||||||
const GuessSilhouette({
|
|
||||||
super.key,
|
|
||||||
required this.pokemon,
|
|
||||||
required this.isShiny,
|
|
||||||
required this.isGuessed,
|
|
||||||
required this.surfaceColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final imageUrl = isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl;
|
|
||||||
return Container(
|
|
||||||
height: 250,
|
|
||||||
width: double.infinity,
|
|
||||||
margin: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF3B6EE3),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: surfaceColor, width: 8),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: isGuessed
|
|
||||||
? PokemonImage(
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
fallbackUrl: pokemon.imageUrl,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
)
|
|
||||||
: PokemonImage(
|
|
||||||
imageUrl: imageUrl,
|
|
||||||
fallbackUrl: pokemon.imageUrl,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
color: isShiny ? Colors.yellow[700]! : Colors.black,
|
|
||||||
colorBlendMode: BlendMode.srcIn,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
color: surfaceColor,
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: Text(
|
|
||||||
isShiny ? l.shinyDetected : l.whosThatPokemon,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: isShiny ? Colors.yellow[400] : Colors.white,
|
|
||||||
fontSize: isShiny ? 18 : 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../core/config/app_constants.dart';
|
|
||||||
|
|
||||||
/// Rangée de cœurs représentant les vies restantes.
|
|
||||||
class LivesRow extends StatelessWidget {
|
|
||||||
final int lives;
|
|
||||||
const LivesRow({super.key, required this.lives});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: List.generate(AppConstants.startingLives, (index) {
|
|
||||||
return Icon(
|
|
||||||
index < lives ? Icons.favorite : Icons.favorite_border,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 32,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Encart affichant le score courant et le meilleur score personnel.
|
|
||||||
class ScoreBoard extends StatelessWidget {
|
|
||||||
final int currentScore;
|
|
||||||
final int bestScore;
|
|
||||||
|
|
||||||
const ScoreBoard({super.key, required this.currentScore, required this.bestScore});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withAlpha(153),
|
|
||||||
border: Border.all(color: Colors.black12),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
l.currentScore,
|
|
||||||
style: const TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"$currentScore",
|
|
||||||
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Color(0xFF3B6EE3)),
|
|
||||||
),
|
|
||||||
const Divider(height: 24),
|
|
||||||
Text(
|
|
||||||
l.personalBest(bestScore),
|
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Bandeau affichant le nombre de Pokémon découverts sur le total.
|
|
||||||
class PokedexCountBar extends StatelessWidget {
|
|
||||||
final int caught;
|
|
||||||
final int total;
|
|
||||||
const PokedexCountBar({super.key, required this.caught, required this.total});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Color(0xFFB0BEC5),
|
|
||||||
border: Border(bottom: BorderSide(color: Color(0xFF78909C), width: 2)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${caught.toString().padLeft(3, '0')} / $total',
|
|
||||||
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(AppLocalizations.of(context)!.pokemonDiscovered,
|
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.black54, letterSpacing: 1)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
/// Barre de titre de la liste du Pokédex.
|
|
||||||
class PokedexListHeader extends StatelessWidget {
|
|
||||||
const PokedexListHeader({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
|
||||||
color: const Color(0xFF90A4AE),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(AppLocalizations.of(context)!.listNational,
|
|
||||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Image réseau d'un Pokémon, avec URL de repli et placeholder en cas d'échec de chargement.
|
|
||||||
class PokemonImage extends StatelessWidget {
|
|
||||||
final String imageUrl;
|
|
||||||
final String? fallbackUrl;
|
|
||||||
final BoxFit fit;
|
|
||||||
final double? width;
|
|
||||||
final double? height;
|
|
||||||
final Color? color;
|
|
||||||
final BlendMode? colorBlendMode;
|
|
||||||
|
|
||||||
const PokemonImage({
|
|
||||||
super.key,
|
|
||||||
required this.imageUrl,
|
|
||||||
this.fallbackUrl,
|
|
||||||
this.fit = BoxFit.contain,
|
|
||||||
this.width,
|
|
||||||
this.height,
|
|
||||||
this.color,
|
|
||||||
this.colorBlendMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Image.network(
|
|
||||||
imageUrl,
|
|
||||||
fit: fit,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
color: color,
|
|
||||||
colorBlendMode: colorBlendMode,
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
// If the primary image fails and we have a fallback, try the fallback
|
|
||||||
if (fallbackUrl != null && fallbackUrl != imageUrl) {
|
|
||||||
return Image.network(
|
|
||||||
fallbackUrl!,
|
|
||||||
fit: fit,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
color: color,
|
|
||||||
colorBlendMode: colorBlendMode,
|
|
||||||
errorBuilder: (context, error, stackTrace) {
|
|
||||||
// If the fallback also fails, show a placeholder
|
|
||||||
return _buildPlaceholder();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// No fallback, show placeholder
|
|
||||||
return _buildPlaceholder();
|
|
||||||
},
|
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
|
||||||
if (loadingProgress == null) return child;
|
|
||||||
return Center(
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
value: loadingProgress.expectedTotalBytes != null
|
|
||||||
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPlaceholder() {
|
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.help_outline,
|
|
||||||
size: (width ?? 40) * 0.5,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
),
|
|
||||||
if ((width ?? 100) > 60)
|
|
||||||
Text(
|
|
||||||
"Not Found",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../domain/entities/pokemon.dart';
|
|
||||||
import 'pokemon_image.dart';
|
|
||||||
|
|
||||||
/// Ligne de liste d'un Pokémon : image et nom si attrapé, masqué (???) sinon.
|
|
||||||
class PokemonTile extends StatelessWidget {
|
|
||||||
const PokemonTile(this.pokemon, {Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
final Pokemon pokemon;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// If not caught, we don't allow navigating to the detail page (to force guessing)
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: pokemon.isCaught ? () {
|
|
||||||
Navigator.pushNamed(context, "/pokemon-detail", arguments: pokemon);
|
|
||||||
} : null,
|
|
||||||
child: Container(
|
|
||||||
height: 80,
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFE2EBF0), // lighter grey for tile surface
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withAlpha(25),
|
|
||||||
blurRadius: 2,
|
|
||||||
offset: const Offset(2, 2),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
// Image box
|
|
||||||
Container(
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: pokemon.isCaught ? const Color(0xFF78909C) : Colors.grey[700],
|
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: pokemon.isCaught
|
|
||||||
? PokemonImage(imageUrl: pokemon.imageUrl, fit: BoxFit.contain)
|
|
||||||
: const SizedBox.expand(),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
|
|
||||||
// Name texts
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'No. ${pokemon.id.toString().padLeft(3, '0')}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
pokemon.isCaught ? pokemon.formatedName.toUpperCase() : '???',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: pokemon.isCaught ? Colors.black87 : Colors.grey[500],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Caught check icon
|
|
||||||
if (pokemon.isCaught)
|
|
||||||
const Icon(Icons.check_circle, color: Colors.green, size: 28)
|
|
||||||
else
|
|
||||||
Icon(Icons.help, color: Colors.grey[400], size: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Effet rétro de scanlines superposé en fond. À placer directement dans un [Stack].
|
|
||||||
class ScanlineOverlay extends StatelessWidget {
|
|
||||||
const ScanlineOverlay({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Positioned.fill(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: 100,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) => Container(
|
|
||||||
height: 4,
|
|
||||||
margin: const EdgeInsets.only(bottom: 4),
|
|
||||||
color: Colors.black.withAlpha(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
import '../../providers/locale_provider.dart';
|
|
||||||
|
|
||||||
/// Sélecteur de langue de l'application (parmi [supportedLocales]).
|
|
||||||
class LanguagePicker extends StatelessWidget {
|
|
||||||
final Locale selected;
|
|
||||||
final Color primaryColor;
|
|
||||||
final void Function(Locale) onSelect;
|
|
||||||
|
|
||||||
const LanguagePicker({
|
|
||||||
super.key,
|
|
||||||
required this.selected,
|
|
||||||
required this.primaryColor,
|
|
||||||
required this.onSelect,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const _flags = {'en': '🇬🇧', 'fr': '🇫🇷'};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final names = {'en': l.langEnglish, 'fr': l.langFrench};
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: supportedLocales.map((locale) {
|
|
||||||
final isSelected = locale.languageCode == selected.languageCode;
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onSelect(locale),
|
|
||||||
child: AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? primaryColor : Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? primaryColor : Colors.grey.shade300,
|
|
||||||
width: isSelected ? 3 : 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(_flags[locale.languageCode] ?? '', style: const TextStyle(fontSize: 22)),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Text(
|
|
||||||
names[locale.languageCode] ?? locale.languageCode,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: isSelected ? Colors.white : Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
if (isSelected) const Icon(Icons.check_circle, color: Colors.white, size: 22),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../../l10n/app_localizations.dart';
|
|
||||||
import '../../providers/theme_provider.dart';
|
|
||||||
|
|
||||||
/// Sélecteur de palette de couleurs de l'application.
|
|
||||||
class PalettePicker extends StatelessWidget {
|
|
||||||
final int selectedIndex;
|
|
||||||
final void Function(int) onSelect;
|
|
||||||
const PalettePicker({super.key, required this.selectedIndex, required this.onSelect});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final l = AppLocalizations.of(context)!;
|
|
||||||
final paletteNames = [l.palette0, l.palette1, l.palette2, l.palette3, l.palette4];
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: List.generate(appPalettes.length, (i) {
|
|
||||||
final p = appPalettes[i];
|
|
||||||
final isSelected = i == selectedIndex;
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onSelect(i),
|
|
||||||
child: AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isSelected ? p.primary : Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? p.primary : Colors.grey.shade300,
|
|
||||||
width: isSelected ? 3 : 1.5,
|
|
||||||
),
|
|
||||||
boxShadow: isSelected
|
|
||||||
? [BoxShadow(color: p.primary.withAlpha(80), blurRadius: 8, offset: const Offset(0, 3))]
|
|
||||||
: [],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: p.primary,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Container(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: p.surface,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: Colors.white, width: 1.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Text(
|
|
||||||
paletteNames[i],
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: isSelected ? Colors.white : Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
if (isSelected) const Icon(Icons.check_circle, color: Colors.white, size: 22),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Titre de section avec une barre verticale colorée à gauche.
|
|
||||||
class SectionTitle extends StatelessWidget {
|
|
||||||
final String text;
|
|
||||||
final Color color;
|
|
||||||
const SectionTitle({super.key, required this.text, required this.color});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Container(width: 4, height: 20, color: color),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
text,
|
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: color, letterSpacing: 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// En-tête de la page Système.
|
|
||||||
class SystemHeader extends StatelessWidget {
|
|
||||||
final Color primaryColor;
|
|
||||||
final String title;
|
|
||||||
const SystemHeader({super.key, required this.primaryColor, required this.title});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: primaryColor,
|
|
||||||
border: Border(bottom: BorderSide(color: primaryColor.withAlpha(180), width: 3)),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold, letterSpacing: 4),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Donnée d'une statistique affichée dans la grille.
|
|
||||||
class StatItem {
|
|
||||||
final String label;
|
|
||||||
final String value;
|
|
||||||
final IconData icon;
|
|
||||||
const StatItem({required this.label, required this.value, required this.icon});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Grille 2 colonnes de cartes de statistiques.
|
|
||||||
class StatsGrid extends StatelessWidget {
|
|
||||||
final Color primaryColor;
|
|
||||||
final List<StatItem> items;
|
|
||||||
const StatsGrid({super.key, required this.primaryColor, required this.items});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GridView.count(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
crossAxisSpacing: 12,
|
|
||||||
mainAxisSpacing: 12,
|
|
||||||
childAspectRatio: 1.4,
|
|
||||||
children: items.map((item) => _StatCard(item: item, color: primaryColor)).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StatCard extends StatelessWidget {
|
|
||||||
final StatItem item;
|
|
||||||
final Color color;
|
|
||||||
const _StatCard({required this.item, required this.color});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: color.withAlpha(80), width: 2),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(color: Colors.black.withAlpha(25), blurRadius: 4, offset: const Offset(0, 2)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(item.icon, color: color, size: 28),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text(item.value, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color)),
|
|
||||||
Text(
|
|
||||||
item.label,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(fontSize: 12, color: Colors.black54),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/pokemon.dart';
|
||||||
|
|
||||||
|
// Convertit un nom de type français (de l'API Tyradex) en PokemonType
|
||||||
|
PokemonType frenchTypeToEnum(String frenchType) {
|
||||||
|
const Map<String, PokemonType> frenchToEnglish = {
|
||||||
|
'Normal': PokemonType.normal,
|
||||||
|
'Combat': PokemonType.fighting,
|
||||||
|
'Vol': PokemonType.flying,
|
||||||
|
'Poison': PokemonType.poison,
|
||||||
|
'Sol': PokemonType.ground,
|
||||||
|
'Roche': PokemonType.rock,
|
||||||
|
'Insecte': PokemonType.bug,
|
||||||
|
'Spectre': PokemonType.ghost,
|
||||||
|
'Acier': PokemonType.steel,
|
||||||
|
'Feu': PokemonType.fire,
|
||||||
|
'Eau': PokemonType.water,
|
||||||
|
'Plante': PokemonType.grass,
|
||||||
|
'Électrik': PokemonType.electric,
|
||||||
|
'Psy': PokemonType.psychic,
|
||||||
|
'Glace': PokemonType.ice,
|
||||||
|
'Dragon': PokemonType.dragon,
|
||||||
|
'Ténèbres': PokemonType.dark,
|
||||||
|
'Fée': PokemonType.fairy,
|
||||||
|
};
|
||||||
|
return frenchToEnglish[frenchType] ?? PokemonType.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permet de mapper un type de Pokémon avec une couleur
|
||||||
|
Color typeToColor(PokemonType type) {
|
||||||
|
Map<PokemonType, Color> typeToColor = {
|
||||||
|
PokemonType.normal: Colors.white,
|
||||||
|
PokemonType.fire: Colors.red,
|
||||||
|
PokemonType.water: Colors.blue,
|
||||||
|
PokemonType.electric: Colors.yellow,
|
||||||
|
PokemonType.grass: Colors.green,
|
||||||
|
PokemonType.ice: Colors.cyan,
|
||||||
|
PokemonType.fighting: Colors.orange,
|
||||||
|
PokemonType.poison: Colors.purple,
|
||||||
|
PokemonType.ground: Colors.brown,
|
||||||
|
PokemonType.flying: Colors.indigo,
|
||||||
|
PokemonType.psychic: Colors.pink,
|
||||||
|
PokemonType.bug: Colors.lightGreen,
|
||||||
|
PokemonType.rock: Colors.grey,
|
||||||
|
PokemonType.ghost: Colors.indigo,
|
||||||
|
PokemonType.dragon: Colors.indigo,
|
||||||
|
PokemonType.dark: Colors.black45,
|
||||||
|
PokemonType.steel: Colors.grey.shade600,
|
||||||
|
PokemonType.fairy: Colors.pinkAccent,
|
||||||
|
PokemonType.unknown: Colors.transparent,
|
||||||
|
PokemonType.shadow: Colors.transparent,
|
||||||
|
};
|
||||||
|
return typeToColor[type] ?? Colors.transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Met le nom du type de Pokémon avec une majuscule
|
||||||
|
String formatedTypeName(PokemonType type) {
|
||||||
|
String typeName = type.toString().split('.').last.replaceAll('PokemonType.', '');
|
||||||
|
return typeName[0].toUpperCase() + typeName.substring(1);
|
||||||
|
}
|
||||||
@@ -6,10 +6,6 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
|
||||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
sqlite3_flutter_libs
|
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
jni
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
@@ -5,12 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import shared_preferences_foundation
|
import sqflite
|
||||||
import sqflite_darwin
|
|
||||||
import sqlite3_flutter_libs
|
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
platform :osx, '10.15'
|
platform :osx, '10.14'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
+15
-46
@@ -1,64 +1,33 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- shared_preferences_foundation (0.0.1):
|
- FMDB (2.7.12):
|
||||||
- Flutter
|
- FMDB/standard (= 2.7.12)
|
||||||
|
- FMDB/Core (2.7.12)
|
||||||
|
- FMDB/standard (2.7.12):
|
||||||
|
- FMDB/Core
|
||||||
|
- sqflite (0.0.2):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqflite_darwin (0.0.4):
|
- FMDB (>= 2.7.5)
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- sqlite3 (3.51.1):
|
|
||||||
- sqlite3/common (= 3.51.1)
|
|
||||||
- sqlite3/common (3.51.1)
|
|
||||||
- sqlite3/dbstatvtab (3.51.1):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/fts5 (3.51.1):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/math (3.51.1):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/perf-threadsafe (3.51.1):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/rtree (3.51.1):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3/session (3.51.1):
|
|
||||||
- sqlite3/common
|
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- sqlite3 (~> 3.51.1)
|
|
||||||
- sqlite3/dbstatvtab
|
|
||||||
- sqlite3/fts5
|
|
||||||
- sqlite3/math
|
|
||||||
- sqlite3/perf-threadsafe
|
|
||||||
- sqlite3/rtree
|
|
||||||
- sqlite3/session
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
||||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
|
||||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- sqlite3
|
- FMDB
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
shared_preferences_foundation:
|
sqflite:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
||||||
sqflite_darwin:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
|
||||||
sqlite3_flutter_libs:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
FMDB: 728731dd336af3936ce00f91d9d8495f5718a0e6
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite: c73556b2499b92f0b6e6946abe4a4084510cdf90
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
|
||||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
/* End PBXAggregateTarget section */
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
0FDCA1A045353300B1A8E8E8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76F7019D0D88279494D5531D /* Pods_Runner.framework */; };
|
2F63F588D69E6E84EF178DDE /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38A997E4AF06585806D63A93 /* Pods_RunnerTests.framework */; };
|
||||||
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
|
||||||
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||||
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||||
851351654985DAD48988C209 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4915ECF99C5E4C9C683ED752 /* Pods_RunnerTests.framework */; };
|
BC0787830DD8D138B3AFA3FE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1ADD6793C62B3D4B8AF3C642 /* Pods_Runner.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
0B6A0CC857D58D26A0B79C8D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
1ADD6793C62B3D4B8AF3C642 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||||
@@ -79,15 +79,15 @@
|
|||||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||||
40D2AEC0C927C89AC54D3DB4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
38A997E4AF06585806D63A93 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4915ECF99C5E4C9C683ED752 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
695FFFED6DD87814A538DFCF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
7370BBE38ED573ED71B9080C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
76F7019D0D88279494D5531D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
8425D271D17EE83BF167A836 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
86FC039ADCC2CFF33032C226 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
C2B4FF7F883B72793F5DD3F2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
A60151594AB2579C4697BCA2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
D5F1F019BFDE6F413FB421E0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
AE1BE7E94A2D9EB80387B250 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
E791602D5E2611FF301C4F22 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
FACE8614944708AF3373EFE5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
851351654985DAD48988C209 /* Pods_RunnerTests.framework in Frameworks */,
|
2F63F588D69E6E84EF178DDE /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0FDCA1A045353300B1A8E8E8 /* Pods_Runner.framework in Frameworks */,
|
BC0787830DD8D138B3AFA3FE /* Pods_Runner.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||||
33CC10EE2044A3C60003C045 /* Products */,
|
33CC10EE2044A3C60003C045 /* Products */,
|
||||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||||
62DC207A1894FA0B7B1EACF9 /* Pods */,
|
B9231BE3DD2D9C2F0FE7683B /* Pods */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -185,15 +185,15 @@
|
|||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
62DC207A1894FA0B7B1EACF9 /* Pods */ = {
|
B9231BE3DD2D9C2F0FE7683B /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D5F1F019BFDE6F413FB421E0 /* Pods-Runner.debug.xcconfig */,
|
A60151594AB2579C4697BCA2 /* Pods-Runner.debug.xcconfig */,
|
||||||
0B6A0CC857D58D26A0B79C8D /* Pods-Runner.release.xcconfig */,
|
8425D271D17EE83BF167A836 /* Pods-Runner.release.xcconfig */,
|
||||||
40D2AEC0C927C89AC54D3DB4 /* Pods-Runner.profile.xcconfig */,
|
86FC039ADCC2CFF33032C226 /* Pods-Runner.profile.xcconfig */,
|
||||||
C2B4FF7F883B72793F5DD3F2 /* Pods-RunnerTests.debug.xcconfig */,
|
E791602D5E2611FF301C4F22 /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
695FFFED6DD87814A538DFCF /* Pods-RunnerTests.release.xcconfig */,
|
FACE8614944708AF3373EFE5 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
7370BBE38ED573ED71B9080C /* Pods-RunnerTests.profile.xcconfig */,
|
AE1BE7E94A2D9EB80387B250 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Pods;
|
name = Pods;
|
||||||
path = Pods;
|
path = Pods;
|
||||||
@@ -202,8 +202,8 @@
|
|||||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
76F7019D0D88279494D5531D /* Pods_Runner.framework */,
|
1ADD6793C62B3D4B8AF3C642 /* Pods_Runner.framework */,
|
||||||
4915ECF99C5E4C9C683ED752 /* Pods_RunnerTests.framework */,
|
38A997E4AF06585806D63A93 /* Pods_RunnerTests.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
91CA1F8B03DD046BE3FEDD74 /* [CP] Check Pods Manifest.lock */,
|
6E388177BD91FCED76707214 /* [CP] Check Pods Manifest.lock */,
|
||||||
331C80D1294CF70F00263BE5 /* Sources */,
|
331C80D1294CF70F00263BE5 /* Sources */,
|
||||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||||
331C80D3294CF70F00263BE5 /* Resources */,
|
331C80D3294CF70F00263BE5 /* Resources */,
|
||||||
@@ -234,13 +234,13 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
EBA006E33EB355F2C0B61BDE /* [CP] Check Pods Manifest.lock */,
|
29B2F3029551E8A809B61379 /* [CP] Check Pods Manifest.lock */,
|
||||||
33CC10E92044A3C60003C045 /* Sources */,
|
33CC10E92044A3C60003C045 /* Sources */,
|
||||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||||
33CC10EB2044A3C60003C045 /* Resources */,
|
33CC10EB2044A3C60003C045 /* Resources */,
|
||||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||||
45EA0EFB7B0E13C201870C85 /* [CP] Embed Pods Frameworks */,
|
C7326370B9BF34B0BE156E06 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -322,6 +322,28 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
29B2F3029551E8A809B61379 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@@ -360,24 +382,7 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||||
};
|
};
|
||||||
45EA0EFB7B0E13C201870C85 /* [CP] Embed Pods Frameworks */ = {
|
6E388177BD91FCED76707214 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
|
||||||
outputFileListPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
91CA1F8B03DD046BE3FEDD74 /* [CP] Check Pods Manifest.lock */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
@@ -399,26 +404,21 @@
|
|||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
EBA006E33EB355F2C0B61BDE /* [CP] Check Pods Manifest.lock */ = {
|
C7326370B9BF34B0BE156E06 /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
name = "[CP] Embed Pods Frameworks";
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
);
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
outputPaths = (
|
|
||||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
@@ -472,7 +472,7 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = C2B4FF7F883B72793F5DD3F2 /* Pods-RunnerTests.debug.xcconfig */;
|
baseConfigurationReference = E791602D5E2611FF301C4F22 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -487,7 +487,7 @@
|
|||||||
};
|
};
|
||||||
331C80DC294CF71000263BE5 /* Release */ = {
|
331C80DC294CF71000263BE5 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 695FFFED6DD87814A538DFCF /* Pods-RunnerTests.release.xcconfig */;
|
baseConfigurationReference = FACE8614944708AF3373EFE5 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -502,7 +502,7 @@
|
|||||||
};
|
};
|
||||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7370BBE38ED573ED71B9080C /* Pods-RunnerTests.profile.xcconfig */;
|
baseConfigurationReference = AE1BE7E94A2D9EB80387B250 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@@ -553,7 +553,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
@@ -632,7 +632,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -679,7 +679,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.network.server</key>
|
||||||
|
|||||||
@@ -4,7 +4,5 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
http:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.8"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.9"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.17"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
sqflite:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
sqflite_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_common
|
||||||
|
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.0"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.4"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "14.3.1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.7.0-0 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user