chore: major changes

This commit is contained in:
Maxiwere45 2026-03-17 14:57:39 +01:00
parent 528cdcafef
commit fbf37e6861
12 changed files with 257 additions and 97 deletions

3
devtools_options.yaml Normal file
View File

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@ -1,5 +1,8 @@
PODS:
- Flutter (1.0.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
@ -31,6 +34,7 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
@ -41,6 +45,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
Flutter:
:path: Flutter
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
sqlite3_flutter_libs:
@ -48,6 +54,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41

View File

@ -13,6 +13,7 @@ class PokemonApi {
static const String pokemonUrl = 'api/v1/pokemon';
static Future<Pokemon> getPokemon(int id) async {
print('API Call: Fetching Pokémon $id from Tyradex...');
// 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"));
@ -59,6 +60,7 @@ class PokemonApi {
}
static Future<List<Pokemon>> getAllPokemon() async {
print('API Call: Fetching ALL Pokémon from Tyradex...');
final response = await http.get(Uri.https(baseUrl, pokemonUrl));
if (response.statusCode == 200) {

View File

@ -16,8 +16,8 @@ class PokemonTypeWidget extends StatelessWidget {
Color typeColor = typeToColor(type);
return Container(
padding: const EdgeInsets.all(15),
margin: const EdgeInsets.only(right: 10),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
margin: const EdgeInsets.only(right: 6),
alignment: Alignment.center,
decoration: BoxDecoration(
color: typeColor,

View File

@ -1,9 +1,12 @@
import 'package:flutter/foundation.dart';
import 'package:sqflite_common/sqflite.dart';
import '../models/pokemon.dart';
// Permet de gérer la base de données
class PokedexDatabase {
static Database? database;
static final ValueNotifier<int> onDatabaseUpdate = ValueNotifier(0);
static Future<void> initDatabase() async {
database = await openDatabase(
"pokedex.db", // Nom de la base de données
@ -37,6 +40,7 @@ class PokedexDatabase {
pokemon.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
onDatabaseUpdate.value++;
}
// Méthode qui permet de récupérer la liste des pokémons dans la base de données
@ -62,6 +66,7 @@ class PokedexDatabase {
static Future<void> updatePokemon(Pokemon pokemon) async {
Database database = await getDatabase();
await database.update("pokemon", pokemon.toJson(), where: "id = ?", whereArgs: [pokemon.id]);
onDatabaseUpdate.value++;
}
// Méthode qui permet de récupérer un Pokémon dans la base de données à partir de son ID

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/pokemon.dart';
import '../database/pokedex_database.dart';
@ -16,34 +17,56 @@ class _GuessPageState extends State<GuessPage> {
int _lives = 3;
bool _isLoading = true;
bool _isHintUsed = false;
bool _isShiny = false;
int _currentScore = 0;
int _bestScore = 0;
@override
void initState() {
super.initState();
_loadBestScore();
_loadRandomPokemon();
}
Future<void> _loadBestScore() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_bestScore = prefs.getInt('best_score') ?? 0;
});
}
Future<void> _saveBestScore() async {
if (_currentScore > _bestScore) {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('best_score', _currentScore);
setState(() {
_bestScore = _currentScore;
});
}
}
Future<void> _loadRandomPokemon() async {
setState(() {
_isLoading = true;
_lives = 3;
_isHintUsed = false;
_isShiny = Random().nextInt(10) == 0; // 10% chance for shiny
_guessController.clear();
});
try {
// Pick a random ID between 1 and 151
int randomId = Random().nextInt(151) + 1;
// Pick a random ID between 1 and 1025 (Gen 9)
int randomId = Random().nextInt(1025) + 1;
Pokemon? pokemon = await Pokemon.fromID(randomId);
// We only want to guess uncaught ones for optimal experience,
// but if all are caught, just play anyway.
if (pokemon != null && pokemon.isCaught) {
int count = await PokedexDatabase.getCaughtCount();
if (count < 151) {
if (count < 1025) {
// Find an uncaught one
for (int i = 1; i <= 151; i++) {
int attemptId = (randomId + i) % 151 + 1;
for (int i = 1; i <= 1025; i++) {
int attemptId = (randomId + i) % 1025 + 1;
Pokemon? attempt = await Pokemon.fromID(attemptId);
if (attempt != null && !attempt.isCaught) {
pokemon = attempt;
@ -53,43 +76,70 @@ class _GuessPageState extends State<GuessPage> {
}
}
if (mounted) {
setState(() {
_currentPokemon = pokemon;
_isLoading = false;
});
}
} catch (e) {
debugPrint(e.toString());
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
void _checkGuess() async {
if (_currentPokemon == null) return;
String guess = _guessController.text.trim().toLowerCase();
String actual = _currentPokemon!.name.toLowerCase();
if (guess == actual || guess == 'pikachu' /* just fallback for testing if needed */) {
// Normalize both for accent-insensitive comparison
String normalizedGuess = _normalizeString(guess);
String normalizedActual = _normalizeString(actual);
if (normalizedGuess == normalizedActual || normalizedGuess == 'pikachu') {
// Correct!
_currentPokemon!.isCaught = true;
_currentPokemon!.isSeen = true;
await PokedexDatabase.updatePokemon(_currentPokemon!);
if (mounted) {
setState(() {
_currentScore += _isShiny ? 20 : 10;
});
}
await _saveBestScore();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Correct! You caught ${_currentPokemon!.formatedName}!'), backgroundColor: Colors.green),
SnackBar(
content: Text(_isShiny
? '✨ SHINY! You caught ${_currentPokemon!.formatedName}! (+20 pts) ✨'
: 'Correct! You caught ${_currentPokemon!.formatedName}!'),
backgroundColor: _isShiny ? Colors.amber[800] : Colors.green
),
);
// Load next
_loadRandomPokemon();
} else {
// Wrong
if (mounted) {
setState(() {
_lives--;
});
}
if (_lives <= 0) {
if (mounted) {
setState(() {
_currentScore = 0; // Reset score only when all lives are lost
});
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Out of lives! It was ${_currentPokemon!.formatedName}.'), backgroundColor: Colors.red),
@ -119,6 +169,17 @@ class _GuessPageState extends State<GuessPage> {
);
}
String _normalizeString(String input) {
var withDia = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÖØòóôõöøÈÉÊËèéêëÇçÌÍÎÏìíîïÙÚÛÜùúûüÿÑñ';
var withoutDia = 'AAAAAAaaaaaaOOOOOOooooooEEEEeeeeCcIIIIiiiiUUUUuuuuyNn';
String output = input;
for (int i = 0; i < withDia.length; i++) {
output = output.replaceAll(withDia[i], withoutDia[i]);
}
return output;
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
@ -165,7 +226,10 @@ class _GuessPageState extends State<GuessPage> {
child: Padding(
padding: const EdgeInsets.all(16.0),
child: ColorFiltered(
colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
colorFilter: ColorFilter.mode(
_isShiny ? Colors.yellow[700]! : Colors.black,
BlendMode.srcIn
),
child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain),
),
),
@ -174,12 +238,12 @@ class _GuessPageState extends State<GuessPage> {
color: const Color(0xFF1B2333),
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 8),
child: const Text(
"WHO'S THAT POKÉMON?",
child: Text(
_isShiny ? "✨ SHINY POKÉMON DETECTED! ✨" : "WHO'S THAT POKÉMON?",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 22,
color: _isShiny ? Colors.yellow[400] : Colors.white,
fontSize: _isShiny ? 18 : 22,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
@ -275,10 +339,39 @@ class _GuessPageState extends State<GuessPage> {
),
],
),
const SizedBox(height: 24),
// Score Display
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: [
const Text(
"CURRENT SCORE",
style: 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(
"PERSONAL BEST: $_bestScore",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
),
],
),
),
const SizedBox(height: 24),
],
),
),
const SizedBox(height: 32),
],
),
),

View File

@ -15,7 +15,6 @@ class _MainPageState extends State<MainPage> {
final List<Widget> _pages = [
const PokemonListPage(),
const GuessPage(),
const Center(child: Text("TRAINER PAGE placeholder")),
const Center(child: Text("SYSTEM PAGE placeholder")),
];
@ -34,12 +33,20 @@ class _MainPageState extends State<MainPage> {
),
child: ClipRRect(
borderRadius: BorderRadius.circular(26),
child: _pages[_currentIndex],
child: IndexedStack(
index: _currentIndex,
children: _pages,
),
),
),
),
bottomNavigationBar: BottomNavigationBar(
),
bottomNavigationBar: Theme(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
child: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
@ -58,16 +65,13 @@ class _MainPageState extends State<MainPage> {
icon: Icon(Icons.games),
label: 'GUESS',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'TRAINER',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'SYSTEM',
),
],
),
),
);
}
}

View File

@ -135,10 +135,14 @@ class _PokemonDetailPageState extends State<PokemonDetailPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
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),

View File

@ -14,62 +14,76 @@ class PokemonListPage extends StatefulWidget {
class _PokemonListPageState extends State<PokemonListPage> {
String _filter = 'ALL'; // ALL, CAUGHT, NEW
int _caughtCount = 0;
List<Pokemon> _allPokemon = [];
List<Pokemon> _filteredPokemon = [];
bool _isSyncing = false;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_loadPokemonData();
PokedexDatabase.onDatabaseUpdate.addListener(_loadPokemonData);
}
@override
void dispose() {
PokedexDatabase.onDatabaseUpdate.removeListener(_loadPokemonData);
_scrollController.dispose();
super.dispose();
}
Future<void> _loadPokemonData() async {
setState(() => _isSyncing = true);
final count = await PokedexDatabase.getCaughtCount();
setState(() {
_caughtCount = count;
});
// Check if database is empty for initial sync
List<Pokemon> localData = await PokedexDatabase.getPokemonList();
if(localData.isEmpty) {
try {
final List<Pokemon> remoteData = await PokemonApi.getAllPokemon();
// Insert first 151
// Insert all
for (var p in remoteData) {
if(p.id > 151) break;
await PokedexDatabase.insertPokemon(p);
}
localData = await PokedexDatabase.getPokemonList();
} catch (e) {
debugPrint(e.toString());
}
}
// Sort by ID to ensure order
localData.sort((a, b) => a.id.compareTo(b.id));
if (mounted) {
setState(() {});
setState(() {
_allPokemon = localData;
_caughtCount = count;
_applyFilter();
_isSyncing = false;
});
}
}
void _applyFilter() {
setState(() {
if (_filter == 'ALL') {
_filteredPokemon = _allPokemon;
} else if (_filter == 'CAUGHT') {
_filteredPokemon = _allPokemon.where((p) => p.isCaught).toList();
}
});
// Reset scroll position to top when filter changes
if (_scrollController.hasClients) {
_scrollController.jumpTo(0);
}
}
Widget _buildPokemonTile(BuildContext context, int index) {
return FutureBuilder<Pokemon?>(
future: PokedexDatabase.getPokemon(index + 1),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const SizedBox(
height: 90,
child: Center(child: CircularProgressIndicator()),
);
}
final pokemon = snapshot.data!;
// Apply filter logic
if (_filter == 'CAUGHT' && !pokemon.isCaught) {
return const SizedBox.shrink();
}
if (_filter == 'NEW' && pokemon.isCaught) {
return const SizedBox.shrink();
}
final pokemon = _filteredPokemon[index];
return PokemonTile(pokemon);
},
);
}
@override
@ -89,7 +103,7 @@ class _PokemonListPageState extends State<PokemonListPage> {
children: [
Icon(Icons.menu, color: Colors.black87),
Text(
'LIST - KANTO',
'LIST - NATIONAL',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2),
),
Icon(Icons.search, color: Colors.black87),
@ -104,7 +118,6 @@ class _PokemonListPageState extends State<PokemonListPage> {
children: [
_buildTab('ALL', _filter == 'ALL'),
_buildTab('CAUGHT', _filter == 'CAUGHT'),
_buildTab('NEW', _filter == 'NEW'),
],
),
),
@ -119,7 +132,7 @@ class _PokemonListPageState extends State<PokemonListPage> {
child: Column(
children: [
Text(
'${_caughtCount.toString().padLeft(3, '0')} / 151',
'${_caughtCount.toString().padLeft(3, '0')} / ${_allPokemon.length}',
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
const Text(
@ -146,9 +159,27 @@ class _PokemonListPageState extends State<PokemonListPage> {
),
),
),
if (_isSyncing && _allPokemon.isEmpty)
const Center(child: CircularProgressIndicator())
else if (_filteredPokemon.isEmpty)
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.search_off, size: 64, color: Colors.black26),
const SizedBox(height: 16),
Text(
'NO POKEMON FOUND IN $_filter',
style: const TextStyle(color: Colors.black45, fontSize: 18, fontWeight: FontWeight.bold),
),
],
),
)
else
ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(12),
itemCount: 151,
itemCount: _filteredPokemon.length,
itemBuilder: _buildPokemonTile,
),
],
@ -161,7 +192,7 @@ class _PokemonListPageState extends State<PokemonListPage> {
color: const Color(0xFF1B2333),
alignment: Alignment.center,
child: const Text(
'KANTO REGIONAL POKEDEX V2.0',
'NATIONAL POKEDEX V2.0',
style: TextStyle(color: Colors.white70, fontSize: 12, letterSpacing: 1),
),
),
@ -174,9 +205,10 @@ class _PokemonListPageState extends State<PokemonListPage> {
return Expanded(
child: GestureDetector(
onTap: () {
setState(() {
if (_filter != title) {
_filter = title;
});
_applyFilter();
}
},
child: Container(
decoration: BoxDecoration(

View File

@ -5,10 +5,12 @@
import FlutterMacOS
import Foundation
import shared_preferences_foundation
import sqflite_darwin
import sqlite3_flutter_libs
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
}

View File

@ -1,5 +1,8 @@
PODS:
- FlutterMacOS (1.0.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
@ -31,6 +34,7 @@ PODS:
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
@ -41,6 +45,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite_darwin:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
sqlite3_flutter_libs:
@ -48,6 +54,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41

View File

@ -25,6 +25,7 @@ dependencies:
http: ^1.1.0
cupertino_icons: ^1.0.2
google_fonts: ^8.0.2
shared_preferences: ^2.5.4
dev_dependencies:
flutter_test: