Changed current flow now its running in Windows.

This commit is contained in:
Bitmap
2024-02-24 15:35:52 +05:30
parent 3b6dfe18b0
commit cf2a6b0b9a
7 changed files with 193 additions and 54 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# AAB TO APK CONVERTER (IN DEVELOPMENT)
# Bundle Tool UI - Desktop (IN DEVELOPMENT)
AAB to APK Converter is an open-source tool built with Kotlin and Desktop Compose that allows users to convert Android App Bundles (AAB) to Android Package (APK) files using Google's [BundleTool](https://github.com/google/bundletool).
Bundle Tool UI - Desktop is an open-source tool built with Kotlin and Desktop Compose that allows users to convert Android App Bundles (AAB) to Android Package (APK) files using Google's [BundleTool](https://github.com/google/bundletool).
![ATA](https://github.com/iammohdzaki/AabToApk-Desktop/blob/main/screenshots/ata.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 20 KiB

+3 -10
View File
@@ -1,17 +1,10 @@
import java.io.File
import java.net.URLDecoder
object Constant {
const val SUCCESS = 0
const val FAILURE = 1
private const val bundleToolFileName = "bundletool-all-1.15.6.jar"
fun getCommand(): String {
if(Utils.isWindowsOS()){
val jarFileEncodedUrl = object {}.javaClass.classLoader.getResource(bundleToolFileName)
val decodedJarPath = URLDecoder.decode(jarFileEncodedUrl.path, "UTF-8")
val bundletool = File(decodedJarPath)
return "java -jar \"${bundletool.absolutePath}\" build-apks --mode=universal --bundle=\"INPUT_FILE_PATH\" --output=\"OUTPUT_FILE_NAME.apks\""
fun getCommand(bundletoolPath: String): String {
if (Utils.isWindowsOS()) {
return "java -jar \"${bundletoolPath}\" build-apks --mode=universal --bundle=\"INPUT_FILE_PATH\" --output=\"OUTPUT_FILE_NAME.apks\""
}
return "bundletool build-apks --mode=universal --bundle=INPUT_FILE_PATH --output=OUTPUT_FILE_NAME.apks"
}
+122 -42
View File
@@ -1,67 +1,98 @@
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.rounded.Done
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import theme.Styles
import theme.components.ChooseFileTextField
import java.awt.Desktop
import java.awt.FileDialog
import java.awt.Frame
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URI
@Composable
@Preview
fun App() {
val coroutineScope = rememberCoroutineScope()
var logs by remember { mutableStateOf("========== Logs View ==========\n\n") }
var bundletoolPath by remember { mutableStateOf("") }
var aabFilePath by remember { mutableStateOf(Pair<String, String>("", "")) }
var isLoading by remember { mutableStateOf(false) }
var isOpen by remember { mutableStateOf(false) }
if (isOpen) {
var isExecute by remember { mutableStateOf(false) }
var isBundletool by remember { mutableStateOf(false) }
if (isOpen && !isLoading) {
FileDialog { fileName, directory ->
isOpen = false
//Get Command to Execute
val cmd = Constant.getCommand().replace("INPUT_FILE_PATH", "${directory}$fileName")
.replace("OUTPUT_FILE_NAME", "${directory}${fileName.split(".")[0]}")
Log.i("Command $cmd")
logs += "Executing Command : \n$cmd\n"
if (isBundletool) {
//Handle Error for Unknown files here
bundletoolPath = "${directory}$fileName"
} else {
aabFilePath = Pair(directory, fileName)
}
}
}
coroutineScope.launch(Dispatchers.IO) {
isLoading = true
val runtime = Runtime.getRuntime()
val startTime = System.currentTimeMillis()
try {
//Launch Runtime to execute command
val process = runtime.exec(cmd)
// Read and log error output
val errorReader = BufferedReader(InputStreamReader(process.errorStream))
Log.i("Process Error Output:")
while (errorReader.readLine().also { logs += "\n ERROR -> $it" } != null) {
isLoading = false
}
process.waitFor()
val endTime = System.currentTimeMillis()
if (process.exitValue() == 0) {
Log.i("Command Executed in ${((endTime - startTime) / 1000)}s")
logs += "\nCommand Executed in ${((endTime - startTime) / 1000)}s\n"
// Do further file operation after new apks is generated
FileHelper.performFileOperations(directory, fileName) { status, message ->
isLoading = false
Log.i("STATUS - $status\nMESSAGE - $message")
logs += "\nFiles Operations Starting...\n$message\n"
}
}
} catch (e: Exception) {
if (isExecute) {
isExecute = false
//Get Command to Execute
val cmd =
Constant.getCommand(bundletoolPath).replace("INPUT_FILE_PATH", "${aabFilePath.first}${aabFilePath.second}")
.replace("OUTPUT_FILE_NAME", "${aabFilePath.first}${aabFilePath.second}".split(".")[0])
Log.i("Command $cmd")
logs += "Executing Command : \n$cmd\n"
coroutineScope.launch(Dispatchers.IO) {
isLoading = true
val runtime = Runtime.getRuntime()
val startTime = System.currentTimeMillis()
try {
//Launch Runtime to execute command
val process = runtime.exec(cmd)
// Read and log error output
val errorReader = BufferedReader(InputStreamReader(process.errorStream))
Log.i("Process Error Output:")
while (errorReader.readLine().also { logs += "\n ERROR -> $it" } != null) {
isLoading = false
logs += "Failed -> ${e.printStackTrace()}"
}
process.waitFor()
val endTime = System.currentTimeMillis()
if (process.exitValue() == 0) {
Log.i("Command Executed in ${((endTime - startTime) / 1000)}s")
logs += "\nCommand Executed in ${((endTime - startTime) / 1000)}s\n"
// Do further file operation after new apks is generated
FileHelper.performFileOperations(aabFilePath.first, aabFilePath.second) { status, message ->
isLoading = false
Log.i("STATUS - $status\nMESSAGE - $message")
logs += "\nFiles Operations Starting...\n$message\n"
}
}
} catch (e: Exception) {
isLoading = false
logs += "Failed -> ${e.printStackTrace()}"
}
}
}
@@ -69,29 +100,78 @@ fun App() {
MaterialTheme {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.Start
) {
Spacer(modifier = Modifier.padding(12.dp))
Text(
text = "Aab To Apk",
style = Styles.TextStyleBold(28.sp)
text = "Android Bundletool UI",
style = Styles.TextStyleBold(28.sp),
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp)
)
Spacer(modifier = Modifier.padding(8.dp))
//Bundle tool select flow
ChooseFileTextField(
bundletoolPath,
"Select Bundletool Jar",
onSelect = {
isOpen = true
isBundletool = true
}
)
val downloadInfo = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Blue,textDecoration = TextDecoration.Underline)) {
append("Download Bundletool from here")
}
addStringAnnotation(
tag = "URL",
annotation = "https://github.com/google/bundletool/releases",
start = 0,
end = length
)
}
ClickableText(
text = downloadInfo,
style = Styles.TextStyleNormal(14.sp),
modifier = Modifier.padding(start = 16.dp),
onClick = {offset ->
val annotations = downloadInfo.getStringAnnotations("URL", offset, offset)
if (annotations.isNotEmpty()) {
val uri = URI(annotations.first().item)
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(uri)
} else {
// Desktop not supported, handle as necessary
}
}
}
)
Spacer(modifier = Modifier.padding(8.dp))
ChooseFileTextField(
"${aabFilePath.first}${aabFilePath.second}",
"Select Aab File",
onSelect = {
isOpen = true
isBundletool = false
}
)
Spacer(modifier = Modifier.padding(8.dp))
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(size = 30.dp),
modifier = Modifier.size(size = 40.dp).padding(start = 16.dp, top = 0.dp, end = 0.dp, bottom = 0.dp),
strokeWidth = 4.dp
)
} else {
Button(
onClick = {
isOpen = true
isLoading = true
isExecute = true
},
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp)
.fillMaxWidth(0.32f),
.wrapContentWidth(),
) {
Text(
text = "Choose File",
text = "Execute",
style = Styles.TextStyleMedium(16.sp),
color = Color.White,
fontWeight = FontWeight.Medium,
@@ -133,11 +213,11 @@ fun main() = application {
Window(
onCloseRequest = ::exitApplication,
state = rememberWindowState(
width = 500.dp, height = 500.dp,
width = 800.dp, height = 800.dp,
position = WindowPosition(Alignment.Center)
),
title = "Aab To Apk"
) {
App()
}
}
}
@@ -0,0 +1,65 @@
package theme.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
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.platform.LocalDensity
import androidx.compose.ui.res.loadSvgPainter
import androidx.compose.ui.res.useResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import theme.Styles
@Composable
fun ChooseFileTextField(value: String, label: String, onSelect: () -> Unit) {
val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML)
Row(
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 0.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
modifier = Modifier.fillMaxWidth(0.5f).height(60.dp)
.border(
BorderStroke(width = 2.dp, color = MaterialTheme.colors.primary),
shape = RoundedCornerShape(topEnd = 0.dp, bottomEnd = 0.dp, topStart = 10.dp, bottomStart = 10.dp)
),
value = value,
singleLine = true,
readOnly = true,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
label = {
Text(
text = label,
style = Styles.TextStyleMedium(16.sp),
fontWeight = FontWeight.Medium,
)
},
textStyle = Styles.TextStyleMedium(16.sp),
onValueChange = {},
)
Button(
onClick = {
onSelect.invoke()
},
modifier = Modifier
.height(60.dp)
.wrapContentWidth(),
) {
Icon(
painter = useResource("open_folder.svg") { loadSvgPainter(it, density) },
contentDescription = ""
)
}
}
}
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640H447l-80-80H160v480l96-320h684L837-217q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm0 0 72-240-72 240Zm-84-400v-80 80Z"/></svg>

After

Width:  |  Height:  |  Size: 334 B