mirror of
https://github.com/izzy2lost/aab2apk.git
synced 2026-06-19 01:20:08 -07:00
Changed current flow now its running in Windows.
This commit is contained in:
@@ -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).
|
||||
|
||||

|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 20 KiB |
@@ -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
@@ -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.
@@ -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 |
Reference in New Issue
Block a user