mirror of
https://github.com/chenasraf/pokedex_flutter.git
synced 2026-05-17 17:48:04 +00:00
feat: move display, weaknesses, abilities
This commit is contained in:
@@ -7,14 +7,27 @@ import '../utils/extensions/iterable_extensions.dart';
|
||||
class PokemonHelper {
|
||||
static String? imageUrl(Pokemon pokemon) => [
|
||||
pokemon.sprites.frontDefault,
|
||||
pokemon.sprites.other?.home.frontDefault,
|
||||
pokemon.sprites.versions?.generation8.icons.frontDefault,
|
||||
pokemon.sprites.other?.home?.frontDefault,
|
||||
pokemon.sprites.versions?.generation8?.icons?.frontDefault,
|
||||
].firstWhereOrNull((url) => url != null);
|
||||
|
||||
static String? shinyImageUrl(Pokemon pokemon) => [
|
||||
pokemon.sprites.frontShiny,
|
||||
pokemon.sprites.other?.home.frontShiny,
|
||||
pokemon.sprites.versions?.generation8.icons.frontShiny,
|
||||
pokemon.sprites.other?.home?.frontShiny,
|
||||
pokemon.sprites.versions?.generation8?.icons?.frontShiny,
|
||||
].firstWhereOrNull((url) => url != null);
|
||||
|
||||
static String? iconImageUrl(Pokemon pokemon) => [
|
||||
pokemon.sprites.versions?.generation8?.icons?.frontDefault,
|
||||
pokemon.sprites.versions?.generation7?.icons?.frontDefault,
|
||||
pokemon.sprites.versions?.generation6?.xy?.frontDefault,
|
||||
pokemon.sprites.versions?.generation5?.blackWhite?.frontDefault,
|
||||
pokemon.sprites.versions?.generation4?.platinum?.frontDefault,
|
||||
pokemon.sprites.versions?.generation3?.emerald?.frontDefault,
|
||||
pokemon.sprites.versions?.generation2?.crystal?.frontDefault,
|
||||
pokemon.sprites.versions?.generation1?.yellow?.frontDefault,
|
||||
pokemon.sprites.other?.officialArtwork?.frontDefault,
|
||||
pokemon.sprites.other?.home?.frontDefault
|
||||
].firstWhereOrNull((url) => url != null);
|
||||
|
||||
static String displayName(Pokemon pokemon, PokemonSpecies? species) {
|
||||
@@ -45,5 +58,10 @@ class PokemonHelper {
|
||||
|
||||
return move.names.firstWhereOrNull((n) => n.language.name == 'en')?.name ?? move.name.capitalize();
|
||||
}
|
||||
|
||||
static String getMoveDescription(Move move) {
|
||||
return move.flavorTextEntries.firstWhereOrNull((n) => n.language.name == 'en')?.flavorText ??
|
||||
(move.flavorTextEntries.isNotEmpty ? move.flavorTextEntries.first.flavorText : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
42
lib/core/utils/localstore_cache.dart
Normal file
42
lib/core/utils/localstore_cache.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
import 'package:pokemon_api/pokemon_api.dart';
|
||||
|
||||
class LocalstoreCache extends CacheManager {
|
||||
static const String collection = 'cache';
|
||||
|
||||
String _filename(String key) => '${md5.convert(utf8.encode(key))}.json';
|
||||
|
||||
@override
|
||||
Future<void> add(String key, value) {
|
||||
return Localstore.instance.collection(collection).doc(_filename(key)).set(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() {
|
||||
return Localstore.instance.collection(collection).delete();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> contains(String key) async {
|
||||
return (await Localstore.instance.collection(collection).doc(_filename(key)).get()) != null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fill(Map<String, dynamic> cache) {
|
||||
return Future.wait(cache.entries.map((doc) => add(doc.key, doc.value)));
|
||||
}
|
||||
|
||||
@override
|
||||
Future get(String key) {
|
||||
return Localstore.instance.collection(collection).doc(_filename(key)).get();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) {
|
||||
return Localstore.instance.collection(collection).doc(_filename(key)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,13 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
import 'package:pokemon_api/pokemon_api.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'core/routes.dart';
|
||||
import 'core/utils/localstore_cache.dart';
|
||||
import 'modules/PokemonDetails/pokemon_details_page.dart';
|
||||
import 'modules/PokemonList/pokemon_list_controller.dart';
|
||||
import 'modules/PokemonList/pokemon_list_page.dart';
|
||||
|
||||
class LocalstoreCache extends CacheManager {
|
||||
static const String collection = 'cache';
|
||||
|
||||
String _filename(String key) => '${md5.convert(utf8.encode(key))}.json';
|
||||
|
||||
@override
|
||||
Future<void> add(String key, value) {
|
||||
return Localstore.instance.collection(collection).doc(_filename(key)).set(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() {
|
||||
return Localstore.instance.collection(collection).delete();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> contains(String key) async {
|
||||
return (await Localstore.instance.collection(collection).doc(_filename(key)).get()) != null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> fill(Map<String, dynamic> cache) {
|
||||
return Future.wait(cache.entries.map((doc) => add(doc.key, doc.value)));
|
||||
}
|
||||
|
||||
@override
|
||||
Future get(String key) {
|
||||
return Localstore.instance.collection(collection).doc(_filename(key)).get();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) {
|
||||
return Localstore.instance.collection(collection).doc(_filename(key)).delete();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
PokemonAPIClient.instance.setCache(LocalstoreCache());
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pokedex/core/models/pokemon_helper.dart';
|
||||
import 'package:pokedex/core/utils/extensions/string_extensions.dart';
|
||||
import 'package:pokemon_api/pokemon_api.dart';
|
||||
import '../../core/models/pokemon_helper.dart';
|
||||
import '../../core/utils/extensions/string_extensions.dart';
|
||||
import '../../widgets/pokemon_move.dart';
|
||||
import 'pokemon_details_args.dart';
|
||||
import '../PokemonList/pokemon_list_controller.dart';
|
||||
import '../../widgets/pokemon_image.dart';
|
||||
@@ -46,40 +48,120 @@ class PokemonDetailsPage extends StatelessWidget {
|
||||
PokemonImage(poke: poke, size: imgSize, shiny: true),
|
||||
],
|
||||
),
|
||||
() => const Text('Weaknesses', textScaleFactor: 1.2),
|
||||
() => FutureBuilder(
|
||||
future: Future.wait(poke.types.map((t) => t.type.get())),
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.hasData
|
||||
? snapshot.data!
|
||||
.toList()
|
||||
.fold(<NamedAPIResource>[], (prev, type) => prev..addAll(type.damageRelations.doubleDamageFrom))
|
||||
: <NamedAPIResource>[];
|
||||
return Wrap(
|
||||
children: [
|
||||
for (final weakness in data)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Chip(
|
||||
label: Text('${weakness.name.capitalize()} x2'),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
() => const Text('Abilities', textScaleFactor: 1.2),
|
||||
() => Wrap(
|
||||
children: [
|
||||
for (final ability in poke.abilities)
|
||||
FutureBuilder(
|
||||
future: ability.ability.get(),
|
||||
builder: (context, snapshot) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Chip(
|
||||
label: Text(snapshot.data?.name.capitalize() ?? '...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
() => const Text('Moves', textScaleFactor: 1.2),
|
||||
() => const Text('Level Moves', textScaleFactor: 1.2),
|
||||
...levelMoves
|
||||
.map((move) => () => FutureBuilder(
|
||||
future: move.move?.get(),
|
||||
builder: (context, snapshot) {
|
||||
final versionDet =
|
||||
move.versionGroupDetails.firstWhere((det) => det.moveLearnMethod?.name == 'level-up');
|
||||
final moveName = snapshot.hasData && snapshot.data is Move
|
||||
? PokemonHelper.getMoveName(snapshot.data as Move)
|
||||
: move.move?.name.capitalize();
|
||||
final level = versionDet.levelLearnedAt;
|
||||
return Text('$moveName (Lv. $level)');
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
() => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Wrap(
|
||||
children: [
|
||||
for (final move in levelMoves)
|
||||
FutureBuilder(
|
||||
future: move.move?.get(),
|
||||
builder: (context, snapshot) {
|
||||
return SizedBox(
|
||||
width: constraints.maxWidth / 2,
|
||||
child: PokemonMoveCard.level(
|
||||
move: snapshot.data,
|
||||
versionDetails: move.versionGroupDetails,
|
||||
loading: !snapshot.hasData,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
() => const SizedBox(height: 16),
|
||||
() => const Text('TM Moves', textScaleFactor: 1.2),
|
||||
...tmMoves
|
||||
.map((move) => () => FutureBuilder(
|
||||
future: move.move?.get(),
|
||||
builder: (context, snapshot) {
|
||||
final moveName = snapshot.hasData
|
||||
? PokemonHelper.getMoveName(snapshot.data as Move)
|
||||
: move.move?.name.capitalize() ?? "Unknown Move";
|
||||
return Text(moveName);
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
() => LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Wrap(
|
||||
children: [
|
||||
for (final move in tmMoves)
|
||||
FutureBuilder(
|
||||
future: move.move?.get(),
|
||||
builder: (context, snapshot) {
|
||||
return SizedBox(
|
||||
width: constraints.maxWidth / 2,
|
||||
child: PokemonMoveCard.tm(
|
||||
move: snapshot.data,
|
||||
versionDetails: move.versionGroupDetails,
|
||||
loading: !snapshot.hasData,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
() => Text('Order: ${poke.order}'),
|
||||
() => SelectableText('img: ${PokemonHelper.imageUrl(poke)}')
|
||||
];
|
||||
|
||||
final icon = PokemonHelper.iconImageUrl(poke);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(PokemonHelper.displayName(poke, species)),
|
||||
centerTitle: true,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null)
|
||||
Container(
|
||||
color: Colors.purple,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: icon,
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
// const SizedBox(width: 8),
|
||||
Text(PokemonHelper.displayName(poke, species)),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemBuilder: (context, index) => children[index].call(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
import 'package:pokemon_api/pokemon_api.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
||||
@@ -20,13 +20,14 @@ class PokemonImage extends StatelessWidget {
|
||||
var img = PokemonHelper.imageUrl(poke);
|
||||
var shinyImg = PokemonHelper.shinyImageUrl(poke);
|
||||
if (img == null) {
|
||||
return const SizedBox.square(dimension: 64, child: Placeholder());
|
||||
return SizedBox.square(dimension: size, child: const Placeholder());
|
||||
}
|
||||
|
||||
return CachedNetworkImage(
|
||||
imageUrl: !shiny ? img : shinyImg ?? img,
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.cover,
|
||||
progressIndicatorBuilder: (context, url, progress) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
|
||||
57
lib/widgets/pokemon_move.dart
Normal file
57
lib/widgets/pokemon_move.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../core/models/pokemon_helper.dart';
|
||||
import '../core/utils/extensions/iterable_extensions.dart';
|
||||
import '../core/utils/extensions/string_extensions.dart';
|
||||
import 'package:pokemon_api/pokemon_api.dart';
|
||||
|
||||
enum MoveType { level, tm }
|
||||
|
||||
class PokemonMoveCard extends StatelessWidget {
|
||||
// const PokemonMoveCard._({super.key, required this.move, required this.moveType});
|
||||
|
||||
const PokemonMoveCard.level({
|
||||
super.key,
|
||||
required this.move,
|
||||
required this.versionDetails,
|
||||
required this.loading,
|
||||
}) : moveType = MoveType.level;
|
||||
|
||||
const PokemonMoveCard.tm({
|
||||
super.key,
|
||||
required this.move,
|
||||
required this.versionDetails,
|
||||
required this.loading,
|
||||
}) : moveType = MoveType.tm;
|
||||
|
||||
final Move? move;
|
||||
final MoveType moveType;
|
||||
final List<PokemonMoveVersion> versionDetails;
|
||||
final bool loading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (loading || move == null) {
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(move?.name.capitalize() ?? '...'),
|
||||
const Text('...'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
final details = versionDetails.firstWhereOrNull((det) => det.moveLearnMethod?.name == 'level-up');
|
||||
return Card(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(PokemonHelper.getMoveName(move!)),
|
||||
// Text(PokemonHelper.getMoveDescription(move!)),
|
||||
moveType == MoveType.level
|
||||
? Text('Level: ${details?.levelLearnedAt ?? '??'}')
|
||||
: const Text('TM'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user