From 9e8ee4cd4b559a27c59c167d743f3dd069f0cce1 Mon Sep 17 00:00:00 2001 From: Bitmap Date: Sun, 25 Feb 2024 22:10:14 +0530 Subject: [PATCH] Added new Options for command - overwrite - universal - aapt2 path - signing mode - release keystore signing - save Jar Path --- .idea/artifacts/AabToApk_jvm_1_0_SNAPSHOT.xml | 8 + .idea/uiDesigner.xml | 124 +++++++ build.gradle.kts | 1 + screenshots/ata.png | Bin 20740 -> 29240 bytes src/jvmMain/kotlin/CommandBuilder.kt | 68 ++++ src/jvmMain/kotlin/Constant.kt | 11 - src/jvmMain/kotlin/Main.kt | 303 ++++++++++++++---- src/jvmMain/kotlin/local/FileStorageHelper.kt | 75 +++++ src/jvmMain/kotlin/local/KiteDbException.kt | 9 + src/jvmMain/kotlin/local/KiteTable.kt | 11 + .../theme/components/CheckboxWithText.kt | 72 +++++ .../theme/components/ChooseFileTextField.kt | 6 +- .../theme/components/CustomTextField.kt | 61 ++++ src/jvmMain/kotlin/utils/Constant.kt | 19 ++ src/jvmMain/kotlin/{ => utils}/FileHelper.kt | 2 + src/jvmMain/kotlin/{ => utils}/FileUtils.kt | 2 + src/jvmMain/kotlin/{ => utils}/Log.kt | 2 + src/jvmMain/kotlin/utils/Strings.kt | 30 ++ src/jvmMain/kotlin/{ => utils}/Utils.kt | 3 +- src/jvmMain/resources/info.svg | 1 + 20 files changed, 724 insertions(+), 84 deletions(-) create mode 100644 .idea/artifacts/AabToApk_jvm_1_0_SNAPSHOT.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 src/jvmMain/kotlin/CommandBuilder.kt delete mode 100644 src/jvmMain/kotlin/Constant.kt create mode 100644 src/jvmMain/kotlin/local/FileStorageHelper.kt create mode 100644 src/jvmMain/kotlin/local/KiteDbException.kt create mode 100644 src/jvmMain/kotlin/local/KiteTable.kt create mode 100644 src/jvmMain/kotlin/theme/components/CheckboxWithText.kt create mode 100644 src/jvmMain/kotlin/theme/components/CustomTextField.kt create mode 100644 src/jvmMain/kotlin/utils/Constant.kt rename src/jvmMain/kotlin/{ => utils}/FileHelper.kt (98%) rename src/jvmMain/kotlin/{ => utils}/FileUtils.kt (99%) rename src/jvmMain/kotlin/{ => utils}/Log.kt (90%) create mode 100644 src/jvmMain/kotlin/utils/Strings.kt rename src/jvmMain/kotlin/{ => utils}/Utils.kt (79%) create mode 100644 src/jvmMain/resources/info.svg 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 d534a668bf14acd9b212cf849b33a6cd552997cf..15cee1d408905f1cfcc3a0581f2a19107b00d2da 100644 GIT binary patch literal 29240 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 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