diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bc67530..488c0b7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -60,6 +60,10 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.composableSheep) + implementation(libs.retro) + implementation(libs.retro.scalar) + implementation(libs.coil) + implementation(libs.coil.network) // Patch annotations + processor implementation(project(":patch-annotations")) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 71df8a0..54335ce 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + +} + +@Patch("PokePatch") +@Preview +@Composable +fun PokePatch( + shouldCapture: Boolean = false, + onBitmap: (ImageBitmap) -> Unit = {}, +) { + var mons by remember { mutableStateOf(listOf()) } + var mon by remember { mutableStateOf(null) } + var error by remember { mutableStateOf(null) } + var isLoading by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + scope.launch(Dispatchers.IO) { + isLoading = true + try { + val api = Retrofit + .Builder() + .baseUrl("https://pokeapi.co/api/v2/") + .addConverterFactory(ScalarsConverterFactory.create()) + .build() + .create() + + val response = api.mons() + if (response.isSuccessful && response.body() != null) { + val body = response.body()!! + mons = JSONObject(body).getJSONArray("results").toMons() + Log.d("PokePatch", "Successfully loaded ${mons.size} Pokemon") + } else { + error = "API response error: ${response.code()}" + Log.e("PokePatch", "API response error: ${response.code()}") + } + } catch (th: Throwable) { + Log.e("PokePatch", "Error loading Pokemon data: ${th.message}", th) + error = "Failed to load Pokemon: ${th.message}" + } finally { + isLoading = false + } + } + } + + Column { + SafeArea( + shouldCapture = shouldCapture, + onBitmap = onBitmap, + ) { + when { + isLoading -> { + Text( + modifier = Modifier.rotate(45f), + text = "Loading Pokemon..." + ) + } + + error != null -> { + Text( + modifier = Modifier.rotate(45f), + text = error!!, + color = Color.Red + ) + } + + mon != null -> { + AsyncImage( + modifier = Modifier + .size(150.dp, 150.dp) + .background(Color.White), + model = mon!!.url, + contentScale = ContentScale.Fit, + contentDescription = mon!!.name, + onError = { error -> + Log.e( + "PokePatch", + "Failed to load image for ${mon!!.name}: ${mon!!.url}, error: $error" + ) + } + ) + Text( + text = mon!!.name.replaceFirstChar { it.uppercaseChar() }, + color = Color.Black + ) + } + + else -> { + Text( + modifier = Modifier.rotate(45f), + text = "Click a Pokemon to see it!" + ) + } + } + } + + if (mons.isNotEmpty()) { + LazyRow { + items(mons) { pokemon -> + Button( + modifier = Modifier.padding(4.dp), + onClick = { + mon = pokemon + Log.d( + "PokePatch", + "Selected Pokemon: ${pokemon.name}, URL: ${pokemon.url}" + ) + } + ) { + Text(text = pokemon.name.replaceFirstChar { it.uppercaseChar() }) + } + } + } + } + } +} + +fun JSONArray.toMons(): List = List(length()) { i -> + try { + val monData = getJSONObject(i) + val name = monData.getString("name") + val pokemonUrl = monData.getString("url") + + // Extract Pokemon ID from URL more safely + // URL format: https://pokeapi.co/api/v2/pokemon/1/ + val urlParts = pokemonUrl.trimEnd('/').split("/") + val pokemonId = urlParts.lastOrNull() ?: return@List null + + // Validate that pokemonId is actually a number + if (pokemonId.toIntOrNull() == null) { + Log.w("PokePatch", "Invalid Pokemon ID: $pokemonId from URL: $pokemonUrl") + return@List null + } + + // Use the correct PokeAPI sprites URL format + val spriteUrl = + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/$pokemonId.png" + + Mon( + name = name, + url = spriteUrl + ) + } catch (e: Exception) { + Log.e("PokePatch", "Error parsing Pokemon data at index $i: ${e.message}") + null + } +}.filterNotNull() \ No newline at end of file diff --git a/converter/build.gradle.kts b/converter/build.gradle.kts index 7a6be91..8817bc1 100644 --- a/converter/build.gradle.kts +++ b/converter/build.gradle.kts @@ -33,7 +33,7 @@ kotlin { chaquopy { defaultConfig { val pythonPath = System.getenv("ZEPATCH_PYTHON_PATH") - if(pythonPath != null) { + if (!pythonPath.isNullOrBlank()) { buildPython(pythonPath) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e9e664..66054f2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,8 @@ appcompat = "1.7.1" material = "1.13.0" jts = "1.20.0" composableSheep = "1.1.0" +retro = "3.0.0" +coil = "3.3.0" [libraries] kotlinpoet = { group = "com.squareup", name = "kotlinpoet", version.ref = "kotlinpoet" } @@ -49,6 +51,10 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version material = { group = "com.google.android.material", name = "material", version.ref = "material" } jts = { group = "org.locationtech.jts", name = "jts-core", version.ref = "jts" } composableSheep = { module = "dev.nstv:composablesheep", version.ref = "composableSheep" } +retro = { module = "com.squareup.retrofit2:retrofit", version.ref = "retro" } +retro-scalar = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retro" } +coil = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }