diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
new file mode 100644
index 0000000..ff13dd2
--- /dev/null
+++ b/.idea/deploymentTargetDropDown.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 5fe4522..59ec8f2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -80,9 +80,9 @@ dependencies {
implementation "androidx.compose.ui:ui-tooling-preview:$compose"
implementation "androidx.activity:activity-compose:$compose_activity"
implementation "com.google.accompanist:accompanist-systemuicontroller:$compose_accompanist"
-// implementation "com.google.accompanist:accompanist-pager:$compose_accompanist"
+ implementation "com.google.accompanist:accompanist-pager:$compose_accompanist"
// implementation "com.google.accompanist:accompanist-pager-indicators:$compose_accompanist"
-// implementation "com.google.accompanist:accompanist-flowlayout:$compose_accompanist"
+ implementation "com.google.accompanist:accompanist-flowlayout:$compose_accompanist"
// implementation "com.google.accompanist:accompanist-insets:$compose_accompanist"
implementation "androidx.navigation:navigation-compose:$compose_navigation"
implementation "androidx.paging:paging-compose:$compose_paging"
diff --git a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonApi.kt b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonApi.kt
index fabd330..077d9f6 100644
--- a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonApi.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonApi.kt
@@ -3,6 +3,7 @@ package com.owenlejeune.mydex.api.pokeapi.v2
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.PaginatedResponse
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
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
import retrofit2.Response
import retrofit2.http.GET
@@ -23,4 +24,7 @@ interface PokemonApi {
@GET("type/{id}")
suspend fun getPokemonType(@Path("id") id: Int): Response
+ @GET("stat/{id}")
+ suspend fun getPokemonStat(@Path("id") id: Int): Response
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonService.kt b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonService.kt
index 3390e2a..dbb619c 100644
--- a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonService.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/PokemonService.kt
@@ -4,6 +4,7 @@ import android.util.Log
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.PaginatedResponse
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
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -42,4 +43,8 @@ class PokemonService: KoinComponent {
return service.getPokemonType(id)
}
+ suspend fun getPokemonStat(id: Int): Response {
+ return service.getPokemonStat(id)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/Pokemon.kt b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/Pokemon.kt
index 21e2af8..dc99356 100644
--- a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/Pokemon.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/Pokemon.kt
@@ -21,6 +21,6 @@ class Pokemon(
@SerializedName("moves") val moves: List,
@SerializedName("species") val species: NameAndUrl,
@SerializedName("sprites") val sprites: Sprites,
- @SerializedName("stats") val state: List,
+ @SerializedName("stats") val stats: List,
@SerializedName("types") val types: List
)
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/PokemonStat.kt b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/PokemonStat.kt
index bae682e..c87e9ec 100644
--- a/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/PokemonStat.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/api/pokeapi/v2/model/pokemon/PokemonStat.kt
@@ -14,5 +14,5 @@ class PokemonStat(
@SerializedName("is_battle_only") val isBattleOnly: Boolean,
@SerializedName("move_damage_class") val moveDamageClass: NameAndUrl,
@SerializedName("name") val name: String,
- @SerializedName("names") val names: NameAndLanguage
+ @SerializedName("names") val names: List
)
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/extensions/ColorExtensions.kt b/app/src/main/java/com/owenlejeune/mydex/extensions/ColorExtensions.kt
new file mode 100644
index 0000000..9222e0a
--- /dev/null
+++ b/app/src/main/java/com/owenlejeune/mydex/extensions/ColorExtensions.kt
@@ -0,0 +1,12 @@
+package com.owenlejeune.mydex.extensions
+
+import androidx.annotation.FloatRange
+import androidx.compose.ui.graphics.Color
+
+fun Color.adjustBy(@FloatRange(from = (-1f).toDouble(), to = 1f.toDouble()) x: Float): Color {
+ return copy(
+ red = (red+x).coerceIn(minimumValue = 0f, maximumValue = 1f),
+ green = (green+x).coerceIn(minimumValue = 0f, maximumValue = 1f),
+ blue = (blue+x).coerceIn(minimumValue = 0f, maximumValue = 1f)
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/components/Helpers.kt b/app/src/main/java/com/owenlejeune/mydex/ui/components/Helpers.kt
new file mode 100644
index 0000000..b6686ac
--- /dev/null
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/components/Helpers.kt
@@ -0,0 +1,31 @@
+package com.owenlejeune.mydex.ui.components
+
+import androidx.compose.ui.layout.MeasurePolicy
+
+//fun flowLayoutMeasurePolicy() = MeasurePolicy { measurables, constraints ->
+// layout(constraints.maxWidth, constraints.maxHeight) {
+// val placeables = measurables.map { measurable ->
+// measurable.measure(constraints)
+// }
+// var yPos = 0
+// var xPos = 0
+// var maxY = 0
+// placeables.forEach { placeable ->
+// if (xPos + placeable.width >
+// constraints.maxWidth
+// ) {
+// xPos = 0
+// yPos += maxY
+// maxY = 0
+// }
+// placeable.placeRelative(
+// x = xPos,
+// y = yPos
+// )
+// xPos += placeable.width
+// if (maxY < placeable.height) {
+// maxY = placeable.height
+// }
+// }
+// }
+//}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/components/Widgets.kt b/app/src/main/java/com/owenlejeune/mydex/ui/components/Widgets.kt
index 7ab7976..1aa919c 100644
--- a/app/src/main/java/com/owenlejeune/mydex/ui/components/Widgets.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/components/Widgets.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.modifier.modifierLocalConsumer
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
@@ -212,4 +213,30 @@ fun PokemonTypeLabel(
color = Color.White
)
}
-}
\ No newline at end of file
+}
+
+@Composable
+fun SmallTabIndicator(
+ modifier: Modifier = Modifier,
+ color: Color = MaterialTheme.colorScheme.primary
+) {
+ Spacer(
+ modifier
+ .padding(horizontal = 28.dp)
+ .height(2.dp)
+ .background(color, RoundedCornerShape(topStartPercent = 100, topEndPercent = 100))
+ )
+}
+
+//@Composable
+//fun FlowLayout(
+// modifier: Modifier = Modifier,
+// content: @Composable () -> Unit
+//) {
+// val measurePolicy = flowLayoutMeasurePolicy()
+// Layout(
+// measurePolicy = measurePolicy,
+// content = content,
+// modifier = modifier
+// )
+//}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/navigation/DataNavItem.kt b/app/src/main/java/com/owenlejeune/mydex/ui/navigation/DataNavItem.kt
index b698403..cee319c 100644
--- a/app/src/main/java/com/owenlejeune/mydex/ui/navigation/DataNavItem.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/navigation/DataNavItem.kt
@@ -15,7 +15,7 @@ sealed class DataNavItem(
): KoinComponent {
companion object {
- val Pages by lazy { listOf(Pokedex, Moves, Abilities, Items, Locations, TypeCharts) }
+ val Pages by lazy { listOf(Pokedex, Moves, Abilities, Items, Locations, TypeCharts, Settings) }
}
private val resourceUtils: ResourceUtils by inject()
@@ -28,5 +28,6 @@ sealed class DataNavItem(
object Items: DataNavItem("items_route", PokeYellow, R.string.items_nav_title)
object Locations: DataNavItem("locations_route", PokePurple, R.string.locations_nav_title)
object TypeCharts: DataNavItem("type_charts_route", PokeBrown, R.string.type_charts_nav_title)
+ object Settings: DataNavItem("settings_route", PokeGrey, R.string.settings_nav_title)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/theme/Color.kt b/app/src/main/java/com/owenlejeune/mydex/ui/theme/Color.kt
index 995856a..b38d6a5 100644
--- a/app/src/main/java/com/owenlejeune/mydex/ui/theme/Color.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/theme/Color.kt
@@ -16,4 +16,25 @@ val PokeLightYellow = Color(0xFFFFCE4B)
val PokePurple = Color(0xFF7C538C)
val PokeRed = Color(0xFFFA6555)
val PokeGreen = Color(0xFF4FC1A6)
-val PokeYellow = Color(0xFFF6C747)
\ No newline at end of file
+val PokeYellow = Color(0xFFF6C747)
+
+val BugType = Color(0xFF98AD19)
+val DarkType = Color(0xFF5C4638)
+val DragonType = Color(0xFF5B10F6)
+val ElectricType = Color(0xFFF7C726)
+val FairyType = Color(0xFFF389D9)
+val FightingType = Color(0xFF7B211E)
+val FireType = Color(0xFFEA3825)
+val FlyingType = Color(0xFF8C73C6)
+val GhostType = Color(0xFF5C4386)
+val GrassType = Color(0xFF68C13F)
+val GroundType = Color(0xFFD7B456)
+val IceType = Color(0xFF88D0CF)
+val NormalType = Color(0xFF979965)
+val PoisonType = Color(0xFF8C288E)
+val PsychicType = Color(0xFFF33D75)
+val RockType = Color(0xFFA8912C)
+val ShadowType = Color(0xFF312536)
+val SteelType = Color(0xFFAAA8C5)
+val UnknownType = Color(0xFF56917E)
+val WaterType = Color(0xFF5579EC)
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/views/DetailView.kt b/app/src/main/java/com/owenlejeune/mydex/ui/views/DetailView.kt
index b06ffd4..0be3edf 100644
--- a/app/src/main/java/com/owenlejeune/mydex/ui/views/DetailView.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/views/DetailView.kt
@@ -2,7 +2,9 @@ package com.owenlejeune.mydex.ui.views
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
@@ -14,6 +16,7 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
@@ -21,15 +24,25 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import coil.compose.AsyncImage
+import com.google.accompanist.flowlayout.FlowRow
+import com.google.accompanist.pager.ExperimentalPagerApi
+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.pokemon.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
+import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonStat
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
+import com.owenlejeune.mydex.extensions.adjustBy
import com.owenlejeune.mydex.extensions.getIdFromUrl
import com.owenlejeune.mydex.extensions.getNameForLanguage
import com.owenlejeune.mydex.ui.components.PokemonTypeLabel
-import com.owenlejeune.mydex.utils.AppCache
-import com.owenlejeune.mydex.utils.ColorUtils
+import com.owenlejeune.mydex.ui.components.SmallTabIndicator
+import com.owenlejeune.mydex.utils.*
+import kotlinx.coroutines.launch
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import org.koin.java.KoinJavaComponent.get
@OptIn(ExperimentalMaterial3Api::class)
@@ -69,19 +82,19 @@ fun PokemonDetailView(
Card(
modifier = Modifier
.fillMaxSize()
- .padding(top = 140.dp),
+ .padding(top = 150.dp),
shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.background)
) {
-
+ Details(pokemon = pokemon, pokemonSpecies = pokemonSpecies)
}
AsyncImage(
- model = pokemon.sprites.frontDefault,
+ model = PokeUtils.spriteFromId(pokemonId),
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
- .size(250.dp)
+ .size(200.dp)
.align(Alignment.TopCenter)
)
}
@@ -98,7 +111,7 @@ fun PokemonDetailView(
title = {},
colors = TopAppBarDefaults
.smallTopAppBarColors(
- containerColor = Color.Transparent//.copy(alpha = 0.4f)
+ containerColor = Color.Transparent
)
)
}
@@ -177,6 +190,234 @@ private fun Header(
}
}
+@OptIn(ExperimentalPagerApi::class)
+@Composable
+private fun ColumnScope.Details(
+ pokemon: Pokemon,
+ pokemonSpecies: PokemonSpecies,
+ service: PokemonService = get(PokemonService::class.java)
+) {
+ val scope = rememberCoroutineScope()
+ val pagerState = rememberPagerState()
+
+ Spacer(modifier = Modifier.height(50.dp))
+
+ val tabs = DetailTab.Items
+ TabRow(
+ selectedTabIndex = pagerState.currentPage,
+ divider = {}
+ ) {
+ tabs.forEachIndexed { index, tab ->
+ Tab(
+ selected = pagerState.currentPage == index,
+ onClick = {
+ scope.launch { pagerState.animateScrollToPage(index) }
+ }
+ ) {
+ Spacer(modifier = Modifier.height(18.dp))
+ Text(text = tab.name, color = MaterialTheme.colorScheme.onBackground)
+ Spacer(modifier = Modifier.height(18.dp))
+ }
+ }
+ }
+ HorizontalPager(count = tabs.size, state = pagerState) { page ->
+ tabs[page].screen(pokemon, pokemonSpecies, service)
+ }
+}
+
+@Composable
+private fun AboutView(
+ pokemon: Pokemon,
+ pokemonSpecies: PokemonSpecies,
+ service: PokemonService
+) {
+ val scrollState = rememberScrollState()
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .padding(all = 24.dp)
+ .verticalScroll(state = scrollState)
+ ) {
+
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun BaseStatsView(
+ pokemon: Pokemon,
+ pokemonSpecies: PokemonSpecies,
+ service: PokemonService
+) {
+ val scrollState = rememberScrollState()
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .padding(all = 24.dp)
+ .verticalScroll(state = scrollState)
+ ) {
+ pokemon.stats.forEach { stat ->
+ val statId = stat.stat.url.getIdFromUrl()
+
+ val fullStat = remember { mutableStateOf(null) }
+
+ LaunchedEffect(key1 = fullStat.value) {
+ fetchPokemonStat(statId, fullStat, service)
+ }
+ fullStat.value?.let {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = it.names.getNameForLanguage() ?: stat.stat.name,
+ modifier = Modifier.fillMaxWidth(.2f)
+ )
+ Text(
+ text = stat.baseStat.toString(),
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.tertiary
+ )
+ LinearProgressIndicator(
+ progress = (stat.baseStat.toFloat() / 255f),
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(5.dp))
+ )
+ }
+ }
+ }
+
+ val doubleDamageRelations = remember { mutableSetOf() }
+ val halfDamageRelations = remember { mutableSetOf() }
+ val noDamageRelations = remember { mutableSetOf() }
+
+ val common = doubleDamageRelations.intersect(halfDamageRelations)
+ val common2 = doubleDamageRelations.intersect(noDamageRelations)
+ doubleDamageRelations.removeAll { it in common || it in common2 }
+ halfDamageRelations.removeAll { it in common || it in common2 }
+ noDamageRelations.removeAll { it in common || it in common2 }
+
+ if (doubleDamageRelations.isNotEmpty()) {
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = stringResource(R.string.type_weaknesses_title),
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold
+ )
+
+ Text(text = stringResource(R.string.type_weaknesses_description))
+ }
+
+ FlowRow(
+ mainAxisSpacing = 8.dp,
+ crossAxisSpacing = 8.dp
+ ) {
+ if (doubleDamageRelations.isEmpty()) {
+ pokemon.types.forEach { t ->
+ val typeId = t.type.url.getIdFromUrl()
+ DataManager.getTypeById(typeId) { type ->
+ type.damageRelations.doubleDamageFrom.forEach {
+ val id = it.url.getIdFromUrl()
+ DataManager.getTypeById(id) { t ->
+ doubleDamageRelations.add(t.names.getNameForLanguage() ?: it.name)
+ }
+ }
+ }
+ }
+ }
+ doubleDamageRelations.forEach { tr ->
+ TypeRelationChip(type = tr)
+ }
+ }
+
+ if (halfDamageRelations.isNotEmpty() || noDamageRelations.isNotEmpty()) {
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = stringResource(R.string.type_defences_title),
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
+ if (halfDamageRelations.isNotEmpty()) {
+ Text(text = stringResource(R.string.type_defences_half_description))
+ }
+
+ FlowRow(
+ mainAxisSpacing = 8.dp,
+ crossAxisSpacing = 8.dp
+ ) {
+ if (halfDamageRelations.isEmpty()) {
+ pokemon.types.forEach { t ->
+ val typeId = t.type.url.getIdFromUrl()
+ DataManager.getTypeById(typeId) { type ->
+ type.damageRelations.halfDamageFrom.forEach {
+ val id = it.url.getIdFromUrl()
+ DataManager.getTypeById(id) { t ->
+ halfDamageRelations.add(t.names.getNameForLanguage() ?: it.name)
+ }
+ }
+ }
+ }
+ }
+ halfDamageRelations.forEach { tr ->
+ TypeRelationChip(type = tr)
+ }
+ }
+
+ if (noDamageRelations.isNotEmpty()) {
+ Text(text = stringResource(R.string.type_defences_no_description))
+ }
+
+ FlowRow(
+ mainAxisSpacing = 8.dp,
+ crossAxisSpacing = 8.dp
+ ) {
+ if (noDamageRelations.isEmpty()) {
+ pokemon.types.forEach { t ->
+ val typeId = t.type.url.getIdFromUrl()
+ DataManager.getTypeById(typeId) { type ->
+ type.damageRelations.noDamageFrom.forEach {
+ val id = it.url.getIdFromUrl()
+ DataManager.getTypeById(id) { t ->
+ noDamageRelations.add(t.names.getNameForLanguage() ?: it.name)
+ }
+ }
+ }
+ }
+ }
+ noDamageRelations.forEach { tr ->
+ TypeRelationChip(type = tr)
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun TypeRelationChip (
+ type: String
+) {
+ val color = ColorUtils.pokeTypeNameToComposeColor(type)
+ Card(
+ modifier = Modifier
+ .clip(RoundedCornerShape(5.dp)),
+ colors = CardDefaults.cardColors(
+ containerColor = color
+ )
+ ) {
+ Text(
+ text = type,
+ color = color.adjustBy(-.4f),
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
+ )
+ }
+}
+
private suspend fun fetchPokemonType(
id: Int,
pokemonType: MutableState,
@@ -192,4 +433,67 @@ private suspend fun fetchPokemonType(
}
}
}
+}
+
+private suspend fun fetchPokemonStat(
+ statId: Int,
+ pokemonStat: MutableState,
+ service: PokemonService
+) {
+ if (pokemonStat.value == null) {
+ service.getPokemonStat(statId).apply {
+ if (isSuccessful) {
+ body()?.let {
+ pokemonStat.value = it
+ AppCache.cachedStats.put(it.id, it)
+ }
+ }
+ }
+ }
+}
+
+private sealed class DetailTab(
+ stringRes: Int,
+ route: String,
+ val screen: @Composable (Pokemon, PokemonSpecies, PokemonService) -> Unit
+): KoinComponent {
+
+ companion object {
+ val Items by lazy { listOf(About, BaseStats, Evolution, Moves) }
+ }
+
+ private val resourceUtils: ResourceUtils by inject()
+
+ val name: String = resourceUtils.getString(stringRes)
+
+ object About: DetailTab(
+ stringRes = R.string.about_tab_title,
+ route = "about_tab",
+ screen = @Composable { pokemon, pokemonSpecies, service ->
+
+ }
+ )
+
+ object BaseStats: DetailTab(
+ stringRes = R.string.base_stats_tab_title,
+ route = "base_stats_tab",
+ screen = @Composable { p, ps, s -> BaseStatsView(p, ps, s) }
+ )
+
+ object Evolution: DetailTab(
+ stringRes = R.string.evolution_tab_title,
+ route = "evolution_tab",
+ screen = @Composable { pokemon, pokemonSpecies, service ->
+
+ }
+ )
+
+ object Moves: DetailTab(
+ stringRes = R.string.moves_tab_title,
+ route = "moves_tab",
+ screen = @Composable { pokemon, pokemonSpecies, service ->
+
+ }
+ )
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/views/MainActivityViews.kt b/app/src/main/java/com/owenlejeune/mydex/ui/views/MainActivityViews.kt
index 97eec54..e7ab338 100644
--- a/app/src/main/java/com/owenlejeune/mydex/ui/views/MainActivityViews.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/views/MainActivityViews.kt
@@ -24,6 +24,7 @@ import com.owenlejeune.mydex.ui.components.MenuItemButton
import com.owenlejeune.mydex.ui.components.SearchBar
import com.owenlejeune.mydex.ui.navigation.DataNavItem
import org.koin.java.KoinJavaComponent
+import kotlin.math.ceil
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -105,14 +106,14 @@ private fun BoxScope.SingleColumnMainContent(appNavController: NavHostController
Spacer(modifier = Modifier.height(32.dp))
val cols = 2
- val rows = DataNavItem.Pages.size/cols
+ val rows = ceil(DataNavItem.Pages.size.toFloat()/cols.toFloat()).toInt()
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
for (i in 0 until rows) {
val first = DataNavItem.Pages[2*i]
- val second = if (2*i+1 <= DataNavItem.Pages.size) {
+ val second = if (2*i+1 < DataNavItem.Pages.size) {
DataNavItem.Pages[2*i+1]
} else {
null
diff --git a/app/src/main/java/com/owenlejeune/mydex/ui/views/PokedexViews.kt b/app/src/main/java/com/owenlejeune/mydex/ui/views/PokedexViews.kt
index a6cad3a..77b32ff 100644
--- a/app/src/main/java/com/owenlejeune/mydex/ui/views/PokedexViews.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/ui/views/PokedexViews.kt
@@ -39,6 +39,7 @@ import com.owenlejeune.mydex.ui.components.PokemonTypeLabel
import com.owenlejeune.mydex.ui.navigation.MainNavItem
import com.owenlejeune.mydex.utils.AppCache
import com.owenlejeune.mydex.utils.ColorUtils
+import com.owenlejeune.mydex.utils.PokeUtils
import org.koin.java.KoinJavaComponent.get
@Composable
@@ -167,8 +168,6 @@ fun PokedexCard(
bgColor.value = ColorUtils.pokeColorToComposeColor(color = species.color.name)
val name = species.names.getNameForLanguage() ?: species.name
- val dexNumber = pokemon.value!!.id.toString().padStart(3, '0')
-
Text(
modifier = Modifier
.align(Alignment.TopStart),
@@ -199,13 +198,13 @@ fun PokedexCard(
Text(
modifier = Modifier
.align(Alignment.TopEnd),
- text = "#${dexNumber}",
+ text = PokeUtils.idToDexNumber(pokemonId),
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
color = Color.Unspecified.copy(alpha = 0.3f)
)
AsyncImage(
- model = pokemon.value?.sprites?.frontDefault,
+ model = PokeUtils.spriteFromId(pokemonId),
contentDescription = name,
modifier = Modifier
.align(Alignment.BottomEnd)
diff --git a/app/src/main/java/com/owenlejeune/mydex/utils/AppCache.kt b/app/src/main/java/com/owenlejeune/mydex/utils/AppCache.kt
index 41fa149..aee7e98 100644
--- a/app/src/main/java/com/owenlejeune/mydex/utils/AppCache.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/utils/AppCache.kt
@@ -3,6 +3,7 @@ package com.owenlejeune.mydex.utils
import android.util.SparseArray
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
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
object AppCache {
@@ -10,5 +11,6 @@ object AppCache {
var cachedSpecies = SparseArray()
var cachedPokemon = SparseArray()
var cachedTypes = SparseArray()
+ var cachedStats = SparseArray()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/utils/ColorUtils.kt b/app/src/main/java/com/owenlejeune/mydex/utils/ColorUtils.kt
index a1cf84e..d13a521 100644
--- a/app/src/main/java/com/owenlejeune/mydex/utils/ColorUtils.kt
+++ b/app/src/main/java/com/owenlejeune/mydex/utils/ColorUtils.kt
@@ -9,7 +9,7 @@ object ColorUtils {
@Composable
fun pokeColorToComposeColor(color: String): Color {
- return when (color) {
+ return when (color.lowercase()) {
"green" -> PokeGreen
"red" -> PokeRed
"blue" -> PokeBlue
@@ -24,4 +24,29 @@ object ColorUtils {
}
}
+ fun pokeTypeNameToComposeColor(type: String): Color {
+ return when (type.lowercase()) {
+ "bug" -> BugType
+ "dark" -> DarkType
+ "dragon" -> DragonType
+ "electric" -> ElectricType
+ "fairy" -> FairyType
+ "fighting" -> FightingType
+ "fire" -> FireType
+ "flying" -> FlyingType
+ "ghost" -> GhostType
+ "grass" -> GrassType
+ "ground" -> GroundType
+ "ice" -> IceType
+ "normal" -> NormalType
+ "poison" -> PoisonType
+ "psychic" -> PsychicType
+ "rock" -> RockType
+ "shadow" -> ShadowType
+ "steel" -> SteelType
+ "water" -> WaterType
+ else -> UnknownType
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/utils/DataManager.kt b/app/src/main/java/com/owenlejeune/mydex/utils/DataManager.kt
new file mode 100644
index 0000000..19c997b
--- /dev/null
+++ b/app/src/main/java/com/owenlejeune/mydex/utils/DataManager.kt
@@ -0,0 +1,95 @@
+package com.owenlejeune.mydex.utils
+
+import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService
+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
+import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import retrofit2.Response
+
+object DataManager: KoinComponent {
+
+ private val service: PokemonService by inject()
+
+ fun getPokemonById(id: Int, callback: (Pokemon) -> Unit) {
+ AppCache.cachedPokemon[id]?.let(callback) ?: run {
+ CoroutineScope(Dispatchers.IO).launch {
+ service.getPokemon(id).apply {
+ if (isSuccessful) {
+ body()?.let {
+ AppCache.cachedPokemon.put(it.id, it)
+ callback(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun getPokemonSpeciesById(id: Int, callback: (PokemonSpecies) -> Unit) {
+ AppCache.cachedSpecies[id]?.let(callback) ?: run {
+ CoroutineScope(Dispatchers.IO).launch {
+ service.getPokemonSpecies(id).apply {
+ if (isSuccessful) {
+ body()?.let {
+ AppCache.cachedSpecies.put(it.id, it)
+ callback(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun getTypeById(id: Int, callback: (PokemonType) -> Unit) {
+ getById(
+ id = id,
+ callback = callback,
+ retriever = { AppCache.cachedTypes[it] },
+ fetcher = { service.getPokemonType(it) },
+ storer = { AppCache.cachedTypes.put(it.id, it) }
+ )
+ }
+
+ fun getStatById(id: Int, callback: (PokemonStat) -> Unit) {
+ AppCache.cachedStats[id]?.let(callback) ?: run {
+ CoroutineScope(Dispatchers.IO).launch {
+ service.getPokemonStat(id).apply {
+ if (isSuccessful) {
+ body()?.let {
+ AppCache.cachedStats.put(it.id, it)
+ callback(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun getById(
+ id: Int,
+ callback: (T) -> Unit,
+ retriever: (Int) -> T?,
+ fetcher: suspend (Int) -> Response,
+ storer: (T) -> Unit
+ ) {
+ retriever(id)?.let(callback) ?: run {
+ CoroutineScope(Dispatchers.IO).launch {
+ fetcher(id).apply {
+ if (isSuccessful) {
+ body()?.let {
+ storer(it)
+ callback(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/owenlejeune/mydex/utils/PokeUtils.kt b/app/src/main/java/com/owenlejeune/mydex/utils/PokeUtils.kt
new file mode 100644
index 0000000..01b3791
--- /dev/null
+++ b/app/src/main/java/com/owenlejeune/mydex/utils/PokeUtils.kt
@@ -0,0 +1,49 @@
+package com.owenlejeune.mydex.utils
+
+import kotlin.math.ceil
+import kotlin.math.floor
+
+object PokeUtils {
+
+ private val HEC_TO_LBS = 0.22f
+ private val HEC_TO_KG = 0.1f
+ private val DEC_TO_CM = 10
+ private val CM_TO_IN = 2.54
+ private val IN_TO_FT = 12
+
+ fun idToDexNumber(id: Int, includeNumberSign: Boolean = true): String {
+ val padded = id.toString().padStart(3, '0')
+ return if (includeNumberSign) {
+ "#$padded"
+ } else {
+ padded
+ }
+ }
+
+ fun spriteFromId(id: Int): String {
+ val paddedNumber = idToDexNumber(id, false)
+ return "https://assets.pokemon.com/assets/cms2/img/pokedex/full/${paddedNumber}.png"
+ }
+
+ fun weightInPounds(weight: Int): Float {
+ return weight * HEC_TO_LBS
+ }
+
+ fun weightInKg(weight: Int): Float {
+ return weight * HEC_TO_KG
+ }
+
+ fun heightToCm(height: Int): Int {
+ return height * DEC_TO_CM
+ }
+
+ fun heightToFtIn(height: Int): Pair {
+ val heightCm = height * DEC_TO_CM
+
+ val feet = floor((height / CM_TO_IN) / IN_TO_FT).toInt()
+ val inches = ceil((height / CM_TO_IN) - (feet * IN_TO_FT)).toInt()
+
+ return Pair(feet, inches)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 53bbf3d..4e04923 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -12,4 +12,14 @@
Items
Locations
Type charts
+ About
+ Base Stats
+ Evolution
+ Moves
+ Settings
+ Type weaknesses
+ Pokémon of these types deal double damage to this Pokémon
+ Type defences
+ Pokémon of these types deal half damage to this Pokémon
+ Pokémon of these types deal no damage to this Pokémon
\ No newline at end of file