Merge pull request #1 from iammohdzaki/feature/command-options

Added New Options for Command build-apks
This commit is contained in:
Mohammad Zaki
2024-02-26 23:51:13 +05:30
committed by GitHub
20 changed files with 724 additions and 84 deletions
+8
View File
@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="AabToApk-jvm-1.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="AabToApk-jvm-1.0-SNAPSHOT.jar">
<element id="module-output" name="AabToApk.jvmMain" />
</root>
</artifact>
</component>
+124
View File
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>
+1
View File
@@ -23,6 +23,7 @@ kotlin {
val jvmMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
api("com.esotericsoftware:kryo:4.0.1")
}
}
val jvmTest by getting
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 29 KiB

+68
View File
@@ -0,0 +1,68 @@
import utils.SigningMode
import utils.Utils
class CommandBuilder {
private var bundletoolPath: String = ""
private var aabFilePath: Pair<String, String> = Pair("", "")
private var isOverwrite: Boolean = false
private var isAapt2PathEnabled: Boolean = false
private var aapt2Path: String = ""
private var isUniversalMode: Boolean = true
private var signingMode: Int = 1
private var keyStorePath: String = ""
private var keyStorePassword: String = ""
private var keyAlias: String = ""
private var keyPassword: String = ""
fun bundletoolPath(path: String) = apply { this.bundletoolPath = path }
fun aabFilePath(path: Pair<String, String>) = apply { this.aabFilePath = path }
fun isOverwrite(overwrite: Boolean) = apply { this.isOverwrite = overwrite }
fun isAapt2PathEnabled(enabled: Boolean) = apply { this.isAapt2PathEnabled = enabled }
fun aapt2Path(path: String) = apply { this.aapt2Path = path }
fun isUniversalMode(universalMode: Boolean) = apply { this.isUniversalMode = universalMode }
fun signingMode(mode: Int) = apply { this.signingMode = mode }
fun keyStorePath(path: String) = apply { this.keyStorePath = path }
fun keyStorePassword(password: String) = apply { this.keyStorePassword = password }
fun keyAlias(alias: String) = apply { this.keyAlias = alias }
fun keyPassword(password: String) = apply { this.keyPassword = password }
fun validateAndGetCommand(): Pair<String, Boolean> {
if (bundletoolPath.isEmpty()) {
return Pair("bundletoolPath", false)
}
if (aabFilePath.first.isEmpty() || aabFilePath.second.isEmpty()) {
return Pair("aabFilePath", false)
}
if (isAapt2PathEnabled && aapt2Path.isEmpty()) {
return Pair("aapt2Path", false)
}
if (signingMode == SigningMode.RELEASE && (keyStorePath.isEmpty() || keyStorePassword.isEmpty() || keyAlias.isEmpty() || keyPassword.isEmpty())) {
return Pair("Check Keystore Info!", false)
}
return Pair(getCommand(), true)
}
private fun getCommand(): String {
val commandBuilder = StringBuilder()
if (Utils.isWindowsOS()) {
commandBuilder.append("java -jar \"${bundletoolPath}\" build-apks ")
if (isUniversalMode) {
commandBuilder.append("--mode=universal ")
}
if (isOverwrite) {
commandBuilder.append("--overwrite ")
}
if (isAapt2PathEnabled) {
commandBuilder.append("--aapt2=\"$aapt2Path\" ")
}
commandBuilder.append("--bundle=\"${aabFilePath.first}${aabFilePath.second}\" --output=\"${aabFilePath.first}${aabFilePath.second.split(".")[0]}.apks\" ")
if (signingMode == SigningMode.RELEASE){
commandBuilder.append("--ks=$keyStorePath --ks-pass=pass:$keyStorePassword --ks-key-alias=$keyAlias --key-pass=pass:$keyPassword ")
}
return commandBuilder.toString()
}
return ""
}
}
-11
View File
@@ -1,11 +0,0 @@
object Constant {
const val SUCCESS = 0
const val FAILURE = 1
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"
}
}
+234 -69
View File
@@ -1,13 +1,7 @@
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
@@ -20,21 +14,26 @@ 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 local.FileStorageHelper
import theme.Styles
import theme.components.CheckboxWithText
import theme.components.ChooseFileTextField
import theme.components.CustomTextField
import utils.*
import java.awt.Desktop
import java.awt.FileDialog
import java.awt.Frame
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URI
import utils.Strings
import java.nio.file.Paths
@Composable
@Preview
fun App() {
fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
val coroutineScope = rememberCoroutineScope()
var logs by remember { mutableStateOf("========== Logs View ==========\n\n") }
var bundletoolPath by remember { mutableStateOf("") }
@@ -42,16 +41,33 @@ fun App() {
var isLoading by remember { mutableStateOf(false) }
var isOpen by remember { mutableStateOf(false) }
var isExecute by remember { mutableStateOf(false) }
var isBundletool by remember { mutableStateOf(false) }
var fileDialogType by remember { mutableStateOf(0) }
var saveJarPath by remember { mutableStateOf(false) }
var isOverwrite by remember { mutableStateOf(false) }
var isAapt2PathEnabled by remember { mutableStateOf(false) }
var aapt2Path by remember { mutableStateOf("") }
var isUniversalMode by remember { mutableStateOf(true) }
var signingMode by remember { mutableStateOf(SigningMode.DEBUG) }
var keyStorePath by remember { mutableStateOf("") }
var keyStorePassword by remember { mutableStateOf("") }
var keyAlias by remember { mutableStateOf("") }
var keyPassword by remember { mutableStateOf("") }
savedPath?.let {
bundletoolPath = it
saveJarPath = true
}
if (isOpen && !isLoading) {
FileDialog { fileName, directory ->
isOpen = false
if (isBundletool) {
//Handle Error for Unknown files here
bundletoolPath = "${directory}$fileName"
} else {
aabFilePath = Pair(directory, fileName)
when (fileDialogType) {
FileDialogType.BUNDLETOOL -> bundletoolPath = "${directory}$fileName"
FileDialogType.AAPT2 -> aapt2Path = "${directory}$fileName"
FileDialogType.KEY_STORE_PATH -> keyStorePath = "${directory}$fileName"
else -> {
aabFilePath = Pair(directory, fileName)
}
}
}
}
@@ -59,41 +75,63 @@ fun App() {
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"
val (cmd, isValid) = CommandBuilder()
.bundletoolPath(bundletoolPath)
.aabFilePath(aabFilePath)
.isOverwrite(isOverwrite)
.isUniversalMode(isUniversalMode)
.isAapt2PathEnabled(isAapt2PathEnabled)
.aapt2Path(aapt2Path)
.signingMode(signingMode)
.keyStorePath(keyStorePath)
.keyStorePassword(keyStorePassword)
.keyAlias(keyAlias)
.keyPassword(keyPassword)
.validateAndGetCommand()
if (isValid) {
Log.i("Command $cmd")
logs += "Executing Command : \n$cmd\n"
coroutineScope.launch(Dispatchers.IO) {
//Save Path in Storage
if (savedPath == null) {
fileStorageHelper.save("path", bundletoolPath)
} else {
if(fileStorageHelper.delete("path"))
saveJarPath = false
}
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(aabFilePath.first, aabFilePath.second) { status, message ->
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
Log.i("STATUS - $status\nMESSAGE - $message")
logs += "\nFiles Operations Starting...\n$message\n"
}
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()}"
}
} catch (e: Exception) {
isLoading = false
logs += "Failed -> ${e.printStackTrace()}"
}
} else {
Log.i("Error $cmd")
logs += "\nError -> $cmd"
isLoading = false
}
}
@@ -105,37 +143,51 @@ fun App() {
Spacer(modifier = Modifier.padding(12.dp))
Text(
text = "Android Bundletool UI",
text = Strings.APP_NAME,
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
}
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.wrapContentSize(),
verticalAlignment = Alignment.CenterVertically
) {
//Bundle tool select flow
ChooseFileTextField(
bundletoolPath,
Strings.SELECT_BUNDLETOOL_JAR,
onSelect = {
fileDialogType = FileDialogType.BUNDLETOOL
isOpen = true
}
)
CheckboxWithText(
Strings.SAVE_JAR_PATH,
saveJarPath,
onCheckedChange = {
saveJarPath = it
},
Strings.SAVE_JAR_PATH_INFO
)
}
val downloadInfo = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Blue,textDecoration = TextDecoration.Underline)) {
append("Download Bundletool from here")
withStyle(style = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) {
append(Strings.DOWNLOAD_BUNDLETOOL)
}
addStringAnnotation(
tag = "URL",
annotation = "https://github.com/google/bundletool/releases",
tag = Strings.URL,
annotation = Constant.BUNDLE_DOWNLOAD_LINK,
start = 0,
end = length
)
}
ClickableText(
text = downloadInfo,
style = Styles.TextStyleNormal(14.sp),
style = Styles.TextStyleMedium(14.sp),
modifier = Modifier.padding(start = 16.dp),
onClick = {offset ->
val annotations = downloadInfo.getStringAnnotations("URL", offset, offset)
onClick = { offset ->
val annotations = downloadInfo.getStringAnnotations(Strings.URL, offset, offset)
if (annotations.isNotEmpty()) {
val uri = URI(annotations.first().item)
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
@@ -149,16 +201,126 @@ fun App() {
Spacer(modifier = Modifier.padding(8.dp))
ChooseFileTextField(
"${aabFilePath.first}${aabFilePath.second}",
"Select Aab File",
Strings.SELECT_AAB_FILE,
onSelect = {
fileDialogType = FileDialogType.AAB
isOpen = true
isBundletool = false
}
)
Spacer(modifier = Modifier.padding(8.dp))
Text(
text = Strings.OPTIONS_FOR_BUILD_APKS,
style = Styles.TextStyleBold(20.sp),
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp)
)
Row(
horizontalArrangement = Arrangement.SpaceBetween
) {
CheckboxWithText(
Strings.OVERWRITE,
isOverwrite,
onCheckedChange = {
isOverwrite = it
},
Strings.OVERWRITE_INFO
)
CheckboxWithText(
Strings.MODE_UNIVERSAL,
isUniversalMode,
onCheckedChange = {
isUniversalMode = it
},
Strings.MODE_UNIVERSAL_INFO
)
CheckboxWithText(
Strings.AAPT2_PATH,
isAapt2PathEnabled,
onCheckedChange = {
isAapt2PathEnabled = it
},
Strings.AAPT2_PATH_INFO
)
}
if (isAapt2PathEnabled) {
ChooseFileTextField(
aapt2Path,
Strings.SELECT_AAPT2_FILE,
onSelect = {
fileDialogType = FileDialogType.AAPT2
isOpen = true
}
)
}
Text(
text = Strings.SIGNING_MODE,
style = Styles.TextStyleBold(16.sp),
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp)
)
Row(
horizontalArrangement = Arrangement.SpaceBetween
) {
CheckboxWithText(
Strings.DEBUG,
signingMode == SigningMode.DEBUG,
onCheckedChange = {
signingMode = if (it) SigningMode.DEBUG
else SigningMode.RELEASE
}
)
CheckboxWithText(
Strings.RELEASE,
signingMode == SigningMode.RELEASE,
onCheckedChange = {
signingMode = if (it) SigningMode.RELEASE
else SigningMode.DEBUG
}
)
}
if (signingMode == SigningMode.RELEASE) {
Row(
horizontalArrangement = Arrangement.SpaceBetween
) {
ChooseFileTextField(
keyStorePath,
Strings.KEYSTORE_PATH,
onSelect = {
fileDialogType = FileDialogType.KEY_STORE_PATH
isOpen = true
}
)
CustomTextField(
keyStorePassword,
Strings.KEYSTORE_PASSWORD,
onValueChange = {
keyStorePassword = it
}
)
}
Row(
horizontalArrangement = Arrangement.SpaceBetween
) {
CustomTextField(
keyAlias,
Strings.KEY_ALIAS,
forPassword = false,
onValueChange = {
keyAlias = it
}
)
CustomTextField(
keyPassword,
Strings.KEY_PASSWORD,
onValueChange = {
keyPassword = it
}
)
}
}
Spacer(modifier = Modifier.padding(8.dp))
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(size = 40.dp).padding(start = 16.dp, top = 0.dp, end = 0.dp, bottom = 0.dp),
modifier = Modifier.size(size = 40.dp)
.padding(start = 16.dp, top = 0.dp, end = 0.dp, bottom = 0.dp),
strokeWidth = 4.dp
)
} else {
@@ -171,7 +333,7 @@ fun App() {
.wrapContentWidth(),
) {
Text(
text = "Execute",
text = Strings.EXECUTE,
style = Styles.TextStyleMedium(16.sp),
color = Color.White,
fontWeight = FontWeight.Medium,
@@ -195,7 +357,7 @@ private fun FileDialog(
onCloseRequest: (fileName: String, directory: String) -> Unit
) = AwtWindow(
create = {
object : FileDialog(parent, "Choose a file", LOAD) {
object : FileDialog(parent, Strings.CHOOSE_FILE, LOAD) {
override fun setVisible(value: Boolean) {
super.setVisible(value)
if (value) {
@@ -209,15 +371,18 @@ private fun FileDialog(
fun main() = application {
val fileStorageHelper = FileStorageHelper()
//Check if path for bundletool exists in local storage
val path = fileStorageHelper.read("path") as String?
Log.showLogs = true
Window(
onCloseRequest = ::exitApplication,
state = rememberWindowState(
width = 800.dp, height = 800.dp,
width = 1000.dp, height = 1000.dp,
position = WindowPosition(Alignment.Center)
),
title = "Aab To Apk"
title = Strings.APP_NAME
) {
App()
App(fileStorageHelper, path)
}
}
@@ -0,0 +1,75 @@
package local
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.file.Paths
const val DB_PATH = "/storage"
class FileStorageHelper {
private lateinit var kryo: Kryo
init {
initializeDir()
}
private fun getKryo(): Kryo {
return if (this::kryo.isInitialized) {
kryo
} else {
val kryo = Kryo()
kryo.register(KiteTable::class.java)
kryo
}
}
private fun initializeDir() {
val file = File(getCurrentDir())
file.mkdir()
}
private fun getCurrentDir(): String {
return Paths.get("").toAbsolutePath().toString() + DB_PATH
}
private fun getPath(key: String): String {
return "${getCurrentDir()}/$key.kb"
}
fun <E> save(key: String, value: E) {
var kryoOutput: Output?
try {
val kiteTable = KiteTable(value)
val fileStream = FileOutputStream(File(getPath(key)))
kryoOutput = Output(fileStream)
getKryo().writeObject(kryoOutput, kiteTable)
kryoOutput.flush()
fileStream.flush()
kryoOutput.close()
} catch (e: Exception) {
throw KiteDbException(e.message)
}
}
fun read(key: String): Any? {
val keyFile = File(getPath(key))
if (!keyFile.exists()) return null
val kryoInput = Input(FileInputStream(keyFile))
val kiteTable = getKryo().readObject(kryoInput, KiteTable::class.java)
return kiteTable.mContent
}
fun delete(key: String): Boolean {
val keyFile = File(getPath(key))
return if (keyFile.exists()) {
keyFile.delete()
} else {
false
}
}
}
@@ -0,0 +1,9 @@
package local
class KiteDbException : RuntimeException {
constructor(detailMessage: String?) : super(detailMessage)
constructor(detailMessage: String?, throwable: Throwable?) : super(detailMessage, throwable)
}
+11
View File
@@ -0,0 +1,11 @@
package local
class KiteTable<T> {
internal constructor()
internal constructor(content: T) {
mContent = content
}
// Serialized content
var mContent: T? = null
}
@@ -0,0 +1,72 @@
package theme.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.TooltipPlacement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Checkbox
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
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.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import theme.Styles
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CheckboxWithText(label: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, toolTipText: String = "") {
val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(end = 16.dp)
) {
Checkbox(
checked = isChecked,
onCheckedChange = { onCheckedChange.invoke(it) },
)
Text(
text = label,
style = Styles.TextStyleMedium(14.sp),
)
if (toolTipText.isNotEmpty()) {
TooltipArea(
tooltip = {
// composable tooltip content
Surface(
modifier = Modifier.shadow(4.dp),
color = Color(255, 255, 210),
shape = RoundedCornerShape(4.dp)
) {
Text(
text = toolTipText,
style = Styles.TextStyleMedium(12.sp),
modifier = Modifier.padding(10.dp)
)
}
},
delayMillis = 200, // in milliseconds
tooltipPlacement = TooltipPlacement.CursorPoint(
alignment = Alignment.BottomEnd,
offset = DpOffset.Zero // tooltip offset
)
){
Icon(
painter = useResource("info.svg") { loadSvgPainter(it, density) },
contentDescription = "Info",
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
}
@@ -25,7 +25,7 @@ fun ChooseFileTextField(value: String, label: String, onSelect: () -> Unit) {
verticalAlignment = Alignment.CenterVertically
) {
TextField(
modifier = Modifier.fillMaxWidth(0.5f).height(60.dp)
modifier = Modifier.fillMaxWidth(0.4f).height(50.dp)
.border(
BorderStroke(width = 2.dp, color = MaterialTheme.colors.primary),
shape = RoundedCornerShape(topEnd = 0.dp, bottomEnd = 0.dp, topStart = 10.dp, bottomStart = 10.dp)
@@ -53,12 +53,12 @@ fun ChooseFileTextField(value: String, label: String, onSelect: () -> Unit) {
onSelect.invoke()
},
modifier = Modifier
.height(60.dp)
.height(50.dp)
.wrapContentWidth(),
) {
Icon(
painter = useResource("open_folder.svg") { loadSvgPainter(it, density) },
contentDescription = ""
contentDescription = "",
)
}
}
@@ -0,0 +1,61 @@
package theme.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
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.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import theme.Styles
@Composable
fun CustomTextField(value: String, label: String, forPassword: Boolean = true, onValueChange: (String) -> Unit) {
Row(
modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 8.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
modifier = Modifier.fillMaxWidth(0.4f).height(50.dp)
.border(
BorderStroke(width = 2.dp, color = MaterialTheme.colors.primary),
shape = RoundedCornerShape(topEnd = 10.dp, bottomEnd = 10.dp, topStart = 10.dp, bottomStart = 10.dp)
),
value = value,
singleLine = true,
visualTransformation = if (forPassword) PasswordVisualTransformation() else VisualTransformation.None,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
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 = {
onValueChange.invoke(it)
},
)
}
}
+19
View File
@@ -0,0 +1,19 @@
package utils
object Constant {
const val SUCCESS = 0
const val FAILURE = 1
const val BUNDLE_DOWNLOAD_LINK = "https://github.com/google/bundletool/releases"
}
object FileDialogType{
const val AAB = -1
const val BUNDLETOOL = 1
const val AAPT2 = 2
const val KEY_STORE_PATH = 3
}
object SigningMode{
const val DEBUG = 1
const val RELEASE = 2
}
@@ -1,3 +1,5 @@
package utils
import java.io.File
import java.io.IOException
@@ -1,3 +1,5 @@
package utils
import java.io.*
import java.util.zip.ZipFile
@@ -1,3 +1,5 @@
package utils
object Log {
var showLogs: Boolean = true
+30
View File
@@ -0,0 +1,30 @@
package utils
object Strings {
const val APP_NAME = "Android Bundletool UI"
const val URL = "URL"
const val CHOOSE_FILE = "Choose a file"
const val SELECT_BUNDLETOOL_JAR = "Select Bundletool Jar"
const val SAVE_JAR_PATH = "Save Jar Path"
const val SAVE_JAR_PATH_INFO = "Saves Path of Bundle Tool Jar for Future."
const val DOWNLOAD_BUNDLETOOL = "Download Bundletool from here"
const val SELECT_AAB_FILE = "Select Aab File"
const val OPTIONS_FOR_BUILD_APKS = "Options for the bundletool build-apks command"
const val OVERWRITE = "Overwrite"
const val OVERWRITE_INFO = "Overwrites any existing output file with the path you specify using the --output option. If you don't include this flag and the output file already exists, you get a build error."
const val AAPT2_PATH = "Aapt2 Path"
const val AAPT2_PATH_INFO = "Specifies a custom path to AAPT2. By default, bundletool includes its own version of AAPT2."
const val SELECT_AAPT2_FILE = "Select Aapt2 File"
const val MODE_UNIVERSAL = "Mode Universal"
const val MODE_UNIVERSAL_INFO = "Sets the mode to universal. Use this option if you want bundletool to build a single APK that includes all of your app's code and resources, so that the APK is compatible with all device configurations your app supports." +
"\nNote: bundletool includes only feature modules that specify <dist:fusing dist:include=\"true\"/> in their manifest in a universal APK. To learn more, read about the feature module manifest.\n" +
"Keep in mind, these APKs are larger than those optimized for a particular device configuration. However, they're easier to share with internal testers who, for example, want to test your app on multiple device configurations."
const val SIGNING_MODE = "Signing Mode"
const val DEBUG = "Debug"
const val RELEASE = "Release"
const val KEYSTORE_PATH = "Select Keystore Path"
const val KEYSTORE_PASSWORD = "Keystore Password"
const val KEY_ALIAS = "Key Alias"
const val KEY_PASSWORD = "Key Password"
const val EXECUTE = "Execute"
}
@@ -1,4 +1,5 @@
import androidx.compose.ui.text.toLowerCase
package utils
import java.util.*
object Utils {
+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="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 676 B