diff --git a/.vscode/plugins.json b/.vscode/plugins.json deleted file mode 100644 index 1f29ca1ec..000000000 --- a/.vscode/plugins.json +++ /dev/null @@ -1 +0,0 @@ -{"plugins":["cordova-clipboard","cordova-plugin-buildinfo","cordova-plugin-device","cordova-plugin-file","cordova-plugin-sftp","cordova-plugin-server","cordova-plugin-ftp","cordova-plugin-sdcard","cordova-plugin-browser","cordova-plugin-iap","cordova-plugin-system","cordova-plugin-advanced-http"]} \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..48354a3df --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,101 @@ +# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Cordova plugins for Capacitor +capacitor-cordova-android-plugins + +# Copied web assets +app/src/main/assets/public + +# Generated Config files +app/src/main/assets/capacitor.config.json +app/src/main/assets/capacitor.plugins.json +app/src/main/res/xml/config.xml diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 000000000..043df802a --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.npmkeep diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..7bfb05f96 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,82 @@ +apply plugin: 'com.android.application' +apply from: 'capacitor.build.gradle' +apply plugin: 'org.jetbrains.kotlin.android' + +android { + kotlinOptions { + jvmTarget = '21' + } + + namespace "com.foxdebug.acode" + compileSdk rootProject.ext.compileSdkVersion + android.buildFeatures.buildConfig true + defaultConfig { + applicationId "com.foxdebug.acode" + + + //sdk versions and other stuff should be changed from variables.build.gradle + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + + //fdroid expect this as literals so no fancy logic + versionCode 957 + versionName "1.11.1" + + flavorDimensions += "default" + productFlavors{ + free{ + applicationId = "com.foxdebug.acodefree" + dimension = "default" + buildConfigField("boolean", "PAID", "false") + } + + paid{ + applicationId = "com.foxdebug.acode" + dimension = "default" + buildConfigField("boolean", "PAID", "true") + } + } + + + aaptOptions { + // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. + // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 + ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +repositories { + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation project(':capacitor-android') + implementation project(':capacitor-cordova-android-plugins') + + //sftp + implementation "com.sshtools:maverick-synergy-client:3.1.2" +} + + + +try { + def servicesJSON = file('google-services.json') + if (servicesJSON.text) { + apply plugin: 'com.google.gms.google-services' + } +} catch(Exception e) { + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") +} diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle new file mode 100644 index 000000000..368208f73 --- /dev/null +++ b/android/app/capacitor.build.gradle @@ -0,0 +1,32 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } +} + +apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" +dependencies { + implementation project(':capacitor-app') + implementation project(':capacitor-filesystem') + implementation "androidx.webkit:webkit:1.4.0" + implementation "commons-net:commons-net:3.11.1" + implementation "com.android.billingclient:billing:6.0.1" + implementation "commons-io:commons-io:2.11.0" + implementation "commons-codec:commons-codec:1.10" + implementation "androidx.documentfile:documentfile:1.0.1" + implementation "org.nanohttpd:nanohttpd:+" + implementation "androidx.documentfile:documentfile:1.0.1" + implementation "commons-io:commons-io:2.11.0" + implementation "com.sshtools:maverick-synergy-client:3.1.2" + implementation "com.sshtools:maverick-bc:3.1.2" + implementation "androidx.core:core:1.6.0" + implementation "androidx.core:core-google-shortcuts:1.0.0" +} +apply from: "../../node_modules/cordova-plugin-buildinfo/src/android/BuildInfo.gradle" + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/app/src/free/AndroidManifest.xml b/android/app/src/free/AndroidManifest.xml new file mode 100644 index 000000000..29c97717e --- /dev/null +++ b/android/app/src/free/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/android/app/src/free/res/values/colors.xml b/android/app/src/free/res/values/colors.xml new file mode 100644 index 000000000..80036ed73 --- /dev/null +++ b/android/app/src/free/res/values/colors.xml @@ -0,0 +1,5 @@ + + + @android:color/white + @android:color/white + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..74c4a98a9 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/com/foxdebug/acode/Acode.kt b/android/app/src/main/java/com/foxdebug/acode/Acode.kt new file mode 100644 index 000000000..affdc4dc8 --- /dev/null +++ b/android/app/src/main/java/com/foxdebug/acode/Acode.kt @@ -0,0 +1,14 @@ +package com.foxdebug.acode + +import android.app.Application + +class Acode : Application() { + companion object{ + lateinit var instance: Acode + private set + } + override fun onCreate() { + super.onCreate() + instance = this + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/foxdebug/acode/MainActivity.kt b/android/app/src/main/java/com/foxdebug/acode/MainActivity.kt new file mode 100644 index 000000000..ada620250 --- /dev/null +++ b/android/app/src/main/java/com/foxdebug/acode/MainActivity.kt @@ -0,0 +1,60 @@ +package com.foxdebug.acode + +import android.content.Context +import android.os.Build +import android.os.Bundle +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.lifecycleScope +import com.foxdebug.acode.plugins.NativeLayer +import com.getcapacitor.BridgeActivity +import kotlinx.coroutines.CoroutineScope +import java.lang.ref.WeakReference + + +class MainActivity : BridgeActivity() { + + companion object { + private var activityRef: WeakReference? = WeakReference(null) + + fun getActivityContext(): Context? { + return activityRef?.get() + } + + val lifeCycleScope: CoroutineScope + get() { + return activityRef?.get()?.lifecycleScope ?: throw IllegalStateException("Activity is not available") + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + //register plugins before calling super + registerPlugin(NativeLayer::class.java) + + super.onCreate(savedInstanceState) + activityRef = WeakReference(this) + + // only apply insets for android 13+ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets -> + val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) + val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) + + v.setPadding( + systemBarsInsets.left, + systemBarsInsets.top, + systemBarsInsets.right, + if (isImeVisible) imeInsets.bottom else systemBarsInsets.bottom + ) + insets + } + } + } + + override fun onDestroy() { + super.onDestroy() + activityRef = WeakReference(null) + activityRef = null + } +} diff --git a/android/app/src/main/java/com/foxdebug/acode/Utils.kt b/android/app/src/main/java/com/foxdebug/acode/Utils.kt new file mode 100644 index 000000000..d4d4b31b3 --- /dev/null +++ b/android/app/src/main/java/com/foxdebug/acode/Utils.kt @@ -0,0 +1,9 @@ +package com.foxdebug.acode + +import com.foxdebug.acode.plugins.NativeLayer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +fun NativeLayer.runOnUiThread(callback: CoroutineScope.() -> Unit) { + scope.launch { callback() } +} diff --git a/android/app/src/main/java/com/foxdebug/acode/plugins/NativeLayer.kt b/android/app/src/main/java/com/foxdebug/acode/plugins/NativeLayer.kt new file mode 100644 index 000000000..23574ed42 --- /dev/null +++ b/android/app/src/main/java/com/foxdebug/acode/plugins/NativeLayer.kt @@ -0,0 +1,200 @@ +package com.foxdebug.acode.plugins + +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.appcompat.app.AlertDialog +import com.foxdebug.acode.Acode +import com.foxdebug.acode.runOnUiThread +import com.getcapacitor.JSObject +import com.getcapacitor.Plugin +import com.getcapacitor.PluginCall +import com.getcapacitor.PluginMethod +import com.getcapacitor.annotation.CapacitorPlugin +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.plus +import androidx.core.net.toUri + +@CapacitorPlugin(name = "NativeLayer") +class NativeLayer : Plugin() { + val scope = MainScope() + CoroutineName("NativeLayer") + + override fun handleOnDestroy() { + super.handleOnDestroy() + scope.cancel() + } + + /** + * Displays a simple alert dialog with a title, message, and an "OK" button. + * + * @param call The PluginCall containing the dialog parameters. It expects the following keys: + * - `title`: (String) The title of the dialog. + * - `message`: (String) The message content of the dialog. + * + * The dialog will display the provided title and message. If either `title` or `message` is not provided, the call will be rejected with an error. + * When the user clicks the "OK" button, the dialog will be dismissed, and the plugin call will be resolved. + */ + @PluginMethod + fun showDialog(call: PluginCall) = with(call) { + autoRejectOnError(onFailure = {}) { + existsNot("title") { return@with } + existsNot("message") { return@with } + + val title = call.getString("title") + val message = call.getString("message") + + runOnUiThread { + AlertDialog.Builder(context).apply { + setTitle(title) + setMessage(message) + setPositiveButton("OK", null) + show() + } + } + + call.resolve() + } + + } + + /** + * Launches an intent based on the provided parameters. + * + * @param call The PluginCall containing the intent parameters. + * The following parameters are expected: + * - `constructor_number`: (Int) The type of intent constructor to use. + * - `0`: Creates an intent using an action string. Requires `action`. + * - `1`: Creates an explicit intent using a class name. Requires `className`. + * - `2`: Creates a generic intent. + * - `launch_type`: (String) The type of intent launch. + * - `"activity"`: Starts an activity. + * - `"service"`: Starts a service. + * - `activity_context`: (Boolean) Whether to use the activity context. If `false`, uses the application context. + * - `action`: (String) The action for the intent (required for `constructor_number` 0). + * - `className`: (String) The fully qualified class name to launch (required for `constructor_number` 1). + * - `new_task`: (Boolean) Whether to launch the intent in a new task. + * - `extras`: (JSObject) Key-value pairs to add as extras to the intent. Supported types are String, Int, Boolean, Double, and Float. + * - `data`: (String, optional) URI to set as the intent data. + * - `type`: (String, optional) MIME type for the intent. + * - `package`: (String, optional) Package name for an explicit intent. + * - `class`: (String, optional) Class name for an explicit intent. Should be used alongside `package`. + * - `foreground_service`: (Boolean, optional) Indicates if the service is a foreground service. (required for service launch) + * Only applies when `launch_type` is "service". Requires API 26+. + * + * If `launch_type` is "service" and `foreground_service` is `true`, it starts a foreground service. + * Otherwise, it starts a regular background service. + * + * If any required parameters are missing or invalid, the call will be rejected with an appropriate error message. + */ + @PluginMethod + fun launchIntent(call: PluginCall) { + call.autoRejectOnError(onFailure = {}) { + val constructorNumber = call.getInt("constructor_number") ?: run { + call.reject("Missing 'constructor_number'") + return + } + + val launchType = call.getString("launch_type") ?: run { + call.reject("Missing 'launch_type'") + return + } + + val useActivityContext = call.getBoolean("activity_context") ?: run { + call.reject("Missing 'activity_context'") + return + } + + val context = if (useActivityContext) context else Acode.instance.applicationContext + + val intent = when (constructorNumber) { + 0 -> { + val action = call.getString("action") ?: run { + call.reject("Missing 'action' for constructor 0") + return + } + Intent(action) + } + + 1 -> { + val className = call.getString("className") ?: run { + call.reject("Missing 'class' for constructor 1") + return + } + Intent(context, Class.forName(className)) + } + + 2 -> { + Intent() + } + + else -> { + call.reject("Unsupported constructor_number: $constructorNumber") + return + } + } + + if (call.getBoolean("new_task") == true) { + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + + call.getObject("extras", JSObject())?.let { extras -> + for (key in extras.keys()) { + when (val value = extras.get(key)) { + is String -> intent.putExtra(key, value) + is Int -> intent.putExtra(key, value) + is Boolean -> intent.putExtra(key, value) + is Double -> intent.putExtra(key, value) + is Float -> intent.putExtra(key, value) + else -> { + call.reject("Unsupported data type encountered while processing intent extras") + return + } + } + } + } + + call.getString("data")?.let { intent.data = it.toUri() } + call.getString("type")?.let(intent::setType) + + if (call.data.has("package") && call.data.has("class")) { + val pkg = call.getString("package") + val cls = call.getString("class") + if (pkg != null && cls != null) { + intent.setClassName(pkg, cls) + } + } + + when (launchType) { + "activity" -> { + context.startActivity(intent) + call.resolve() + } + + "service" -> { + val isForeground = call.getBoolean("foreground_service") ?: run { + call.reject("Missing 'foreground_service' for service launch") + return + } + + if (isForeground) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent) + call.resolve() + } else { + call.unavailable("Foreground service requires API 26+") + } + } else { + context.startService(intent) + call.resolve() + } + } + + else -> { + call.reject("Invalid launch_type: $launchType") + } + } + } + } +} diff --git a/android/app/src/main/java/com/foxdebug/acode/plugins/PluginUtils.kt b/android/app/src/main/java/com/foxdebug/acode/plugins/PluginUtils.kt new file mode 100644 index 000000000..71a3698c5 --- /dev/null +++ b/android/app/src/main/java/com/foxdebug/acode/plugins/PluginUtils.kt @@ -0,0 +1,43 @@ +package com.foxdebug.acode.plugins + +import com.foxdebug.acode.MainActivity +import com.getcapacitor.PluginCall +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext + +inline fun PluginCall.autoRejectOnError(onFailure: (Throwable?) -> Unit, invoke: () -> Unit) { + runCatching { + invoke() + }.onFailure { + it.printStackTrace() + reject("Unknown Exception \nMessage: ${it.message}\n${it.stackTraceToString()}") + onFailure(it) + } +} + +inline fun PluginCall.exists(key: String, onFailure: (() -> Unit) = {}, onSuccess: (() -> Unit) = {}) { + if (data.has(key).not()) { + reject("$key is not set") + onFailure.invoke() + } else { + onSuccess.invoke() + } +} + +inline fun PluginCall.existsNot(key: String, onFailure: (() -> Unit) = {}) { + if (data.has(key).not()) { + reject("$key is not set") + onFailure.invoke() + } +} + +fun async( + context: CoroutineContext = Dispatchers.IO, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +) { + MainActivity.lifeCycleScope.launch(context, start, block) +} diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..1668f1856 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..b5ad13870 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..ef49c9917 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..6b5433be0 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #3a3e54 + \ No newline at end of file diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..228fb4634 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,5 @@ + + + #3a3e54 + #3a3e54 + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..2cbb01dc6 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + Acode + Acode + com.foxdebug.acode + com.foxdebug.acode + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..2c9008fb6 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..9c5ad89af --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,3 @@ + + + diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 000000000..bd0c4d80d --- /dev/null +++ b/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000..855a5e4c7 --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + * + localhost + + + + diff --git a/android/app/src/paid/res/values/colors.xml b/android/app/src/paid/res/values/colors.xml new file mode 100644 index 000000000..228fb4634 --- /dev/null +++ b/android/app/src/paid/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #3a3e54 + #3a3e54 + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..765617162 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,33 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + ext { + kotlin_version = '1.9.24' + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.7.2' + classpath 'com.google.gms:google-services:4.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: "variables.gradle" + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle new file mode 100644 index 000000000..2d74be502 --- /dev/null +++ b/android/capacitor.settings.gradle @@ -0,0 +1,9 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') + +include ':capacitor-app' +project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') + +include ':capacitor-filesystem' +project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android') diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..2e87c52f8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..a4b76b953 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..03230925a --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +networkTimeout=10000 +validateDistributionUrl=true diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 000000000..f5feea6d6 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 000000000..9b42019c7 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..3b4431d77 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,5 @@ +include ':app' +include ':capacitor-cordova-android-plugins' +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') + +apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/android/variables.gradle b/android/variables.gradle new file mode 100644 index 000000000..2c8e4083f --- /dev/null +++ b/android/variables.gradle @@ -0,0 +1,16 @@ +ext { + minSdkVersion = 23 + compileSdkVersion = 35 + targetSdkVersion = 35 + androidxActivityVersion = '1.9.2' + androidxAppCompatVersion = '1.7.0' + androidxCoordinatorLayoutVersion = '1.2.0' + androidxCoreVersion = '1.15.0' + androidxFragmentVersion = '1.8.4' + coreSplashScreenVersion = '1.0.1' + androidxWebkitVersion = '1.12.1' + junitVersion = '4.13.2' + androidxJunitVersion = '1.2.1' + androidxEspressoCoreVersion = '3.6.1' + cordovaAndroidVersion = '10.1.1' +} \ No newline at end of file diff --git a/biome.json b/biome.json index da281e682..0ced76cbb 100644 --- a/biome.json +++ b/biome.json @@ -44,7 +44,8 @@ "hooks/**/*", "fastlane/**/*", "res/**/*", - "platforms/**/*" + "platforms/**/*", + "android/**" ] } } diff --git a/capacitor.config.json b/capacitor.config.json new file mode 100644 index 000000000..2aa5799a1 --- /dev/null +++ b/capacitor.config.json @@ -0,0 +1,10 @@ +{ + "appId": "com.foxdebug.acode", + "appName": "Acode", + "webDir": "www", + "cordova": { + "preferences": { + "AndroidBlacklistSecureSocketProtocols": "SSLv3,TLSv1" + } + } +} diff --git a/package-lock.json b/package-lock.json index 8f58a8be7..c39ab78a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "1.11.0", "license": "MIT", "dependencies": { + "@capacitor/android": "^7.2.0", + "@capacitor/app": "^7.0.1", + "@capacitor/filesystem": "^7.0.1", "@deadlyjack/ajax": "^1.2.6", "@ungap/custom-elements": "^1.3.0", "autosize": "^6.0.1", @@ -41,6 +44,7 @@ "@babel/runtime": "^7.24.6", "@babel/runtime-corejs3": "^7.24.6", "@biomejs/biome": "1.8.3", + "@capacitor/cli": "^7.2.0", "@types/ace": "^0.0.52", "@types/url-parse": "^1.4.11", "autoprefixer": "^10.4.19", @@ -2083,6 +2087,244 @@ "node": ">=14.21.3" } }, + "node_modules/@capacitor/android": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-7.2.0.tgz", + "integrity": "sha512-zdhEy3jZPG5Toe/pGzKtDgIiBGywjaoEuQWnGVjBYPlSAEUtAhpZ2At7V0SCb26yluAuzrAUV0Ue+LQeEtHwFQ==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^7.2.0" + } + }, + "node_modules/@capacitor/app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@capacitor/app/-/app-7.0.1.tgz", + "integrity": "sha512-ArlVZAAla4MwQoKh26x2AaTDOBh5Vhp1VhMKR3RwqZSsZnazKTFGNrPbr9Ez5r1knnEDfApyjwp1uZnXK1WTYQ==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": ">=7.0.0" + } + }, + "node_modules/@capacitor/cli": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-7.2.0.tgz", + "integrity": "sha512-RNW9vtYYYSDmOdguYBSW0VpRnG/d6lGydlc9DLrJ7qbSPxFrotTz9IjkM48O+SruUma61DyuSqJttdbay2xSxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/cli-framework-output": "^2.2.8", + "@ionic/utils-subprocess": "^3.0.1", + "@ionic/utils-terminal": "^2.3.5", + "commander": "^12.1.0", + "debug": "^4.4.0", + "env-paths": "^2.2.0", + "fs-extra": "^11.2.0", + "kleur": "^4.1.5", + "native-run": "^2.0.1", + "open": "^8.4.0", + "plist": "^3.1.0", + "prompts": "^2.4.2", + "rimraf": "^6.0.1", + "semver": "^7.6.3", + "tar": "^6.1.11", + "tslib": "^2.8.1", + "xml2js": "^0.6.2" + }, + "bin": { + "cap": "bin/capacitor", + "capacitor": "bin/capacitor" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@capacitor/cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@capacitor/cli/node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@capacitor/cli/node_modules/jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@capacitor/cli/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@capacitor/cli/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@capacitor/cli/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@capacitor/cli/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@capacitor/cli/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@capacitor/cli/node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@capacitor/cli/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@capacitor/cli/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@capacitor/core": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.2.0.tgz", + "integrity": "sha512-2zCnA6RJeZ9ec4470o8QMZEQTWpekw9FNoqm5TLc10jeCrhvHVI8MPgxdZVc3mOdFlyieYu4AS1fNxSqbS57Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@capacitor/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/@capacitor/filesystem": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-7.0.1.tgz", + "integrity": "sha512-dxuKNLFoUejm7tBKkQPs1j7+BLpDh5JKPSVu7nT8jgCbA/KXt5FoCLiepfkjWkYfq60X0JNFzGnIquc5FPOLzQ==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": ">=7.0.0" + } + }, "node_modules/@chevrotain/cst-dts-gen": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", @@ -2134,6 +2376,210 @@ "node": ">=10.0.0" } }, + "node_modules/@ionic/cli-framework-output": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ionic/cli-framework-output/-/cli-framework-output-2.2.8.tgz", + "integrity": "sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-terminal": "2.3.5", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/cli-framework-output/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-array": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.6.tgz", + "integrity": "sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-array/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-fs": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@ionic/utils-fs/-/utils-fs-3.1.7.tgz", + "integrity": "sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^8.0.0", + "debug": "^4.0.0", + "fs-extra": "^9.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-fs/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@ionic/utils-fs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-object": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@ionic/utils-object/-/utils-object-2.1.6.tgz", + "integrity": "sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-object/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-process": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@ionic/utils-process/-/utils-process-2.1.12.tgz", + "integrity": "sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-object": "2.1.6", + "@ionic/utils-terminal": "2.3.5", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-process/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@ionic/utils-stream/-/utils-stream-3.1.7.tgz", + "integrity": "sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-stream/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-subprocess": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ionic/utils-subprocess/-/utils-subprocess-3.0.1.tgz", + "integrity": "sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-array": "2.1.6", + "@ionic/utils-fs": "3.1.7", + "@ionic/utils-process": "2.1.12", + "@ionic/utils-stream": "3.1.7", + "@ionic/utils-terminal": "2.3.5", + "cross-spawn": "^7.0.3", + "debug": "^4.0.0", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-subprocess/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@ionic/utils-terminal": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@ionic/utils-terminal/-/utils-terminal-2.3.5.tgz", + "integrity": "sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/slice-ansi": "^4.0.0", + "debug": "^4.0.0", + "signal-exit": "^3.0.3", + "slice-ansi": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "tslib": "^2.0.1", + "untildify": "^4.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@ionic/utils-terminal/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "license": "ISC", @@ -2790,6 +3236,16 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "dev": true, @@ -2829,6 +3285,13 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -3318,6 +3781,16 @@ "node": ">=0.8" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "2.6.4", "license": "MIT", @@ -3329,6 +3802,16 @@ "version": "0.4.0", "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/atomically": { "version": "1.7.0", "license": "MIT", @@ -3671,6 +4154,16 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4934,10 +5427,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4968,6 +5463,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "license": "MIT", @@ -5494,6 +5999,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/figures": { "version": "2.0.0", "license": "MIT", @@ -6297,6 +6812,16 @@ "version": "2.0.4", "license": "ISC" }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/init-package-json": { "version": "5.0.0", "license": "ISC", @@ -6875,6 +7400,16 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/lie": { "version": "3.3.0", "license": "MIT", @@ -7382,7 +7917,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/mustache": { @@ -7414,6 +7951,39 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/native-run": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.1.tgz", + "integrity": "sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ionic/utils-fs": "^3.1.7", + "@ionic/utils-terminal": "^2.3.4", + "bplist-parser": "^0.3.2", + "debug": "^4.3.4", + "elementtree": "^0.1.7", + "ini": "^4.1.1", + "plist": "^3.1.0", + "split2": "^4.2.0", + "through2": "^4.0.2", + "tslib": "^2.6.2", + "yauzl": "^2.10.0" + }, + "bin": { + "native-run": "bin/native-run" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/native-run/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/negotiator": { "version": "0.6.3", "license": "MIT", @@ -7955,6 +8525,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pacote": { "version": "15.2.0", "license": "ISC", @@ -8112,6 +8689,13 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/performance-now": { "version": "2.1.0", "license": "MIT" @@ -8450,6 +9034,30 @@ "node": ">=10" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/promzard": { "version": "1.0.0", "license": "ISC", @@ -9206,11 +9814,6 @@ "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -9328,6 +9931,13 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/slash": { "version": "2.0.0", "dev": true, @@ -9336,6 +9946,60 @@ "node": ">=6" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/smart-buffer": { "version": "4.2.0", "license": "MIT", @@ -9419,6 +10083,16 @@ "version": "3.0.13", "license": "CC0-1.0" }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sshpk": { "version": "1.17.0", "license": "MIT", @@ -9762,6 +10436,31 @@ "version": "2.3.8", "license": "MIT" }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/tmp": { "version": "0.2.1", "license": "MIT", @@ -9819,6 +10518,16 @@ "node": ">= 4.0.0" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/treeverse": { "version": "3.0.0", "license": "ISC", @@ -10471,6 +11180,30 @@ "node": ">=8" } }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -10517,6 +11250,17 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", diff --git a/package.json b/package.json index f424a589e..e5040ce56 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ "description": "Acode is a code editor for android", "scripts": { "lang": "node ./utils/lang.js", - "build": "sh utils/scripts/build.sh", - "start": "sh utils/scripts/start.sh", - "clean": "sh utils/scripts/clean.sh android android", + "build": "biome check --write && biome lint --write && node scripts/build.js", + "launch":"biome check --write && biome lint --write && node scripts/build.js && adb install android/app/build/outputs/apk/paid/debug/app-paid-debug.apk && adb shell am start -n com.foxdebug.acode/.MainActivity", + "clean": "sh scripts/gradlew-link clean && rm -rf www/js/build && rm -rf www/css/build", "plugin": "sh utils/scripts/plugin.sh", - "setup": "node ./utils/setup.js", + "setup": "npx cap sync", + "sync": "npx cap sync && node scripts/postSync.js", "lint": "biome lint --write", "format": "biome format --write", "check": "biome check --write", @@ -57,6 +58,7 @@ "@babel/runtime": "^7.24.6", "@babel/runtime-corejs3": "^7.24.6", "@biomejs/biome": "1.8.3", + "@capacitor/cli": "^7.2.0", "@types/ace": "^0.0.52", "@types/url-parse": "^1.4.11", "autoprefixer": "^10.4.19", @@ -88,6 +90,9 @@ "webpack-cli": "^5.1.4" }, "dependencies": { + "@capacitor/android": "^7.2.0", + "@capacitor/app": "^7.0.1", + "@capacitor/filesystem": "^7.0.1", "@deadlyjack/ajax": "^1.2.6", "@ungap/custom-elements": "^1.3.0", "autosize": "^6.0.1", @@ -113,4 +118,4 @@ "yargs": "^17.7.2" }, "browserslist": "cover 100%,not android < 5" -} \ No newline at end of file +} diff --git a/package.json.save b/package.json.save new file mode 100644 index 000000000..396837c80 --- /dev/null +++ b/package.json.save @@ -0,0 +1,116 @@ +{ + "name": "com.foxdebug.acode", + "displayName": "Acode", + "version": "1.11.0", + "description": "Acode is a code editor for android", + "scripts": { + "lang": "node ./utils/lang.js", + "build": "sh scripts/build-debug.sh", + "plugin": "sh utils/scripts/plugin.sh", + "setup": "npx cap sync && node scripts/postSync.js", + "lint": "biome lint --write", + "format": "biome format --write", + "check": "biome check --write", + "updateAce": "node ./utils/updateAce.js" + }, + "keywords": [ + "ecosystem:cordova" + ], + "author": "Foxdebug (Ajit Kumar)", + "license": "MIT", + "cordova": { + "plugins": { + "cordova-clipboard": {}, + "cordova-plugin-buildinfo": {}, + "cordova-plugin-device": {}, + "cordova-plugin-file": {}, + "cordova-plugin-sftp": {}, + "cordova-plugin-server": {}, + "cordova-plugin-ftp": {}, + "cordova-plugin-sdcard": {}, + "cordova-plugin-browser": {}, + "cordova-plugin-iap": {}, + "cordova-plugin-system": {}, + "cordova-plugin-advanced-http": { + "ANDROIDBLACKLISTSECURESOCKETPROTOCOLS": "SSLv3,TLSv1" + } + }, + "platforms": [ + "android" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/deadlyjack/acode.git" + }, + "bugs": { + "url": "https://github.com/deadlyjack/acode/issues" + }, + "homepage": "https://github.com/deadlyjack/acode#readme", + "devDependencies": { + "@babel/cli": "^7.24.6", + "@babel/core": "^7.24.6", + "@babel/plugin-transform-runtime": "^7.24.6", + "@babel/preset-env": "^7.24.6", + "@babel/runtime": "^7.24.6", + "@babel/runtime-corejs3": "^7.24.6", + "@biomejs/biome": "1.8.3", + "@capacitor/cli": "^7.2.0", + "@types/ace": "^0.0.52", + "@types/url-parse": "^1.4.11", + "autoprefixer": "^10.4.19", + "babel-loader": "^9.1.3", + "cordova-android": "^14.0.0", + "cordova-clipboard": "^1.3.0", + "cordova-plugin-advanced-http": "^3.3.1", + "cordova-plugin-browser": "file:src/plugins/browser", + "cordova-plugin-buildinfo": "^4.0.0", + "cordova-plugin-device": "^2.0.3", + "cordova-plugin-file": "^8.0.1", + "cordova-plugin-ftp": "file:src/plugins/ftp", + "cordova-plugin-iap": "file:src/plugins/iap", + "cordova-plugin-sdcard": "file:src/plugins/sdcard", + "cordova-plugin-server": "file:src/plugins/server", + "cordova-plugin-sftp": "file:src/plugins/sftp", + "cordova-plugin-system": "file:src/plugins/system", + "css-loader": "^7.1.2", + "mini-css-extract-plugin": "^2.9.0", + "path-browserify": "^1.0.1", + "postcss-loader": "^8.1.1", + "prettier": "^3.2.5", + "prettier-plugin-java": "^2.6.0", + "raw-loader": "^4.0.2", + "sass": "^1.77.2", + "sass-loader": "^14.2.1", + "style-loader": "^4.0.0", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "@capacitor/android": "^7.2.0", + "@deadlyjack/ajax": "^1.2.6", + "@ungap/custom-elements": "^1.3.0", + "autosize": "^6.0.1", + "cordova": "12.0.0", + "core-js": "^3.37.1", + "crypto-js": "^4.2.0", + "dompurify": "^3.2.4", + "escape-string-regexp": "^5.0.0", + "esprima": "^4.0.1", + "filesize": "^10.1.2", + "html-tag-js": "^1.5.1", + "js-base64": "^3.7.7", + "jszip": "^3.10.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^9.2.0", + "markdown-it-github-alerts": "^0.3.0", + "markdown-it-task-lists": "^2.1.1", + "mime-types": "^2.1.35", + "minimatch": "^9.0.4", + "mustache": "^4.2.0", + "url-parse": "^1.5.10", + "vanilla-picker": "^2.12.3", + "yargs": "^17.7.2" + }, + "browserslist": "cover 100%,not android < 5" +} diff --git a/readme.md b/readme.md index daf0a9c6a..5b55a8317 100644 --- a/readme.md +++ b/readme.md @@ -41,10 +41,10 @@ Acode/ Enhance Acode's capabilities by adding new languages easily. Just create a file with the language code (e.g., en-us for English) in [`src/lang/`](https://github.com/Acode-Foundation/Acode/tree/main/src/lang) and include it in [`src/lib/lang.js`](https://github.com/Acode-Foundation/Acode/blob/main/src/lib/lang.js). Manage strings across languages effortlessly using utility commands: ```shell -yarn lang add -yarn lang remove -yarn lang search -yarn lang update +npm run lang add +npm run lang remove +npm run lang search +npm run lang update ``` ## • Building the Application @@ -54,13 +54,14 @@ To build the APK, ensure you have Node.js, NPM, and Apache Cordova installed on 1. Initial setup (required only once): ```shell -yarn setup +npm install +npm run sync ``` 2. Build the project: ```shell -yarn build +npm run build-release ``` ## • Contributing @@ -81,14 +82,14 @@ Acode Editor is an open-source project, and we welcome contributions from the co Please ensure that your code is clean, well-formatted, and follows the project's coding standards. Acode uses [Biomejs](https://biomejs.dev/) for formatting and linting and [typos](https://github.com/crate-ci/typos) for low false positives source code spell checking. You can use following commands to lints/format your code locally: ```shell -yarn lint # for linting -yarn format # for formatting -yarn check # it runs both lint and format +npm run lint # for linting +npm run format # for formatting +npm run check # it runs both lint and format ``` Also, ensure that your code is well-documented and includes comments where necessary. > [!Note] -> You can use any package manager like npm or yarn or pnpm or bun. +> You can use any package manager like npm or npm run or pnpm or bun. > You can use your editor specific Biomejs plugin for auto-formatting and linting based on Acode's configs. ## • Contributors diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 000000000..072d970bc --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,167 @@ +const os = require("os"); +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +const platform = os.platform(); +const isWindows = platform === "win32"; + +// Check for supported platforms +if ( + !["linux", "darwin", "win32"].includes(platform) && + !platform.toLowerCase().includes("bsd") +) { + console.error(`Unsupported platform: ${platform}`); + process.exit(1); +} + + +// CLI args: appType (free/paid), buildMode (d/p or debug/prod) +const appType = (process.argv[2] || "paid").toLowerCase(); +const buildMode = (process.argv[3] || "d").toLowerCase(); + +// Normalize app type and mode +const normalizedType = appType.startsWith("f") ? "Free" : "Paid"; +const normalizedMode = + buildMode === "p" || buildMode === "prod" || buildMode === "release" + ? "Release" + : "Debug"; + +// Terminal colors +const colors = { + reset: "\x1b[0m", + green: "\x1b[32;1m", + blue: "\x1b[34;1m", + yellow: "\x1b[33;1m", + red: "\x1b[31;1m", + white: "\x1b[37m", +}; + +// Logging helpers +const log = { + success: (msg) => + console.log(`${colors.green}[+] ${colors.white}${msg}${colors.reset}`), + info: (msg) => + console.log(`${colors.blue}[*] ${colors.white}${msg}${colors.reset}`), + warn: (msg) => + console.log(`${colors.yellow}[~] ${colors.white}${msg}${colors.reset}`), + error: (msg) => { + console.error(`${colors.red}[!] ${colors.white}${msg}${colors.reset}`); + process.exit(1); + }, +}; + +// Check JAVA_HOME +if (!process.env.JAVA_HOME) { + log.warn( + "JAVA_HOME is not set. Please set it to your Java installation path." + ); +} else { + log.info(`JAVA_HOME: ${process.env.JAVA_HOME}`); +} + +// Check ANDROID_HOME +if (!process.env.ANDROID_HOME) { + log.warn("ANDROID_HOME is not set. Please set it to your Android SDK path."); +} else { + log.info(`ANDROID_HOME: ${process.env.ANDROID_HOME}`); +} + +// Verify Java version is 21 +try { + const versionOutput = execSync("java -version 2>&1").toString(); + const match = versionOutput.match(/version\s+"(\d+)(?:\.(\d+))?/); + const majorVersion = match ? parseInt(match[1]) : null; + + const versionString = match?.[1] + ? `${match[1]}${match[2] ? "." + match[2] : ""}` + : "unknown"; + log.info(`Current Java version is ${versionString}.`); + + if (!majorVersion || majorVersion < 21) { + log.error(`Java 21 is required.`); + } +} catch { + log.error("Java is not installed or not accessible."); +} + + +// Display config +log.info(`App Type: ${normalizedType}`); +log.info(`Build Mode: ${normalizedMode}`); + +// Constants +//for some reason ad id is not properly added to the android manifest by this cordovalugin so instead of editing this +//edit the android/app/src/free/AndroidManifest.xml file +const AD_APP_ID = "ca-app-pub-5911839694379275~4255791238"; +const PROJECT_ROOT = execSync("npm prefix").toString().trim(); +const androidDir = path.join(PROJECT_ROOT, "android"); + +try { + // Plugin logic based on app type + if (normalizedType === "Paid") { + log.info("Removing Admob plugins for paid build"); + if ( + execSync("cordova plugin ls") + .toString() + .includes("cordova-plugin-consent") + ) { + execSync("cordova plugin remove cordova-plugin-consent --save", { + stdio: "inherit", + }); + } + if ( + execSync("cordova plugin ls").toString().includes("admob-plus-cordova") + ) { + execSync("cordova plugin remove admob-plus-cordova --save", { + stdio: "inherit", + }); + } + } else { + log.info("Adding Admob plugins for free build"); + execSync("cordova plugin add cordova-plugin-consent@2.4.0 --save", { + stdio: "inherit", + }); + execSync( + `cordova plugin add admob-plus-cordova@1.28.0 --save --variable APP_ID_ANDROID="${AD_APP_ID}" --variable PLAY_SERVICES_VERSION="21.5.0"`, + { stdio: "inherit" } + ); + } + + // Create build folders + fs.mkdirSync(path.join(PROJECT_ROOT, "www", "css", "build"), { + recursive: true, + }); + fs.mkdirSync(path.join(PROJECT_ROOT, "www", "js", "build"), { + recursive: true, + }); + + // Webpack + const webpackMode = + normalizedMode === "Release" ? "production" : "development"; + execSync(`webpack --progress --mode ${webpackMode}`, { stdio: "inherit" }); + + // Preload styles + execSync("node ./utils/loadStyles.js", { stdio: "inherit" }); + + // Sync + execSync("npm run sync", { stdio: "inherit" }); + + // Ensure gradlew is executable on Unix systems + const gradlewPath = path.join( + androidDir, + isWindows ? "gradlew.bat" : "gradlew", + ); + if (!isWindows) { + fs.chmodSync(gradlewPath, 0o755); + } + + // Run gradle task + const gradleTask = `assemble${normalizedType}${normalizedMode}`; + const gradleCmd = `${isWindows ? "gradlew.bat" : "./gradlew"} ${gradleTask}`; + execSync(gradleCmd, { cwd: androidDir, stdio: "inherit" }); + + log.success("Build completed successfully."); +} catch (e) { + log.error(`Build failed: ${e.message}`); +} diff --git a/scripts/postSync.js b/scripts/postSync.js new file mode 100644 index 000000000..32c4ca052 --- /dev/null +++ b/scripts/postSync.js @@ -0,0 +1,61 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * Recursively find all `build.gradle` files in the `android/` directory + */ +function findBuildGradleFiles(dir) { + let results = []; + const list = fs.readdirSync(dir); + + list.forEach(file => { + const filepath = path.join(dir, file); + const stat = fs.statSync(filepath); + + if (stat && stat.isDirectory()) { + results = results.concat(findBuildGradleFiles(filepath)); + } else if (file === 'build.gradle') { + results.push(filepath); + } + }); + + return results; +} + +/** + * Patch build.gradle if needed + */ +function patchBuildGradle(filePath) { + if(filePath.includes("/app/")){ + return + } + let contents = fs.readFileSync(filePath, 'utf8'); + + if (!contents.includes('android {')) { + console.log(`⏭️ Skipping ${filePath} (no android block)`); + return; + } + + if (contents.includes('buildConfig true')) { + console.log(`✅ ${filePath} already contains buildConfig = true`); + return; + } + + contents = contents.replace( + /android\s*\{/, + `android {\n buildFeatures {\n buildConfig true\n }` + ); + + fs.writeFileSync(filePath, contents); + console.log(`✅ Patched ${filePath}`); +} + +// Run it +const gradleFiles = findBuildGradleFiles(path.resolve('android')); + +if (gradleFiles.length === 0) { + console.log('❌ No build.gradle files found.'); +} else { + gradleFiles.forEach(patchBuildGradle); +} + diff --git a/src/fileSystem/internalFs.js b/src/fileSystem/internalFs.js index c53654cf7..34fb387c5 100644 --- a/src/fileSystem/internalFs.js +++ b/src/fileSystem/internalFs.js @@ -1,24 +1,47 @@ +import { Directory, Encoding, Filesystem } from "@capacitor/filesystem"; import ajax from "@deadlyjack/ajax"; +import { data } from "autoprefixer"; import fsOperation from "fileSystem"; +import path from "path-browserify"; import Url from "utils/Url"; import { decode, encode } from "utils/encodings"; import helpers from "utils/helpers"; const internalFs = { /** - * + * List files from a Directory (not recursive) * @param {string} url * @returns {Promise} */ listDir(url) { return new Promise((resolve, reject) => { reject = setMessage(reject); - window.resolveLocalFileSystemURL(url, success, reject); - function success(fs) { - const reader = fs.createReader(); - reader.readEntries(resolve, reject); - } + Filesystem.readdir({ path: url }) + .then((result) => { + console.log( + `Listed files/directories successfully for url: ${url}, Result: `, + result, + ); + resolve( + result.files.map((file) => ({ + name: file.name, + url: file.uri, + size: file.size, + ctime: file.ctime, + mtime: file.mtime, + isFile: file.type === "file", + isDirectory: file.type === "directory", + isLink: false, + })), + ); + }) + .catch((error) => { + console.log( + `Error while listing Directory for url: ${url}, error:`, + error, + ); + }); }); }, @@ -32,33 +55,54 @@ const internalFs = { * @param {boolean} exclusive If true, and the create option is also true, * the file must not exist prior to issuing the call. * Instead, it must be possible for it to be created newly at call time. The default is true. - * @returns {Promise} + * @returns {Promise} URL where the file was written into. */ - writeFile(filename, data, create = false, exclusive = true) { + writeFile(filename, udata, create = false, exclusive = true) { exclusive = create ? exclusive : false; const name = filename.split("/").pop(); - const dirname = Url.dirname(filename); return new Promise((resolve, reject) => { + if (udata === undefined || udata == null) { + reject("udata is null"); + } + + let options = { + path: filename, + recursive: create, + }; + reject = setMessage(reject); - window.resolveLocalFileSystemURL( - dirname, - (entry) => { - entry.getFile( - name, - { create, exclusive }, - (fileEntry) => { - fileEntry.createWriter((file) => { - file.onwriteend = (res) => resolve(filename); - file.onerror = (err) => reject(err.target.error); - file.write(data); - }); - }, - reject, + + if ( + udata instanceof ArrayBuffer || + Object.prototype.toString.call(udata) === "[object ArrayBuffer]" + ) { + // Binary data — store as base64 + options.data = btoa(String.fromCharCode(...new Uint8Array(udata))); + options.encoding = Encoding.BASE64; + } else if (typeof udata === "string") { + // Text data — store as UTF-8 + options.data = udata; + options.encoding = Encoding.UTF8; + } else { + reject("Unsupported udata type"); + return; + } + + Filesystem.writeFile(options) + .then((file) => { + console.log( + `Successfully written into (name: ${name}) ${filename} file`, ); - }, - reject, - ); + resolve(file.uri); + }) + .catch((error) => { + console.error( + `Failed to write into (name: ${name}) ${filename} file, error: `, + error, + ); + reject(error); + }); }); }, @@ -67,20 +111,32 @@ const internalFs = { * @param {string} filename * @returns {Promise} */ + delete(filename) { - return new Promise((resolve, reject) => { - reject = setMessage(reject); - window.resolveLocalFileSystemURL( - filename, - (entry) => { - if (entry.isFile) { - entry.remove(resolve, reject); - } else { - entry.removeRecursively(resolve, reject); - } - }, - reject, - ); + return new Promise(async (resolve, reject) => { + console.log("Deleting " + filename); + + if (!(await this.exists(filename))) { + console.warn(`File ${filename} doesnt exists`); + resolve(); + } else { + Filesystem.stat({ path: filename }) + .then((stats) => { + if (stats.type === "directory") { + return Filesystem.rmdir({ path: filename, recursive: true }); + } else { + return Filesystem.deleteFile({ path: filename }); + } + }) + .then(() => { + console.log("Deleted successfully!"); + resolve(); + }) + .catch((error) => { + console.error("Error while deleting:", error); + reject(error); + }); + } }); }, @@ -90,35 +146,65 @@ const internalFs = { * @param {string} encoding * @returns {Promise} */ + readFile(filename) { return new Promise((resolve, reject) => { reject = setMessage(reject); - window.resolveLocalFileSystemURL( - filename, - (fileEntry) => { - (async () => { - const url = fileEntry.toInternalURL(); - try { - const data = await ajax({ - url: url, - responseType: "arraybuffer", - }); + Filesystem.readFile({ path: filename, encoding: Encoding.UTF8 }) + .then((readFileResult) => { + const encoder = new TextEncoder(); + const buffer = encoder.encode(readFileResult.data).buffer; - resolve({ data }); - } catch (error) { - fileEntry.file((file) => { - const fileReader = new FileReader(); - fileReader.onerror = reject; - fileReader.readAsArrayBuffer(file); - fileReader.onloadend = () => { - resolve({ data: fileReader.result }); - }; - }, reject); - } - })(); - }, - reject, - ); + resolve({ data: buffer }); + }) + .catch((error) => { + console.error( + `Failed to Read File contents of "${filename}", error: `, + error, + ); + reject(error); + }); + }); + }, + + readFileRaw(filename) { + return new Promise((resolve, reject) => { + reject = setMessage(reject); + Filesystem.readFile({ path: filename, encoding: Encoding.BASE64 }) + .then((readFileResult) => { + const base64 = readFileResult.data; + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + + resolve({ data: bytes.buffer }); + }) + .catch((error) => { + console.error( + `Failed to read raw file "${filename}", error: `, + error, + ); + reject(error); + }); + }); + }, + + readStringFile(filename) { + return new Promise((resolve, reject) => { + reject = setMessage(reject); + Filesystem.readFile({ path: filename, encoding: Encoding.UTF8 }) + .then((readFileResult) => { + resolve({ data: readFileResult.data }); + }) + .catch((error) => { + console.error( + `Failed to Read File contents of "${filename}", error: `, + error, + ); + reject(error); + }); }); }, @@ -160,21 +246,24 @@ const internalFs = { createDir(path, dirname) { return new Promise((resolve, reject) => { reject = setMessage(reject); - window.resolveLocalFileSystemURL( - path, - (fs) => { - fs.getDirectory( - dirname, - { create: true }, - async () => { - const stats = await this.stats(Url.join(path, dirname)); - resolve(stats.url); - }, - reject, + // TODO!: ask about `recursive` option + Filesystem.mkdir({ + path: `${path}/${dirname}`, + recursive: true, + }) + .then(() => { + console.log(`Created ${path}/${dirname}`); + Filesystem.stat({ path: `${path}/${dirname}` }) + .then((stats) => resolve(stats.url)) + .catch(reject); + }) + .catch((error) => { + console.error( + `Failed to create ${dirname} directory in path: ${path}, error:`, + error, ); - }, - reject, - ); + reject(error); + }); }); }, @@ -210,14 +299,33 @@ const internalFs = { reject = setMessage(reject); this.verify(src, dest) .then((res) => { - const { src, dest } = res; - - src[action]( - dest, - undefined, - (entry) => resolve(decodeURIComponent(entry.nativeURL)), - reject, - ); + if (action === "copyTO") { + Filesystem.copy({ + from: src, + to: dest, + }) + .then((copyResult) => { + console.log(`Successfully copied from "${src}" to "${dest}"`); + resolve(copyResult.uri); + }) + .catch((error) => { + console.error(`Failed to copy from "${src}" to "${dest}"`); + reject(error); + }); + } else if (action === "moveTO") { + Filesystem.rename({ + from: src, + to: dest, + }) + .then((moveResult) => { + console.log(`Successfully moved from "${src}" to "${dest}"`); + resolve(dest); + }) + .catch((error) => { + console.error(`Failed to move from "${src}" to "${dest}"`); + reject(error); + }); + } }) .catch(reject); }); @@ -231,11 +339,14 @@ const internalFs = { stats(filename) { return new Promise((resolve, reject) => { reject = setMessage(reject); - window.resolveLocalFileSystemURL( - filename, - (entry) => { + Filesystem.stat({ path: filename }) + .then((entry) => { + console.log( + `Successfully returned stats for "${filename}", Result: `, + entry, + ); sdcard.stats( - entry.nativeURL, + entry.uri, (stats) => { helpers.defineDeprecatedProperty( stats, @@ -252,13 +363,19 @@ const internalFs = { }, reject, ); - }, - reject, - ); + }) + .catch((error) => { + console.error( + `Failed to show stats for "${filename}", error:`, + error, + ); + reject(error); + }); }); }, /** + * TODO: check this function with Rohit. * Verify if a file or directory exists * @param {string} src * @param {string} dest @@ -267,36 +384,48 @@ const internalFs = { verify(src, dest) { return new Promise((resolve, reject) => { reject = setMessage(reject); - window.resolveLocalFileSystemURL( - src, - (srcEntry) => { - window.resolveLocalFileSystemURL( - dest, - (destEntry) => { - window.resolveLocalFileSystemURL( - Url.join(destEntry.nativeURL, srcEntry.name), - (res) => { - reject({ - code: 12, - }); - }, - (err) => { - if (err.code === 1) { - resolve({ - src: srcEntry, - dest: destEntry, - }); - } else { - reject(err); - } - }, + + // check if source exists + Filesystem.stat({ + path: src, + }) + .then((srcStat) => { + console.log( + `"${src}" source dir/file verified successful, checking if source dir/file already exists in "${dest}" destination file/dir`, + ); + // Check if file/folder already exists at the destination + Filesystem.stat({ + path: `${dest}/${srcStat.name}`, + }) + .then(() => { + // File already exists error. + reject({ + code: 12, + }); + }) + .catch((fileExistsErr) => { + console.error( + "Failed to verify source in destination, error: ", + error, ); - }, - reject, + // if we get a "not found" error (code 1), that's good - we can copy + if (fileExistsErr.code === 1) { + resolve({ + src: { path: src }, + dest: { path: dest }, + }); + } else { + reject(fileExistsErr); + } + }); + }) + .catch((error) => { + console.error( + `Failed to verify "${src}" source dir/file, error: `, + error, ); - }, - reject, - ); + reject(error); + }); }); }, @@ -307,16 +436,21 @@ const internalFs = { exists(url) { return new Promise((resolve, reject) => { reject = setMessage(reject); - window.resolveLocalFileSystemURL( - url, - (entry) => { + Filesystem.stat({ + path: url, + }) + .then((stats) => { + if (!stats.uri) return resolve(false); + console.log( + `Successfully found (name: ${stats.name || "name not found"}) "${url}" existing`, + ); resolve(true); - }, - (err) => { - if (err.code === 1) resolve(false); - reject(err); - }, - ); + }) + .catch((err) => { + // on-error defaulting to false, + // as capacitor doesn't emit error codes, for error types(file not found, etc) + resolve(false); + }); }); }, @@ -405,6 +539,7 @@ function createFs(url) { return files; }, async readFile(encoding) { + console.log("fs read " + url); let { data } = await internalFs.readFile(url, encoding); if (encoding) { diff --git a/src/handlers/intent.js b/src/handlers/intent.js index ae526e79d..58c0edc48 100644 --- a/src/handlers/intent.js +++ b/src/handlers/intent.js @@ -1,4 +1,5 @@ import fsOperation from "fileSystem"; +import internalFs from "fileSystem/internalFs"; import openFile from "lib/openFile"; import helpers from "utils/helpers"; @@ -9,9 +10,11 @@ const handlers = []; * @param {Intent} intent */ export default async function HandleIntent(intent = {}) { - const type = intent.action.split(".").slice(-1)[0]; - - if (["SEND", "VIEW", "EDIT"].includes(type)) { + if ( + intent !== undefined && + intent.action !== undefined && + ["SEND", "VIEW", "EDIT"].includes(intent.action.split(".").slice(-1)[0]) + ) { /**@type {string} */ const url = intent.fileUri || intent.data; if (!url) return; @@ -32,7 +35,7 @@ export default async function HandleIntent(intent = {}) { if (module === "plugin") { const { default: Plugin } = await import("pages/plugin"); - const installed = await fsOperation(PLUGIN_DIR, value).exists(); + const installed = await internalFs.exists(PLUGIN_DIR); Plugin({ id: value, installed, install: action === "install" }); } diff --git a/src/lib/acode.js b/src/lib/acode.js index 6ff2dcfc7..734a3ad7a 100644 --- a/src/lib/acode.js +++ b/src/lib/acode.js @@ -179,7 +179,7 @@ export default class Acode { return; } - fsOperation(Url.join(PLUGIN_DIR, pluginId)) + fsOperation(`${PLUGIN_DIR}/${pluginId}`) .exists() .then((isPluginExists) => { if (isPluginExists) { @@ -189,12 +189,9 @@ export default class Acode { let purchaseToken; let product; - const pluginUrl = Url.join( - constants.API_BASE, - `plugin/${pluginId}`, - ); - fsOperation(pluginUrl) - .readFile("json") + const pluginUrl = `${PLUGIN_DIR}/${pluginId}`; + fsOperation(`${pluginUrl}/plugin.json`) + .readFile() .catch(() => { reject(new Error("Failed to fetch plugin details")); return null; diff --git a/src/lib/checkFiles.js b/src/lib/checkFiles.js index a964e2899..f37f56271 100644 --- a/src/lib/checkFiles.js +++ b/src/lib/checkFiles.js @@ -47,7 +47,7 @@ export default async function checkFiles() { * @returns {Promise} */ async function checkFile(file) { - if (file.isUnsaved || !file.loaded || file.loading) return; + if (!file.loaded || file.loading || file.isUnsaved) return; if (file.uri) { const fs = fsOperation(file.uri); diff --git a/src/lib/checkPluginsUpdate.js b/src/lib/checkPluginsUpdate.js index f58dee687..ecf5be115 100644 --- a/src/lib/checkPluginsUpdate.js +++ b/src/lib/checkPluginsUpdate.js @@ -1,25 +1,30 @@ import ajax from "@deadlyjack/ajax"; +import internalFs from "fileSystem/internalFs"; import fsOperation from "../fileSystem"; import Url from "../utils/Url"; export default async function checkPluginsUpdate() { - const plugins = await fsOperation(PLUGIN_DIR).lsDir(); + const plugins = await internalFs.listDir(PLUGIN_DIR); const promises = []; const updates = []; plugins.forEach((pluginDir) => { promises.push( (async () => { - const plugin = await fsOperation( - Url.join(pluginDir.url, "plugin.json"), - ).readFile("json"); + try { + const plugin = await fsOperation( + Url.join(pluginDir.url, "plugin.json"), + ).readFile("json"); - const res = await ajax({ - url: `https://acode.app/api/plugin/check-update/${plugin.id}/${plugin.version}`, - }); + const res = await ajax({ + url: `https://acode.app/api/plugin/check-update/${plugin.id}/${plugin.version}`, + }); - if (res.update) { - updates.push(plugin.id); + if (res && res.update === true) { + updates.push(plugin.id); + } + } catch (e) { + console.warn(e); } })(), ); diff --git a/src/lib/constants.js b/src/lib/constants.js index 7c1283227..4c44fff2a 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -23,4 +23,5 @@ export default { // API_BASE: 'https://192.168.0.102:3001/api', // test api SKU_LIST: ["basic", "bronze", "silver", "gold", "platinum"], LOG_FILE_NAME: "Acode.log", + CONSOLE_BUILD_PATH: "/js/build/console.build.js", }; diff --git a/src/lib/installPlugin.js b/src/lib/installPlugin.js index 84b9b20bd..56ee9462f 100644 --- a/src/lib/installPlugin.js +++ b/src/lib/installPlugin.js @@ -3,6 +3,7 @@ import alert from "dialogs/alert"; import confirm from "dialogs/confirm"; import loader from "dialogs/loader"; import fsOperation from "fileSystem"; +import internalFs from "fileSystem/internalFs"; import purchaseListener from "handlers/purchase"; import JSZip from "jszip"; import Url from "utils/Url"; @@ -29,6 +30,7 @@ export default async function installPlugin( purchaseToken, isDependency, ) { + console.log(`Installing ${name}`); if (!isDependency) { loaderDialog = loader.create(name || "Plugin", strings.installing, { timeout: 6000, @@ -41,13 +43,14 @@ export default async function installPlugin( let state; try { - if (!(await fsOperation(PLUGIN_DIR).exists())) { - await fsOperation(DATA_STORAGE).createDirectory("plugins"); + if (!(await internalFs.exists(PLUGIN_DIR))) { + await internalFs.createDir(DATA_STORAGE, "plugins"); } } catch (error) { window.log("error", error); } + console.log("installing -------------------------"); if (!/^(https?|file|content):/.test(id)) { pluginUrl = Url.join( constants.API_BASE, @@ -66,6 +69,8 @@ export default async function installPlugin( try { if (!isDependency) loaderDialog.show(); + console.log("installing ..."); + let plugin; if ( pluginUrl.includes(constants.API_BASE) || @@ -93,9 +98,11 @@ export default async function installPlugin( (response) => { resolve(response.data); loaderDialog.setMessage(`${strings.loading} 100%`); + console.log("Download complete"); }, (error) => { reject(error); + console.log("Download failed"); }, ); }); @@ -104,15 +111,19 @@ export default async function installPlugin( if (plugin) { const zip = new JSZip(); await zip.loadAsync(plugin); + console.log("Plugin zip loaded into memory"); if (!zip.files["plugin.json"]) { throw new Error(strings["invalid plugin"]); } + console.log("Plugin Json start"); + const jsonStr = await zip.files["plugin.json"].async("text"); + console.log(jsonStr); + console.log("Plugin json end"); + /** @type {{ dependencies: string[] }} */ - const pluginJson = JSON.parse( - await zip.files["plugin.json"].async("text"), - ); + const pluginJson = JSON.parse(jsonStr); /** patch main in manifest */ if (!zip.files[pluginJson.main]) { @@ -167,20 +178,25 @@ export default async function installPlugin( pluginDir = Url.join(PLUGIN_DIR, id); } + console.log("Begin Install state"); state = await InstallState.new(id); + console.log("Install state end"); - if (!(await fsOperation(pluginDir).exists())) { - await fsOperation(PLUGIN_DIR).createDirectory(id); + if (!(await internalFs.exists(pluginDir))) { + await internalFs.createDir(PLUGIN_DIR, id); } const promises = Object.keys(zip.files).map(async (file) => { try { let correctFile = file; + if (/\\/.test(correctFile)) { correctFile = correctFile.replace(/\\/g, "/"); } + console.log(`Correct file ${correctFile}`); const fileUrl = Url.join(pluginDir, correctFile); + console.log("file Url " + fileUrl); if (!state.exists(correctFile)) { await createFileRecursive(pluginDir, correctFile); @@ -196,7 +212,9 @@ export default async function installPlugin( } if (!(await state.isUpdated(correctFile, data))) return; + console.log("writing file"); await fsOperation(fileUrl).writeFile(data); + console.log("file written"); return; } catch (error) { console.error(`Error processing file ${file}:`, error); @@ -206,17 +224,27 @@ export default async function installPlugin( // Wait for all files to be processed await Promise.allSettled(promises); + console.log("done"); + if (isDependency) { depsLoaders.push(async () => { await loadPlugin(id, true); }); } else { + console.log("loaders"); for (const loader of depsLoaders) { + console.log("loading loader"); + console.log(loader); await loader(); } + console.log("loader loading done"); + console.log("loading plugin"); await loadPlugin(id, true); + console.log("loading plugin done"); } + console.log("successfully loaded"); + await state.save(); deleteRedundantFiles(pluginDir, state); } diff --git a/src/lib/installState.js b/src/lib/installState.js index 4b7859b0f..5fc12b887 100644 --- a/src/lib/installState.js +++ b/src/lib/installState.js @@ -25,7 +25,11 @@ export default class InstallState { } state.storeUrl = Url.join(INSTALL_STATE_STORAGE, state.id); - if (await fsOperation(state.storeUrl).exists()) { + + console.log("store url"); + console.log(state.storeUrl); + + if ((await fsOperation(state.storeUrl).exists()) && false) { state.store = JSON.parse( await fsOperation(state.storeUrl).readFile("utf-8"), ); @@ -41,6 +45,10 @@ export default class InstallState { await fsOperation(INSTALL_STATE_STORAGE).createFile(state.id); } + await fsOperation(state.storeUrl).delete(); + console.log("returned state"); + console.log(state); + return state; } catch (e) { console.error(e); diff --git a/src/lib/loadPlugin.js b/src/lib/loadPlugin.js index cc0b0d13f..00aaeab4c 100644 --- a/src/lib/loadPlugin.js +++ b/src/lib/loadPlugin.js @@ -1,11 +1,15 @@ import Page from "components/page"; import fsOperation from "fileSystem"; +import internalFs from "fileSystem/internalFs"; import Url from "utils/Url"; import helpers from "utils/helpers"; import actionStack from "./actionStack"; export default async function loadPlugin(pluginId, justInstalled = false) { - const baseUrl = await helpers.toInternalUri(Url.join(PLUGIN_DIR, pluginId)); + const baseUrl = Url.join(PLUGIN_DIR, pluginId); + + console.log("Base url " + baseUrl); + const cacheFile = Url.join(CACHE_STORAGE, pluginId); const pluginJson = await fsOperation( @@ -21,10 +25,32 @@ export default async function loadPlugin(pluginId, justInstalled = false) { mainUrl = Url.join(baseUrl, "main.js"); } - return new Promise((resolve, reject) => { - const $script = ; + console.log(`main url ${mainUrl}`); + + return new Promise(async (resolve, reject) => { + if (pluginId === undefined) { + console.error("Skipping loading plugin with undefined id"); + reject("Skipping loading plugin with undefined id"); + return; + } + + const result = await internalFs.readStringFile(mainUrl); + + console.log(`result ${result}`); + + const data = result.data; + + const blob = new Blob([data], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + + const $script = document.createElement("script"); + $script.src = url; + $script.type = "text/javascript"; + + console.log("script created"); $script.onerror = (error) => { + URL.revokeObjectURL(url); reject( new Error( `Failed to load script for plugin ${pluginId}: ${error.message || error}`, @@ -32,7 +58,10 @@ export default async function loadPlugin(pluginId, justInstalled = false) { ); }; + console.log("on error registered"); + $script.onload = async () => { + URL.revokeObjectURL(url); const $page = Page("Plugin"); $page.show = () => { actionStack.push({ @@ -48,15 +77,21 @@ export default async function loadPlugin(pluginId, justInstalled = false) { }; try { + console.log("trying"); if (!(await fsOperation(cacheFile).exists())) { await fsOperation(CACHE_STORAGE).createFile(pluginId); } - await acode.initPlugin(pluginId, baseUrl, $page, { - cacheFileUrl: await helpers.toInternalUri(cacheFile), - cacheFile: fsOperation(cacheFile), - firstInit: justInstalled, - }); + await acode.initPlugin( + pluginId, + Capacitor.convertFileSrc(baseUrl), + $page, + { + cacheFileUrl: Capacitor.convertFileSrc(cacheFile), + cacheFile: fsOperation(cacheFile), + firstInit: justInstalled, + }, + ); resolve(); } catch (error) { @@ -64,6 +99,8 @@ export default async function loadPlugin(pluginId, justInstalled = false) { } }; + console.log("attaching script"); document.head.append($script); + console.log("script attached"); }); } diff --git a/src/lib/loadPlugins.js b/src/lib/loadPlugins.js index d3eee52d3..9449543e8 100644 --- a/src/lib/loadPlugins.js +++ b/src/lib/loadPlugins.js @@ -1,3 +1,4 @@ +import internalFs from "fileSystem/internalFs"; import fsOperation from "../fileSystem"; import Url from "../utils/Url"; import loadPlugin from "./loadPlugin"; @@ -25,7 +26,7 @@ const THEME_IDENTIFIERS = new Set([ ]); export default async function loadPlugins(loadOnlyTheme = false) { - const plugins = await fsOperation(PLUGIN_DIR).lsDir(); + const plugins = await internalFs.listDir(PLUGIN_DIR); const results = []; const failedPlugins = []; const loadedPlugins = new Set(); @@ -53,6 +54,7 @@ export default async function loadPlugins(loadOnlyTheme = false) { // Load plugins concurrently const loadPromises = pluginsToLoad.map(async (pluginDir) => { + console.log("loading"); const pluginId = Url.basename(pluginDir.url); if (loadOnlyTheme && currentTheme) { diff --git a/src/lib/main.js b/src/lib/main.js index 91017d999..5347ae7ee 100644 --- a/src/lib/main.js +++ b/src/lib/main.js @@ -56,6 +56,8 @@ import { getEncoding, initEncodings } from "utils/encodings"; import auth, { loginEvents } from "./auth"; import constants from "./constants"; +import { App as CapacitorApp } from "@capacitor/app"; + const previousVersionCode = Number.parseInt(localStorage.versionCode, 10); window.onload = Main; @@ -431,7 +433,7 @@ async function loadApp() { editorManager.on("rename-file", onFileUpdate); editorManager.on("switch-file", onFileUpdate); editorManager.on("file-loaded", onFileUpdate); - navigator.app.overrideButton("menubutton", true); + //navigator.app.overrideButton("menubutton", true); system.setIntentHandler(intentHandler, intentHandler.onError); system.getCordovaIntent(intentHandler, intentHandler.onError); setTimeout(showTutorials, 1000); @@ -658,12 +660,21 @@ function showTutorials() { } } -function backButtonHandler() { +function backButtonHandler(event) { + event.preventDefault(); + if (keydownState.esc) { keydownState.esc = false; return; } - actionStack.pop(); + + const top = actionStack.pop(); + + if (top && typeof top.action === "function") { + top.action(); + } else { + //CapacitorApp.exitApp(); + } } function menuButtonHandler() { diff --git a/src/lib/run.js b/src/lib/run.js index 1934d2e93..02ab65b4d 100644 --- a/src/lib/run.js +++ b/src/lib/run.js @@ -1,3 +1,4 @@ +import ajax from "@deadlyjack/ajax"; import tutorial from "components/tutorial"; import alert from "dialogs/alert"; import box from "dialogs/box"; @@ -134,7 +135,7 @@ async function run( //this extra www is incorrect because asset_directory itself has www //but keeping it in case something depends on it - pathName = `${ASSETS_DIRECTORY}www/`; + pathName = "/"; port = constants.CONSOLE_PORT; start(); @@ -192,9 +193,9 @@ async function run( isConsole || appSettings.value.console === appSettings.CONSOLE_LEGACY ) { - url = `${ASSETS_DIRECTORY}/js/build/console.build.js`; + url = constants.CONSOLE_BUILD_PATH; } else { - url = `${DATA_STORAGE}/eruda.js`; + url = `${DATA_STORAGE}eruda.js`; } sendFileContent(url, reqId, "application/javascript"); break; @@ -479,24 +480,22 @@ async function run( * @returns */ async function sendFileContent(url, id, mime, processText) { - let fs = fsOperation(url); - - if (!(await fs.exists())) { - const xfs = fsOperation(Url.join(pathName, filename)); + let text; + if (url.includes(constants.CONSOLE_BUILD_PATH)) { + text = await ajax.get(url, { + responseType: "text", + }); + } else { + const fs = fsOperation(url); - if (await xfs.exists()) { - fs = xfs; - isFallback = true; - console.log(`fallback ${Url.join(pathName, filename)}`); - } else { - console.log(`${url} doesnt exists`); + if (!(await fs.exists())) { error(id); + return; } - return; + text = await fs.readFile(appSettings.value.defaultFileEncoding); } - let text = await fs.readFile(appSettings.value.defaultFileEncoding); text = processText ? processText(text) : text; if (mime === MIMETYPE_HTML) { sendHTML(text, id); diff --git a/src/pages/plugin/plugin.js b/src/pages/plugin/plugin.js index 63bde9d14..ece6d0945 100644 --- a/src/pages/plugin/plugin.js +++ b/src/pages/plugin/plugin.js @@ -75,10 +75,19 @@ export default async function PluginInclude( const installedPlugin = await fsOperation( Url.join(PLUGIN_DIR, id, "plugin.json"), ).readFile("json"); + + console.log(installPlugin); + const { author } = installedPlugin; + + console.log(author); + const description = await fsOperation( Url.join(PLUGIN_DIR, id, installedPlugin.readme), ).readFile("utf8"); + + console.log(description); + let changelogs = ""; if (installedPlugin.changelogs) { const changelogPath = Url.join( @@ -92,16 +101,12 @@ export default async function PluginInclude( } } - const iconUrl = await helpers.toInternalUri( + const iconUrl = Capacitor.convertFileSrc( Url.join(PLUGIN_DIR, id, installedPlugin.icon), ); - const iconData = await fsOperation(iconUrl).readFile(); - const icon = URL.createObjectURL( - new Blob([iconData], { type: "image/png" }), - ); plugin = { id, - icon, + iconUrl, name: installedPlugin.name, version: installedPlugin.version, author: author.name, diff --git a/src/pages/plugins/item.js b/src/pages/plugins/item.js index 393b64ee1..d39c28bae 100644 --- a/src/pages/plugins/item.js +++ b/src/pages/plugins/item.js @@ -23,6 +23,7 @@ export default function Item({ downloads, installed, }) { + console.log(`ICON ${icon}`) const authorName = (() => { const displayName = typeof author === "object" ? author.name : author || "Unknown"; diff --git a/src/pages/plugins/plugins.js b/src/pages/plugins/plugins.js index cd2f2c0e0..c4ebbd82c 100644 --- a/src/pages/plugins/plugins.js +++ b/src/pages/plugins/plugins.js @@ -14,6 +14,7 @@ import installPlugin from "lib/installPlugin"; import prompt from "dialogs/prompt"; import actionStack from "lib/actionStack"; import Contextmenu from "components/contextmenu"; +import internalFs from "fileSystem/internalFs"; /** * @@ -305,7 +306,7 @@ export default function PluginsInclude(updates) { hasMore = false; } - const installed = await fsOperation(PLUGIN_DIR).lsDir(); + const installed = await internalFs.listDir(PLUGIN_DIR) installed.forEach(({ url }) => { const plugin = newPlugins.find(({ id }) => id === Url.basename(url)); if (plugin) { @@ -330,15 +331,14 @@ export default function PluginsInclude(updates) { async function getInstalledPlugins(updates) { $list.installed.setAttribute("empty-msg", strings["loading..."]); plugins.installed = []; - const installed = await fsOperation(PLUGIN_DIR).lsDir(); + const installed = await internalFs.listDir(PLUGIN_DIR) await Promise.all( installed.map(async (item) => { const id = Url.basename(item.url); if (!((updates && updates.includes(id)) || !updates)) return; const url = Url.join(item.url, "plugin.json"); const plugin = await fsOperation(url).readFile("json"); - const iconUrl = getLocalRes(id, plugin.icon); - plugin.icon = await helpers.toInternalUri(iconUrl); + plugin.icon = Capacitor.convertFileSrc(getLocalRes(id, plugin.icon)) plugin.installed = true; plugins.installed.push(plugin); if ($list.installed.get(`[data-id="${id}"]`)) return; diff --git a/src/plugins/browser/android/com/foxdebug/browser/Menu.java b/src/plugins/browser/android/com/foxdebug/browser/Menu.java index 8b5bbb544..e6ee650bd 100644 --- a/src/plugins/browser/android/com/foxdebug/browser/Menu.java +++ b/src/plugins/browser/android/com/foxdebug/browser/Menu.java @@ -15,7 +15,7 @@ import android.widget.PopupWindow; import android.widget.ScrollView; import android.widget.TextView; -import com.foxdebug.acode.R; +import capacitor.cordova.android.plugins.R; import com.foxdebug.system.Ui; public class Menu extends PopupWindow { diff --git a/src/plugins/browser/index.js b/src/plugins/browser/index.js index 0670ef9fd..023ea4750 100644 --- a/src/plugins/browser/index.js +++ b/src/plugins/browser/index.js @@ -4,6 +4,7 @@ import themes from 'theme/list'; const SERVICE = 'Browser'; function open(url, isConsole = false) { + console.log("opening "+url) const ACTION = 'open'; const success = () => { }; const error = () => { }; diff --git a/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java b/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java index 05887c4a6..5387c1c67 100644 --- a/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java +++ b/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java @@ -298,14 +298,15 @@ public Response serve(IHTTPSession session) { responseObject.getString("body") ); - Iterator keys = responseObject.getJSONObject("headers").keys(); - while (keys.hasNext()) { - String key = (String) keys.next(); - response.addHeader( - key, - responseObject.getJSONObject("headers").getString(key) - ); + if (responseObject.has("headers")) { + JSONObject headers = responseObject.getJSONObject("headers"); + Iterator keys = headers.keys(); + while (keys.hasNext()) { + String key = (String) keys.next(); + response.addHeader(key, headers.getString(key)); + } } + } catch (JSONException e) { e.printStackTrace(); } diff --git a/src/plugins/sftp/plugin.xml b/src/plugins/sftp/plugin.xml index f2c2dfc6d..11ce569b4 100644 --- a/src/plugins/sftp/plugin.xml +++ b/src/plugins/sftp/plugin.xml @@ -13,18 +13,21 @@ + + + + + - + - - - + diff --git a/src/plugins/system/android/com/foxdebug/system/System.java b/src/plugins/system/android/com/foxdebug/system/System.java index fb4839dc1..a8bfe116b 100644 --- a/src/plugins/system/android/com/foxdebug/system/System.java +++ b/src/plugins/system/android/com/foxdebug/system/System.java @@ -47,6 +47,9 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import android.webkit.WebView; +import capacitor.cordova.android.plugins.BuildConfig; + public class System extends CordovaPlugin { @@ -66,6 +69,11 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) { this.activity = cordova.getActivity(); this.webView = webView; + //enable webview debugging when in debug mode + if (BuildConfig.DEBUG){ + ((WebView)webView.getView()).setWebContentsDebuggingEnabled(true); + } + // Set up global exception handler Thread.setDefaultUncaughtExceptionHandler( new Thread.UncaughtExceptionHandler() { @@ -912,6 +920,7 @@ private void setInputType(String type) { } else if (type.equals("NO_SUGGESTIONS_AGGRESSIVE")) { mode = 1; } - webView.setInputType(mode); + //no such method + //webView.setInputType(mode); } } diff --git a/src/plugins/system/plugin.xml b/src/plugins/system/plugin.xml index e42796afb..4158dcafb 100644 --- a/src/plugins/system/plugin.xml +++ b/src/plugins/system/plugin.xml @@ -17,11 +17,11 @@ - - - - - + + + + + diff --git a/src/sidebarApps/extensions/index.js b/src/sidebarApps/extensions/index.js index feca1639b..d4719bad7 100644 --- a/src/sidebarApps/extensions/index.js +++ b/src/sidebarApps/extensions/index.js @@ -6,10 +6,12 @@ import Sidebar from "components/sidebar"; import prompt from "dialogs/prompt"; import select from "dialogs/select"; import fsOperation from "fileSystem"; +import internalFs from "fileSystem/internalFs"; import purchaseListener from "handlers/purchase"; import constants from "lib/constants"; import InstallState from "lib/installState"; import settings from "lib/settings"; +import mimeType from "mime-types"; import FileBrowser from "pages/fileBrowser"; import plugin from "pages/plugin"; import Url from "utils/Url"; @@ -301,18 +303,49 @@ async function loadExplore() { } async function listInstalledPlugins() { + let dirItems; + + try { + dirItems = await internalFs.listDir(PLUGIN_DIR); + } catch (err) { + return []; + } + const plugins = await Promise.all( - (await fsOperation(PLUGIN_DIR).lsDir()).map(async (item) => { - const id = Url.basename(item.url); - const url = Url.join(item.url, "plugin.json"); - const plugin = await fsOperation(url).readFile("json"); - const iconUrl = getLocalRes(id, plugin.icon); - plugin.icon = await helpers.toInternalUri(iconUrl); + dirItems.map(async (item, index) => { + let id, url; + try { + id = Url.basename(item.url); + url = Url.join(item.url, "plugin.json"); + } catch (err) { + return null; + } + + let plugin; + try { + plugin = await fsOperation(url).readFile("json"); + } catch (err) { + console.error(err); + return null; + } + + try { + const iconUrl = Capacitor.convertFileSrc(getLocalRes(id, plugin.icon)); + plugin.icon = iconUrl; + } catch (err) { + plugin.icon = null; + } + plugin.installed = true; + plugin.id = id; + plugin.path = item.uri; + return plugin; }), ); - return plugins; + + const filteredPlugins = plugins.filter(Boolean); + return filteredPlugins; } async function getFilteredPlugins(filterName) { diff --git a/utils/scripts/build.sh b/utils/scripts/build.sh index 066bb3ab3..043adb0a0 100644 --- a/utils/scripts/build.sh +++ b/utils/scripts/build.sh @@ -43,4 +43,4 @@ echo \"${RED}$script3${NC}\"; $script3; echo \"${RED}$script4${NC}\"; $script4; -" +" \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index de748a083..4901560dd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -85,6 +85,12 @@ module.exports = (env, options) => { filename: '../../css/build/[name].css', }), ], + optimization: { + minimize: mode === 'production', + // Only set a custom minimizer in production; otherwise, omit it. + ...(mode === 'production' ? {} : { minimizer: [] }), + }, + }; return [main];