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