From ab3e4c2624402d2497faeb5dbc27d0b2200ce473 Mon Sep 17 00:00:00 2001 From: Bitmap Date: Sat, 9 Mar 2024 17:50:50 +0530 Subject: [PATCH 1/5] - Added ADB Setup - Added Command Executor for executing commands. - Added Generic Loading Dialog - Saved ADB path to storage - TODO : Need to add Device options if ADB setup is done - Updated kotlin_lint.yml --- .github/workflows/kotlin_lint.yml | 3 + src/jvmMain/kotlin/Main.kt | 87 +++++++++++++++---- .../kotlin/{ => command}/CommandBuilder.kt | 23 ++++- src/jvmMain/kotlin/command/CommandExecutor.kt | 55 ++++++++++++ .../kotlin/ui/components/ButtonWithToolTip.kt | 73 ++++++++++++++++ .../kotlin/ui/components/CheckboxWithText.kt | 1 + .../kotlin/ui/components/LoadingDialog.kt | 57 ++++++++++++ .../kotlin/ui/components/TextWithIcon.kt | 36 ++++++++ src/jvmMain/kotlin/utils/Constant.kt | 10 ++- src/jvmMain/kotlin/utils/Strings.kt | 8 +- src/jvmMain/resources/done.svg | 1 + src/jvmMain/resources/info.svg | 4 +- 12 files changed, 336 insertions(+), 22 deletions(-) rename src/jvmMain/kotlin/{ => command}/CommandBuilder.kt (79%) create mode 100644 src/jvmMain/kotlin/command/CommandExecutor.kt create mode 100644 src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt create mode 100644 src/jvmMain/kotlin/ui/components/LoadingDialog.kt create mode 100644 src/jvmMain/kotlin/ui/components/TextWithIcon.kt create mode 100644 src/jvmMain/resources/done.svg diff --git a/.github/workflows/kotlin_lint.yml b/.github/workflows/kotlin_lint.yml index 39d508e..ba83209 100644 --- a/.github/workflows/kotlin_lint.yml +++ b/.github/workflows/kotlin_lint.yml @@ -1,6 +1,9 @@ name: kotlin_lint on: + push: + branches: + - "*" pull_request: paths: - "**/*.kt" diff --git a/src/jvmMain/kotlin/Main.kt b/src/jvmMain/kotlin/Main.kt index f6eeaa1..f404a0a 100644 --- a/src/jvmMain/kotlin/Main.kt +++ b/src/jvmMain/kotlin/Main.kt @@ -17,13 +17,13 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.* +import command.CommandBuilder +import command.CommandExecutor import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import local.FileStorageHelper import ui.Styles -import ui.components.CheckboxWithText -import ui.components.ChooseFileTextField -import ui.components.CustomTextField +import ui.components.* import utils.* import java.awt.Desktop import java.awt.FileDialog @@ -35,7 +35,7 @@ import utils.Strings @Composable @Preview -fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) { +fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: String?) { val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML) val coroutineScope = rememberCoroutineScope() var logs by remember { mutableStateOf("") } @@ -57,21 +57,58 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) { var keyPassword by remember { mutableStateOf("") } var isAutoUnzip by remember { mutableStateOf(true) } var savedJarPath by remember { mutableStateOf(savedPath) } + var isAdbSetupDone by remember { mutableStateOf(false) } + var adbPath by remember { mutableStateOf("") } + var showLoadingDialog by remember { mutableStateOf(Pair("", false)) } - - //TODO: KNOWN ISSUE - Can't update file path once saved, For now Delete path.kb file inside storage directory. + //TODO: (Fixed this issue need to test more!) - Can't update file path once saved, For now Delete path.kb file inside storage directory. savedJarPath?.let { bundletoolPath = it saveJarPath = true } + //Check if ADB Setup is Done or Not + adbSavedPath?.let { + adbPath = it + isAdbSetupDone = true + } + if (isOpen && !isLoading) { FileDialog { fileName, directory -> isOpen = false + if (fileName.isNullOrEmpty() || directory.isNullOrEmpty()) { + return@FileDialog + } when (fileDialogType) { FileDialogType.BUNDLETOOL -> bundletoolPath = "${directory}$fileName" FileDialogType.AAPT2 -> aapt2Path = "${directory}$fileName" FileDialogType.KEY_STORE_PATH -> keyStorePath = "${directory}$fileName" + FileDialogType.ADB_PATH -> { + adbPath = "${directory}${fileName}" + //Show Loading Here + showLoadingDialog = Pair(Strings.VERIFYING_ADB_PATH, true) + CommandExecutor().executeCommand( + CommandBuilder() + .verifyAdbPath(true, adbPath) + .getAdbVerifyCommand(), coroutineScope, + onSuccess = { + logs += it + isAdbSetupDone = true + Log.i("Saving Path in DB $adbPath") + fileStorageHelper.save(DBConstants.ADB_PATH, adbPath) + //Hide Loading + Thread.sleep(1000L) + showLoadingDialog = Pair(Strings.VERIFYING_ADB_PATH, false) + }, + onFailure = { + logs += it + isAdbSetupDone = false + //Hide Loading + showLoadingDialog = Pair(Strings.VERIFYING_ADB_PATH, false) + } + ) + } + else -> { aabFilePath = Pair(directory, fileName) } @@ -79,6 +116,10 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) { } } + if (showLoadingDialog.second) { + LoadingDialog(showLoadingDialog.first) + } + if (isExecute) { isExecute = false //Get Command to Execute @@ -158,13 +199,26 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) { modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start ) { - Spacer(modifier = Modifier.padding(12.dp)) - Text( - text = Strings.APP_NAME, - style = Styles.TextStyleBold(28.sp), - modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = Strings.APP_NAME, + style = Styles.TextStyleBold(28.sp), + modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) + ) + ButtonWithToolTip( + if (isAdbSetupDone) Strings.ABD_SETUP_DONE else Strings.SETUP_ADB, + onClick = { + fileDialogType = FileDialogType.ADB_PATH + isOpen = true + }, + Strings.SETUP_ADB_INFO, + icon = if (isAdbSetupDone) "done" else "info", + ) + } Spacer(modifier = Modifier.padding(8.dp)) Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -412,7 +466,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) { @Composable private fun FileDialog( parent: Frame? = null, - onCloseRequest: (fileName: String, directory: String) -> Unit + onCloseRequest: (fileName: String?, directory: String?) -> Unit ) = AwtWindow( create = { object : FileDialog(parent, Strings.CHOOSE_FILE, LOAD) { @@ -431,16 +485,17 @@ 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? + val path = fileStorageHelper.read(DBConstants.BUNDLETOOL_PATH) as String? + val adbPath = fileStorageHelper.read(DBConstants.ADB_PATH) as String? Log.showLogs = true Window( onCloseRequest = ::exitApplication, state = rememberWindowState( - width = 1000.dp, height = 1000.dp, + width = 1200.dp, height = 1000.dp, position = WindowPosition(Alignment.Center) ), title = Strings.APP_NAME ) { - App(fileStorageHelper, path) + App(fileStorageHelper, path, adbPath) } } \ No newline at end of file diff --git a/src/jvmMain/kotlin/CommandBuilder.kt b/src/jvmMain/kotlin/command/CommandBuilder.kt similarity index 79% rename from src/jvmMain/kotlin/CommandBuilder.kt rename to src/jvmMain/kotlin/command/CommandBuilder.kt index a13dda8..5300748 100644 --- a/src/jvmMain/kotlin/CommandBuilder.kt +++ b/src/jvmMain/kotlin/command/CommandBuilder.kt @@ -1,3 +1,5 @@ +package command + import utils.SigningMode import utils.Utils @@ -13,6 +15,7 @@ class CommandBuilder { private var keyStorePassword: String = "" private var keyAlias: String = "" private var keyPassword: String = "" + private var adbVerifyCommandExecute = Pair(false, "") fun bundletoolPath(path: String) = apply { this.bundletoolPath = path } fun aabFilePath(path: Pair) = apply { this.aabFilePath = path } @@ -26,6 +29,16 @@ class CommandBuilder { fun keyAlias(alias: String) = apply { this.keyAlias = alias } fun keyPassword(password: String) = apply { this.keyPassword = password } + fun verifyAdbPath(value: Boolean, path: String) = apply { this.adbVerifyCommandExecute = Pair(value,path) } + + fun getAdbVerifyCommand(): String { + val (forVerify,path) = adbVerifyCommandExecute + if (forVerify){ + return "\"${path}\" version" + } + return "" + } + fun validateAndGetCommand(): Pair { if (bundletoolPath.isEmpty()) { return Pair("bundletoolPath", false) @@ -55,9 +68,15 @@ class CommandBuilder { if (isAapt2PathEnabled) { commandBuilder.append("--aapt2=\"$aapt2Path\" ") } - commandBuilder.append("--bundle=\"${aabFilePath.first}${aabFilePath.second}\" --output=\"${aabFilePath.first}${aabFilePath.second.split(".")[0]}.apks\" ") + commandBuilder.append( + "--bundle=\"${aabFilePath.first}${aabFilePath.second}\" --output=\"${aabFilePath.first}${ + aabFilePath.second.split( + "." + )[0] + }.apks\" " + ) - if (signingMode == SigningMode.RELEASE){ + if (signingMode == SigningMode.RELEASE) { commandBuilder.append("--ks=$keyStorePath --ks-pass=pass:$keyStorePassword --ks-key-alias=$keyAlias --key-pass=pass:$keyPassword ") } diff --git a/src/jvmMain/kotlin/command/CommandExecutor.kt b/src/jvmMain/kotlin/command/CommandExecutor.kt new file mode 100644 index 0000000..6657df6 --- /dev/null +++ b/src/jvmMain/kotlin/command/CommandExecutor.kt @@ -0,0 +1,55 @@ +package command + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.BufferedReader +import java.io.InputStreamReader + +class CommandExecutor { + + fun executeCommand( + cmd: String, + coroutineScope: CoroutineScope, + onSuccess: (String) -> Unit, + onFailure: (Throwable) -> Unit + ) { + coroutineScope.launch(Dispatchers.IO){ + try { + val runtime = Runtime.getRuntime() + val startTime = System.currentTimeMillis() + + val process = runtime.exec(cmd) + val outputReader = BufferedReader(InputStreamReader(process.inputStream)) + val errorReader = BufferedReader(InputStreamReader(process.errorStream)) + + var output = "" + var errorOutput = "" + + // Read command output + var line: String? + while (outputReader.readLine().also { line = it } != null) { + output += line + "\n" + } + + // Read error output + while (errorReader.readLine().also { line = it } != null) { + errorOutput += line + "\n" + } + + process.waitFor() + val endTime = System.currentTimeMillis() + if (process.exitValue() == 0) { + val executionTime = ((endTime - startTime) / 1000).toString() + "s" + onSuccess("$cmd\n$output\nCommand Executed in $executionTime") + } else { + val exception = Exception("Command execution failed: $cmd$errorOutput") + onFailure(exception) + } + } catch (e: Exception) { + onFailure(e) + } + } + } + +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt b/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt new file mode 100644 index 0000000..dd1543f --- /dev/null +++ b/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt @@ -0,0 +1,73 @@ +package ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.TooltipArea +import androidx.compose.foundation.TooltipPlacement +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.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.text.font.FontWeight +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import ui.Styles + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ButtonWithToolTip(label: String, onClick: () -> Unit, toolTipText: String = "", icon: String = "info", buttonColors: ButtonColors = ButtonDefaults.buttonColors()) { + val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML) + Button( + onClick = { + onClick.invoke() + }, + colors = buttonColors, + modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) + .wrapContentWidth(), + ) { + Text( + text = label, + style = Styles.TextStyleMedium(16.sp), + color = Color.White, + fontWeight = FontWeight.Medium, + ) + 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, + color = Color.Black, + 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("$icon.svg") { loadSvgPainter(it, density) }, + contentDescription = icon, + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt b/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt index ed92aea..6f1c88a 100644 --- a/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt +++ b/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt @@ -54,6 +54,7 @@ fun CheckboxWithText(label: String, isChecked: Boolean, onCheckedChange: (Boolea ) { Text( text = toolTipText, + style = Styles.TextStyleMedium(12.sp), modifier = Modifier.padding(10.dp) ) diff --git a/src/jvmMain/kotlin/ui/components/LoadingDialog.kt b/src/jvmMain/kotlin/ui/components/LoadingDialog.kt new file mode 100644 index 0000000..3496623 --- /dev/null +++ b/src/jvmMain/kotlin/ui/components/LoadingDialog.kt @@ -0,0 +1,57 @@ +package ui.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.CircularProgressIndicator +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.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import ui.Styles + +@Composable +fun LoadingDialog(text: String) { + Surface( + shape = RoundedCornerShape(8.dp), + elevation = 8.dp + ) { + Box( + modifier = Modifier + .padding(4.dp) + .border( + BorderStroke(1.dp, Color.LightGray), + shape = RoundedCornerShape(8.dp) + ) + ) { + Dialog( + title = text, + onCloseRequest = { }, + undecorated = false, + resizable = false, + enabled = false + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.padding(16.dp).fillMaxWidth().fillMaxHeight() + ) { + Text( + text = text, + style = Styles.TextStyleBold(20.sp), + modifier = Modifier.padding(start = 8.dp,end = 8.dp, bottom = 8.dp) + ) + CircularProgressIndicator() + } + } + } + + } + +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/ui/components/TextWithIcon.kt b/src/jvmMain/kotlin/ui/components/TextWithIcon.kt new file mode 100644 index 0000000..6fe735c --- /dev/null +++ b/src/jvmMain/kotlin/ui/components/TextWithIcon.kt @@ -0,0 +1,36 @@ +package ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.loadSvgPainter +import androidx.compose.ui.res.useResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import ui.Styles + +@Composable +fun TextWithIcon(label: String, onIconClick: () -> Unit) { + val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML) + Row { + Text( + text = label, + style = Styles.TextStyleMedium(16.sp), + color = Color.Black, + fontWeight = FontWeight.Medium, + ) + Icon( + painter = useResource("info.svg") { loadSvgPainter(it, density) }, + contentDescription = "Info", + modifier = Modifier.padding(start = 8.dp) + .clickable { onIconClick.invoke() }, + ) + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/utils/Constant.kt b/src/jvmMain/kotlin/utils/Constant.kt index aca1089..a2f6418 100644 --- a/src/jvmMain/kotlin/utils/Constant.kt +++ b/src/jvmMain/kotlin/utils/Constant.kt @@ -6,14 +6,20 @@ object Constant { const val BUNDLE_DOWNLOAD_LINK = "https://github.com/google/bundletool/releases" } -object FileDialogType{ +object DBConstants { + const val BUNDLETOOL_PATH = "bundletool_path" + const val ADB_PATH = "adb_path" +} + +object FileDialogType { const val AAB = -1 const val BUNDLETOOL = 1 const val AAPT2 = 2 const val KEY_STORE_PATH = 3 + const val ADB_PATH = 4 } -object SigningMode{ +object SigningMode { const val DEBUG = 1 const val RELEASE = 2 } diff --git a/src/jvmMain/kotlin/utils/Strings.kt b/src/jvmMain/kotlin/utils/Strings.kt index f752659..cee16d7 100644 --- a/src/jvmMain/kotlin/utils/Strings.kt +++ b/src/jvmMain/kotlin/utils/Strings.kt @@ -28,8 +28,14 @@ object Strings { const val KEY_PASSWORD = "Key Password" const val EXECUTE = "Execute" const val FILE_OPTIONS = "File Options" + const val DEVICE_OPTIONS = "Device Options" const val AUTO_UNZIP = "Automatically Unzip and Delete Apks File" - const val AUTO_UNZIP_INFO = "Automatically Unzip and Delete Apks File" const val CLEAR_LOGS = "Clear Logs" const val LOGS_VIEW = "Logs View" + const val SETUP_ADB = "Set up Adb" + const val ABD_SETUP_DONE = "Adb Connected" + const val SETUP_ADB_INFO = "Setup ADB path to create builds based on connected device" + const val DEVICE_ID = "Device Id" + const val DEVICE_ID_INFO = "Device Id based on the Serial Number" + const val VERIFYING_ADB_PATH = "Verifying ADB Path.." } \ No newline at end of file diff --git a/src/jvmMain/resources/done.svg b/src/jvmMain/resources/done.svg new file mode 100644 index 0000000..1655d12 --- /dev/null +++ b/src/jvmMain/resources/done.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/jvmMain/resources/info.svg b/src/jvmMain/resources/info.svg index 0f10691..e87bc17 100644 --- a/src/jvmMain/resources/info.svg +++ b/src/jvmMain/resources/info.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + \ No newline at end of file From cd82d882ff2ccf3cb5ac89a4a1b30525a035d5b9 Mon Sep 17 00:00:00 2001 From: Bitmap Date: Sun, 10 Mar 2024 00:32:14 +0530 Subject: [PATCH 2/5] Added Test Cases for CommandExecutor Updated kotlin_lint.yml Fixed Lint Errors --- .github/workflows/kotlin_lint.yml | 3 +- src/jvmMain/kotlin/Main.kt | 90 +++++++++++++------ src/jvmMain/kotlin/command/CommandBuilder.kt | 6 +- src/jvmMain/kotlin/command/CommandExecutor.kt | 3 +- src/jvmMain/kotlin/local/FileStorageHelper.kt | 2 +- src/jvmMain/kotlin/local/KiteDbException.kt | 1 - src/jvmMain/kotlin/ui/Styles.kt | 9 +- src/jvmMain/kotlin/ui/Typography.kt | 6 +- .../kotlin/ui/components/ButtonWithToolTip.kt | 19 +++- .../kotlin/ui/components/CheckboxWithText.kt | 4 +- .../ui/components/ChooseFileTextField.kt | 21 +++-- .../kotlin/ui/components/CustomTextField.kt | 4 +- .../kotlin/ui/components/LoadingDialog.kt | 11 ++- .../kotlin/ui/components/TextWithIcon.kt | 4 +- src/jvmMain/kotlin/utils/Constant.kt | 3 +- src/jvmMain/kotlin/utils/FileHelper.kt | 6 +- src/jvmMain/kotlin/utils/FileUtils.kt | 8 +- src/jvmMain/kotlin/utils/Utils.kt | 5 +- .../kotlin/command/CommandExecutorTest.kt | 55 ++++++++++++ .../ui/components/CheckboxWithTextTest.kt | 7 +- 20 files changed, 189 insertions(+), 78 deletions(-) create mode 100644 src/jvmTest/kotlin/command/CommandExecutorTest.kt diff --git a/.github/workflows/kotlin_lint.yml b/.github/workflows/kotlin_lint.yml index ba83209..c5b4d16 100644 --- a/.github/workflows/kotlin_lint.yml +++ b/.github/workflows/kotlin_lint.yml @@ -2,7 +2,7 @@ name: kotlin_lint on: push: - branches: + branches-ignore: - "*" pull_request: paths: @@ -12,7 +12,6 @@ on: jobs: ktlint: runs-on: ubuntu-latest - steps: - name: "checkout" uses: actions/checkout@v2 diff --git a/src/jvmMain/kotlin/Main.kt b/src/jvmMain/kotlin/Main.kt index f404a0a..ce77061 100644 --- a/src/jvmMain/kotlin/Main.kt +++ b/src/jvmMain/kotlin/Main.kt @@ -1,8 +1,28 @@ import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.text.ClickableText -import androidx.compose.material.* -import androidx.compose.runtime.* +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -16,15 +36,28 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.* +import androidx.compose.ui.window.AwtWindow +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPosition +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState import command.CommandBuilder import command.CommandExecutor import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import local.FileStorageHelper import ui.Styles -import ui.components.* -import utils.* +import ui.components.ButtonWithToolTip +import ui.components.CheckboxWithText +import ui.components.ChooseFileTextField +import ui.components.CustomTextField +import ui.components.LoadingDialog +import utils.Constant +import utils.DBConstants +import utils.FileDialogType +import utils.FileHelper +import utils.Log +import utils.SigningMode import java.awt.Desktop import java.awt.FileDialog import java.awt.Frame @@ -61,13 +94,13 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: var adbPath by remember { mutableStateOf("") } var showLoadingDialog by remember { mutableStateOf(Pair("", false)) } - //TODO: (Fixed this issue need to test more!) - Can't update file path once saved, For now Delete path.kb file inside storage directory. + // TODO: (Fixed this issue need to test more!) - Can't update file path once saved, For now Delete path.kb file inside storage directory. savedJarPath?.let { bundletoolPath = it saveJarPath = true } - //Check if ADB Setup is Done or Not + // Check if ADB Setup is Done or Not adbSavedPath?.let { adbPath = it isAdbSetupDone = true @@ -80,12 +113,12 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: return@FileDialog } when (fileDialogType) { - FileDialogType.BUNDLETOOL -> bundletoolPath = "${directory}$fileName" - FileDialogType.AAPT2 -> aapt2Path = "${directory}$fileName" - FileDialogType.KEY_STORE_PATH -> keyStorePath = "${directory}$fileName" + FileDialogType.BUNDLETOOL -> bundletoolPath = "$directory$fileName" + FileDialogType.AAPT2 -> aapt2Path = "$directory$fileName" + FileDialogType.KEY_STORE_PATH -> keyStorePath = "$directory$fileName" FileDialogType.ADB_PATH -> { - adbPath = "${directory}${fileName}" - //Show Loading Here + adbPath = "$directory$fileName" + // Show Loading Here showLoadingDialog = Pair(Strings.VERIFYING_ADB_PATH, true) CommandExecutor().executeCommand( CommandBuilder() @@ -96,14 +129,14 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: isAdbSetupDone = true Log.i("Saving Path in DB $adbPath") fileStorageHelper.save(DBConstants.ADB_PATH, adbPath) - //Hide Loading + // Hide Loading Thread.sleep(1000L) showLoadingDialog = Pair(Strings.VERIFYING_ADB_PATH, false) }, onFailure = { logs += it isAdbSetupDone = false - //Hide Loading + // Hide Loading showLoadingDialog = Pair(Strings.VERIFYING_ADB_PATH, false) } ) @@ -122,7 +155,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: if (isExecute) { isExecute = false - //Get Command to Execute + // Get Command to Execute val (cmd, isValid) = CommandBuilder() .bundletoolPath(bundletoolPath) .aabFilePath(aabFilePath) @@ -143,7 +176,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: val runtime = Runtime.getRuntime() val startTime = System.currentTimeMillis() try { - //Launch Runtime to execute command + // Launch Runtime to execute command val process = runtime.exec(cmd) // Read and log error output val errorReader = BufferedReader(InputStreamReader(process.errorStream)) @@ -169,8 +202,8 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: isLoading = false } - //Save Path in Storage - //If We don't have any saved path in file storage and save jar path option is checked.Then,we can save new value in storage. + // Save Path in Storage + // If We don't have any saved path in file storage and save jar path option is checked.Then,we can save new value in storage. if (savedJarPath == null && saveJarPath) { fileStorageHelper.save("path", bundletoolPath) } @@ -216,7 +249,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: isOpen = true }, Strings.SETUP_ADB_INFO, - icon = if (isAdbSetupDone) "done" else "info", + icon = if (isAdbSetupDone) "done" else "info" ) } Spacer(modifier = Modifier.padding(8.dp)) @@ -225,7 +258,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: modifier = Modifier.wrapContentSize(), verticalAlignment = Alignment.CenterVertically ) { - //Bundle tool select flow + // Bundle tool select flow ChooseFileTextField( bundletoolPath, Strings.SELECT_BUNDLETOOL_JAR, @@ -415,13 +448,13 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: isExecute = true }, modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - .wrapContentWidth(), + .wrapContentWidth() ) { Text( text = Strings.EXECUTE, style = Styles.TextStyleMedium(16.sp), color = Color.White, - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Medium ) } } @@ -439,17 +472,17 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: modifier = Modifier.padding(end = 0.dp, bottom = 8.dp), onClick = { logs = "" - }, + } ) { Text( text = Strings.CLEAR_LOGS, - style = Styles.TextStyleBold(13.sp), + style = Styles.TextStyleBold(13.sp) ) Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing)) Icon( painter = useResource("clear.svg") { loadSvgPainter(it, density) }, contentDescription = "Clear", - modifier = Modifier.size(ButtonDefaults.IconSize), + modifier = Modifier.size(ButtonDefaults.IconSize) ) } } @@ -457,7 +490,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: modifier = Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 16.dp), value = logs, textStyle = Styles.TextStyleMedium(16.sp), - onValueChange = {}, + onValueChange = {} ) } } @@ -481,10 +514,9 @@ private fun FileDialog( dispose = FileDialog::dispose ) - fun main() = application { val fileStorageHelper = FileStorageHelper() - //Check if path for bundletool exists in local storage + // Check if path for bundletool exists in local storage val path = fileStorageHelper.read(DBConstants.BUNDLETOOL_PATH) as String? val adbPath = fileStorageHelper.read(DBConstants.ADB_PATH) as String? Log.showLogs = true diff --git a/src/jvmMain/kotlin/command/CommandBuilder.kt b/src/jvmMain/kotlin/command/CommandBuilder.kt index 5300748..4fab55a 100644 --- a/src/jvmMain/kotlin/command/CommandBuilder.kt +++ b/src/jvmMain/kotlin/command/CommandBuilder.kt @@ -29,11 +29,11 @@ class CommandBuilder { fun keyAlias(alias: String) = apply { this.keyAlias = alias } fun keyPassword(password: String) = apply { this.keyPassword = password } - fun verifyAdbPath(value: Boolean, path: String) = apply { this.adbVerifyCommandExecute = Pair(value,path) } + fun verifyAdbPath(value: Boolean, path: String) = apply { this.adbVerifyCommandExecute = Pair(value, path) } fun getAdbVerifyCommand(): String { - val (forVerify,path) = adbVerifyCommandExecute - if (forVerify){ + val (forVerify, path) = adbVerifyCommandExecute + if (forVerify) { return "\"${path}\" version" } return "" diff --git a/src/jvmMain/kotlin/command/CommandExecutor.kt b/src/jvmMain/kotlin/command/CommandExecutor.kt index 6657df6..0454c1e 100644 --- a/src/jvmMain/kotlin/command/CommandExecutor.kt +++ b/src/jvmMain/kotlin/command/CommandExecutor.kt @@ -14,7 +14,7 @@ class CommandExecutor { onSuccess: (String) -> Unit, onFailure: (Throwable) -> Unit ) { - coroutineScope.launch(Dispatchers.IO){ + coroutineScope.launch(Dispatchers.IO) { try { val runtime = Runtime.getRuntime() val startTime = System.currentTimeMillis() @@ -51,5 +51,4 @@ class CommandExecutor { } } } - } \ No newline at end of file diff --git a/src/jvmMain/kotlin/local/FileStorageHelper.kt b/src/jvmMain/kotlin/local/FileStorageHelper.kt index f152501..c7cb24e 100644 --- a/src/jvmMain/kotlin/local/FileStorageHelper.kt +++ b/src/jvmMain/kotlin/local/FileStorageHelper.kt @@ -17,6 +17,7 @@ class FileStorageHelper { init { initializeDir() } + private fun getKryo(): Kryo { return if (this::kryo.isInitialized) { kryo @@ -71,5 +72,4 @@ class FileStorageHelper { false } } - } \ No newline at end of file diff --git a/src/jvmMain/kotlin/local/KiteDbException.kt b/src/jvmMain/kotlin/local/KiteDbException.kt index 657c3fc..a8237bb 100644 --- a/src/jvmMain/kotlin/local/KiteDbException.kt +++ b/src/jvmMain/kotlin/local/KiteDbException.kt @@ -5,5 +5,4 @@ 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/ui/Styles.kt b/src/jvmMain/kotlin/ui/Styles.kt index 9a47e0a..76bc587 100644 --- a/src/jvmMain/kotlin/ui/Styles.kt +++ b/src/jvmMain/kotlin/ui/Styles.kt @@ -10,28 +10,27 @@ object Styles { TextStyle( fontWeight = FontWeight.Normal, fontFamily = codeFontFamily, - fontSize = size, + fontSize = size ) fun TextStyleMedium(size: TextUnit) = TextStyle( fontWeight = FontWeight.Medium, fontFamily = codeFontFamily, - fontSize = size, + fontSize = size ) fun TextStyleSemiBold(size: TextUnit) = TextStyle( fontWeight = FontWeight.SemiBold, fontFamily = codeFontFamily, - fontSize = size, + fontSize = size ) fun TextStyleBold(size: TextUnit) = TextStyle( fontWeight = FontWeight.Bold, fontFamily = codeFontFamily, - fontSize = size, + fontSize = size ) - } \ No newline at end of file diff --git a/src/jvmMain/kotlin/ui/Typography.kt b/src/jvmMain/kotlin/ui/Typography.kt index 1934935..717a87f 100644 --- a/src/jvmMain/kotlin/ui/Typography.kt +++ b/src/jvmMain/kotlin/ui/Typography.kt @@ -9,9 +9,9 @@ import androidx.compose.ui.unit.sp val codeFontFamily = FontFamily( Font(resource = "fonts/sans_regular.ttf", weight = FontWeight.Light), - Font( resource = "fonts/sans_thin.ttf", weight = FontWeight.Normal), - Font( resource = "fonts/sans_medium.ttf", weight = FontWeight.Medium), - Font( resource = "fonts/sans_bold.ttf", weight = FontWeight.Bold) + Font(resource = "fonts/sans_thin.ttf", weight = FontWeight.Normal), + Font(resource = "fonts/sans_medium.ttf", weight = FontWeight.Medium), + Font(resource = "fonts/sans_bold.ttf", weight = FontWeight.Bold) ) val typography = Typography( diff --git a/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt b/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt index dd1543f..cd7ffc7 100644 --- a/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt +++ b/src/jvmMain/kotlin/ui/components/ButtonWithToolTip.kt @@ -6,7 +6,12 @@ import androidx.compose.foundation.TooltipPlacement import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Icon +import androidx.compose.material.Surface +import androidx.compose.material.Button +import androidx.compose.material.ButtonColors +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -23,7 +28,13 @@ import ui.Styles @OptIn(ExperimentalFoundationApi::class) @Composable -fun ButtonWithToolTip(label: String, onClick: () -> Unit, toolTipText: String = "", icon: String = "info", buttonColors: ButtonColors = ButtonDefaults.buttonColors()) { +fun ButtonWithToolTip( + label: String, + onClick: () -> Unit, + toolTipText: String = "", + icon: String = "info", + buttonColors: ButtonColors = ButtonDefaults.buttonColors() +) { val density = LocalDensity.current // to calculate the intrinsic size of vector images (SVG, XML) Button( onClick = { @@ -31,13 +42,13 @@ fun ButtonWithToolTip(label: String, onClick: () -> Unit, toolTipText: String = }, colors = buttonColors, modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - .wrapContentWidth(), + .wrapContentWidth() ) { Text( text = label, style = Styles.TextStyleMedium(16.sp), color = Color.White, - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Medium ) if (toolTipText.isNotEmpty()) { TooltipArea( diff --git a/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt b/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt index 6f1c88a..888785b 100644 --- a/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt +++ b/src/jvmMain/kotlin/ui/components/CheckboxWithText.kt @@ -36,12 +36,12 @@ fun CheckboxWithText(label: String, isChecked: Boolean, onCheckedChange: (Boolea Checkbox( modifier = Modifier.testTag(TestTags.CHECKBOX_TAG), checked = isChecked, - onCheckedChange = { onCheckedChange.invoke(it) }, + onCheckedChange = { onCheckedChange.invoke(it) } ) Text( modifier = Modifier.testTag(TestTags.TEXT_TAG), text = label, - style = Styles.TextStyleMedium(14.sp), + style = Styles.TextStyleMedium(14.sp) ) if (toolTipText.isNotEmpty()) { TooltipArea( diff --git a/src/jvmMain/kotlin/ui/components/ChooseFileTextField.kt b/src/jvmMain/kotlin/ui/components/ChooseFileTextField.kt index fbd5434..1d988e0 100644 --- a/src/jvmMain/kotlin/ui/components/ChooseFileTextField.kt +++ b/src/jvmMain/kotlin/ui/components/ChooseFileTextField.kt @@ -2,9 +2,18 @@ package ui.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.* +import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.MaterialTheme +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 @@ -42,11 +51,11 @@ fun ChooseFileTextField(value: String, label: String, onSelect: () -> Unit) { Text( text = label, style = Styles.TextStyleMedium(16.sp), - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Medium ) }, textStyle = Styles.TextStyleMedium(16.sp), - onValueChange = {}, + onValueChange = {} ) Button( onClick = { @@ -54,11 +63,11 @@ fun ChooseFileTextField(value: String, label: String, onSelect: () -> Unit) { }, modifier = Modifier .height(50.dp) - .wrapContentWidth(), + .wrapContentWidth() ) { Icon( painter = useResource("open_folder.svg") { loadSvgPainter(it, density) }, - contentDescription = "", + contentDescription = "" ) } } diff --git a/src/jvmMain/kotlin/ui/components/CustomTextField.kt b/src/jvmMain/kotlin/ui/components/CustomTextField.kt index a4c1d73..a173cec 100644 --- a/src/jvmMain/kotlin/ui/components/CustomTextField.kt +++ b/src/jvmMain/kotlin/ui/components/CustomTextField.kt @@ -49,13 +49,13 @@ fun CustomTextField(value: String, label: String, forPassword: Boolean = true, o Text( text = label, style = Styles.TextStyleMedium(16.sp), - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Medium ) }, textStyle = Styles.TextStyleMedium(16.sp), onValueChange = { onValueChange.invoke(it) - }, + } ) } } \ No newline at end of file diff --git a/src/jvmMain/kotlin/ui/components/LoadingDialog.kt b/src/jvmMain/kotlin/ui/components/LoadingDialog.kt index 3496623..15bb87d 100644 --- a/src/jvmMain/kotlin/ui/components/LoadingDialog.kt +++ b/src/jvmMain/kotlin/ui/components/LoadingDialog.kt @@ -2,7 +2,12 @@ package ui.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Surface @@ -45,13 +50,11 @@ fun LoadingDialog(text: String) { Text( text = text, style = Styles.TextStyleBold(20.sp), - modifier = Modifier.padding(start = 8.dp,end = 8.dp, bottom = 8.dp) + modifier = Modifier.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) ) CircularProgressIndicator() } } } - } - } \ No newline at end of file diff --git a/src/jvmMain/kotlin/ui/components/TextWithIcon.kt b/src/jvmMain/kotlin/ui/components/TextWithIcon.kt index 6fe735c..ca3b404 100644 --- a/src/jvmMain/kotlin/ui/components/TextWithIcon.kt +++ b/src/jvmMain/kotlin/ui/components/TextWithIcon.kt @@ -24,13 +24,13 @@ fun TextWithIcon(label: String, onIconClick: () -> Unit) { text = label, style = Styles.TextStyleMedium(16.sp), color = Color.Black, - fontWeight = FontWeight.Medium, + fontWeight = FontWeight.Medium ) Icon( painter = useResource("info.svg") { loadSvgPainter(it, density) }, contentDescription = "Info", modifier = Modifier.padding(start = 8.dp) - .clickable { onIconClick.invoke() }, + .clickable { onIconClick.invoke() } ) } } \ No newline at end of file diff --git a/src/jvmMain/kotlin/utils/Constant.kt b/src/jvmMain/kotlin/utils/Constant.kt index a2f6418..6cbd0c7 100644 --- a/src/jvmMain/kotlin/utils/Constant.kt +++ b/src/jvmMain/kotlin/utils/Constant.kt @@ -22,5 +22,4 @@ object FileDialogType { object SigningMode { const val DEBUG = 1 const val RELEASE = 2 -} - +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/utils/FileHelper.kt b/src/jvmMain/kotlin/utils/FileHelper.kt index 182b35f..7f1b7f4 100644 --- a/src/jvmMain/kotlin/utils/FileHelper.kt +++ b/src/jvmMain/kotlin/utils/FileHelper.kt @@ -12,7 +12,10 @@ object FileHelper { Log.i("CHANGED NAME\n Unzipping...") try { FileUtils.unzip(newFile, directory) - fileStatus.invoke(Constant.SUCCESS, "Rename and Unzip Successful\nFile Saved at ${directory.removeSuffix("\\")}.") + fileStatus.invoke( + Constant.SUCCESS, + "Rename and Unzip Successful\nFile Saved at ${directory.removeSuffix("\\")}." + ) Log.i("TRYING DELETING FILE") val value = FileUtils.deleteFile(newFile) Log.i("DELETE STATUS : $value") @@ -25,5 +28,4 @@ object FileHelper { fileStatus.invoke(Constant.FAILURE, "FAILED RENAMING THE FILE!!") } } - } \ No newline at end of file diff --git a/src/jvmMain/kotlin/utils/FileUtils.kt b/src/jvmMain/kotlin/utils/FileUtils.kt index 306ecd2..8d81876 100644 --- a/src/jvmMain/kotlin/utils/FileUtils.kt +++ b/src/jvmMain/kotlin/utils/FileUtils.kt @@ -1,6 +1,10 @@ package utils -import java.io.* +import java.io.BufferedOutputStream +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.io.FileOutputStream import java.util.zip.ZipFile object FileUtils { @@ -13,7 +17,6 @@ object FileUtils { fun deleteFile(file: File) = file.delete() - /** * @param oldFile * @param newFile @@ -72,5 +75,4 @@ object FileUtils { } bos.close() } - } \ No newline at end of file diff --git a/src/jvmMain/kotlin/utils/Utils.kt b/src/jvmMain/kotlin/utils/Utils.kt index 60efa01..e788a1d 100644 --- a/src/jvmMain/kotlin/utils/Utils.kt +++ b/src/jvmMain/kotlin/utils/Utils.kt @@ -1,11 +1,10 @@ package utils -import java.util.* +import java.util.Locale object Utils { - fun isWindowsOS(): Boolean{ + fun isWindowsOS(): Boolean { return System.getProperty("os.name").lowercase(Locale.getDefault()).contains("windows") } - } \ No newline at end of file diff --git a/src/jvmTest/kotlin/command/CommandExecutorTest.kt b/src/jvmTest/kotlin/command/CommandExecutorTest.kt new file mode 100644 index 0000000..ce178c6 --- /dev/null +++ b/src/jvmTest/kotlin/command/CommandExecutorTest.kt @@ -0,0 +1,55 @@ +package command + +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.runBlocking +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Before +import org.junit.Test + +class CommandExecutorTest { + private lateinit var commandExecutor: CommandExecutor + + @Before + fun setUp() { + commandExecutor = CommandExecutor() + } + + // These test cases may not work for MAC and Linux need to verify. + @Test + fun `execute valid command successfully`() { + val expectedOutput = "openjdk 11.0.18 2023-01-17" + val cmd = "java --version" + + runBlocking { + commandExecutor.executeCommand( + cmd, + this, + onSuccess = { + println("SUCCESS -> $it") + assertThat(expectedOutput, it.contains(expectedOutput)) + }, + onFailure = { + println("ERROR -> ${it.message}") + assertThat(expectedOutput, it.message?.contains(expectedOutput) ?: false) + } + ) + } + } + + @Test + fun `execute invalid command and handle failure`() { + val cmd = "invalid_command" + runBlocking { + commandExecutor.executeCommand( + cmd, + this, + onSuccess = { + assertTrue(it.isEmpty()) + }, + onFailure = { + assertTrue(it.message?.contains("The system cannot find the file specified") ?: false) + } + ) + } + } +} \ No newline at end of file diff --git a/src/jvmTest/kotlin/ui/components/CheckboxWithTextTest.kt b/src/jvmTest/kotlin/ui/components/CheckboxWithTextTest.kt index c3fb360..5af21ea 100644 --- a/src/jvmTest/kotlin/ui/components/CheckboxWithTextTest.kt +++ b/src/jvmTest/kotlin/ui/components/CheckboxWithTextTest.kt @@ -4,8 +4,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.test.* +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.assertIsOn +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.performClick import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag import org.junit.Rule import org.junit.Test import utils.TestTags @@ -57,5 +61,4 @@ class CheckboxWithTextTest { checkbox.performClick() checkbox.assertIsOff() } - } \ No newline at end of file From 1349bcd87dede28e1c4b0b4d791968bdb4d39a99 Mon Sep 17 00:00:00 2001 From: Bitmap Date: Sun, 10 Mar 2024 13:16:55 +0530 Subject: [PATCH 3/5] Added Test Cases for CommandBuilderTest --- .../kotlin/command/CommandBuilderTest.kt | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/jvmTest/kotlin/command/CommandBuilderTest.kt diff --git a/src/jvmTest/kotlin/command/CommandBuilderTest.kt b/src/jvmTest/kotlin/command/CommandBuilderTest.kt new file mode 100644 index 0000000..53a089d --- /dev/null +++ b/src/jvmTest/kotlin/command/CommandBuilderTest.kt @@ -0,0 +1,116 @@ +package command + +import org.junit.Assert.assertEquals +import org.junit.Test +import utils.SigningMode + +class CommandBuilderTest { + + @Test + fun `validateAndGetCommand should return error if bundletoolPath is empty`() { + val result = CommandBuilder() + .validateAndGetCommand() + assertEquals("bundletoolPath", result.first) + assertEquals(false, result.second) + } + + @Test + fun `validateAndGetCommand should return error if aabFilePath is empty`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .validateAndGetCommand() + assertEquals("aabFilePath", result.first) + assertEquals(false, result.second) + } + + @Test + fun `validateAndGetCommand should return error if aapt2Path is empty but isAapt2PathEnabled is true`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path", "gg.gg")) + .isAapt2PathEnabled(true) + .validateAndGetCommand() + assertEquals("aapt2Path", result.first) + assertEquals(false, result.second) + } + + @Test + fun `validateAndGetCommand should return error if signingMode is RELEASE but keystore information is missing`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .signingMode(SigningMode.RELEASE) + .validateAndGetCommand() + assertEquals("Check Keystore Info!", result.first) + assertEquals(false, result.second) + } + + @Test + fun `validateAndGetCommand should return valid command for RELEASE signing mode with all required parameters`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .signingMode(SigningMode.RELEASE) + .keyStorePath("/path/to/keystore.jks") + .keyStorePassword("keystorePassword") + .keyAlias("keyAlias") + .keyPassword("keyPassword") + .isUniversalMode(false).validateAndGetCommand() + assertEquals( + "java -jar \"bundletool.jar\" build-apks --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" --ks=/path/to/keystore.jks --ks-pass=pass:keystorePassword --ks-key-alias=keyAlias --key-pass=pass:keyPassword ", + result.first + ) + assertEquals(true, result.second) + } + + @Test + fun `validateAndGetCommand should return valid command for DEBUG signing mode without keystore information`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .signingMode(SigningMode.DEBUG) + .isUniversalMode(false) + .validateAndGetCommand() + assertEquals( + "java -jar \"bundletool.jar\" build-apks --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" ", + result.first + ) + assertEquals(true, result.second) + } + + @Test + fun `validateAndGetCommand should return valid command for verifying adb path`() { + val result = CommandBuilder() + .verifyAdbPath(true, "/path/to/adb") + .getAdbVerifyCommand() + assertEquals("\"/path/to/adb\" version", result) + } + + @Test + fun `validateAndGetCommand should return valid command for universal mode enabled`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .isUniversalMode(true) + .validateAndGetCommand() + assertEquals( + "java -jar \"bundletool.jar\" build-apks --mode=universal --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" ", + result.first + ) + assertEquals(true, result.second) + } + + @Test + fun `validateAndGetCommand should return valid command with overwrite enabled`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .isOverwrite(true) + .isUniversalMode(false).validateAndGetCommand() + assertEquals( + "java -jar \"bundletool.jar\" build-apks --overwrite --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" ", + result.first + ) + assertEquals(true, result.second) + } +} From f7a142351f8ffb1a4d992e0ae1987efc5832a82b Mon Sep 17 00:00:00 2001 From: Bitmap Date: Sun, 10 Mar 2024 17:19:28 +0530 Subject: [PATCH 4/5] Migrated command logic from main to Command Executor --- src/jvmMain/kotlin/Main.kt | 41 ++++++++++++-------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/src/jvmMain/kotlin/Main.kt b/src/jvmMain/kotlin/Main.kt index ce77061..5f1b132 100644 --- a/src/jvmMain/kotlin/Main.kt +++ b/src/jvmMain/kotlin/Main.kt @@ -43,8 +43,6 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import command.CommandBuilder import command.CommandExecutor -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import local.FileStorageHelper import ui.Styles import ui.components.ButtonWithToolTip @@ -58,13 +56,11 @@ import utils.FileDialogType import utils.FileHelper import utils.Log import utils.SigningMode +import utils.Strings 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 @Composable @Preview @@ -171,24 +167,13 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: .validateAndGetCommand() if (isValid) { Log.i("Command $cmd") - logs += "Executing Command : \n$cmd\n" - coroutineScope.launch(Dispatchers.IO) { - 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" + // logs += "Executing Command : \n$cmd\n" + CommandExecutor() + .executeCommand( + cmd, + coroutineScope, + onSuccess = { + logs += "$it\n" // Do further file operation after new apks is generated // From Auto Zip you can control further file operations. if (isAutoUnzip) { @@ -214,12 +199,12 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: if (fileStorageHelper.delete("path")) saveJarPath = false } + }, + onFailure = { + isLoading = false + logs += "Failed -> ${it.printStackTrace()}" } - } catch (e: Exception) { - isLoading = false - logs += "Failed -> ${e.printStackTrace()}" - } - } + ) } else { Log.i("Error $cmd") logs += "\nError -> $cmd" From 9ef3d962094ee0431e98f2c6a173e8c4d57ee8ef Mon Sep 17 00:00:00 2001 From: Bitmap Date: Sun, 10 Mar 2024 19:21:29 +0530 Subject: [PATCH 5/5] Added Device ID option to build apk for particular Serial ID --- src/jvmMain/kotlin/Main.kt | 52 +++++++++++++++ src/jvmMain/kotlin/command/CommandBuilder.kt | 65 +++++++++++-------- src/jvmMain/kotlin/utils/Strings.kt | 5 +- src/jvmMain/resources/device_fetch.svg | 1 + .../kotlin/command/CommandBuilderTest.kt | 39 +++++++++++ .../kotlin/command/CommandExecutorTest.kt | 55 ---------------- 6 files changed, 135 insertions(+), 82 deletions(-) create mode 100644 src/jvmMain/resources/device_fetch.svg delete mode 100644 src/jvmTest/kotlin/command/CommandExecutorTest.kt diff --git a/src/jvmMain/kotlin/Main.kt b/src/jvmMain/kotlin/Main.kt index 5f1b132..2f90b2b 100644 --- a/src/jvmMain/kotlin/Main.kt +++ b/src/jvmMain/kotlin/Main.kt @@ -89,6 +89,8 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: var isAdbSetupDone by remember { mutableStateOf(false) } var adbPath by remember { mutableStateOf("") } var showLoadingDialog by remember { mutableStateOf(Pair("", false)) } + var isDeviceIdEnabled by remember { mutableStateOf(false) } + var deviceSerialId by remember { mutableStateOf("") } // TODO: (Fixed this issue need to test more!) - Can't update file path once saved, For now Delete path.kb file inside storage directory. savedJarPath?.let { @@ -420,6 +422,56 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?, adbSavedPath: Strings.AUTO_UNZIP ) Spacer(modifier = Modifier.padding(8.dp)) + if (isAdbSetupDone) { + Text( + text = Strings.DEVICE_OPTIONS, + style = Styles.TextStyleBold(16.sp), + modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CheckboxWithText( + Strings.DEVICE_ID, + isDeviceIdEnabled, + onCheckedChange = { + isDeviceIdEnabled = it + }, + Strings.DEVICE_ID_INFO + ) + if (isDeviceIdEnabled) { + CustomTextField( + deviceSerialId, + Strings.SERIAL_ID, + forPassword = false, + onValueChange = { + deviceSerialId = it + } + ) + ButtonWithToolTip( + Strings.FETCH_DEVICES, + onClick = { + CommandExecutor() + .executeCommand( + CommandBuilder() + .getAdbFetchCommand(adbSavedPath!!), + coroutineScope, + onSuccess = { + logs += it + }, + onFailure = { + logs += it.printStackTrace() + } + ) + }, + Strings.FETCH_DEVICES_INFO, + icon = "device_fetch" + ) + } + } + Spacer(modifier = Modifier.padding(8.dp)) + } if (isLoading) { CircularProgressIndicator( modifier = Modifier.size(size = 40.dp) diff --git a/src/jvmMain/kotlin/command/CommandBuilder.kt b/src/jvmMain/kotlin/command/CommandBuilder.kt index 4fab55a..59168f8 100644 --- a/src/jvmMain/kotlin/command/CommandBuilder.kt +++ b/src/jvmMain/kotlin/command/CommandBuilder.kt @@ -1,7 +1,6 @@ package command import utils.SigningMode -import utils.Utils class CommandBuilder { private var bundletoolPath: String = "" @@ -16,6 +15,8 @@ class CommandBuilder { private var keyAlias: String = "" private var keyPassword: String = "" private var adbVerifyCommandExecute = Pair(false, "") + private var isDeviceIdEnabled: Boolean = false + private var adbSerialId: String = "" fun bundletoolPath(path: String) = apply { this.bundletoolPath = path } fun aabFilePath(path: Pair) = apply { this.aabFilePath = path } @@ -31,6 +32,10 @@ class CommandBuilder { fun verifyAdbPath(value: Boolean, path: String) = apply { this.adbVerifyCommandExecute = Pair(value, path) } + fun isDeviceSerialIdEnabled(value: Boolean) = apply { this.isDeviceIdEnabled = value } + + fun adbSerialId(value: String) = apply { this.adbSerialId = value } + fun getAdbVerifyCommand(): String { val (forVerify, path) = adbVerifyCommandExecute if (forVerify) { @@ -39,6 +44,10 @@ class CommandBuilder { return "" } + fun getAdbFetchCommand(adbPath: String): String { + return "\"${adbPath}\" devices" + } + fun validateAndGetCommand(): Pair { if (bundletoolPath.isEmpty()) { return Pair("bundletoolPath", false) @@ -52,36 +61,40 @@ class CommandBuilder { if (signingMode == SigningMode.RELEASE && (keyStorePath.isEmpty() || keyStorePassword.isEmpty() || keyAlias.isEmpty() || keyPassword.isEmpty())) { return Pair("Check Keystore Info!", false) } + if (isDeviceIdEnabled && adbSerialId.isEmpty()) { + return Pair("Invalid Serial ID", 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() + commandBuilder.append("java -jar \"${bundletoolPath}\" build-apks ") + if (isUniversalMode) { + commandBuilder.append("--mode=universal ") } - return "" + 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 ") + } + + if (isDeviceIdEnabled) { + commandBuilder.append("--device-id=$adbSerialId ") + } + + return commandBuilder.toString() } } \ No newline at end of file diff --git a/src/jvmMain/kotlin/utils/Strings.kt b/src/jvmMain/kotlin/utils/Strings.kt index cee16d7..9df72af 100644 --- a/src/jvmMain/kotlin/utils/Strings.kt +++ b/src/jvmMain/kotlin/utils/Strings.kt @@ -36,6 +36,9 @@ object Strings { const val ABD_SETUP_DONE = "Adb Connected" const val SETUP_ADB_INFO = "Setup ADB path to create builds based on connected device" const val DEVICE_ID = "Device Id" - const val DEVICE_ID_INFO = "Device Id based on the Serial Number" + const val DEVICE_ID_INFO = "Device Id based on the Serial Number. Use with Mode Universal to get one apk." const val VERIFYING_ADB_PATH = "Verifying ADB Path.." + const val SERIAL_ID = "Serial Id" + const val FETCH_DEVICES = "Fetch Devices" + const val FETCH_DEVICES_INFO = "Fetch Connected Devices Info in LogView" } \ No newline at end of file diff --git a/src/jvmMain/resources/device_fetch.svg b/src/jvmMain/resources/device_fetch.svg new file mode 100644 index 0000000..2d8124b --- /dev/null +++ b/src/jvmMain/resources/device_fetch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/jvmTest/kotlin/command/CommandBuilderTest.kt b/src/jvmTest/kotlin/command/CommandBuilderTest.kt index 53a089d..feeb665 100644 --- a/src/jvmTest/kotlin/command/CommandBuilderTest.kt +++ b/src/jvmTest/kotlin/command/CommandBuilderTest.kt @@ -56,6 +56,7 @@ class CommandBuilderTest { .keyAlias("keyAlias") .keyPassword("keyPassword") .isUniversalMode(false).validateAndGetCommand() + println(result) assertEquals( "java -jar \"bundletool.jar\" build-apks --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" --ks=/path/to/keystore.jks --ks-pass=pass:keystorePassword --ks-key-alias=keyAlias --key-pass=pass:keyPassword ", result.first @@ -71,6 +72,7 @@ class CommandBuilderTest { .signingMode(SigningMode.DEBUG) .isUniversalMode(false) .validateAndGetCommand() + println(result) assertEquals( "java -jar \"bundletool.jar\" build-apks --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" ", result.first @@ -93,6 +95,7 @@ class CommandBuilderTest { .aabFilePath(Pair("/path/to/", "file.aab")) .isUniversalMode(true) .validateAndGetCommand() + println(result) assertEquals( "java -jar \"bundletool.jar\" build-apks --mode=universal --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" ", result.first @@ -107,10 +110,46 @@ class CommandBuilderTest { .aabFilePath(Pair("/path/to/", "file.aab")) .isOverwrite(true) .isUniversalMode(false).validateAndGetCommand() + println(result) assertEquals( "java -jar \"bundletool.jar\" build-apks --overwrite --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" ", result.first ) assertEquals(true, result.second) } + + @Test + fun `validateAndGetCommand should return error when device id enabled but serial id is empty`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .isOverwrite(false) + .isUniversalMode(false) + .isDeviceSerialIdEnabled(true) + .adbSerialId("") + .validateAndGetCommand() + assertEquals( + "Invalid Serial ID", + result.first + ) + assertEquals(false, result.second) + } + + @Test + fun `validateAndGetCommand should return valid command with device id enabled`() { + val result = CommandBuilder() + .bundletoolPath("bundletool.jar") + .aabFilePath(Pair("/path/to/", "file.aab")) + .isOverwrite(false) + .isUniversalMode(false) + .isDeviceSerialIdEnabled(true) + .adbSerialId("RZCWC0EZLEH") + .validateAndGetCommand() + println(result) + assertEquals( + "java -jar \"bundletool.jar\" build-apks --bundle=\"/path/to/file.aab\" --output=\"/path/to/file.apks\" --device-id=RZCWC0EZLEH ", + result.first + ) + assertEquals(true, result.second) + } } diff --git a/src/jvmTest/kotlin/command/CommandExecutorTest.kt b/src/jvmTest/kotlin/command/CommandExecutorTest.kt deleted file mode 100644 index ce178c6..0000000 --- a/src/jvmTest/kotlin/command/CommandExecutorTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package command - -import junit.framework.TestCase.assertTrue -import kotlinx.coroutines.runBlocking -import org.hamcrest.MatcherAssert.assertThat -import org.junit.Before -import org.junit.Test - -class CommandExecutorTest { - private lateinit var commandExecutor: CommandExecutor - - @Before - fun setUp() { - commandExecutor = CommandExecutor() - } - - // These test cases may not work for MAC and Linux need to verify. - @Test - fun `execute valid command successfully`() { - val expectedOutput = "openjdk 11.0.18 2023-01-17" - val cmd = "java --version" - - runBlocking { - commandExecutor.executeCommand( - cmd, - this, - onSuccess = { - println("SUCCESS -> $it") - assertThat(expectedOutput, it.contains(expectedOutput)) - }, - onFailure = { - println("ERROR -> ${it.message}") - assertThat(expectedOutput, it.message?.contains(expectedOutput) ?: false) - } - ) - } - } - - @Test - fun `execute invalid command and handle failure`() { - val cmd = "invalid_command" - runBlocking { - commandExecutor.executeCommand( - cmd, - this, - onSuccess = { - assertTrue(it.isEmpty()) - }, - onFailure = { - assertTrue(it.message?.contains("The system cannot find the file specified") ?: false) - } - ) - } - } -} \ No newline at end of file