Add Navigation via Voyager

This commit is contained in:
FirephoenixX02 2024-04-12 10:41:11 +02:00
parent 699a60dffe
commit 2368d7b829
5 changed files with 213 additions and 103 deletions

View file

@ -35,6 +35,9 @@ kotlin {
implementation(libs.kamel.image)
implementation(libs.ktor.client.okhttp)
implementation(libs.json)
implementation(libs.voyager.navigator)
implementation(libs.voyager.tab.navigator)
implementation(libs.voyager.transitions)
}
desktopMain.dependencies {
implementation(compose.desktop.currentOs)

View file

@ -1,51 +1,48 @@
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.kamel.image.KamelImage
import io.kamel.image.asyncPainterResource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.FadeTransition
import cafe.adriel.voyager.transitions.SlideTransition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.json.JSONObject
import org.jetbrains.compose.resources.ExperimentalResourceApi
import pokedex.composeapp.generated.resources.Res
import pokedex.composeapp.generated.resources.*;
import pokedex.composeapp.generated.resources.bug
import pokedex.composeapp.generated.resources.dark
import pokedex.composeapp.generated.resources.dragon
import pokedex.composeapp.generated.resources.electric
import pokedex.composeapp.generated.resources.fairy
import pokedex.composeapp.generated.resources.fighting
import pokedex.composeapp.generated.resources.fire
import pokedex.composeapp.generated.resources.flying
import pokedex.composeapp.generated.resources.ghost
import pokedex.composeapp.generated.resources.grass
import pokedex.composeapp.generated.resources.ground
import pokedex.composeapp.generated.resources.ice
import pokedex.composeapp.generated.resources.normal
import pokedex.composeapp.generated.resources.poison
import pokedex.composeapp.generated.resources.psychic
import pokedex.composeapp.generated.resources.rock
import pokedex.composeapp.generated.resources.steel
import pokedex.composeapp.generated.resources.water
import java.net.URL
import java.util.Locale
var pokemap = ArrayList<Pokemon>()
val apiString = "https://pokeapi.co/api/v2/pokemon/"
@OptIn(ExperimentalResourceApi::class)
@Composable
@Preview
fun App() {
var pokemap = ArrayList<Pokemon>()
val apiString = "https://pokeapi.co/api/v2/pokemon/"
var dataLoaded by remember { mutableStateOf(false) }
val orange = Color(0xFFffa500)
val pokemonTypeDrawableMap = hashMapOf(
"normal" to Res.drawable.normal,
@ -70,6 +67,10 @@ fun App() {
MaterialTheme {
if (!dataLoaded) {
Navigator(screen = LoadingScreen()) {
navigator -> FadeTransition(navigator)
}
// Load data asynchronously
LaunchedEffect(Unit) {
pokemap = withContext(Dispatchers.IO) {
@ -83,88 +84,23 @@ fun App() {
dataLoaded = true
}
//Show Loading Circle
Box(modifier = Modifier.background(color = orange).fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
modifier = Modifier.size(128.dp),
color = Color.White
)
Text(
"Fetching data from API...",
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier.height(200.dp)
)
}
}
} else {
println("Pokemon should show up any second!")
//Display Pokemon Grid
Box(modifier = Modifier.background(color = orange).fillMaxSize()) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 256.dp),
) {
items(pokemap.size) { index ->
Box(modifier = Modifier.size(256.dp).padding(5.dp)) {
Canvas(modifier = Modifier.matchParentSize()) {
drawRoundRect(
color = Color.White,
topLeft = Offset(0f, 0f),
size = Size(size.width, size.height),
cornerRadius = CornerRadius(20f, 20f),
)
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
KamelImage(
resource = asyncPainterResource(pokemap[index].imageUrl),
modifier = Modifier.size(128.dp),
contentDescription = "",
alignment = Alignment.Center
)
}
Column(
modifier = Modifier.fillMaxSize().padding(vertical = 8.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally
) {
val type: String = pokemap[index].type
Box(modifier = Modifier.size(32.dp)) {
Image(
org.jetbrains.compose.resources.painterResource(
pokemonTypeDrawableMap[type]!!
), contentDescription = null
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = pokemap[index].name,
textAlign = TextAlign.Center,
color = Color.Black,
fontWeight = FontWeight.Bold
)
}
}
}
}
Navigator(screen = HomeScreen(getPokeMap(), pokemonTypeDrawableMap)) {
navigator -> SlideTransition(navigator)
}
}
}
}
// Function to load Pokemon data asynchronously
// Function to load Pokemon data asynchronously, leave suspend even if IDE complains
suspend fun loadPokemonData(apiString: String, startId: Int, endId: Int): List<Pokemon> {
val pokemap = ArrayList<Pokemon>()
for (i in startId..endId) {
val json = JSONObject(URL(apiString + i).readText());
val sprites = json.optJSONObject("sprites")
val types = json.getJSONArray("types")
val firstType = types.optJSONObject(0)
val type: String = firstType.optJSONObject("type").optString("name")
val type: String = json.getJSONArray("types").optJSONObject(0).optJSONObject("type").optString("name")
val name: String = json.optString("name")
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
@ -176,3 +112,7 @@ suspend fun loadPokemonData(apiString: String, startId: Int, endId: Int): List<P
}
return pokemap
}
fun getPokeMap(): ArrayList<Pokemon> {
return pokemap
}

View file

@ -0,0 +1,127 @@
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PaintingStyle.Companion.Stroke
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.DrawStyle
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import io.kamel.image.KamelImage
import io.kamel.image.asyncPainterResource
import org.json.JSONObject
import java.net.URL
import java.util.Locale
data class DetailScreen(
val pokemon: Pokemon
) : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val orange = Color(0xFFffa500)
val apiString = "https://pokeapi.co/api/v2/pokemon/"
val json = JSONObject(URL(apiString + pokemon.name.lowercase()).readText());
val type: String =
json.getJSONArray("types").optJSONObject(0).optJSONObject("type").optString("name")
val name: String = json.optString("name")
.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
val xp: String = json.optString("base_experience")
val height: String = json.optString("height")
val weight: String = json.optString("weight")
Box(
modifier = Modifier.background(color = orange).fillMaxSize(),
contentAlignment = Alignment.Center
) {
//Back Button
Button(onClick = { navigator.push(navigator.lastItem) }) {
Text("Back")
}
//Details
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
KamelImage(
resource = asyncPainterResource(pokemon.imageUrl),
modifier = Modifier.size(256.dp),
contentDescription = "",
alignment = Alignment.Center
)
}
Box(
modifier = Modifier.size(256.dp).padding(5.dp), contentAlignment = Alignment.Center
) {
Canvas(modifier = Modifier.matchParentSize()) {
drawRoundRect(
color = Color.White,
topLeft = Offset(0f, 0f),
size = Size(size.width, size.height),
cornerRadius = CornerRadius(20f, 20f),
)
drawRoundRect(
color = Color.Black,
topLeft = Offset(0f, 0f),
size = Size(size.width, size.height),
cornerRadius = CornerRadius(20f, 20f),
style = Stroke(1.dp.toPx())
)
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(16.dp)
) {
Text(
"Name: $name",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
Text(
"Base XP: $xp",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
Text(
"Height: $height",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
Text(
"Weight: ${weight}kg",
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
)
}
}
}
}
}

View file

@ -0,0 +1,36 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
class LoadingScreen : Screen {
@Composable
override fun Content() {
val orange = Color(0xFFffa500)
Box(modifier = Modifier.background(color = orange).fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(
modifier = Modifier.size(128.dp),
color = Color.White
)
Text(
"Fetching data from API...",
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = Modifier.height(200.dp)
)
}
}
}
}

View file

@ -22,6 +22,7 @@ kotlin = "1.9.22"
lifecycleProcess = "2.7.0"
media3Effect = "1.3.1"
ktor = "2.3.10"
voyagerNavigator = "1.0.0"
[libraries]
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycleProcess" }
@ -42,6 +43,9 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
androidx-media3-effect = { group = "androidx.media3", name = "media3-effect", version.ref = "media3Effect" }
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyagerNavigator" }
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyagerNavigator" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyagerNavigator" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }