- 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
This commit is contained in:
Bitmap
2024-03-10 19:42:35 +05:30
parent cb1e6e61cf
commit ab3e4c2624
12 changed files with 336 additions and 22 deletions
+3
View File
@@ -1,6 +1,9 @@
name: kotlin_lint
on:
push:
branches:
- "*"
pull_request:
paths:
- "**/*.kt"
+71 -16
View File
@@ -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)
}
}
@@ -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<String, String>) = 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<String, Boolean> {
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 ")
}
@@ -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)
}
}
}
}
@@ -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)
)
}
}
}
}
@@ -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)
)
@@ -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()
}
}
}
}
}
@@ -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() },
)
}
}
+8 -2
View File
@@ -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
}
+7 -1
View File
@@ -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.."
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/></svg>

After

Width:  |  Height:  |  Size: 159 B

+3 -1
View File
@@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24">
<path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 682 B