diff --git a/.github/workflows/bundletool-android-arm64.yml b/.github/workflows/bundletool-android-arm64.yml index de9512e..2b28914 100644 --- a/.github/workflows/bundletool-android-arm64.yml +++ b/.github/workflows/bundletool-android-arm64.yml @@ -17,12 +17,22 @@ jobs: - name: Setup Docker buildx uses: docker/setup-buildx-action@v2 - - name: Build aarch64 bundletool using GraalVM aarch64 container + - name: Build aarch64 bundletool using buildx run: | - docker run --rm --platform linux/arm64 \ - -v "${{ github.workspace }}":/work -w /work \ - ghcr.io/graalvm/native-image:23.3.2-java21 \ - sh -c "apt-get update && apt-get install -y unzip zip build-essential clang pkg-config zlib1g-dev && native-image --no-fallback -jar bundletool/bundletool.jar -H:Name=bundletool-arm64" + docker buildx create --use || true + docker buildx build --load --platform linux/arm64 \ + -f - . <<'DOCKERFILE' + FROM ubuntu:22.04 + RUN apt-get update && apt-get install -y curl unzip zip build-essential clang pkg-config zlib1g-dev openjdk-21-jdk-headless + WORKDIR /work + COPY bundletool/bundletool.jar . + RUN curl -L -O https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-23.3.2/graalvm-ce-java21-linux-aarch64-23.3.2.tar.gz \ + && tar -xzf graalvm-ce-java21-linux-aarch64-23.3.2.tar.gz \ + && rm graalvm-ce-java21-linux-aarch64-23.3.2.tar.gz + ENV PATH=/work/graalvm-ce-java21-23.3.2/bin:$PATH + RUN native-image --no-fallback -jar bundletool.jar -H:Name=bundletool-arm64 + RUN ls -lh bundletool-arm64 + DOCKERFILE - name: Verify binary (if produced) run: | diff --git a/.github/workflows/bundletool-native.yml b/.github/workflows/bundletool-native.yml index c53f01b..131a7e8 100644 --- a/.github/workflows/bundletool-native.yml +++ b/.github/workflows/bundletool-native.yml @@ -12,13 +12,12 @@ jobs: uses: actions/checkout@v4 - name: Set up GraalVM (Java 21) with native-image - uses: graalvm/setup-graalvm@v2 + uses: graalvm/setup-graalvm@v1 with: - distribution: 'graalvm-ce' - graalvm-version: '23.3.2' + distribution: 'graalvm-community' + version: '23.3.2' java-version: '21' components: 'native-image' - github-token: '${{ secrets.GITHUB_TOKEN }}' - name: Install build deps run: | diff --git a/.gradle/8.14.3/checksums/checksums.lock b/.gradle/8.14.3/checksums/checksums.lock index 59b5931..ed754c9 100644 Binary files a/.gradle/8.14.3/checksums/checksums.lock and b/.gradle/8.14.3/checksums/checksums.lock differ diff --git a/.gradle/8.14.3/checksums/md5-checksums.bin b/.gradle/8.14.3/checksums/md5-checksums.bin index 045e918..619d759 100644 Binary files a/.gradle/8.14.3/checksums/md5-checksums.bin and b/.gradle/8.14.3/checksums/md5-checksums.bin differ diff --git a/.gradle/8.14.3/checksums/sha1-checksums.bin b/.gradle/8.14.3/checksums/sha1-checksums.bin index f276e3e..0bba2b6 100644 Binary files a/.gradle/8.14.3/checksums/sha1-checksums.bin and b/.gradle/8.14.3/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.14.3/executionHistory/executionHistory.bin b/.gradle/8.14.3/executionHistory/executionHistory.bin new file mode 100644 index 0000000..81be1bf Binary files /dev/null and b/.gradle/8.14.3/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.14.3/executionHistory/executionHistory.lock b/.gradle/8.14.3/executionHistory/executionHistory.lock index d973f4a..1a3d69b 100644 Binary files a/.gradle/8.14.3/executionHistory/executionHistory.lock and b/.gradle/8.14.3/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.14.3/fileHashes/fileHashes.bin b/.gradle/8.14.3/fileHashes/fileHashes.bin new file mode 100644 index 0000000..54d9757 Binary files /dev/null and b/.gradle/8.14.3/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.14.3/fileHashes/fileHashes.lock b/.gradle/8.14.3/fileHashes/fileHashes.lock index b50454b..6427f55 100644 Binary files a/.gradle/8.14.3/fileHashes/fileHashes.lock and b/.gradle/8.14.3/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.14.3/fileHashes/resourceHashesCache.bin b/.gradle/8.14.3/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..b90b36c Binary files /dev/null and b/.gradle/8.14.3/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index f2c2734..41e22fd 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..6bc071d Binary files /dev/null and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000..6562c6c Binary files /dev/null and b/.gradle/file-system.probe differ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e5f4346..5d75516 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,6 +3,11 @@ plugins { kotlin("android") } +repositories { + google() + mavenCentral() +} + android { namespace = "com.aab2apk" compileSdk = 36 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 09c19f1..84c6ced 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/assets/bundletool.jar b/app/src/main/assets/bundletool.jar new file mode 100644 index 0000000..b95c3be Binary files /dev/null and b/app/src/main/assets/bundletool.jar differ diff --git a/app/src/main/java/com/aab2apk/AabToApkManager.kt b/app/src/main/java/com/aab2apk/AabToApkManager.kt index feafcbe..a92c500 100644 --- a/app/src/main/java/com/aab2apk/AabToApkManager.kt +++ b/app/src/main/java/com/aab2apk/AabToApkManager.kt @@ -41,32 +41,49 @@ object AabToApkManager { } } - // Ensure a bundletool asset (if packaged) is installed to filesDir and set executable - val bundletoolFile = File(context.filesDir, "bundletool") + // Check for bundletool.jar in assets and copy to internal storage + val bundletoolJarFile = File(context.filesDir, "bundletool.jar") try { - if (!bundletoolFile.exists()) { - // Attempt to copy bundled asset (if present) into filesDir - context.assets.open("bundletool").use { input -> - bundletoolFile.outputStream().use { output -> input.copyTo(output) } + if (!bundletoolJarFile.exists()) { + // Attempt to copy bundled jar (if present) into filesDir + context.assets.open("bundletool.jar").use { input -> + bundletoolJarFile.outputStream().use { output -> input.copyTo(output) } } - bundletoolFile.setExecutable(true) } } catch (e: Exception) { - // If asset not present or copy failed, we'll proceed to check for an existing executable. + // If jar not present or copy failed, we'll proceed to check for an existing jar. } - if (bundletoolFile.exists() && bundletoolFile.canExecute()) { + + if (bundletoolJarFile.exists()) { // Build output path val outFile = File(context.filesDir, "output.apks") - val cmd = arrayListOf(bundletoolFile.absolutePath, "build-apks", "--bundle=${aabFile.absolutePath}", "--output=${outFile.absolutePath}", "--mode=universal") + + // Use Java runtime to execute bundletool.jar + val javaHome = System.getProperty("java.home") + val javaExecutable = File(javaHome, "bin/java").absolutePath + + val cmd = arrayListOf( + javaExecutable, + "-jar", + bundletoolJarFile.absolutePath, + "build-apks", + "--bundle=${aabFile.absolutePath}", + "--output=${outFile.absolutePath}", + "--mode=universal" + ) + Log.i(TAG, "Running: ${cmd.joinToString(" ")}") val procBuilder = ProcessBuilder(cmd) procBuilder.redirectErrorStream(true) + procBuilder.environment()["JAVA_HOME"] = javaHome + val proc = procBuilder.start() val stdout = StringBuilder() proc.inputStream.bufferedReader().useLines { lines -> lines.forEach { stdout.append(it).append("\n") } } val exit = proc.waitFor() + if (exit == 0) { // If outputDirUri provided, copy the result there if (outputDirUri != null) { @@ -86,9 +103,9 @@ object AabToApkManager { return@withContext Result.failure(Exception("bundletool failed with exit $exit\n${stdout}")) } } else { - // No bundletool available — inform user what to do - val msg = """bundletool executable not found in app files. -To convert an AAB to APK on-device you must provide a bundletool binary built for Android and place it at: ${context.filesDir}/bundletool + // No bundletool.jar available — inform user what to do + val msg = """bundletool.jar not found in app assets. +To convert an AAB to APK on-device you must place bundletool.jar at: app/src/main/assets/bundletool.jar Alternatively, convert the AAB on a desktop using bundletool.jar: java -jar bundletool-all.jar build-apks --bundle=app.aab --output=app.apks --mode=universal Then transfer resulting .apks/.apks.zip to the phone.""" diff --git a/app/src/main/java/com/aab2apk/Main.kt b/app/src/main/java/com/aab2apk/Main.kt deleted file mode 100644 index 693b705..0000000 --- a/app/src/main/java/com/aab2apk/Main.kt +++ /dev/null @@ -1,575 +0,0 @@ -package com.aab2apk.original - -import androidx.compose.desktop.ui.tooling.preview.Preview -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.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 -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.loadSvgPainter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.useResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -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.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 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.net.URI - -@Composable -@Preview -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("") } - var bundletoolPath by remember { mutableStateOf("") } - var aabFilePath by remember { mutableStateOf(Pair("", "")) } - var isLoading by remember { mutableStateOf(false) } - var isOpen by remember { mutableStateOf(false) } - var isExecute by remember { mutableStateOf(false) } - var fileDialogType by remember { mutableStateOf(0) } - var saveJarPath by remember { mutableStateOf(false) } - var isOverwrite by remember { mutableStateOf(false) } - var isAapt2PathEnabled by remember { mutableStateOf(false) } - var aapt2Path by remember { mutableStateOf("") } - var isUniversalMode by remember { mutableStateOf(true) } - var signingMode by remember { mutableStateOf(SigningMode.DEBUG) } - var keyStorePath by remember { mutableStateOf("") } - var keyStorePassword by remember { mutableStateOf("") } - var keyAlias by remember { mutableStateOf("") } - var keyPassword by remember { mutableStateOf("") } - 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: (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) - } - } - } - } - - if (showLoadingDialog.second) { - LoadingDialog(showLoadingDialog.first) - } - - if (isExecute) { - isExecute = false - // Get Command to Execute - val (cmd, isValid) = CommandBuilder() - .bundletoolPath(bundletoolPath) - .aabFilePath(aabFilePath) - .isOverwrite(isOverwrite) - .isUniversalMode(isUniversalMode) - .isAapt2PathEnabled(isAapt2PathEnabled) - .aapt2Path(aapt2Path) - .signingMode(signingMode) - .keyStorePath(keyStorePath) - .keyStorePassword(keyStorePassword) - .keyAlias(keyAlias) - .keyPassword(keyPassword) - .validateAndGetCommand() - if (isValid) { - Log.i("Command $cmd") - // logs += "Executing Command : \n$cmd\n" - 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) { - FileHelper.performFileOperations(aabFilePath.first, aabFilePath.second) { status, message -> - isLoading = false - Log.i("STATUS - $status\nMESSAGE - $message") - logs += "\nFiles Operations Starting...\n$message\n" - } - } else { - logs += "\nFile will be saved at ${aabFilePath.first.removeSuffix("\\")}.\n" - 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. - if (savedJarPath == null && saveJarPath) { - fileStorageHelper.save("path", bundletoolPath) - } - // If we have saved path in file storage, and it is not changed and save jar path is checked. Then, we can update the value in file storage. - else if (savedJarPath != null && savedJarPath != bundletoolPath && saveJarPath) { - fileStorageHelper.save("path", bundletoolPath) - } else { - if (fileStorageHelper.delete("path")) - saveJarPath = false - } - }, - onFailure = { - isLoading = false - logs += "Failed -> ${it.printStackTrace()}" - } - ) - } else { - Log.i("Error $cmd") - logs += "\nError -> $cmd" - isLoading = false - } - } - - MaterialTheme { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.Start - ) { - Spacer(modifier = Modifier.padding(12.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 - ChooseFileTextField( - bundletoolPath, - Strings.SELECT_BUNDLETOOL_JAR, - onSelect = { - fileDialogType = FileDialogType.BUNDLETOOL - isOpen = true - } - ) - CheckboxWithText( - Strings.SAVE_JAR_PATH, - saveJarPath, - onCheckedChange = { - saveJarPath = it - }, - Strings.SAVE_JAR_PATH_INFO - ) - } - val downloadInfo = buildAnnotatedString { - withStyle(style = SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)) { - append(Strings.DOWNLOAD_BUNDLETOOL) - } - addStringAnnotation( - tag = Strings.URL, - annotation = Constant.BUNDLE_DOWNLOAD_LINK, - start = 0, - end = length - ) - } - ClickableText( - text = downloadInfo, - style = Styles.TextStyleMedium(14.sp), - modifier = Modifier.padding(start = 16.dp), - onClick = { offset -> - val annotations = downloadInfo.getStringAnnotations(Strings.URL, offset, offset) - if (annotations.isNotEmpty()) { - val uri = URI(annotations.first().item) - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(uri) - } else { - // Desktop not supported, handle as necessary - } - } - } - ) - Spacer(modifier = Modifier.padding(8.dp)) - ChooseFileTextField( - "${aabFilePath.first}${aabFilePath.second}", - Strings.SELECT_AAB_FILE, - onSelect = { - fileDialogType = FileDialogType.AAB - isOpen = true - } - ) - Spacer(modifier = Modifier.padding(8.dp)) - Text( - text = Strings.OPTIONS_FOR_BUILD_APKS, - style = Styles.TextStyleBold(20.sp), - modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween - ) { - CheckboxWithText( - Strings.OVERWRITE, - isOverwrite, - onCheckedChange = { - isOverwrite = it - }, - Strings.OVERWRITE_INFO - ) - CheckboxWithText( - Strings.MODE_UNIVERSAL, - isUniversalMode, - onCheckedChange = { - isUniversalMode = it - }, - Strings.MODE_UNIVERSAL_INFO - ) - CheckboxWithText( - Strings.AAPT2_PATH, - isAapt2PathEnabled, - onCheckedChange = { - isAapt2PathEnabled = it - }, - Strings.AAPT2_PATH_INFO - ) - } - if (isAapt2PathEnabled) { - ChooseFileTextField( - aapt2Path, - Strings.SELECT_AAPT2_FILE, - onSelect = { - fileDialogType = FileDialogType.AAPT2 - isOpen = true - } - ) - } - Text( - text = Strings.SIGNING_MODE, - style = Styles.TextStyleBold(16.sp), - modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - ) - Row( - horizontalArrangement = Arrangement.SpaceBetween - ) { - CheckboxWithText( - Strings.DEBUG, - signingMode == SigningMode.DEBUG, - onCheckedChange = { - signingMode = if (it) SigningMode.DEBUG - else SigningMode.RELEASE - } - ) - CheckboxWithText( - Strings.RELEASE, - signingMode == SigningMode.RELEASE, - onCheckedChange = { - signingMode = if (it) SigningMode.RELEASE - else SigningMode.DEBUG - } - ) - } - if (signingMode == SigningMode.RELEASE) { - Row( - horizontalArrangement = Arrangement.SpaceBetween - ) { - ChooseFileTextField( - keyStorePath, - Strings.KEYSTORE_PATH, - onSelect = { - fileDialogType = FileDialogType.KEY_STORE_PATH - isOpen = true - } - ) - CustomTextField( - keyStorePassword, - Strings.KEYSTORE_PASSWORD, - onValueChange = { - keyStorePassword = it - } - ) - } - Row( - horizontalArrangement = Arrangement.SpaceBetween - ) { - CustomTextField( - keyAlias, - Strings.KEY_ALIAS, - forPassword = false, - onValueChange = { - keyAlias = it - } - ) - CustomTextField( - keyPassword, - Strings.KEY_PASSWORD, - onValueChange = { - keyPassword = it - } - ) - } - } - Text( - text = Strings.FILE_OPTIONS, - style = Styles.TextStyleBold(16.sp), - modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - ) - CheckboxWithText( - Strings.AUTO_UNZIP, - isAutoUnzip, - onCheckedChange = { - isAutoUnzip = it - }, - 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) - .padding(start = 16.dp, top = 0.dp, end = 0.dp, bottom = 0.dp), - strokeWidth = 4.dp - ) - } else { - Button( - onClick = { - isLoading = true - isExecute = true - }, - modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp) - .wrapContentWidth() - ) { - Text( - text = Strings.EXECUTE, - style = Styles.TextStyleMedium(16.sp), - color = Color.White, - fontWeight = FontWeight.Medium - ) - } - } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 8.dp), - verticalAlignment = Alignment.Bottom - ) { - Text( - text = Strings.LOGS_VIEW, - style = Styles.TextStyleBold(20.sp), - modifier = Modifier.padding(start = 0.dp, end = 0.dp, bottom = 8.dp) - ) - Button( - modifier = Modifier.padding(end = 0.dp, bottom = 8.dp), - onClick = { - logs = "" - } - ) { - Text( - text = Strings.CLEAR_LOGS, - 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) - ) - } - } - TextField( - modifier = Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 16.dp), - value = logs, - textStyle = Styles.TextStyleMedium(16.sp), - onValueChange = {} - ) - } - } -} - -@Composable -private fun FileDialog( - parent: Frame? = null, - onCloseRequest: (fileName: String?, directory: String?) -> Unit -) = AwtWindow( - create = { - object : FileDialog(parent, Strings.CHOOSE_FILE, LOAD) { - override fun setVisible(value: Boolean) { - super.setVisible(value) - if (value) { - onCloseRequest(file, directory) - } - } - } - }, - dispose = FileDialog::dispose -) - -fun main() = application { - val fileStorageHelper = FileStorageHelper() - // 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? - val icon = painterResource("launcher.png") - Log.showLogs = true - Window( - icon = icon, - onCloseRequest = ::exitApplication, - state = rememberWindowState( - width = 1200.dp, height = 1000.dp, - position = WindowPosition(Alignment.Center) - ), - title = Strings.APP_NAME - ) { - App(fileStorageHelper, path, adbPath) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/aab2apk/command/CommandBuilder.kt b/app/src/main/java/com/aab2apk/command/CommandBuilder.kt index 426b6cf..354f0a4 100644 --- a/app/src/main/java/com/aab2apk/command/CommandBuilder.kt +++ b/app/src/main/java/com/aab2apk/command/CommandBuilder.kt @@ -1,9 +1,9 @@ -package com.aab2apk.original +package com.aab2apk.command -import utils.Log -import utils.SigningMode -import utils.Utils +import com.aab2apk.utils.Log +import com.aab2apk.utils.SigningMode +import com.aab2apk.utils.Utils class CommandBuilder { private var bundletoolPath: String = "" diff --git a/app/src/main/java/com/aab2apk/command/CommandExecutor.kt b/app/src/main/java/com/aab2apk/command/CommandExecutor.kt index 871b386..55ca529 100644 --- a/app/src/main/java/com/aab2apk/command/CommandExecutor.kt +++ b/app/src/main/java/com/aab2apk/command/CommandExecutor.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.command import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/com/aab2apk/local/FileStorageHelper.kt b/app/src/main/java/com/aab2apk/local/FileStorageHelper.kt index 7c29efa..b8f2269 100644 --- a/app/src/main/java/com/aab2apk/local/FileStorageHelper.kt +++ b/app/src/main/java/com/aab2apk/local/FileStorageHelper.kt @@ -1,74 +1,43 @@ -package com.aab2apk.original +package com.aab2apk.local +import android.content.Context +import android.content.SharedPreferences -import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.nio.file.Paths +class FileStorageHelper(private val context: Context) { -const val DB_PATH = "/storage" + private val prefs: SharedPreferences = context.getSharedPreferences("aab2apk_prefs", Context.MODE_PRIVATE) -class FileStorageHelper { - - private lateinit var kryo: Kryo - - init { - initializeDir() - } - - private fun getKryo(): Kryo { - return if (this::kryo.isInitialized) { - kryo - } else { - val kryo = Kryo() - kryo.register(KiteTable::class.java) - kryo + fun save(key: String, value: Any) { + when (value) { + is String -> prefs.edit().putString(key, value).apply() + is Int -> prefs.edit().putInt(key, value).apply() + is Boolean -> prefs.edit().putBoolean(key, value).apply() + is Float -> prefs.edit().putFloat(key, value).apply() + is Long -> prefs.edit().putLong(key, value).apply() + else -> prefs.edit().putString(key, value.toString()).apply() } } - private fun initializeDir() { - val file = File(getCurrentDir()) - file.mkdir() - } - - private fun getCurrentDir(): String { - return Paths.get("").toAbsolutePath().toString() + DB_PATH - } - - private fun getPath(key: String): String { - return "${getCurrentDir()}/$key.kb" - } - - fun save(key: String, value: E) { - var kryoOutput: Output? - try { - val kiteTable = KiteTable(value) - val fileStream = FileOutputStream(File(getPath(key))) - kryoOutput = Output(fileStream) - getKryo().writeObject(kryoOutput, kiteTable) - kryoOutput.flush() - fileStream.flush() - kryoOutput.close() - } catch (e: Exception) { - throw KiteDbException(e.message) + @Suppress("UNCHECKED_CAST") + fun read(key: String, defaultValue: T? = null): T? { + return when (defaultValue) { + is String -> prefs.getString(key, defaultValue) as T? + is Int -> prefs.getInt(key, defaultValue) as T? + is Boolean -> prefs.getBoolean(key, defaultValue) as T? + is Float -> prefs.getFloat(key, defaultValue) as T? + is Long -> prefs.getLong(key, defaultValue) as T? + null -> when { + prefs.contains(key) -> prefs.getString(key, null) as T? + else -> null + } + else -> prefs.getString(key, defaultValue.toString()) as T? } } - fun read(key: String): Any? { - val keyFile = File(getPath(key)) - if (!keyFile.exists()) return null - val kryoInput = Input(FileInputStream(keyFile)) - val kiteTable = getKryo().readObject(kryoInput, KiteTable::class.java) - return kiteTable.mContent - } - fun delete(key: String): Boolean { - val keyFile = File(getPath(key)) - return if (keyFile.exists()) { - keyFile.delete() + return if (prefs.contains(key)) { + prefs.edit().remove(key).apply() + true } else { false } diff --git a/app/src/main/java/com/aab2apk/local/KiteDbException.kt b/app/src/main/java/com/aab2apk/local/KiteDbException.kt index 060882c..88be2a4 100644 --- a/app/src/main/java/com/aab2apk/local/KiteDbException.kt +++ b/app/src/main/java/com/aab2apk/local/KiteDbException.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.local class KiteDbException : RuntimeException { diff --git a/app/src/main/java/com/aab2apk/local/KiteTable.kt b/app/src/main/java/com/aab2apk/local/KiteTable.kt index 2703be5..5085447 100644 --- a/app/src/main/java/com/aab2apk/local/KiteTable.kt +++ b/app/src/main/java/com/aab2apk/local/KiteTable.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.local class KiteTable { diff --git a/app/src/main/java/com/aab2apk/ui/Styles.kt b/app/src/main/java/com/aab2apk/ui/Styles.kt index 98ad5cf..d745313 100644 --- a/app/src/main/java/com/aab2apk/ui/Styles.kt +++ b/app/src/main/java/com/aab2apk/ui/Styles.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui import androidx.compose.ui.text.TextStyle diff --git a/app/src/main/java/com/aab2apk/ui/Typography.kt b/app/src/main/java/com/aab2apk/ui/Typography.kt index 0ab8568..af6ef1c 100644 --- a/app/src/main/java/com/aab2apk/ui/Typography.kt +++ b/app/src/main/java/com/aab2apk/ui/Typography.kt @@ -1,22 +1,12 @@ -package com.aab2apk.original +package com.aab2apk.ui - -import androidx.compose.material.Typography +import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.platform.Font 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) -) - +// Using default system fonts for Android val typography = Typography( - defaultFontFamily = codeFontFamily, h1 = TextStyle( fontWeight = FontWeight.Light, fontSize = 96.sp, diff --git a/app/src/main/java/com/aab2apk/ui/components/ButtonWithToolTip.kt b/app/src/main/java/com/aab2apk/ui/components/ButtonWithToolTip.kt index 48497ce..139bf83 100644 --- a/app/src/main/java/com/aab2apk/ui/components/ButtonWithToolTip.kt +++ b/app/src/main/java/com/aab2apk/ui/components/ButtonWithToolTip.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui.components import androidx.compose.foundation.ExperimentalFoundationApi @@ -25,7 +25,7 @@ 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 +import com.aab2apk.ui.Styles @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/app/src/main/java/com/aab2apk/ui/components/CheckboxWithText.kt b/app/src/main/java/com/aab2apk/ui/components/CheckboxWithText.kt index f2dcb08..0db4d3e 100644 --- a/app/src/main/java/com/aab2apk/ui/components/CheckboxWithText.kt +++ b/app/src/main/java/com/aab2apk/ui/components/CheckboxWithText.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui.components import androidx.compose.foundation.ExperimentalFoundationApi @@ -23,8 +23,8 @@ import androidx.compose.ui.res.useResource import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import ui.Styles -import utils.TestTags +import com.aab2apk.ui.Styles +import com.aab2apk.utils.TestTags @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/app/src/main/java/com/aab2apk/ui/components/ChooseFileTextField.kt b/app/src/main/java/com/aab2apk/ui/components/ChooseFileTextField.kt index f958114..4a3d374 100644 --- a/app/src/main/java/com/aab2apk/ui/components/ChooseFileTextField.kt +++ b/app/src/main/java/com/aab2apk/ui/components/ChooseFileTextField.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui.components import androidx.compose.foundation.BorderStroke @@ -25,7 +25,7 @@ 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 +import com.aab2apk.ui.Styles @Composable fun ChooseFileTextField(value: String, label: String, onSelect: () -> Unit) { diff --git a/app/src/main/java/com/aab2apk/ui/components/CustomTextField.kt b/app/src/main/java/com/aab2apk/ui/components/CustomTextField.kt index 7f08a10..e3a4c8b 100644 --- a/app/src/main/java/com/aab2apk/ui/components/CustomTextField.kt +++ b/app/src/main/java/com/aab2apk/ui/components/CustomTextField.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui.components import androidx.compose.foundation.BorderStroke @@ -23,7 +23,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import ui.Styles +import com.aab2apk.ui.Styles @Composable fun CustomTextField(value: String, label: String, forPassword: Boolean = true, onValueChange: (String) -> Unit) { diff --git a/app/src/main/java/com/aab2apk/ui/components/LoadingDialog.kt b/app/src/main/java/com/aab2apk/ui/components/LoadingDialog.kt index 7a8eab5..e6bffde 100644 --- a/app/src/main/java/com/aab2apk/ui/components/LoadingDialog.kt +++ b/app/src/main/java/com/aab2apk/ui/components/LoadingDialog.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui.components import androidx.compose.foundation.BorderStroke @@ -20,7 +20,7 @@ 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 +import com.aab2apk.ui.Styles @Composable fun LoadingDialog(text: String) { diff --git a/app/src/main/java/com/aab2apk/ui/components/TextWithIcon.kt b/app/src/main/java/com/aab2apk/ui/components/TextWithIcon.kt index 495a9a0..22fa7f9 100644 --- a/app/src/main/java/com/aab2apk/ui/components/TextWithIcon.kt +++ b/app/src/main/java/com/aab2apk/ui/components/TextWithIcon.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.ui.components import androidx.compose.foundation.clickable @@ -15,7 +15,7 @@ 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 +import com.aab2apk.ui.Styles @Composable fun TextWithIcon(label: String, onIconClick: () -> Unit) { diff --git a/app/src/main/java/com/aab2apk/utils/Constant.kt b/app/src/main/java/com/aab2apk/utils/Constant.kt index 6c89efe..11deae6 100644 --- a/app/src/main/java/com/aab2apk/utils/Constant.kt +++ b/app/src/main/java/com/aab2apk/utils/Constant.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.utils object Constant { diff --git a/app/src/main/java/com/aab2apk/utils/FileHelper.kt b/app/src/main/java/com/aab2apk/utils/FileHelper.kt index c931584..ed7500f 100644 --- a/app/src/main/java/com/aab2apk/utils/FileHelper.kt +++ b/app/src/main/java/com/aab2apk/utils/FileHelper.kt @@ -1,6 +1,9 @@ -package com.aab2apk.original +package com.aab2apk.utils +import com.aab2apk.utils.Constant +import com.aab2apk.utils.FileUtils +import com.aab2apk.utils.Log import java.io.File import java.io.IOException diff --git a/app/src/main/java/com/aab2apk/utils/FileUtils.kt b/app/src/main/java/com/aab2apk/utils/FileUtils.kt index c55f1b0..b77eaec 100644 --- a/app/src/main/java/com/aab2apk/utils/FileUtils.kt +++ b/app/src/main/java/com/aab2apk/utils/FileUtils.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.utils import java.io.BufferedOutputStream diff --git a/app/src/main/java/com/aab2apk/utils/Log.kt b/app/src/main/java/com/aab2apk/utils/Log.kt index e39a2e5..dde13c7 100644 --- a/app/src/main/java/com/aab2apk/utils/Log.kt +++ b/app/src/main/java/com/aab2apk/utils/Log.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.utils object Log { diff --git a/app/src/main/java/com/aab2apk/utils/Strings.kt b/app/src/main/java/com/aab2apk/utils/Strings.kt index c3b9352..0007044 100644 --- a/app/src/main/java/com/aab2apk/utils/Strings.kt +++ b/app/src/main/java/com/aab2apk/utils/Strings.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.utils object Strings { diff --git a/app/src/main/java/com/aab2apk/utils/TestTags.kt b/app/src/main/java/com/aab2apk/utils/TestTags.kt index a483d69..580038e 100644 --- a/app/src/main/java/com/aab2apk/utils/TestTags.kt +++ b/app/src/main/java/com/aab2apk/utils/TestTags.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.utils object TestTags { diff --git a/app/src/main/java/com/aab2apk/utils/Utils.kt b/app/src/main/java/com/aab2apk/utils/Utils.kt index 77e2e0c..eb85df4 100644 --- a/app/src/main/java/com/aab2apk/utils/Utils.kt +++ b/app/src/main/java/com/aab2apk/utils/Utils.kt @@ -1,4 +1,4 @@ -package com.aab2apk.original +package com.aab2apk.utils import java.util.Locale diff --git a/build.gradle.kts b/build.gradle.kts index f3b54e7..9cebca9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,3 +2,11 @@ plugins { id("com.android.application") version "8.13.1" apply false kotlin("jvm") version (extra["kotlin.version"] as String) apply false } + +allprojects { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} diff --git a/gradle.properties b/gradle.properties index 51ee65e..2e2126c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,3 +3,4 @@ kotlin.version=1.8.0 agp.version=8.13.1 compose.version=1.6.0 ndk.version=28.2.13676358 +android.useAndroidX=true