some base evolution details implementation

This commit is contained in:
Owen LeJeune
2022-10-06 13:53:30 -04:00
parent 9dd598bafb
commit 9619992729
10 changed files with 389 additions and 17 deletions

View File

@@ -1,6 +1,11 @@
package com.owenlejeune.mydex.api.pokeapi.v2
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionChain
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionTrigger
import com.owenlejeune.mydex.api.pokeapi.v2.model.items.Item
import com.owenlejeune.mydex.api.pokeapi.v2.model.location.Location
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.PaginatedResponse
import com.owenlejeune.mydex.api.pokeapi.v2.model.move.Move
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonStat
@@ -31,4 +36,19 @@ interface PokemonApi {
@GET("egg-group/{id}")
suspend fun getEggGroup(@Path("id") id: Int): Response<EggGroup>
@GET("evolution-chain/{id}")
suspend fun getEvolutionChain(@Path("id") id: Int): Response<EvolutionChain>
@GET("evolution-trigger/{id}")
suspend fun getEvolutionTrigger(@Path("id") id: Int): Response<EvolutionTrigger>
@GET("item/{id}")
suspend fun getItem(@Path("id") id: Int): Response<Item>
@GET("move/{id}")
suspend fun getMove(@Path("id") id: Int): Response<Move>
@GET("location/{id}")
suspend fun getLocation(@Path("id") id: Int): Response<Location>
}

View File

@@ -1,7 +1,12 @@
package com.owenlejeune.mydex.api.pokeapi.v2
import android.util.Log
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionChain
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionTrigger
import com.owenlejeune.mydex.api.pokeapi.v2.model.items.Item
import com.owenlejeune.mydex.api.pokeapi.v2.model.location.Location
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.PaginatedResponse
import com.owenlejeune.mydex.api.pokeapi.v2.model.move.Move
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonStat
@@ -51,5 +56,25 @@ class PokemonService: KoinComponent {
suspend fun getEggGroup(id: Int): Response<EggGroup> {
return service.getEggGroup(id)
}
suspend fun getEvolutionChain(id: Int): Response<EvolutionChain> {
return service.getEvolutionChain(id)
}
suspend fun getEvolutionTrigger(id: Int): Response<EvolutionTrigger> {
return service.getEvolutionTrigger(id)
}
suspend fun getItem(id: Int): Response<Item> {
return service.getItem(id)
}
suspend fun getMove(id: Int): Response<Move> {
return service.getMove(id)
}
suspend fun getLocation(id: Int): Response<Location> {
return service.getLocation(id)
}
}

View File

@@ -6,6 +6,6 @@ import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.NameAndUrl
class ChainLink(
@SerializedName("is_baby") val isBaby: Boolean,
@SerializedName("species") val species: NameAndUrl,
@SerializedName("evolution_details") val evolutionDetails: EvolutionDetails,
@SerializedName("evolution_details") val evolutionDetails: List<EvolutionDetails>,
@SerializedName("evolves_to") val evolves_to: List<ChainLink?>?
)

View File

@@ -4,22 +4,22 @@ import com.google.gson.annotations.SerializedName
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.NameAndUrl
class EvolutionDetails(
@SerializedName("item") val item: NameAndUrl,
@SerializedName("item") val item: NameAndUrl?,
@SerializedName("trigger") val trigger: NameAndUrl,
@SerializedName("gender") val gender: Int,
@SerializedName("held_item") val heldItem: NameAndUrl,
@SerializedName("known_move") val knownMove: NameAndUrl,
@SerializedName("known_move_type") val knownMoveType: NameAndUrl,
@SerializedName("location") val location: NameAndUrl,
@SerializedName("min_level") val minLevel: Int,
@SerializedName("min_happiness") val minHappiness: Int,
@SerializedName("min_beauty") val minBeauty: Int,
@SerializedName("min_affection") val minAffection: Int,
@SerializedName("gender") val gender: Int?,
@SerializedName("held_item") val heldItem: NameAndUrl?,
@SerializedName("known_move") val knownMove: NameAndUrl?,
@SerializedName("known_move_type") val knownMoveType: NameAndUrl?,
@SerializedName("location") val location: NameAndUrl?,
@SerializedName("min_level") val minLevel: Int?,
@SerializedName("min_happiness") val minHappiness: Int?,
@SerializedName("min_beauty") val minBeauty: Int?,
@SerializedName("min_affection") val minAffection: Int?,
@SerializedName("needs_overworld_rain") val needsOverworldRain: Boolean,
@SerializedName("party_species") val partySpecies: NameAndUrl,
@SerializedName("party_type") val partyType: NameAndUrl,
@SerializedName("relative_physical_stats") val relativePhysicalStats: Int,
@SerializedName("time_of_day") val timeOfDay: String,
@SerializedName("trade_species") val tradeSpecies: NameAndUrl,
@SerializedName("party_species") val partySpecies: NameAndUrl?,
@SerializedName("party_type") val partyType: NameAndUrl?,
@SerializedName("relative_physical_stats") val relativePhysicalStats: Int?,
@SerializedName("time_of_day") val timeOfDay: String?,
@SerializedName("trade_species") val tradeSpecies: NameAndUrl?,
@SerializedName("turn_upside_down") val turnUpsideDown: Boolean
)

View File

@@ -7,4 +7,8 @@ fun List<NameAndLanguage>.getNameForLanguage(): String? {
val lang = Locale.current.language
val defLang = "en"
return find { it.language.name == lang }?.name ?: find { it.language.name == defLang}?.name
}
fun <T> List<T>.getIfNotEmpty(index: Int): T? {
return if (isNotEmpty()) get(index) else null
}

View File

@@ -32,6 +32,8 @@ import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import com.owenlejeune.mydex.R
import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.ChainLink
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionChain
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonStat
@@ -39,6 +41,7 @@ import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.egggroup.EggGroup
import com.owenlejeune.mydex.extensions.adjustBy
import com.owenlejeune.mydex.extensions.getIdFromUrl
import com.owenlejeune.mydex.extensions.getIfNotEmpty
import com.owenlejeune.mydex.extensions.getNameForLanguage
import com.owenlejeune.mydex.preferences.AppPreferences
import com.owenlejeune.mydex.ui.components.PokemonTypeLabel
@@ -528,10 +531,52 @@ private fun EvolutionView(
modifier = Modifier
.padding(all = 24.dp)
.verticalScroll(state = scrollState)
.background(color = Color.Red)
.fillMaxSize()
) {
Text(text = "Evolution")
val id = pokemonSpecies.evolutionChainUrl.getIdFromUrl()
val evolutionChain = remember { mutableStateOf<EvolutionChain?>(null) }
if (evolutionChain.value == null) {
DataManager.getEvolutionChainById(id) { ec ->
evolutionChain.value = ec
}
}
val evolutionStages = remember { mutableListOf<EvolutionStage>() }
val nextChain = evolutionChain.value?.chain
if (evolutionStages.isEmpty()) {
EvolutionUtils.getEvolutionFromChainLink(nextChain) { es ->
evolutionStages.addAll(es)
}
}
evolutionStages.forEach { evolutionStage ->
if (evolutionStage.evolvesTo?.isNotEmpty() == true) {
Row {
Text(
text = evolutionStage.evolvesFrom.names.getNameForLanguage()
?: evolutionStage.evolvesFrom.name, modifier = Modifier.weight(1f)
)
Column(
modifier = Modifier.weight(2f)
) {
evolutionStage.evolvesTo.forEach { et ->
Row {
Column(
modifier = Modifier.weight(1f)
) {
Text(text = et.triggerAndCondition.trigger)
Text(text = et.triggerAndCondition.condition)
}
Text(text = et.evolvesTo.name, modifier = Modifier.weight(1f))
}
Divider(modifier = Modifier.height(2.dp), color = MaterialTheme.colorScheme.onSurface)
}
}
}
}
}
}
}

View File

@@ -1,6 +1,11 @@
package com.owenlejeune.mydex.utils
import android.util.SparseArray
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionChain
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionTrigger
import com.owenlejeune.mydex.api.pokeapi.v2.model.items.Item
import com.owenlejeune.mydex.api.pokeapi.v2.model.location.Location
import com.owenlejeune.mydex.api.pokeapi.v2.model.move.Move
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonStat
@@ -14,5 +19,10 @@ object AppCache {
var cachedTypes = SparseArray<PokemonType>()
var cachedStats = SparseArray<PokemonStat>()
var cachedEggGroups = SparseArray<EggGroup>()
var cachedEvolutionChains = SparseArray<EvolutionChain>()
var cachedEvolutionTriggers = SparseArray<EvolutionTrigger>()
var cachedItems = SparseArray<Item>()
var cachedMoves = SparseArray<Move>()
var cachedLocations = SparseArray<Location>()
}

View File

@@ -1,6 +1,11 @@
package com.owenlejeune.mydex.utils
import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionChain
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionTrigger
import com.owenlejeune.mydex.api.pokeapi.v2.model.items.Item
import com.owenlejeune.mydex.api.pokeapi.v2.model.location.Location
import com.owenlejeune.mydex.api.pokeapi.v2.model.move.Move
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonStat
@@ -27,6 +32,15 @@ object DataManager: KoinComponent {
)
}
suspend fun getPokemonByIdSync(id: Int): Pokemon {
return getByIdSync(
id = id,
retriever = { AppCache.cachedPokemon[it] },
fetcher = { service.getPokemon(it) },
storer = { AppCache.cachedPokemon.put(it.id, it) }
)
}
fun getPokemonSpeciesById(id: Int, callback: (PokemonSpecies) -> Unit) {
getById(
id = id,
@@ -37,6 +51,15 @@ object DataManager: KoinComponent {
)
}
suspend fun getPokemonSpeciesByIdSync(id: Int): PokemonSpecies {
return getByIdSync(
id = id,
retriever = { AppCache.cachedSpecies[id] },
fetcher = { service.getPokemonSpecies(id) },
storer = { AppCache.cachedSpecies.put(it.id, it) }
)
}
fun getTypeById(id: Int, callback: (PokemonType) -> Unit) {
getById(
id = id,
@@ -47,6 +70,15 @@ object DataManager: KoinComponent {
)
}
suspend fun getTypeByIdSync(id: Int): PokemonType {
return getByIdSync(
id = id,
retriever = { AppCache.cachedTypes[it] },
fetcher = { service.getPokemonType(it) },
storer = { AppCache.cachedTypes.put(it.id, it) }
)
}
fun getStatById(id: Int, callback: (PokemonStat) -> Unit) {
getById(
id = id,
@@ -57,6 +89,15 @@ object DataManager: KoinComponent {
)
}
suspend fun getStatByIdSync(id: Int): PokemonStat {
return getByIdSync(
id = id,
retriever = { AppCache.cachedStats[id] },
fetcher = { service.getPokemonStat(id) },
storer = { AppCache.cachedStats.put(it.id, it) }
)
}
fun getEggGroupById(id: Int, callback: (EggGroup) -> Unit) {
getById(
id = id,
@@ -67,6 +108,101 @@ object DataManager: KoinComponent {
)
}
suspend fun getEggGroupById(id: Int): EggGroup {
return getByIdSync(
id = id,
retriever = { AppCache.cachedEggGroups[id] },
fetcher = { service.getEggGroup(id) },
storer = { AppCache.cachedEggGroups.put(it.id, it) }
)
}
fun getEvolutionChainById(id: Int, callback: (EvolutionChain) -> Unit) {
getById(
id = id,
callback = callback,
retriever = { AppCache.cachedEvolutionChains[id] },
fetcher = { service.getEvolutionChain(id) },
storer = { AppCache.cachedEvolutionChains.put(it.id, it) }
)
}
fun getEvolutionTriggerById(id: Int, callback: (EvolutionTrigger) -> Unit) {
getById(
id = id,
callback = callback,
retriever = { AppCache.cachedEvolutionTriggers[id] },
fetcher = { service.getEvolutionTrigger(id) },
storer = { AppCache.cachedEvolutionTriggers.put(it.id, it) }
)
}
suspend fun getEvolutionTriggerByIdSync(id: Int): EvolutionTrigger {
return getByIdSync(
id = id,
retriever = { AppCache.cachedEvolutionTriggers[id] },
fetcher = { service.getEvolutionTrigger(id) },
storer = { AppCache.cachedEvolutionTriggers.put(it.id, it) }
)
}
fun getItemById(id: Int, callback: (Item) -> Unit) {
getById(
id = id,
callback = callback,
retriever = { AppCache.cachedItems[id] },
fetcher = { service.getItem(id) },
storer = { AppCache.cachedItems.put(it.id, it) }
)
}
suspend fun getItemByIdSync(id: Int): Item {
return getByIdSync(
id = id,
retriever = { AppCache.cachedItems[id] },
fetcher = { service.getItem(id) },
storer = { AppCache.cachedItems.put(it.id, it) }
)
}
fun getMoveById(id: Int, callback: (Move) -> Unit) {
getById(
id = id,
callback = callback,
retriever = { AppCache.cachedMoves[id] },
fetcher = { service.getMove(id) },
storer = { AppCache.cachedMoves.put(it.id, it) }
)
}
suspend fun getMoveByIdSync(id: Int): Move {
return getByIdSync(
id = id,
retriever = { AppCache.cachedMoves[id] },
fetcher = { service.getMove(id) },
storer = { AppCache.cachedMoves.put(it.id, it) }
)
}
fun getLocationById(id: Int, callback: (Location) -> Unit) {
getById(
id = id,
callback = callback,
retriever = { AppCache.cachedLocations[id] },
fetcher = { service.getLocation(id) },
storer = { AppCache.cachedLocations.put(it.id, it) }
)
}
suspend fun getLocationByIdSync(id: Int): Location {
return getByIdSync(
id = id,
retriever = { AppCache.cachedLocations[id] },
fetcher = { service.getLocation(id) },
storer = { AppCache.cachedLocations.put(it.id, it) }
)
}
private fun <T> getById(
id: Int,
callback: (T) -> Unit,
@@ -88,4 +224,18 @@ object DataManager: KoinComponent {
}
}
private suspend fun <T> getByIdSync(
id: Int,
retriever: (Int) -> T?,
fetcher: suspend (Int) -> Response<T>,
storer: (T) -> Unit
): T {
val retreived = retriever(id)
if (retreived != null) {
return retreived
}
val fetched = fetcher(id)
return fetched.body()!!.apply { storer(this) }
}
}

View File

@@ -0,0 +1,113 @@
package com.owenlejeune.mydex.utils
import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.ChainLink
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionDetails
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionTrigger
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.extensions.getIdFromUrl
import com.owenlejeune.mydex.extensions.getIfNotEmpty
import com.owenlejeune.mydex.extensions.getNameForLanguage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
object EvolutionUtils {
fun getEvolutionFromChainLink(
link: ChainLink?,
callback: (List<EvolutionStage>) -> Unit
) {
CoroutineScope(Dispatchers.IO).launch {
val evolutionStages = ArrayList<EvolutionStage>()
var nextLink: ChainLink? = link
while(nextLink != null) {
val id = nextLink.species.url.getIdFromUrl()
val evolvesFrom = DataManager.getPokemonSpeciesByIdSync(id)
if (nextLink.evolves_to != null) {
val evolutions = ArrayList<Evolution>()
nextLink.evolves_to!!.forEach { et ->
et?.let {
val etId = it.species.url.getIdFromUrl()
val evolvesTo = DataManager.getPokemonSpeciesByIdSync(etId)
it.evolutionDetails.forEach { details ->
val triggerAndCondition =
evolutionDetailsToTriggerAndCondition(details)
evolutions.add(Evolution(evolvesTo, triggerAndCondition))
}
}
}
val es = EvolutionStage(evolvesFrom, evolutions)
evolutionStages.add(es)
}
nextLink = nextLink.evolves_to?.getIfNotEmpty(0)
}
withContext(Dispatchers.Main) {
callback(evolutionStages)
}
}
}
private fun evolutionDetailsToTriggerAndCondition(details: EvolutionDetails): EvolutionTriggerAndCondition {
val trigger = details.trigger.name
val condition = if (details.item != null) {
details.item.name
} else if (details.gender != null) {
if (details.gender == 1) "female" else "male"
} else if (details.heldItem != null) {
details.heldItem.name
} else if (details.knownMove != null) {
details.knownMove.name
} else if (details.knownMoveType != null) {
details.knownMoveType.name
} else if (details.location != null) {
details.location.name
} else if (details.minLevel != null) {
details.minLevel.toString()
} else if (details.minHappiness != null) {
details.minHappiness.toString()
} else if (details.minBeauty != null) {
details.minBeauty.toString()
} else if (details.minAffection != null) {
details.minAffection.toString()
} else if (details.needsOverworldRain) {
"Needs rain"
} else if (details.partySpecies != null) {
details.partySpecies.name
} else if (details.partyType != null) {
details.partyType.name
} else if (details.relativePhysicalStats != null) {
details.relativePhysicalStats.toString()
} else if (details.timeOfDay != null) {
details.timeOfDay
} else if (details.tradeSpecies != null) {
details.tradeSpecies.name
} else if (details.turnUpsideDown) {
"Turn 3DS upside-down"
} else {
""
}
return EvolutionTriggerAndCondition(trigger, condition)
}
}
class EvolutionStage(
val evolvesFrom: PokemonSpecies,
val evolvesTo: List<Evolution>?
)
class Evolution(
val evolvesTo: PokemonSpecies,
val triggerAndCondition: EvolutionTriggerAndCondition
)
class EvolutionTriggerAndCondition(
// trigger: EvolutionTrigger,
val trigger: String,
val condition: String
)

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.mydex.utils
import com.owenlejeune.mydex.api.pokeapi.v2.model.evolution.EvolutionDetails
import kotlin.math.ceil
import kotlin.math.floor
@@ -51,4 +52,8 @@ object PokeUtils {
return genderRate.toFloat() * GENDER_RATE * 100
}
fun evolutionDetailsToConditionString(evolutionDetails: EvolutionDetails): String {
return ""
}
}