feat: move display, weaknesses, abilities

This commit is contained in:
Chen Asraf
2023-07-11 01:30:14 +03:00
parent 2c4e051667
commit ab4aa19351
7 changed files with 235 additions and 72 deletions

View File

@@ -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 : '');
}
}

View 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();
}
}

View File

@@ -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());

View File

@@ -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(),

View File

@@ -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';

View File

@@ -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(),
),

View 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'),
],
),
);
}
}