chore: major changes
This commit is contained in:
parent
528cdcafef
commit
fbf37e6861
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal 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:
|
||||||
@ -1,5 +1,8 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -31,6 +34,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
|
|
||||||
@ -41,6 +45,8 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
@ -48,6 +54,7 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||||
|
|||||||
@ -13,6 +13,7 @@ class PokemonApi {
|
|||||||
static const String pokemonUrl = 'api/v1/pokemon';
|
static const String pokemonUrl = 'api/v1/pokemon';
|
||||||
|
|
||||||
static Future<Pokemon> getPokemon(int id) async {
|
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 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
|
// On utilise Uri.https pour construire l'URL de la requête
|
||||||
var response = await http.get(Uri.https(baseUrl, "$pokemonUrl/$id"));
|
var response = await http.get(Uri.https(baseUrl, "$pokemonUrl/$id"));
|
||||||
@ -59,6 +60,7 @@ class PokemonApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Pokemon>> getAllPokemon() async {
|
static Future<List<Pokemon>> getAllPokemon() async {
|
||||||
|
print('API Call: Fetching ALL Pokémon from Tyradex...');
|
||||||
final response = await http.get(Uri.https(baseUrl, pokemonUrl));
|
final response = await http.get(Uri.https(baseUrl, pokemonUrl));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|||||||
@ -16,8 +16,8 @@ class PokemonTypeWidget extends StatelessWidget {
|
|||||||
Color typeColor = typeToColor(type);
|
Color typeColor = typeToColor(type);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
margin: const EdgeInsets.only(right: 10),
|
margin: const EdgeInsets.only(right: 6),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: typeColor,
|
color: typeColor,
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:sqflite_common/sqflite.dart';
|
import 'package:sqflite_common/sqflite.dart';
|
||||||
import '../models/pokemon.dart';
|
import '../models/pokemon.dart';
|
||||||
|
|
||||||
// Permet de gérer la base de données
|
// Permet de gérer la base de données
|
||||||
class PokedexDatabase {
|
class PokedexDatabase {
|
||||||
static Database? database;
|
static Database? database;
|
||||||
|
static final ValueNotifier<int> onDatabaseUpdate = ValueNotifier(0);
|
||||||
|
|
||||||
static Future<void> initDatabase() async {
|
static Future<void> initDatabase() async {
|
||||||
database = await openDatabase(
|
database = await openDatabase(
|
||||||
"pokedex.db", // Nom de la base de données
|
"pokedex.db", // Nom de la base de données
|
||||||
@ -37,6 +40,7 @@ class PokedexDatabase {
|
|||||||
pokemon.toJson(),
|
pokemon.toJson(),
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||||
);
|
);
|
||||||
|
onDatabaseUpdate.value++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Méthode qui permet de récupérer la liste des pokémons dans la base de données
|
// 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 {
|
static Future<void> updatePokemon(Pokemon pokemon) async {
|
||||||
Database database = await getDatabase();
|
Database database = await getDatabase();
|
||||||
await database.update("pokemon", pokemon.toJson(), where: "id = ?", whereArgs: [pokemon.id]);
|
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
|
// Méthode qui permet de récupérer un Pokémon dans la base de données à partir de son ID
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../models/pokemon.dart';
|
import '../models/pokemon.dart';
|
||||||
import '../database/pokedex_database.dart';
|
import '../database/pokedex_database.dart';
|
||||||
|
|
||||||
@ -16,34 +17,56 @@ class _GuessPageState extends State<GuessPage> {
|
|||||||
int _lives = 3;
|
int _lives = 3;
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
bool _isHintUsed = false;
|
bool _isHintUsed = false;
|
||||||
|
bool _isShiny = false;
|
||||||
|
int _currentScore = 0;
|
||||||
|
int _bestScore = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_loadBestScore();
|
||||||
_loadRandomPokemon();
|
_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 {
|
Future<void> _loadRandomPokemon() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
_lives = 3;
|
_lives = 3;
|
||||||
_isHintUsed = false;
|
_isHintUsed = false;
|
||||||
|
_isShiny = Random().nextInt(10) == 0; // 10% chance for shiny
|
||||||
_guessController.clear();
|
_guessController.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pick a random ID between 1 and 151
|
// Pick a random ID between 1 and 1025 (Gen 9)
|
||||||
int randomId = Random().nextInt(151) + 1;
|
int randomId = Random().nextInt(1025) + 1;
|
||||||
Pokemon? pokemon = await Pokemon.fromID(randomId);
|
Pokemon? pokemon = await Pokemon.fromID(randomId);
|
||||||
|
|
||||||
// We only want to guess uncaught ones for optimal experience,
|
// We only want to guess uncaught ones for optimal experience,
|
||||||
// but if all are caught, just play anyway.
|
// but if all are caught, just play anyway.
|
||||||
if (pokemon != null && pokemon.isCaught) {
|
if (pokemon != null && pokemon.isCaught) {
|
||||||
int count = await PokedexDatabase.getCaughtCount();
|
int count = await PokedexDatabase.getCaughtCount();
|
||||||
if (count < 151) {
|
if (count < 1025) {
|
||||||
// Find an uncaught one
|
// Find an uncaught one
|
||||||
for (int i = 1; i <= 151; i++) {
|
for (int i = 1; i <= 1025; i++) {
|
||||||
int attemptId = (randomId + i) % 151 + 1;
|
int attemptId = (randomId + i) % 1025 + 1;
|
||||||
Pokemon? attempt = await Pokemon.fromID(attemptId);
|
Pokemon? attempt = await Pokemon.fromID(attemptId);
|
||||||
if (attempt != null && !attempt.isCaught) {
|
if (attempt != null && !attempt.isCaught) {
|
||||||
pokemon = attempt;
|
pokemon = attempt;
|
||||||
@ -53,15 +76,19 @@ class _GuessPageState extends State<GuessPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
if (mounted) {
|
||||||
_currentPokemon = pokemon;
|
setState(() {
|
||||||
_isLoading = false;
|
_currentPokemon = pokemon;
|
||||||
});
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(e.toString());
|
debugPrint(e.toString());
|
||||||
setState(() {
|
if (mounted) {
|
||||||
_isLoading = false;
|
setState(() {
|
||||||
});
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,26 +97,49 @@ class _GuessPageState extends State<GuessPage> {
|
|||||||
String guess = _guessController.text.trim().toLowerCase();
|
String guess = _guessController.text.trim().toLowerCase();
|
||||||
String actual = _currentPokemon!.name.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!
|
// Correct!
|
||||||
_currentPokemon!.isCaught = true;
|
_currentPokemon!.isCaught = true;
|
||||||
_currentPokemon!.isSeen = true;
|
_currentPokemon!.isSeen = true;
|
||||||
await PokedexDatabase.updatePokemon(_currentPokemon!);
|
await PokedexDatabase.updatePokemon(_currentPokemon!);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_currentScore += _isShiny ? 20 : 10;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await _saveBestScore();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
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
|
// Load next
|
||||||
_loadRandomPokemon();
|
_loadRandomPokemon();
|
||||||
} else {
|
} else {
|
||||||
// Wrong
|
// Wrong
|
||||||
setState(() {
|
if (mounted) {
|
||||||
_lives--;
|
setState(() {
|
||||||
});
|
_lives--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (_lives <= 0) {
|
if (_lives <= 0) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_currentScore = 0; // Reset score only when all lives are lost
|
||||||
|
});
|
||||||
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Out of lives! It was ${_currentPokemon!.formatedName}.'), backgroundColor: Colors.red),
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isLoading) {
|
if (_isLoading) {
|
||||||
@ -165,7 +226,10 @@ class _GuessPageState extends State<GuessPage> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: ColorFiltered(
|
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),
|
child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -174,12 +238,12 @@ class _GuessPageState extends State<GuessPage> {
|
|||||||
color: const Color(0xFF1B2333),
|
color: const Color(0xFF1B2333),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: const Text(
|
child: Text(
|
||||||
"WHO'S THAT POKÉMON?",
|
_isShiny ? "✨ SHINY POKÉMON DETECTED! ✨" : "WHO'S THAT POKÉMON?",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: _isShiny ? Colors.yellow[400] : Colors.white,
|
||||||
fontSize: 22,
|
fontSize: _isShiny ? 18 : 22,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: 2,
|
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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -15,7 +15,6 @@ class _MainPageState extends State<MainPage> {
|
|||||||
final List<Widget> _pages = [
|
final List<Widget> _pages = [
|
||||||
const PokemonListPage(),
|
const PokemonListPage(),
|
||||||
const GuessPage(),
|
const GuessPage(),
|
||||||
const Center(child: Text("TRAINER PAGE placeholder")),
|
|
||||||
const Center(child: Text("SYSTEM PAGE placeholder")),
|
const Center(child: Text("SYSTEM PAGE placeholder")),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -34,39 +33,44 @@ class _MainPageState extends State<MainPage> {
|
|||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(26),
|
borderRadius: BorderRadius.circular(26),
|
||||||
child: _pages[_currentIndex],
|
child: IndexedStack(
|
||||||
|
index: _currentIndex,
|
||||||
|
children: _pages,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: Theme(
|
||||||
currentIndex: _currentIndex,
|
data: Theme.of(context).copyWith(
|
||||||
onTap: (index) {
|
splashColor: Colors.transparent,
|
||||||
setState(() {
|
highlightColor: Colors.transparent,
|
||||||
_currentIndex = index;
|
),
|
||||||
});
|
child: BottomNavigationBar(
|
||||||
},
|
currentIndex: _currentIndex,
|
||||||
type: BottomNavigationBarType.fixed,
|
onTap: (index) {
|
||||||
selectedItemColor: const Color(0xFFD32F2F),
|
setState(() {
|
||||||
unselectedItemColor: Colors.grey,
|
_currentIndex = index;
|
||||||
items: const [
|
});
|
||||||
BottomNavigationBarItem(
|
},
|
||||||
icon: Icon(Icons.grid_view),
|
type: BottomNavigationBarType.fixed,
|
||||||
label: 'LIST',
|
selectedItemColor: const Color(0xFFD32F2F),
|
||||||
),
|
unselectedItemColor: Colors.grey,
|
||||||
BottomNavigationBarItem(
|
items: const [
|
||||||
icon: Icon(Icons.games),
|
BottomNavigationBarItem(
|
||||||
label: 'GUESS',
|
icon: Icon(Icons.grid_view),
|
||||||
),
|
label: 'LIST',
|
||||||
BottomNavigationBarItem(
|
),
|
||||||
icon: Icon(Icons.person),
|
BottomNavigationBarItem(
|
||||||
label: 'TRAINER',
|
icon: Icon(Icons.games),
|
||||||
),
|
label: 'GUESS',
|
||||||
BottomNavigationBarItem(
|
),
|
||||||
icon: Icon(Icons.settings),
|
BottomNavigationBarItem(
|
||||||
label: 'SYSTEM',
|
icon: Icon(Icons.settings),
|
||||||
),
|
label: 'SYSTEM',
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,10 +135,14 @@ class _PokemonDetailPageState extends State<PokemonDetailPage> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
pokemon.formatedName.toUpperCase(),
|
child: Text(
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 2),
|
pokemon.formatedName.toUpperCase(),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
PokemonTypeWidget(pokemon.type1),
|
PokemonTypeWidget(pokemon.type1),
|
||||||
|
|||||||
@ -14,62 +14,76 @@ class PokemonListPage extends StatefulWidget {
|
|||||||
class _PokemonListPageState extends State<PokemonListPage> {
|
class _PokemonListPageState extends State<PokemonListPage> {
|
||||||
String _filter = 'ALL'; // ALL, CAUGHT, NEW
|
String _filter = 'ALL'; // ALL, CAUGHT, NEW
|
||||||
int _caughtCount = 0;
|
int _caughtCount = 0;
|
||||||
|
List<Pokemon> _allPokemon = [];
|
||||||
|
List<Pokemon> _filteredPokemon = [];
|
||||||
|
bool _isSyncing = false;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadPokemonData();
|
_loadPokemonData();
|
||||||
|
PokedexDatabase.onDatabaseUpdate.addListener(_loadPokemonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
PokedexDatabase.onDatabaseUpdate.removeListener(_loadPokemonData);
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadPokemonData() async {
|
Future<void> _loadPokemonData() async {
|
||||||
|
setState(() => _isSyncing = true);
|
||||||
|
|
||||||
final count = await PokedexDatabase.getCaughtCount();
|
final count = await PokedexDatabase.getCaughtCount();
|
||||||
setState(() {
|
|
||||||
_caughtCount = count;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if database is empty for initial sync
|
// Check if database is empty for initial sync
|
||||||
List<Pokemon> localData = await PokedexDatabase.getPokemonList();
|
List<Pokemon> localData = await PokedexDatabase.getPokemonList();
|
||||||
if(localData.isEmpty) {
|
if(localData.isEmpty) {
|
||||||
try {
|
try {
|
||||||
final List<Pokemon> remoteData = await PokemonApi.getAllPokemon();
|
final List<Pokemon> remoteData = await PokemonApi.getAllPokemon();
|
||||||
// Insert first 151
|
// Insert all
|
||||||
for (var p in remoteData) {
|
for (var p in remoteData) {
|
||||||
if(p.id > 151) break;
|
|
||||||
await PokedexDatabase.insertPokemon(p);
|
await PokedexDatabase.insertPokemon(p);
|
||||||
}
|
}
|
||||||
|
localData = await PokedexDatabase.getPokemonList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(e.toString());
|
debugPrint(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort by ID to ensure order
|
||||||
|
localData.sort((a, b) => a.id.compareTo(b.id));
|
||||||
|
|
||||||
if (mounted) {
|
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) {
|
Widget _buildPokemonTile(BuildContext context, int index) {
|
||||||
return FutureBuilder<Pokemon?>(
|
final pokemon = _filteredPokemon[index];
|
||||||
future: PokedexDatabase.getPokemon(index + 1),
|
return PokemonTile(pokemon);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return PokemonTile(pokemon);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -89,7 +103,7 @@ class _PokemonListPageState extends State<PokemonListPage> {
|
|||||||
children: [
|
children: [
|
||||||
Icon(Icons.menu, color: Colors.black87),
|
Icon(Icons.menu, color: Colors.black87),
|
||||||
Text(
|
Text(
|
||||||
'LIST - KANTO',
|
'LIST - NATIONAL',
|
||||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2),
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||||
),
|
),
|
||||||
Icon(Icons.search, color: Colors.black87),
|
Icon(Icons.search, color: Colors.black87),
|
||||||
@ -104,7 +118,6 @@ class _PokemonListPageState extends State<PokemonListPage> {
|
|||||||
children: [
|
children: [
|
||||||
_buildTab('ALL', _filter == 'ALL'),
|
_buildTab('ALL', _filter == 'ALL'),
|
||||||
_buildTab('CAUGHT', _filter == 'CAUGHT'),
|
_buildTab('CAUGHT', _filter == 'CAUGHT'),
|
||||||
_buildTab('NEW', _filter == 'NEW'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -119,7 +132,7 @@ class _PokemonListPageState extends State<PokemonListPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${_caughtCount.toString().padLeft(3, '0')} / 151',
|
'${_caughtCount.toString().padLeft(3, '0')} / ${_allPokemon.length}',
|
||||||
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Text(
|
const Text(
|
||||||
@ -146,11 +159,29 @@ class _PokemonListPageState extends State<PokemonListPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListView.builder(
|
if (_isSyncing && _allPokemon.isEmpty)
|
||||||
padding: const EdgeInsets.all(12),
|
const Center(child: CircularProgressIndicator())
|
||||||
itemCount: 151,
|
else if (_filteredPokemon.isEmpty)
|
||||||
itemBuilder: _buildPokemonTile,
|
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: _filteredPokemon.length,
|
||||||
|
itemBuilder: _buildPokemonTile,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -161,7 +192,7 @@ class _PokemonListPageState extends State<PokemonListPage> {
|
|||||||
color: const Color(0xFF1B2333),
|
color: const Color(0xFF1B2333),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'KANTO REGIONAL POKEDEX V2.0',
|
'NATIONAL POKEDEX V2.0',
|
||||||
style: TextStyle(color: Colors.white70, fontSize: 12, letterSpacing: 1),
|
style: TextStyle(color: Colors.white70, fontSize: 12, letterSpacing: 1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -174,9 +205,10 @@ class _PokemonListPageState extends State<PokemonListPage> {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
if (_filter != title) {
|
||||||
_filter = title;
|
_filter = title;
|
||||||
});
|
_applyFilter();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
import sqlite3_flutter_libs
|
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"))
|
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -31,6 +34,7 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- 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`)
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
|
|
||||||
@ -41,6 +45,8 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
|
shared_preferences_foundation:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
@ -48,6 +54,7 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||||
|
|||||||
@ -25,6 +25,7 @@ dependencies:
|
|||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
google_fonts: ^8.0.2
|
google_fonts: ^8.0.2
|
||||||
|
shared_preferences: ^2.5.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user