patch small issue with trad and theme
This commit is contained in:
@@ -2,24 +2,43 @@ import 'package:sqflite_common/sqflite.dart';
|
||||
import '../../domain/entities/pokemon.dart';
|
||||
import '../dto/pokemon_dto.dart';
|
||||
|
||||
/// Accès SQLite local au Pokédex. Schéma et migrations identiques à l'ancien PokedexDatabase.
|
||||
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: 2,
|
||||
version: 5,
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
if (oldVersion < 2) {
|
||||
await db.execute('DROP TABLE IF EXISTS pokemon');
|
||||
await db.execute(
|
||||
'CREATE TABLE pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT, hp INTEGER NOT NULL, atk INTEGER NOT NULL, def INTEGER NOT NULL, spd INTEGER NOT NULL, description TEXT, isCaught INTEGER NOT NULL DEFAULT 0, isSeen INTEGER NOT NULL DEFAULT 0)');
|
||||
}
|
||||
// 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(
|
||||
'CREATE TABLE IF NOT EXISTS pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT, hp INTEGER NOT NULL, atk INTEGER NOT NULL, def INTEGER NOT NULL, spd INTEGER NOT NULL, description TEXT, isCaught INTEGER NOT NULL DEFAULT 0, isSeen INTEGER NOT NULL DEFAULT 0)');
|
||||
await db.execute(_createSql);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -27,14 +46,14 @@ class PokemonLocalDataSource {
|
||||
Future<List<Pokemon>> getAll() async {
|
||||
final db = await _getDb();
|
||||
final rows = await db.query('pokemon');
|
||||
return rows.map(PokemonDto.fromDb).toList();
|
||||
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);
|
||||
return PokemonDto.fromDb(rows.first, languageCode: languageCode);
|
||||
}
|
||||
|
||||
Future<void> saveAll(List<Pokemon> pokemons) async {
|
||||
|
||||
@@ -8,12 +8,13 @@ import '../dto/pokemon_dto.dart';
|
||||
/// Accès distant à l'API Tyradex.
|
||||
class PokemonRemoteDataSource {
|
||||
final http.Client _client;
|
||||
final String languageCode;
|
||||
|
||||
PokemonRemoteDataSource({http.Client? client})
|
||||
PokemonRemoteDataSource({http.Client? client, this.languageCode = 'fr'})
|
||||
: _client = client ?? http.Client();
|
||||
|
||||
Future<Pokemon> getById(int id) async {
|
||||
AppLogger.info('API: fetching Pokémon $id');
|
||||
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) {
|
||||
@@ -21,11 +22,29 @@ class PokemonRemoteDataSource {
|
||||
'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);
|
||||
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');
|
||||
AppLogger.info('API: fetching ALL Pokémon (lang: $languageCode)');
|
||||
final response = await _client
|
||||
.get(Uri.https(AppConstants.apiBaseUrl, AppConstants.apiPokemonPath));
|
||||
if (response.statusCode != 200) {
|
||||
@@ -34,9 +53,12 @@ class PokemonRemoteDataSource {
|
||||
final List<dynamic> jsonList = jsonDecode(response.body);
|
||||
final result = <Pokemon>[];
|
||||
for (final json in jsonList) {
|
||||
if (json['pokedex_id'] == 0) continue; // entrée générique Tyradex
|
||||
if (json['pokedex_id'] == 0) continue;
|
||||
try {
|
||||
result.add(PokemonDto.fromTyradexJson(json as Map<String, dynamic>));
|
||||
result.add(PokemonDto.fromTyradexJson(
|
||||
json as Map<String, dynamic>,
|
||||
languageCode: languageCode,
|
||||
));
|
||||
} catch (e, st) {
|
||||
AppLogger.error('Parsing pokemon échoué: ${json['name']}', e, st);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import '../../domain/entities/pokemon.dart';
|
||||
class PokemonDto {
|
||||
PokemonDto._();
|
||||
|
||||
/// Mappe un nom de type français (API Tyradex) vers l'enum.
|
||||
static PokemonType frenchTypeToEnum(String frenchType) {
|
||||
static PokemonType _frenchTypeToEnum(String type) {
|
||||
const map = {
|
||||
'Normal': PokemonType.normal,
|
||||
'Combat': PokemonType.fighting,
|
||||
@@ -26,41 +25,65 @@ class PokemonDto {
|
||||
'Ténèbres': PokemonType.dark,
|
||||
'Fée': PokemonType.fairy,
|
||||
};
|
||||
return map[frenchType] ?? PokemonType.unknown;
|
||||
return map[type] ?? PokemonType.unknown;
|
||||
}
|
||||
|
||||
/// Construit une entité depuis la réponse Tyradex (objet unique ou élément de liste).
|
||||
/// [fallbackId] sert quand le JSON ne contient pas `pokedex_id`.
|
||||
static Pokemon fromTyradexJson(Map<String, dynamic> json, {int? fallbackId}) {
|
||||
// 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 name = nameMap?['fr'] ?? nameMap?['en'] ?? 'unknown';
|
||||
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'] ?? [];
|
||||
final type1 =
|
||||
types.isNotEmpty ? frenchTypeToEnum(types[0]['name']) : PokemonType.unknown;
|
||||
final type2 = types.length > 1 ? frenchTypeToEnum(types[1]['name']) : null;
|
||||
// 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, // 'vit' = vitesse chez Tyradex
|
||||
description: json['category'],
|
||||
spd: stats?['vit'] ?? 0,
|
||||
description: descFr,
|
||||
descriptionEn: descEn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Sérialise pour SQLite.
|
||||
/// 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,
|
||||
@@ -69,15 +92,23 @@ class PokemonDto {
|
||||
'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.
|
||||
static Pokemon fromDb(Map<String, dynamic> row) {
|
||||
/// [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: row['name'],
|
||||
name: name,
|
||||
nameFr: nameFr,
|
||||
nameEn: nameEn,
|
||||
id: row['id'],
|
||||
type1: PokemonType.values.firstWhere(
|
||||
(e) => e.name == row['type1'],
|
||||
@@ -94,6 +125,7 @@ class PokemonDto {
|
||||
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,
|
||||
);
|
||||
|
||||
@@ -60,4 +60,7 @@ class PokemonRepositoryImpl implements PokemonRepository {
|
||||
|
||||
@override
|
||||
Future<int> seenCount() async => (await local?.seenCount()) ?? 0;
|
||||
|
||||
@override
|
||||
Future<String?> getEnglishGenus(int id) => remote.getEnglishGenus(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user