diff --git a/.idea/artifacts/AabToApk_jvm_1_0_SNAPSHOT.xml b/.idea/artifacts/AabToApk_jvm_1_0_SNAPSHOT.xml
new file mode 100644
index 0000000..c831c1a
--- /dev/null
+++ b/.idea/artifacts/AabToApk_jvm_1_0_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 2c0c90b..2806e83 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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
diff --git a/screenshots/ata.png b/screenshots/ata.png
index d534a66..15cee1d 100644
Binary files a/screenshots/ata.png and b/screenshots/ata.png differ
diff --git a/src/jvmMain/kotlin/CommandBuilder.kt b/src/jvmMain/kotlin/CommandBuilder.kt
new file mode 100644
index 0000000..a13dda8
--- /dev/null
+++ b/src/jvmMain/kotlin/CommandBuilder.kt
@@ -0,0 +1,68 @@
+import utils.SigningMode
+import utils.Utils
+
+class CommandBuilder {
+ private var bundletoolPath: String = ""
+ private var aabFilePath: Pair = 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) = 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 {
+ 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 ""
+ }
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/Constant.kt b/src/jvmMain/kotlin/Constant.kt
deleted file mode 100644
index 3682583..0000000
--- a/src/jvmMain/kotlin/Constant.kt
+++ /dev/null
@@ -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"
- }
-}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/Main.kt b/src/jvmMain/kotlin/Main.kt
index 4db77d6..e2777a1 100644
--- a/src/jvmMain/kotlin/Main.kt
+++ b/src/jvmMain/kotlin/Main.kt
@@ -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)
}
}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/local/FileStorageHelper.kt b/src/jvmMain/kotlin/local/FileStorageHelper.kt
new file mode 100644
index 0000000..f152501
--- /dev/null
+++ b/src/jvmMain/kotlin/local/FileStorageHelper.kt
@@ -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 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
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/local/KiteDbException.kt b/src/jvmMain/kotlin/local/KiteDbException.kt
new file mode 100644
index 0000000..657c3fc
--- /dev/null
+++ b/src/jvmMain/kotlin/local/KiteDbException.kt
@@ -0,0 +1,9 @@
+package local
+
+class KiteDbException : RuntimeException {
+
+ constructor(detailMessage: String?) : super(detailMessage)
+
+ constructor(detailMessage: String?, throwable: Throwable?) : super(detailMessage, throwable)
+
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/local/KiteTable.kt b/src/jvmMain/kotlin/local/KiteTable.kt
new file mode 100644
index 0000000..246639d
--- /dev/null
+++ b/src/jvmMain/kotlin/local/KiteTable.kt
@@ -0,0 +1,11 @@
+package local
+
+class KiteTable {
+ internal constructor()
+ internal constructor(content: T) {
+ mContent = content
+ }
+
+ // Serialized content
+ var mContent: T? = null
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/theme/components/CheckboxWithText.kt b/src/jvmMain/kotlin/theme/components/CheckboxWithText.kt
new file mode 100644
index 0000000..2635fab
--- /dev/null
+++ b/src/jvmMain/kotlin/theme/components/CheckboxWithText.kt
@@ -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)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/theme/components/ChooseFileTextField.kt b/src/jvmMain/kotlin/theme/components/ChooseFileTextField.kt
index bf67443..2251698 100644
--- a/src/jvmMain/kotlin/theme/components/ChooseFileTextField.kt
+++ b/src/jvmMain/kotlin/theme/components/ChooseFileTextField.kt
@@ -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 = "",
)
}
}
diff --git a/src/jvmMain/kotlin/theme/components/CustomTextField.kt b/src/jvmMain/kotlin/theme/components/CustomTextField.kt
new file mode 100644
index 0000000..434d2b4
--- /dev/null
+++ b/src/jvmMain/kotlin/theme/components/CustomTextField.kt
@@ -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)
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/utils/Constant.kt b/src/jvmMain/kotlin/utils/Constant.kt
new file mode 100644
index 0000000..0ea7285
--- /dev/null
+++ b/src/jvmMain/kotlin/utils/Constant.kt
@@ -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
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/FileHelper.kt b/src/jvmMain/kotlin/utils/FileHelper.kt
similarity index 98%
rename from src/jvmMain/kotlin/FileHelper.kt
rename to src/jvmMain/kotlin/utils/FileHelper.kt
index e508d5e..7d4c2a5 100644
--- a/src/jvmMain/kotlin/FileHelper.kt
+++ b/src/jvmMain/kotlin/utils/FileHelper.kt
@@ -1,3 +1,5 @@
+package utils
+
import java.io.File
import java.io.IOException
diff --git a/src/jvmMain/kotlin/FileUtils.kt b/src/jvmMain/kotlin/utils/FileUtils.kt
similarity index 99%
rename from src/jvmMain/kotlin/FileUtils.kt
rename to src/jvmMain/kotlin/utils/FileUtils.kt
index f49952e..306ecd2 100644
--- a/src/jvmMain/kotlin/FileUtils.kt
+++ b/src/jvmMain/kotlin/utils/FileUtils.kt
@@ -1,3 +1,5 @@
+package utils
+
import java.io.*
import java.util.zip.ZipFile
diff --git a/src/jvmMain/kotlin/Log.kt b/src/jvmMain/kotlin/utils/Log.kt
similarity index 90%
rename from src/jvmMain/kotlin/Log.kt
rename to src/jvmMain/kotlin/utils/Log.kt
index 5a349fd..2ab499e 100644
--- a/src/jvmMain/kotlin/Log.kt
+++ b/src/jvmMain/kotlin/utils/Log.kt
@@ -1,3 +1,5 @@
+package utils
+
object Log {
var showLogs: Boolean = true
diff --git a/src/jvmMain/kotlin/utils/Strings.kt b/src/jvmMain/kotlin/utils/Strings.kt
new file mode 100644
index 0000000..cd7378d
--- /dev/null
+++ b/src/jvmMain/kotlin/utils/Strings.kt
@@ -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 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"
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/Utils.kt b/src/jvmMain/kotlin/utils/Utils.kt
similarity index 79%
rename from src/jvmMain/kotlin/Utils.kt
rename to src/jvmMain/kotlin/utils/Utils.kt
index 22bc2da..60efa01 100644
--- a/src/jvmMain/kotlin/Utils.kt
+++ b/src/jvmMain/kotlin/utils/Utils.kt
@@ -1,4 +1,5 @@
-import androidx.compose.ui.text.toLowerCase
+package utils
+
import java.util.*
object Utils {
diff --git a/src/jvmMain/resources/info.svg b/src/jvmMain/resources/info.svg
new file mode 100644
index 0000000..0f10691
--- /dev/null
+++ b/src/jvmMain/resources/info.svg
@@ -0,0 +1 @@
+
\ No newline at end of file