mirror of
https://github.com/izzy2lost/aab2apk.git
synced 2026-06-19 01:20:08 -07:00
Merge pull request #9 from iammohdzaki/feature/device-options
Feature/device options
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
name: kotlin_lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths:
|
||||
- "**/*.kt"
|
||||
@@ -9,7 +12,6 @@ on:
|
||||
jobs:
|
||||
ktlint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "checkout"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
+185
-61
@@ -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,26 +36,35 @@ 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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
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 local.FileStorageHelper
|
||||
import ui.Styles
|
||||
import ui.components.ButtonWithToolTip
|
||||
import ui.components.CheckboxWithText
|
||||
import ui.components.ChooseFileTextField
|
||||
import ui.components.CustomTextField
|
||||
import utils.*
|
||||
import ui.components.LoadingDialog
|
||||
import utils.Constant
|
||||
import utils.DBConstants
|
||||
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
|
||||
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 +86,60 @@ 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)) }
|
||||
var isDeviceIdEnabled by remember { mutableStateOf(false) }
|
||||
var deviceSerialId by remember { mutableStateOf("") }
|
||||
|
||||
|
||||
//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.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,9 +147,13 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
}
|
||||
}
|
||||
|
||||
if (showLoadingDialog.second) {
|
||||
LoadingDialog(showLoadingDialog.first)
|
||||
}
|
||||
|
||||
if (isExecute) {
|
||||
isExecute = false
|
||||
//Get Command to Execute
|
||||
// Get Command to Execute
|
||||
val (cmd, isValid) = CommandBuilder()
|
||||
.bundletoolPath(bundletoolPath)
|
||||
.aabFilePath(aabFilePath)
|
||||
@@ -97,24 +169,13 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
.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) {
|
||||
@@ -128,8 +189,8 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
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)
|
||||
}
|
||||
@@ -140,12 +201,12 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
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"
|
||||
@@ -158,20 +219,33 @@ 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,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
//Bundle tool select flow
|
||||
// Bundle tool select flow
|
||||
ChooseFileTextField(
|
||||
bundletoolPath,
|
||||
Strings.SELECT_BUNDLETOOL_JAR,
|
||||
@@ -348,6 +422,56 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
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)
|
||||
@@ -361,13 +485,13 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -385,17 +509,17 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -403,7 +527,7 @@ fun App(fileStorageHelper: FileStorageHelper, savedPath: String?) {
|
||||
modifier = Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
value = logs,
|
||||
textStyle = Styles.TextStyleMedium(16.sp),
|
||||
onValueChange = {},
|
||||
onValueChange = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -412,7 +536,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) {
|
||||
@@ -427,20 +551,20 @@ private fun FileDialog(
|
||||
dispose = FileDialog::dispose
|
||||
)
|
||||
|
||||
|
||||
fun main() = application {
|
||||
val fileStorageHelper = FileStorageHelper()
|
||||
//Check if path for bundletool exists in local storage
|
||||
val path = fileStorageHelper.read("path") as String?
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
+52
-20
@@ -1,5 +1,6 @@
|
||||
package command
|
||||
|
||||
import utils.SigningMode
|
||||
import utils.Utils
|
||||
|
||||
class CommandBuilder {
|
||||
private var bundletoolPath: String = ""
|
||||
@@ -13,6 +14,9 @@ class CommandBuilder {
|
||||
private var keyStorePassword: String = ""
|
||||
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<String, String>) = apply { this.aabFilePath = path }
|
||||
@@ -26,6 +30,24 @@ 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 isDeviceSerialIdEnabled(value: Boolean) = apply { this.isDeviceIdEnabled = value }
|
||||
|
||||
fun adbSerialId(value: String) = apply { this.adbSerialId = value }
|
||||
|
||||
fun getAdbVerifyCommand(): String {
|
||||
val (forVerify, path) = adbVerifyCommandExecute
|
||||
if (forVerify) {
|
||||
return "\"${path}\" version"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fun getAdbFetchCommand(adbPath: String): String {
|
||||
return "\"${adbPath}\" devices"
|
||||
}
|
||||
|
||||
fun validateAndGetCommand(): Pair<String, Boolean> {
|
||||
if (bundletoolPath.isEmpty()) {
|
||||
return Pair("bundletoolPath", false)
|
||||
@@ -39,30 +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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,5 +5,4 @@ class KiteDbException : RuntimeException {
|
||||
constructor(detailMessage: String?) : super(detailMessage)
|
||||
|
||||
constructor(detailMessage: String?, throwable: Throwable?) : super(detailMessage, throwable)
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
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.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
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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 = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package ui.components
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.border
|
||||
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
|
||||
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() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,15 +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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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!!")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,8 +28,17 @@ 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. 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"
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-120q-138 0-240.5-91.5T122-440h82q14 104 92.5 172T480-200q117 0 198.5-81.5T760-480q0-117-81.5-198.5T480-760q-69 0-129 32t-101 88h110v80H120v-240h80v94q51-64 124.5-99T480-840q75 0 140.5 28.5t114 77q48.5 48.5 77 114T840-480q0 75-28.5 140.5t-77 114q-48.5 48.5-114 77T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z"/></svg>
|
||||
|
After Width: | Height: | Size: 425 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user