pokedex view

This commit is contained in:
Owen LeJeune
2022-09-16 09:59:43 -04:00
parent edd4559971
commit b1e2902581
18 changed files with 328 additions and 121 deletions

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-09-15T01:01:05.349795Z" />
</component>
</project>

View File

@@ -2,6 +2,8 @@ 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.misc.PaginatedResponse
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon 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.PokemonType
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
@@ -15,4 +17,10 @@ interface PokemonApi {
@GET("pokemon/") @GET("pokemon/")
suspend fun getPaginatedPokemon(@Query("offset") offset: Int, @Query("limit") limit: Int): Response<PaginatedResponse> suspend fun getPaginatedPokemon(@Query("offset") offset: Int, @Query("limit") limit: Int): Response<PaginatedResponse>
@GET("pokemon-species/{id}")
suspend fun getPokemonSpecies(@Path("id") id: Int): Response<PokemonSpecies>
@GET("type/{id}")
suspend fun getPokemonType(@Path("id") id: Int): Response<PokemonType>
} }

View File

@@ -3,6 +3,8 @@ package com.owenlejeune.mydex.api.pokeapi.v2
import android.util.Log import android.util.Log
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.PaginatedResponse 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.Pokemon
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonSpecies
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.PokemonType
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import retrofit2.Response import retrofit2.Response
@@ -10,7 +12,7 @@ import retrofit2.Response
class PokemonService: KoinComponent { class PokemonService: KoinComponent {
companion object { companion object {
private const val DEFAULT_LIMIT = 20 private const val DEFAULT_LIMIT = 60
val TAG = PokemonService::class.java.simpleName val TAG = PokemonService::class.java.simpleName
} }
@@ -23,7 +25,7 @@ class PokemonService: KoinComponent {
suspend fun getPaginatedPokemon(page: Int = 1): Response<PaginatedResponse> { suspend fun getPaginatedPokemon(page: Int = 1): Response<PaginatedResponse> {
val limit = DEFAULT_LIMIT val limit = DEFAULT_LIMIT
val offset = DEFAULT_LIMIT * page val offset = DEFAULT_LIMIT * (page-1)
Log.d(TAG, "Paginated: page=$page, limit=$limit, offset=$offset") Log.d(TAG, "Paginated: page=$page, limit=$limit, offset=$offset")
@@ -32,4 +34,12 @@ class PokemonService: KoinComponent {
} }
} }
suspend fun getPokemonSpecies(id: Int): Response<PokemonSpecies> {
return service.getPokemonSpecies(id)
}
suspend fun getPokemonType(id: Int): Response<PokemonType> {
return service.getPokemonType(id)
}
} }

View File

@@ -4,6 +4,5 @@ import com.google.gson.annotations.SerializedName
class NameAndLanguage( class NameAndLanguage(
@SerializedName("name") val name: String, @SerializedName("name") val name: String,
@SerializedName("language.name") val language: String, @SerializedName("language") val language: NameAndUrl
@SerializedName("language.url") val languageUrl: String
) )

View File

@@ -5,11 +5,13 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.NameAndUrl import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.NameAndUrl
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.Pokemon
import com.owenlejeune.mydex.utils.AppCache
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
class PokemonPagingSource: PagingSource<Int, Pokemon>(), KoinComponent { class PokemonPagingSource: PagingSource<Int, NameAndUrl>(), KoinComponent {
companion object { companion object {
private val TAG = PokemonPagingSource::class.java.simpleName private val TAG = PokemonPagingSource::class.java.simpleName
@@ -17,21 +19,22 @@ class PokemonPagingSource: PagingSource<Int, Pokemon>(), KoinComponent {
private val pokemonService: PokemonService by inject() private val pokemonService: PokemonService by inject()
override fun getRefreshKey(state: PagingState<Int, Pokemon>): Int? { override fun getRefreshKey(state: PagingState<Int, NameAndUrl>): Int? {
return state.anchorPosition return state.anchorPosition
} }
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Pokemon> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, NameAndUrl> {
return try { return try {
val nextPage = params.key ?: 1 val nextPage = params.key ?: 1
val response = pokemonService.getPokemon(nextPage) val response = pokemonService.getPaginatedPokemon(nextPage)
if (response.isSuccessful) { if (response.isSuccessful) {
val responseBody = response.body()!! val responseBody = response.body()
// Log.d(TAG, "${results.map { it.name }}") val results = responseBody?.results ?: emptyList()
Log.d(TAG, "${results.map { it.name }}")
LoadResult.Page( LoadResult.Page(
data = listOf(responseBody), data = results,
prevKey = if (nextPage == 1) null else nextPage - 1, prevKey = if (nextPage == 1) null else nextPage - 1,
nextKey = if (responseBody == null) null else nextPage + 1 nextKey = if (results.isEmpty()) null else nextPage + 1
) )
} else { } else {
LoadResult.Invalid() LoadResult.Invalid()
@@ -41,4 +44,23 @@ class PokemonPagingSource: PagingSource<Int, Pokemon>(), KoinComponent {
} }
} }
// fun t() {
// val nextPage = params.key ?: 1
// var pokemon = AppCache.cachedPokemon[nextPage]
// if (pokemon == null) {
// val response = pokemonService.getPokemon(nextPage)
// if (response.isSuccessful) {
// pokemon = response.body()!!
// } else {
// return LoadResult.Invalid()
// }
// }
// Log.d(TAG, pokemon.name)
// LoadResult.Page(
// data = listOf(pokemon),
// prevKey = if (nextPage == 1) null else nextPage - 1,
// nextKey = nextPage + 1
// )
// }
} }

View File

@@ -12,7 +12,7 @@ import com.owenlejeune.mydex.api.pokeapi.v2.paging.PokemonPagingSource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class PokemonViewModel: ViewModel() { class PokemonViewModel: ViewModel() {
val pokemon: Flow<PagingData<Pokemon>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) { val pokemon: Flow<PagingData<NameAndUrl>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) {
PokemonPagingSource() PokemonPagingSource()
}.flow.cachedIn(viewModelScope) }.flow.cachedIn(viewModelScope)
} }

View File

@@ -19,15 +19,6 @@ fun <T: Any> LazyGridScope.lazyPagingItems(
} }
} }
fun <T: Any> LazyGridScope.listItems(
items: List<T>,
itemContent: @Composable (value: T) -> Unit
) {
items(items.size) { index ->
itemContent(items[index])
}
}
fun LazyGridScope.header( fun LazyGridScope.header(
content: @Composable LazyGridItemScope.() -> Unit content: @Composable LazyGridItemScope.() -> Unit
) { ) {
@@ -38,31 +29,3 @@ fun LazyGridScope.header(
content = content content = content
) )
} }
fun <T: Any> LazyListScope.listItems(
items: Collection<T>,
itemContent: @Composable (value: T) -> Unit
) {
items(items.size) { index ->
itemContent(items.elementAt(index))
}
}
fun <T: Any?> LazyListScope.listItems(
items: List<T?>,
key: (T?) -> Any,
itemContent: @Composable (value: T?) -> Unit
) {
items(items.size, key = { key(items[it]) }) { index ->
itemContent(items[index])
}
}
fun <T: Any> LazyListScope.lazyPagingItems(
lazyPagingItems: LazyPagingItems<T>,
itemContent: @Composable LazyItemScope.(value: T?) -> Unit
) {
items(lazyPagingItems.itemCount) { index ->
itemContent(lazyPagingItems[index])
}
}

View File

@@ -1,5 +1,5 @@
package com.owenlejeune.mydex.extensions package com.owenlejeune.mydex.extensions
fun String.charAtFromEnd(index: Int): Char { fun String.getIdFromUrl(): Int {
return get(length-1-index) return split("/").find { it.toIntOrNull() != null }?.toInt() ?: -1
} }

View File

@@ -7,9 +7,11 @@ import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.kieronquinn.monetcompat.app.MonetCompatActivity import com.kieronquinn.monetcompat.app.MonetCompatActivity
import com.owenlejeune.mydex.preferences.AppPreferences import com.owenlejeune.mydex.preferences.AppPreferences
import com.owenlejeune.mydex.ui.navigation.DataNavItem import com.owenlejeune.mydex.ui.navigation.DataNavItem
@@ -17,6 +19,8 @@ import com.owenlejeune.mydex.ui.navigation.MainNavItem
import com.owenlejeune.mydex.ui.theme.MYDexTheme import com.owenlejeune.mydex.ui.theme.MYDexTheme
import com.owenlejeune.mydex.ui.views.AppScaffold import com.owenlejeune.mydex.ui.views.AppScaffold
import com.owenlejeune.mydex.ui.views.PokedexView import com.owenlejeune.mydex.ui.views.PokedexView
import com.owenlejeune.mydex.ui.views.PokemonDetailView
import com.owenlejeune.mydex.utils.AppCache
import org.koin.java.KoinJavaComponent import org.koin.java.KoinJavaComponent
class MainActivity : MonetCompatActivity() { class MainActivity : MonetCompatActivity() {
@@ -49,6 +53,17 @@ class MainActivity : MonetCompatActivity() {
composable(DataNavItem.Pokedex.route) { composable(DataNavItem.Pokedex.route) {
PokedexView(appNavController = appNavController) PokedexView(appNavController = appNavController)
} }
composable(
MainNavItem.PokemonDetailView.route.plus("/{id}"),
arguments = listOf(
navArgument("id") { type = NavType.IntType }
)
) {
val id = it.arguments?.getInt("id")
id?.let {
PokemonDetailView(pokemonId = it)
}
}
} }
} }
} }

View File

@@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.modifier.modifierLocalConsumer import androidx.compose.ui.modifier.modifierLocalConsumer
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage import coil.compose.AsyncImage
@@ -192,3 +193,23 @@ fun MenuItemButton(
} }
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PokemonTypeLabel(
type: String
) {
Card(
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = Color(0x38FFFFFF))
) {
Text(
text = type,
modifier = Modifier
.padding(vertical = 3.dp, horizontal = 8.dp),
textAlign = TextAlign.Center,
fontSize = 12.sp,
color = Color.White
)
}
}

View File

@@ -22,7 +22,7 @@ sealed class DataNavItem(
val title = resourceUtils.getString(titleRes) val title = resourceUtils.getString(titleRes)
object Pokedex: DataNavItem("pokedex_route", PokeTeal, R.string.pokedex_nav_title) object Pokedex: DataNavItem("pokedex_route", PokeGreen, R.string.pokedex_nav_title)
object Moves: DataNavItem("moves_route", PokeRed, R.string.moves_nav_title) object Moves: DataNavItem("moves_route", PokeRed, R.string.moves_nav_title)
object Abilities: DataNavItem("abilities_route", PokeLightBlue, R.string.abilities_nav_title) object Abilities: DataNavItem("abilities_route", PokeLightBlue, R.string.abilities_nav_title)
object Items: DataNavItem("items_route", PokeYellow, R.string.items_nav_title) object Items: DataNavItem("items_route", PokeYellow, R.string.items_nav_title)

View File

@@ -7,5 +7,6 @@ sealed class MainNavItem(val route: String) {
} }
object MainView: MainNavItem("main_route") object MainView: MainNavItem("main_route")
object PokemonDetailView: MainNavItem("pokemon_route")
} }

View File

@@ -3,15 +3,17 @@ package com.owenlejeune.mydex.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val PokeBlack = Color(0xFF303943) val PokeBlack = Color(0xFF303943)
val PokeGrey = Color(0xFF434C57)
val PokeWhite = Color(0xFF898A8B)
val PokeBlue = Color(0xFF429BED) val PokeBlue = Color(0xFF429BED)
val PokeBrown = Color(0xFFB1736C) val PokeBrown = Color(0xFFB1736C)
val PokeLightBlue = Color(0xFF58ABF6) val PokeLightBlue = Color(0xFF58ABF6)
val PokeLightBrown = Color(0xFFCA8179) val PokeLightBrown = Color(0xFFCA8179)
val PokeLightPurple = Color(0xFF9F5BBA) val PokeLightPurple = Color(0xFF9F5BBA)
val PokeLightRed = Color(0xFFF7786B) val PokeLightRed = Color(0xFFF7786B)
val PokeLightTeal = Color(0xFF2CDAB1) val PokeLightGreen = Color(0xFF2CDAB1)
val PokeLightYellow = Color(0xFFFFCE4B) val PokeLightYellow = Color(0xFFFFCE4B)
val PokePurple = Color(0xFF7C538C) val PokePurple = Color(0xFF7C538C)
val PokeRed = Color(0xFFFA6555) val PokeRed = Color(0xFFFA6555)
val PokeTeal = Color(0xFF4FC1A6) val PokeGreen = Color(0xFF4FC1A6)
val PokeYellow = Color(0xFFF6C747) val PokeYellow = Color(0xFFF6C747)

View File

@@ -0,0 +1,13 @@
package com.owenlejeune.mydex.ui.views
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import com.owenlejeune.mydex.utils.AppCache
@Composable
fun PokemonDetailView(
pokemonId: Int
) {
val pokemon = AppCache.cachedSpecies[pokemonId]
Text(text = pokemon.name)
}

View File

@@ -4,23 +4,21 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -30,11 +28,16 @@ import com.owenlejeune.mydex.R
import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService import com.owenlejeune.mydex.api.pokeapi.v2.PokemonService
import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.NameAndUrl import com.owenlejeune.mydex.api.pokeapi.v2.model.misc.NameAndUrl
import com.owenlejeune.mydex.api.pokeapi.v2.model.pokemon.Pokemon 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.PokemonType
import com.owenlejeune.mydex.api.pokeapi.v2.viewmodel.PokemonViewModel import com.owenlejeune.mydex.api.pokeapi.v2.viewmodel.PokemonViewModel
import com.owenlejeune.mydex.extensions.charAtFromEnd import com.owenlejeune.mydex.extensions.getIdFromUrl
import com.owenlejeune.mydex.extensions.header import com.owenlejeune.mydex.extensions.header
import com.owenlejeune.mydex.extensions.lazyPagingItems import com.owenlejeune.mydex.extensions.lazyPagingItems
import kotlinx.coroutines.launch 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 org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
@Composable @Composable
@@ -57,6 +60,39 @@ fun PokedexView(
colorFilter = ColorFilter.tint(tint) colorFilter = ColorFilter.tint(tint)
) )
val lazyPokemon = PokemonViewModel().pokemon.collectAsLazyPagingItems()
LazyVerticalGrid(
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(start = 36.dp, end = 36.dp)
.fillMaxSize()
) {
header {
Column {
Spacer(modifier = Modifier.height(160.dp))
Text(
text = stringResource(id = R.string.pokedex_nav_title),
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
color = MaterialTheme.colorScheme.onBackground
),
modifier = Modifier.padding(bottom = 50.dp)
)
}
}
lazyPagingItems(lazyPokemon) { item ->
item?.let {
PokedexCard(appNavController = appNavController, nameAndUrl = item)
}
}
}
SmallTopAppBar( SmallTopAppBar(
modifier = Modifier modifier = Modifier
.statusBarsPadding() .statusBarsPadding()
@@ -72,54 +108,45 @@ fun PokedexView(
containerColor = Color.Transparent.copy(alpha = 0.4f) containerColor = Color.Transparent.copy(alpha = 0.4f)
) )
) )
val scrollState = rememberScrollState()
val lazyPokemon = PokemonViewModel().pokemon.collectAsLazyPagingItems()
LazyVerticalGrid(
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(start = 36.dp, end = 36.dp, top = 160.dp)
.fillMaxSize()
) {
header {
Text(
text = stringResource(id = R.string.main_screen_title),
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
color = MaterialTheme.colorScheme.onBackground
),
modifier = Modifier.padding(bottom = 50.dp)
)
}
lazyPagingItems(lazyPokemon) { item ->
item?.let {
PokedexCard(pokemon = item)
}
}
}
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PokedexCard( fun PokedexCard(
pokemon: Pokemon appNavController: NavController,
nameAndUrl: NameAndUrl,
service: PokemonService = get(PokemonService::class.java)
) { ) {
val pokemonId = nameAndUrl.url.getIdFromUrl()
val pokemon = remember { mutableStateOf<Pokemon?>(AppCache.cachedPokemon[pokemonId]) }
LaunchedEffect(key1 = pokemon.value) {
fetchPokemon(nameAndUrl, service, pokemon)
}
val species = remember { mutableStateOf<PokemonSpecies?>(AppCache.cachedSpecies[pokemonId]) }
LaunchedEffect(key1 = pokemon.value) {
fetchPokemonSpecies(pokemon, species, service)
}
val defBg = ColorUtils.pokeColorToComposeColor(color = "")
val bgColor = remember { mutableStateOf(defBg) }
Card( Card(
modifier = Modifier modifier = Modifier
.height(80.dp) .height(160.dp)
.fillMaxWidth(), .fillMaxWidth(),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer) colors = CardDefaults.cardColors(containerColor = bgColor.value),
onClick = {
pokemon.value?.let {
appNavController.navigate("${MainNavItem.PokemonDetailView.route}/${it.id}")
}
}
) { ) {
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier
.fillMaxSize()
) { ) {
AsyncImage( AsyncImage(
model = R.drawable.pokeball_s, model = R.drawable.pokeball_s,
@@ -131,13 +158,115 @@ fun PokedexCard(
colorFilter = ColorFilter.tint(Color.White.copy(alpha = 0.15f)) colorFilter = ColorFilter.tint(Color.White.copy(alpha = 0.15f))
) )
Text( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.CenterStart) .fillMaxSize()
.padding(start = 16.dp), .padding(all = 12.dp)
text = pokemon.name, ) {
style = MaterialTheme.typography.bodyLarge.copy(color = Color.White) species.value?.let { species ->
) bgColor.value = ColorUtils.pokeColorToComposeColor(color = species.color.name)
val locale = Locale.current.language
val name = species.names.find { it.language.name == locale }?.name ?: species.name
val dexNumber = pokemon.value!!.id.toString().padStart(3, '0')
Text(
modifier = Modifier
.align(Alignment.TopStart),
text = name,
style = MaterialTheme.typography.bodyLarge.copy(color = Color.White, fontWeight = FontWeight.Bold)
)
Column(
modifier = Modifier
.align(Alignment.TopStart)
.padding(top = 35.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
pokemon.value?.types?.forEach { type ->
val id = type.type.url.getIdFromUrl()
val pokemonType = remember { mutableStateOf<PokemonType?>(AppCache.cachedTypes[id]) }
LaunchedEffect(key1 = pokemonType.value) {
fetchPokemonType(id, pokemonType, service)
}
pokemonType.value?.let { t ->
val typeName = t.names.find { it.language.name == locale }?.name ?: ""//type.type.name
PokemonTypeLabel(type = typeName)
}
}
}
Text(
modifier = Modifier
.align(Alignment.TopEnd),
text = "#${dexNumber}",
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
color = Color.Unspecified.copy(alpha = 0.3f)
)
AsyncImage(
model = pokemon.value?.sprites?.frontDefault,
contentDescription = name,
modifier = Modifier
.align(Alignment.BottomEnd)
.size(70.dp),
contentScale = ContentScale.FillBounds
)
}
}
}
}
}
private suspend fun fetchPokemon(
nameAndUrl: NameAndUrl,
service: PokemonService,
pokemon: MutableState<Pokemon?>
) {
if (pokemon.value == null) {
val id = nameAndUrl.url.getIdFromUrl()
service.getPokemon(id).apply {
if (isSuccessful) {
body()?.let {
pokemon.value = it
AppCache.cachedPokemon.put(it.id, it)
}
}
}
}
}
private suspend fun fetchPokemonSpecies(
pokemon: MutableState<Pokemon?>,
species: MutableState<PokemonSpecies?>,
service: PokemonService
) {
if (species.value == null && pokemon.value != null) {
service.getPokemonSpecies(pokemon.value!!.id).apply {
if (isSuccessful) {
body()?.let {
species.value = it
AppCache.cachedSpecies.put(it.id, it)
}
}
}
}
}
private suspend fun fetchPokemonType(
id: Int,
pokemonType: MutableState<PokemonType?>,
service: PokemonService
) {
if (pokemonType.value == null) {
service.getPokemonType(id).apply {
if (isSuccessful) {
body()?.let {
pokemonType.value = it
AppCache.cachedTypes.put(it.id, it)
}
}
} }
} }
} }

View File

@@ -0,0 +1,14 @@
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.PokemonType
object AppCache {
var cachedSpecies = SparseArray<PokemonSpecies>()
var cachedPokemon = SparseArray<Pokemon>()
var cachedTypes = SparseArray<PokemonType>()
}

View File

@@ -0,0 +1,27 @@
package com.owenlejeune.mydex.utils
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.owenlejeune.mydex.ui.theme.*
object ColorUtils {
@Composable
fun pokeColorToComposeColor(color: String): Color {
return when (color) {
"green" -> PokeGreen
"red" -> PokeRed
"blue" -> PokeBlue
"black" -> PokeBlack
"brown" -> PokeBrown
"purple" -> PokePurple
"pink" -> PokeLightBrown
"grey" -> PokeGrey
"white" -> PokeWhite
"yellow" -> PokeYellow
else -> MaterialTheme.colorScheme.secondaryContainer
}
}
}

View File

@@ -6,7 +6,7 @@
<string name="main_search_placeholder">Search Pokémon, Move, Ability, etc.</string> <string name="main_search_placeholder">Search Pokémon, Move, Ability, etc.</string>
<!-- nav titles --> <!-- nav titles -->
<string name="pokedex_nav_title">Pokedex</string> <string name="pokedex_nav_title">Pokédex</string>
<string name="moves_nav_title">Moves</string> <string name="moves_nav_title">Moves</string>
<string name="abilities_nav_title">Abilities</string> <string name="abilities_nav_title">Abilities</string>
<string name="items_nav_title">Items</string> <string name="items_nav_title">Items</string>