diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 5d17f78..0e712e1 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -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) diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index e2c814a..bf4ece8 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -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() +val apiString = "https://pokeapi.co/api/v2/pokemon/" + @OptIn(ExperimentalResourceApi::class) @Composable @Preview fun App() { - var pokemap = ArrayList() - 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 { val pokemap = ArrayList() 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

{ + return pokemap +} diff --git a/composeApp/src/commonMain/kotlin/DetailScreen.kt b/composeApp/src/commonMain/kotlin/DetailScreen.kt new file mode 100644 index 0000000..1d4e321 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/DetailScreen.kt @@ -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 + ) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/LoadingScreen.kt b/composeApp/src/commonMain/kotlin/LoadingScreen.kt new file mode 100644 index 0000000..dab24c0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/LoadingScreen.kt @@ -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) + ) + } + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6a4e346..f1abca0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }