diff --git a/apkbuilder/build.gradle.kts b/apkbuilder/build.gradle.kts index 7d5430650..8c0a1c200 100644 --- a/apkbuilder/build.gradle.kts +++ b/apkbuilder/build.gradle.kts @@ -27,21 +27,14 @@ android { } dependencies { - implementation("com.squareup.okhttp3:okhttp:4.10.0") - implementation("androidx.core:core-ktx:1.8.0") -} -dependencies { - testImplementation("org.springframework.boot:spring-boot-starter-test") { - exclude("junit", "junit") - } - androidTestImplementation("androidx.test.espresso:espresso-core:3.1.1-alpha01") { - exclude(group = "com.android.support", module = "support-annotations") - } - testImplementation("junit:junit:4.13.2") - api(fileTree("libs") { include("*.jar") }) + + implementation(libs.okhttp) + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.espresso.core) + testImplementation(libs.junit) api(files("libs/tiny-sign-0.9.jar")) - api(files("libs/commons-io-2.5.jar")) - implementation("androidx.core:core-ktx:1.8.0") + api(libs.commons.io) + implementation(libs.core.ktx) } repositories { mavenCentral() diff --git a/apkbuilder/libs/commons-io-2.5.jar b/apkbuilder/libs/commons-io-2.5.jar deleted file mode 100755 index 123491827..000000000 Binary files a/apkbuilder/libs/commons-io-2.5.jar and /dev/null differ diff --git a/apkbuilder/src/androidTest/java/com/stardust/autojs/apkbuilder/ExampleInstrumentedTest.java b/apkbuilder/src/androidTest/java/com/stardust/autojs/apkbuilder/ExampleInstrumentedTest.java index be7e498e6..12003475f 100755 --- a/apkbuilder/src/androidTest/java/com/stardust/autojs/apkbuilder/ExampleInstrumentedTest.java +++ b/apkbuilder/src/androidTest/java/com/stardust/autojs/apkbuilder/ExampleInstrumentedTest.java @@ -1,8 +1,8 @@ package com.stardust.autojs.apkbuilder; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,7 +19,7 @@ public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); + Context appContext = InstrumentationRegistry.getInstrumentation().getContext(); assertEquals("com.stardust.autojs.apkbuilder.test", appContext.getPackageName()); } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bcad083bb..62b1f311e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -227,7 +227,7 @@ dependencies { //MultiLevelListView implementation("com.github.hyb1996:android-multi-level-listview:1.1") //Licenses Dialog - implementation("de.psdev.licensesdialog:licensesdialog:1.9.0") + implementation("de.psdev.licensesdialog:licensesdialog:2.2.0") //Expandable RecyclerView implementation("com.bignerdranch.android:expandablerecyclerview:3.0.0-RC1") //FlexibleDivider @@ -282,6 +282,7 @@ dependencies { implementation(project(":common")) implementation(project(":autojs")) implementation(project(":apkbuilder")) + implementation(project(":codeeditor")) implementation("androidx.multidex:multidex:2.0.1") val lifecycle_version = "2.5.1" diff --git a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt index b9806fbd6..10bce27d8 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt @@ -3,6 +3,7 @@ package org.autojs.autojs.ui.main import android.Manifest import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import android.widget.Toast import androidx.activity.compose.setContent @@ -24,6 +25,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.fragment.app.FragmentActivity import androidx.viewpager2.widget.ViewPager2 +import com.aiselp.autojs.codeeditor.EditActivity import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.systemuicontroller.rememberSystemUiController @@ -47,10 +49,9 @@ import org.autojs.autojs.ui.main.components.LogButton import org.autojs.autojs.ui.main.drawer.DrawerPage import org.autojs.autojs.ui.main.scripts.ScriptListFragment import org.autojs.autojs.ui.main.task.TaskManagerFragmentKt -import org.autojs.autojs.ui.main.web.WebViewFragment +import org.autojs.autojs.ui.main.web.EditorAppManager import org.autojs.autojs.ui.widget.fillMaxSize import org.autojs.autoxjs.R - data class BottomNavigationItem(val icon: Int, val label: String) class MainActivity : FragmentActivity() { @@ -62,7 +63,7 @@ class MainActivity : FragmentActivity() { private val scriptListFragment by lazy { ScriptListFragment() } private val taskManagerFragment by lazy { TaskManagerFragmentKt() } - private val webViewFragment by lazy { WebViewFragment() } + private val webViewFragment by lazy { EditorAppManager() } private var lastBackPressedTime = 0L private var drawerState: DrawerState? = null private val viewPager: ViewPager2 by lazy { ViewPager2(this) } @@ -142,7 +143,7 @@ fun MainPage( activity: FragmentActivity, scriptListFragment: ScriptListFragment, taskManagerFragment: TaskManagerFragmentKt, - webViewFragment: WebViewFragment, + webViewFragment: EditorAppManager, onDrawerState: (DrawerState) -> Unit, viewPager: ViewPager2 ) { @@ -318,7 +319,7 @@ private fun TopBar( requestOpenDrawer: () -> Unit, onSearch: (String) -> Unit, scriptListFragment: ScriptListFragment, - webViewFragment: WebViewFragment, + webViewFragment: EditorAppManager, ) { var isSearch by remember { mutableStateOf(false) @@ -422,6 +423,13 @@ fun TopAppBarMenu( NewFile(context, scriptListFragment, onDismissRequest) ImportFile(context, scriptListFragment, onDismissRequest) NewProject(context, scriptListFragment, onDismissRequest) + DropdownMenuItem(onClick = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + context.startActivity(Intent(context,EditActivity::class.java)) + } + }) { + Text(text = "打开新编辑器") + } // DropdownMenuItem(onClick = { /*TODO*/ }) { // MyIcon( // painter = painterResource(id = R.drawable.ic_timed_task), diff --git a/app/src/main/java/org/autojs/autojs/ui/main/ViewPager2Adapter.kt b/app/src/main/java/org/autojs/autojs/ui/main/ViewPager2Adapter.kt index e820cdac3..b15fff356 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/ViewPager2Adapter.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/ViewPager2Adapter.kt @@ -5,13 +5,13 @@ import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import org.autojs.autojs.ui.main.scripts.ScriptListFragment import org.autojs.autojs.ui.main.task.TaskManagerFragmentKt -import org.autojs.autojs.ui.main.web.WebViewFragment +import org.autojs.autojs.ui.main.web.EditorAppManager class ViewPager2Adapter( fragmentActivity: FragmentActivity, private val scriptListFragment: ScriptListFragment, private val taskManagerFragment: TaskManagerFragmentKt, - private val webViewFragment: WebViewFragment + private val webViewFragment: EditorAppManager ) : FragmentStateAdapter(fragmentActivity) { override fun createFragment(position: Int): Fragment { diff --git a/app/src/main/java/org/autojs/autojs/ui/main/scripts/ScriptListFragment.kt b/app/src/main/java/org/autojs/autojs/ui/main/scripts/ScriptListFragment.kt index 734c230c9..da80a2b81 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/scripts/ScriptListFragment.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/scripts/ScriptListFragment.kt @@ -1,6 +1,7 @@ package org.autojs.autojs.ui.main.scripts import android.content.Context +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -25,6 +26,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager +import com.aiselp.autojs.codeeditor.EditActivity import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.leinardi.android.speeddial.compose.FabWithLabel import com.leinardi.android.speeddial.compose.SpeedDial @@ -46,6 +48,7 @@ import org.autojs.autojs.ui.main.showExternalStoragePermissionToast import org.autojs.autojs.ui.viewmodel.ExplorerItemList.SortConfig import org.autojs.autojs.ui.widget.fillMaxSize import org.autojs.autoxjs.R +import java.io.File /** * Created by wilinz on 2022/7/15. @@ -237,7 +240,9 @@ class ScriptListFragment : Fragment() { setOnItemClickListener { _, item -> item?.let { if (item.isEditable) { - edit(requireContext(), item.toScriptFile()) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + EditActivity.editFile(requireContext(), File(item.path)) + } else edit(requireContext(), item.toScriptFile()) } else { IntentUtil.viewFile(get(), item.path, AppFileProvider.AUTHORITY) } diff --git a/app/src/main/java/org/autojs/autojs/ui/main/web/WebViewFragment.kt b/app/src/main/java/org/autojs/autojs/ui/main/web/EditorAppManager.kt similarity index 94% rename from app/src/main/java/org/autojs/autojs/ui/main/web/WebViewFragment.kt rename to app/src/main/java/org/autojs/autojs/ui/main/web/EditorAppManager.kt index 456864015..6088c6484 100644 --- a/app/src/main/java/org/autojs/autojs/ui/main/web/WebViewFragment.kt +++ b/app/src/main/java/org/autojs/autojs/ui/main/web/EditorAppManager.kt @@ -9,7 +9,7 @@ import org.autojs.autojs.ui.widget.SwipeRefreshWebView import org.autojs.autojs.ui.widget.WebDataKt import org.autojs.autojs.ui.widget.fillMaxSize -class WebViewFragment : Fragment() { +class EditorAppManager : Fragment() { val swipeRefreshWebView by lazy { SwipeRefreshWebView(requireContext()) } diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/LicenseInfo.kt b/app/src/main/java/org/autojs/autojs/ui/settings/LicenseInfo.kt new file mode 100644 index 000000000..150f99de8 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/settings/LicenseInfo.kt @@ -0,0 +1,33 @@ +package org.autojs.autojs.ui.settings + +import android.content.Context +import de.psdev.licensesdialog.LicenseResolver +import de.psdev.licensesdialog.licenses.License +import org.autojs.autoxjs.R + +object LicenseInfo { + fun install(){ + LicenseResolver.registerLicense(MozillaPublicLicense20()) + } + +} +class MozillaPublicLicense20 : License() { + override fun getName(): String { + return "Mozilla Public License 2.0" + } + override fun readSummaryTextFromResources(context: Context): String { + return getContent(context, R.raw.mpl_20_summary) + } + + override fun readFullTextFromResources(context: Context): String { + return getContent(context, R.raw.mpl_20_full) + } + + override fun getVersion(): String { + return "2.0" + } + + override fun getUrl(): String { + return "https://www.mozilla.org/en-US/MPL/2.0/" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java b/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java index 88e2d94c1..58b3d512b 100644 --- a/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java +++ b/app/src/main/java/org/autojs/autojs/ui/settings/SettingsActivity.java @@ -36,6 +36,9 @@ @EActivity(R.layout.activity_settings) public class SettingsActivity extends BaseActivity { + static { + LicenseInfo.INSTANCE.install(); + } private static final List> COLOR_ITEMS = new ListBuilder>() .add(new Pair<>(R.color.theme_color_red, R.string.theme_color_red)) .add(new Pair<>(R.color.theme_color_pink, R.string.theme_color_pink)) @@ -140,7 +143,6 @@ public boolean onPreferenceTreeClick(Preference preference) { } private void showLicenseDialog() { - LicenseResolver.registerLicense(MozillaPublicLicense20.instance); new LicensesDialog.Builder(getActivity()) .setNotices(R.raw.licenses) .setIncludeOwnLicense(true) @@ -158,35 +160,5 @@ private void showLicenseDialog2() { .show(); } - public static class MozillaPublicLicense20 extends License { - - public static MozillaPublicLicense20 instance = new MozillaPublicLicense20(); - - @Override - public String getName() { - return "Mozilla Public License 2.0"; - } - - @Override - public String readSummaryTextFromResources(Context context) { - return getContent(context, R.raw.mpl_20_summary); - } - - @Override - public String readFullTextFromResources(Context context) { - return getContent(context, R.raw.mpl_20_full); - } - - @Override - public String getVersion() { - return "2.0"; - } - - @Override - public String getUrl() { - return "https://www.mozilla.org/en-US/MPL/2.0/"; - } - } - } } diff --git a/app/src/main/res/raw/licenses.xml b/app/src/main/res/raw/licenses.xml index ba287f5a3..93b92ea98 100644 --- a/app/src/main/res/raw/licenses.xml +++ b/app/src/main/res/raw/licenses.xml @@ -149,4 +149,16 @@ https://github.com/paddlepaddle/paddle Apache Software License 2.0 + + AndServer + + https://github.com/yanzhenjie/AndServer/ + Apache Software License 2.0 + + + vscode-mobile + + https://github.com/aiselp/vscode-mobile + Apache Software License 2.0 + \ No newline at end of file diff --git a/autojs/build.gradle.kts b/autojs/build.gradle.kts index d1f99f66c..5d806c44b 100644 --- a/autojs/build.gradle.kts +++ b/autojs/build.gradle.kts @@ -36,12 +36,12 @@ dependencies { testImplementation(libs.junit) implementation(libs.documentfile) implementation("androidx.preference:preference-ktx:1.2.0") - api("org.greenrobot:eventbus:3.3.1") + api(libs.eventbus) api("net.lingala.zip4j:zip4j:1.3.2") api("com.afollestad.material-dialogs:core:0.9.2.3"){ exclude(group = "com.android.support") } - api("com.google.android.material:material:1.7.0-beta01") + api(libs.material) api("com.github.hyb1996:EnhancedFloaty:0.31") api("com.makeramen:roundedimageview:2.3.0") // OkHttp @@ -51,7 +51,7 @@ dependencies { // RootShell api("com.github.Stericson:RootShell:1.6") // Gson - api("com.google.code.gson:gson:2.9.1") + api(libs.google.gson) // log4j api(group = "de.mindpipe.android", name = "android-logging-log4j", version = "1.0.3") api(group = "log4j", name = "log4j", version = "1.2.17") diff --git a/build.gradle.kts b/build.gradle.kts index e7a1b95e2..065bc72ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ buildscript { classpath(kotlin("gradle-plugin", version = "$kotlin_version")) classpath("com.jakewharton:butterknife-gradle-plugin:10.2.3") classpath("org.codehaus.groovy:groovy-json:3.0.8") + classpath("com.yanzhenjie.andserver:plugin:2.1.12") } } diff --git a/codeeditor/.gitignore b/codeeditor/.gitignore new file mode 100644 index 000000000..b4385dbb8 --- /dev/null +++ b/codeeditor/.gitignore @@ -0,0 +1,2 @@ +/build +/src/main/assets/codeeditor \ No newline at end of file diff --git a/codeeditor/build.gradle.kts b/codeeditor/build.gradle.kts new file mode 100644 index 000000000..813c023ea --- /dev/null +++ b/codeeditor/build.gradle.kts @@ -0,0 +1,82 @@ +import java.net.URL +plugins { + id("com.android.library") + id("kotlin-android") + id("com.yanzhenjie.andserver") + id("kotlin-kapt") +} + +android { + namespace = "com.aiselp.autojs.codeeditor" + compileSdk = 33 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.andserver.api) + kapt(libs.andserver.processor) + implementation(libs.kotlinx.coroutines.android) + api(libs.nanohttpd.webserver) + api(libs.androidx.webkit) + implementation(libs.google.gson) + implementation(libs.core.ktx) + implementation(libs.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.espresso.core) +} + +tasks.register("downloadEditor") { + val tag = "dev-0.3.0" + val version = 3 + val uri = "https://github.com/aiselp/vscode-mobile/releases/download/${tag}/dist.zip" + val assetsDir = File(projectDir, "/src/main/assets/codeeditor") + val versionFile = File(assetsDir, "version.txt") + doFirst { + logger.log(org.gradle.api.logging.LogLevel.LIFECYCLE,"start downloadEditor") + assetsDir.mkdirs() + if (versionFile.isFile){ + val dowversion = versionFile.readText().toInt() + if (dowversion == version) { + logger.log(org.gradle.api.logging.LogLevel.LIFECYCLE,"skip download") + return@doFirst + } + } + URL(uri).openStream().use { + File(assetsDir, "dist.zip").outputStream().use { out-> + it.copyTo(out) + } + } + versionFile.writeText(version.toString()) + } +} +tasks.findByName("preBuild")?.dependsOn("downloadEditor") +tasks.findByName("preDebugBuild")?.dependsOn("downloadEditor") +tasks.names.forEach { +// logger.error(it) +} \ No newline at end of file diff --git a/codeeditor/consumer-rules.pro b/codeeditor/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/codeeditor/proguard-rules.pro b/codeeditor/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/codeeditor/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 \ No newline at end of file diff --git a/codeeditor/src/androidTest/java/com/aiselp/autojs/codeeditor/ExampleInstrumentedTest.kt b/codeeditor/src/androidTest/java/com/aiselp/autojs/codeeditor/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..f4043cd3b --- /dev/null +++ b/codeeditor/src/androidTest/java/com/aiselp/autojs/codeeditor/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.aiselp.autojs.codeeditor + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.aiselp.autojs.codeeditor.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/codeeditor/src/main/AndroidManifest.xml b/codeeditor/src/main/AndroidManifest.xml new file mode 100644 index 000000000..91a3bbd09 --- /dev/null +++ b/codeeditor/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/EditActivity.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/EditActivity.kt new file mode 100644 index 000000000..615a4d635 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/EditActivity.kt @@ -0,0 +1,69 @@ +package com.aiselp.autojs.codeeditor + +import android.content.Context +import android.content.Intent +import android.graphics.Rect +import android.os.Build +import android.os.Bundle +import android.widget.FrameLayout +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import com.aiselp.autojs.codeeditor.web.EditorAppManager +import java.io.File + +@RequiresApi(Build.VERSION_CODES.M) +class EditActivity : AppCompatActivity() { + private lateinit var editorAppManager: EditorAppManager + private lateinit var contextFrameLayout: FrameLayout + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + editorAppManager = EditorAppManager(this) + contextFrameLayout = FrameLayout(this) + contextFrameLayout.addView(editorAppManager.webView) + setContentView(contextFrameLayout) + setKeyboardEvent() + editorAppManager.opendeFile = intent.getStringExtra(EXTRA_PATH) + } + + override fun onDestroy() { + super.onDestroy() + editorAppManager.destroy() + } + + private fun setKeyboardEvent() { + val rootView = contextFrameLayout.rootView + rootView.viewTreeObserver.addOnGlobalLayoutListener { + val r = Rect() + rootView.getWindowVisibleDisplayFrame(r) + val currentHeight = rootView.height + var resultBottom = r.bottom + if (currentHeight - resultBottom > 200) { + editorAppManager.onKeyboardDidShow() + } else { + editorAppManager.onKeyboardDidHide() + } + } + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + val path = intent?.getStringExtra(EXTRA_PATH) + if (path != null) { + editorAppManager.openFile(path) + } + } + @Deprecated("Deprecated in Java") + override fun onBackPressed() { +// editorAppManager.onBackButton() + moveTaskToBack(false) + } + + companion object { + private const val EXTRA_PATH = "path"; + fun editFile(context: Context, path: File) { + val intent = Intent(context, EditActivity::class.java) + .putExtra(EXTRA_PATH, path.path) + context.startActivity(intent) + } + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/plugins/FileSystem.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/plugins/FileSystem.kt new file mode 100644 index 000000000..f188ab1c4 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/plugins/FileSystem.kt @@ -0,0 +1,150 @@ +package com.aiselp.autojs.codeeditor.plugins + +import android.os.Build +import android.os.Environment +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import com.aiselp.autojs.codeeditor.web.PluginManager +import com.aiselp.autojs.codeeditor.web.annotation.WebFunction +import com.google.gson.Gson +import java.io.File + +@RequiresApi(Build.VERSION_CODES.M) +class FileSystem() { + companion object { + const val TAG = "FileSystem" + const val MaxFileSize = 1024 * 1024 * 5 + val basePath: File = Environment.getExternalStorageDirectory() + } + + private val gson = Gson() + + private fun parsePath(path: String?): File { + check(path != null) { "path is null" } + return File(basePath, path) + } + + @WebFunction + fun stat(call: PluginManager.WebCall) { + Log.i(TAG, "stat: ${call.data}") + val fileRequest = gson.fromJson(call.data, FileRequest::class.java) + val file = parsePath(fileRequest.path) + if (!file.isFile && !file.isDirectory) { + return call.onError(Exception("${fileRequest.path} not file or directory")) + } + val stat = StatResult() + stat.type = if (file.isFile) StatResult.FileType else StatResult.DirectoryType + stat.size = file.length() + stat.name = file.name + stat.ctime = file.lastModified() + stat.mtine = file.lastModified() + call.onSuccess(gson.toJson(stat)) + } + + @WebFunction + fun readDirectory(call: PluginManager.WebCall) { + Log.i(TAG, "readDirectory: ${call.data}") + val fileRequest = gson.fromJson(call.data, FileRequest::class.java) + val file = parsePath(fileRequest.path) + val list: List = file.listFiles()?.map { + val stat = StatResult() + stat.type = if (it.isFile) StatResult.FileType else StatResult.DirectoryType + stat.size = it.length() + stat.name = it.name + stat.ctime = it.lastModified() + stat.mtine = it.lastModified() + return@map stat + } ?: throw Error("${fileRequest.path} not directory") + call.onSuccess(gson.toJson(list)) + } + + @WebFunction + fun createDirectory(call: PluginManager.WebCall) { + Log.i(TAG, "createDirectory: ${call.data}") + val req = gson.fromJson(call.data, FileRequest::class.java) + val path = parsePath(req.path) + path.mkdirs() + call.onSuccess(null) + } + + @WebFunction + fun readFile(call: PluginManager.WebCall) { + Log.i(TAG, "readFile: ${call.data}") + val req = gson.fromJson(call.data, FileRequest::class.java) + val file = parsePath(req.path) + if (file.isFile) { + if (file.length() > MaxFileSize) throw Exception("${req.path} too large") + val b64 = Base64.encodeToString(file.readBytes(), Base64.DEFAULT) + call.onSuccess(b64) + } else call.onError(Exception("${req.path} not file")) + } + + @WebFunction + fun writeFile(call: PluginManager.WebCall) { + Log.i(TAG, "writeFile: ${call.data}") + val req = gson.fromJson(call.data, FileRequest::class.java) + val file = parsePath(req.path) + val data = Base64.decode(req.data!!, Base64.DEFAULT) + file.writeBytes(data) + call.onSuccess(null) + } + + @WebFunction + fun delete(call: PluginManager.WebCall) { + Log.i(TAG, "delete: ${call.data}") + val req = gson.fromJson(call.data, FileRequest::class.java) + val path = parsePath(req.path) + path.delete() + call.onSuccess(null) + } + + @WebFunction + fun rename(call: PluginManager.WebCall) { + Log.i(TAG, "rename: ${call.data}") + val req = gson.fromJson(call.data, RenameFileRequest::class.java) + val form = parsePath(req.form) + val to = parsePath(req.to) + form.renameTo(to) + call.onSuccess(null) + } + + @WebFunction + fun copy(call: PluginManager.WebCall) { + Log.i(TAG, "copy: ${call.data}") + val req = gson.fromJson(call.data, CopyFileRequest::class.java) + val form = parsePath(req.form) + val to = parsePath(req.to) + form.copyTo(to) + call.onSuccess(null) + } + + class FileRequest { + var path: String = "null" + var data: String? = null + var encoding: String? = null + } + + class RenameFileRequest { + var form: String = "null" + var to: String = "null" + } + + class CopyFileRequest { + var form: String = "null" + var to: String = "null" + } + + class StatResult { + companion object { + const val FileType = "file" + const val DirectoryType = "directory" + } + + var type: String = FileType + var name: String? = null + var size: Long = 0 + var ctime: Long = 0 + var mtine: Long = 0 + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/AssetWebViewClient.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/AssetWebViewClient.kt new file mode 100644 index 000000000..3591b84e9 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/AssetWebViewClient.kt @@ -0,0 +1,72 @@ +package com.aiselp.autojs.codeeditor.web + +import android.content.Context +import android.net.Uri +import android.os.Build +import android.util.Log +import android.webkit.MimeTypeMap +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import androidx.annotation.RequiresApi +import androidx.webkit.WebViewAssetLoader +import androidx.webkit.WebViewAssetLoader.PathHandler +import java.io.File +import java.io.IOException +import java.io.InputStream + +@RequiresApi(Build.VERSION_CODES.M) +class AssetWebViewClient(assetPath: String, context: Context) : JsBridge.SuperWebViewClient() { + private val assetLoader: WebViewAssetLoader = + WebViewAssetLoader.Builder() + .setHttpAllowed(true) +// .addPathHandler("/", AssetsPathHandler(context, assetPath)) + .addPathHandler("/", WebViewAssetLoader.InternalStoragePathHandler(context,File(context.filesDir,"public"))) + .build() + + + @Deprecated("Deprecated in Java") + override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? { + return assetLoader.shouldInterceptRequest(Uri.parse(url)) ?: super.shouldInterceptRequest( + view, url + ) + } + + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + return assetLoader.shouldInterceptRequest(request.url) ?: super.shouldInterceptRequest( + view, request + ) + } + + class AssetsPathHandler(val context: Context, private val assetPath: String) : PathHandler { + + private fun parsePath(path: String): String { + var newPath = File(assetPath, path).path + if (newPath.startsWith("/")) { + newPath = newPath.substring(1) + } + println("path: $path to-------- newPath: $newPath") + return newPath + } + + override fun handle(path: String): WebResourceResponse? { + return try { + val newPath = parsePath(path) + val `is`: InputStream = context.assets.open(newPath) + val ext = MimeTypeMap.getFileExtensionFromUrl(newPath) + val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext) + WebResourceResponse(mimeType, null, `is`) + } catch (e: IOException) { + Log.e(TAG, "Error opening asset path: $path", e) + WebResourceResponse(null, null, null) + } + } + + companion object { + const val TAG = "AssetsPathHandler" + } + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/EditorAppManager.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/EditorAppManager.kt new file mode 100644 index 000000000..a4918890a --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/EditorAppManager.kt @@ -0,0 +1,159 @@ +package com.aiselp.autojs.codeeditor.web + +import android.app.Activity +import android.content.Context +import android.os.Build +import android.util.Log +import android.view.ViewGroup +import android.webkit.WebView +import androidx.annotation.RequiresApi +import com.aiselp.autojs.codeeditor.plugins.FileSystem +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream + +@RequiresApi(Build.VERSION_CODES.M) +class EditorAppManager(val context: Activity) { + companion object { + const val TAG = "EditorAppManager" + const val WEB_DIST_PATH = "codeeditor/dist.zip" + const val WEB_PUBLIC_PATH = "editorWeb/" + } + + private val coroutineScope = CoroutineScope(Dispatchers.Default) + private val executors: ExecutorService = Executors.newSingleThreadExecutor() + val webView = createWebView(context) + private val jsBridge = JsBridge(webView) + private val fileHttpServer = FileHttpServer( + context, File( + context.filesDir, "$WEB_PUBLIC_PATH/dist" + ) + ) + private val pluginManager = PluginManager(jsBridge, coroutineScope) + var opendeFile: String? = null + + init { + webView.webViewClient = JsBridge.SuperWebViewClient() + installPlugin() + coroutineScope.launch { + launch(executors.asCoroutineDispatcher()) { fileHttpServer.start() } + async { initWebResources() }.await() + delay(300) + withContext(Dispatchers.Main) { + webView.loadUrl(fileHttpServer.getAddress()) +// webView.loadUrl("http://192.168.10.10:8009") + } + } +// webView.loadUrl("http://appassets.androidplatform.net/index.html") + jsBridge.registerHandler("app.init", JsBridge.Handle { _, _ -> + val file = opendeFile + if (file != null) { + openFile(file) + } + }) + } + + private fun installPlugin() { + pluginManager.registerPlugin("FileSystem", FileSystem()) + jsBridge.registerHandler("app.exitApp", JsBridge.Handle { _, _ -> + context.moveTaskToBack(false) + }) + } + + private fun initWebResources() { + val webDir = File(context.filesDir, WEB_PUBLIC_PATH) + val versionFile = File(webDir, "version.txt") + if (isUpdate(versionFile)) { + Log.i(TAG, "skip initWebResources") + return + } + Log.i(TAG, "initWebResources") + webDir.mkdirs() + context.assets.open(WEB_DIST_PATH).use { it -> + ZipInputStream(it).use { zip -> + var zipEntry: ZipEntry? = null; + while (true) { + zipEntry = zip.nextEntry + if (zipEntry == null) break + val file = File(webDir, zipEntry.name) + if (zipEntry.isDirectory) { + file.mkdirs() + } else { + file.outputStream().use { + zip.copyTo(it) + } + } + zip.closeEntry() + } + } + } + val versionCode = context.packageManager.getPackageInfo(context.packageName, 0).versionCode + versionFile.writeText(versionCode.toString()) + } + + private fun isUpdate(file: File): Boolean { + if (!file.isFile) return false + return try { + val text = file.readText().toLong() + val versionCode = + context.packageManager.getPackageInfo(context.packageName, 0).versionCode + versionCode.toLong() == text + } catch (e: Exception) { + false + } + } + + fun destroy() { + webView.destroy() + fileHttpServer.stop() + coroutineScope.cancel() + executors.shutdownNow() + } + + fun openFile(path: String) { + jsBridge.callHandler("app.openFile", path.replace(FileSystem.basePath.path, ""), null) + } + + fun onKeyboardDidShow() { + Log.d(TAG, "onKeyboardDidShow") + jsBridge.callHandler("app.onKeyboardDidShow", null, null) + } + + fun onKeyboardDidHide() { + Log.d(TAG, "onKeyboardDidHide") + jsBridge.callHandler("app.onKeyboardDidHide", null, null) + } + + fun onBackButton() { + Log.d(TAG, "onBackButton") + jsBridge.callHandler("app.onBackButton", null, null) + } + + private fun createWebView(context: Context): WebView { + return WebView(context).apply { + settings.apply { + javaScriptEnabled = true + domStorageEnabled = true + allowFileAccess = true + allowContentAccess = true + allowFileAccessFromFileURLs = true + allowUniversalAccessFromFileURLs = true + useWideViewPort = true + } + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileHttpServer.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileHttpServer.kt new file mode 100644 index 000000000..c4592caf3 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileHttpServer.kt @@ -0,0 +1,71 @@ +package com.aiselp.autojs.codeeditor.web + +import android.content.Context +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.yanzhenjie.andserver.AndServer +import com.yanzhenjie.andserver.Server +import com.yanzhenjie.andserver.annotation.Config +import com.yanzhenjie.andserver.framework.config.WebConfig +import com.yanzhenjie.andserver.framework.website.StorageWebsite +import java.io.File +import java.io.IOException +import java.net.ServerSocket +import java.util.concurrent.TimeUnit +import kotlin.random.Random + + +class FileHttpServer(context: Context, path: File) { + companion object { + fun isPortAvailable(port: Int): Boolean { + try { + ServerSocket(port).use { + return true // 端口可用 + } + } catch (e: IOException) { + return false // 端口已被占用 + } + } + } + + private val port = getPort() + val server: Server = AndServer.webServer(context) + .port(port) + .timeout(10, TimeUnit.SECONDS) + .build(); + + private fun getPort(): Int { + while (true) { + val port = Random.nextInt(2000, 50000) + if (isPortAvailable(port)) { + return port + } + } + } + fun getAddress(): String { + return "http://127.0.0.1:$port" + } + fun start(){ + Log.d("FileHttpServer", "FileHttpServer init host: 127.0.0.1 port: $port") + server.startup() + } + fun stop(){ + server.shutdown() + } +} + +@RequiresApi(Build.VERSION_CODES.M) +@Config +class AppConfig : WebConfig { + override fun onConfig(context: Context, delegate: WebConfig.Delegate) { + // 增加一个位于/sdcard/Download/AndServer/目录的网站 + delegate.addWebsite( + StorageWebsite( + File( + context.filesDir, "${EditorAppManager.WEB_PUBLIC_PATH}/dist" + ).path + ) + ) + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/JsBridge.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/JsBridge.kt new file mode 100644 index 000000000..bd486a890 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/JsBridge.kt @@ -0,0 +1,194 @@ +package com.aiselp.autojs.codeeditor.web + +import android.os.Build +import android.os.Looper +import android.util.Log +import android.webkit.JavascriptInterface +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import androidx.annotation.RequiresApi +import androidx.webkit.WebViewClientCompat +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.annotations.Expose +import java.io.ByteArrayOutputStream +import kotlin.random.Random + + +@RequiresApi(Build.VERSION_CODES.M) +class JsBridge(private val webView: WebView) { + companion object { + const val WEBOBJECTNAME = "\$autox" + const val JAVABRIDGE = "AutoxJavaBridge" + const val sdkPath = "web/autox.sdk.v1.js" + const val LOG_TAG = "JsBridge" + + fun evaluateJavascript(js: String, webView: WebView) { + Looper.getMainLooper().queue.addIdleHandler { + webView.evaluateJavascript(js, null) + false + } + } + + fun injectionJsBridge(webView: WebView) { + val js: String = try { + val inputStream = webView.context.assets.open(sdkPath) + val byteArrayOutputStream = ByteArrayOutputStream() + inputStream.use { it -> + it.copyTo(byteArrayOutputStream) + } + String(byteArrayOutputStream.toByteArray()) + } catch (e: Exception) { + "" + } + evaluateJavascript(js, webView); + } + + val gson: Gson = GsonBuilder().excludeFieldsWithoutExposeAnnotation().create() + } + + private val handles = HashMap() + private val callHandlerData = HashMap() + + init { + webView.addJavascriptInterface(this.JsObject(), JAVABRIDGE) + } + + fun registerHandler(event: String, handle: Handle): JsBridge { + handles[event] = handle + return this + } + + fun removeHandler(event: String): JsBridge { + handles.remove(event) + return this + } + + fun callHandler(event: String, data: String?, callBack: Handle?) { + val pos = Pos(getId(), event, data) + callHandlerData[pos.id] = pos + callBack?.let { + it.name = "callBack" + pos.callBackId = pos.id + pos.callBack = it + } + val js = "$WEBOBJECTNAME._onCallHandler(${pos.id})" + evaluateJavascript(js, this.webView) + } + + + private fun getId(): Int { + val nextInt = Random.nextInt() + if (callHandlerData.containsKey(nextInt)) { + return getId() + } + return nextInt + } + + class Handle : (String?, Handle?) -> Unit { + private var ktFn: ((data: String?, Handle?) -> Unit)? = null + var name: String = "handle" + + constructor(ktFn: (data: String?, Handle?) -> Unit) { + this.ktFn = ktFn + } + + override fun invoke(p1: String?, p2: Handle?) { + ktFn?.invoke(p1, p2) + } + + fun invokeToMainThread(p1: String?, p2: Handle?) { + Looper.getMainLooper().queue.addIdleHandler { + invoke(p1, p2) + false + } + } + } + + inner class JsObject { + private val callBackData = HashMap() + + @JavascriptInterface + //web调用安卓 + fun callHandle(reqData: String) { + val pos = gson.fromJson(reqData, Pos::class.java) + Log.i(LOG_TAG, "onHandle: ${pos.event}") + val handler = handles[pos.event] + val callBack: Handle? = if (pos.callBackId != null) { + Handle { data, _ -> + callBackData[pos.callBackId!!] = object { + val data = data + val callBackId = pos.callBackId + } + val js = "$WEBOBJECTNAME._onCallBack(${pos.callBackId})" + evaluateJavascript(js, webView) + } + } else null + handler?.let { it.name = "callBack" } + handler?.invokeToMainThread(pos.data, callBack) + } + + @JavascriptInterface + fun getCallBackData(id: Int): String { + val data = callBackData[id] + callBackData.remove(id) + return Gson().toJson(data) + } + + @JavascriptInterface + fun callBack(callBackId: Int, data: String?) { + val callBack = callHandlerData[callBackId]?.callBack + callHandlerData.remove(callBackId) + callBack?.invokeToMainThread(data, null) + } + + @JavascriptInterface + fun getCallHandlerData(id: Int): String { + val pos = callHandlerData[id] + if (pos != null && pos.callBack == null) { + callHandlerData.remove(id) + } + return gson.toJson(pos) + } + } + + data class Pos(@Expose val id: Int, @Expose val event: String, @Expose var data: String?) { + @Expose + var callBackId: Int? = null + + @Expose(serialize = false, deserialize = false) + var callBack: Handle? = null + } + + open class SuperWebViewClient : WebViewClientCompat() { + companion object { + const val SDKPATH = "autox://sdk.v1.js" + } + + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + val url = request.url + val context = view.context + if (url.toString() == SDKPATH) { + val webResponse: WebResourceResponse? = try { + val inputStream = context.assets.open(sdkPath) + WebResourceResponse("application/javascript", "UTF-8", inputStream) + } catch (e: Exception) { + super.shouldInterceptRequest(view, request) + } + return webResponse + } + return super.shouldInterceptRequest(view, request) + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + if (view != null) { + injectionJsBridge(view) + } + } + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/PluginManager.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/PluginManager.kt new file mode 100644 index 000000000..93a5c7fbe --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/PluginManager.kt @@ -0,0 +1,103 @@ +package com.aiselp.autojs.codeeditor.web + +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.aiselp.autojs.codeeditor.web.interfaces.CallEvent +import com.aiselp.autojs.codeeditor.web.interfaces.CallbackEvent +import com.aiselp.autojs.codeeditor.web.interfaces.PluginInfo +import com.google.gson.Gson +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@RequiresApi(Build.VERSION_CODES.M) +class PluginManager(private val jsBridge: JsBridge, private val coroutineScope: CoroutineScope) { + companion object { + const val TAG = "PluginManager" + const val PluginCallEventName = "__Plugin_Call" + private val gson = Gson() + } + + private val pluginMap = mutableMapOf() + + private val webHandle = JsBridge.Handle { data, handle -> + val call: CallEvent + try { + check(handle != null) + call = gson.fromJson(data, CallEvent::class.java) + } catch (e: Exception) { + Log.e(TAG, "error-call: $data") + Log.e(TAG, e.toString()) + return@Handle + } + if (call.type == CallEvent.LoadType) { + val plugin = pluginMap[call.pluginId] + if (plugin != null) { + val pluginInfo = PluginInfoBuilder(plugin).build() + return@Handle handle(gson.toJson(pluginInfo), null) + } else return@Handle handle(null, null) + } else if (call.type == CallEvent.CallType) { + callPluginMethod(call, handle) + } + } + + init { + jsBridge.registerHandler(PluginCallEventName, webHandle) + } + + private fun callPluginMethod(call: CallEvent, handle: JsBridge.Handle) { + val plugin = pluginMap[call.pluginId] + val methodName = call.method + check(plugin != null && methodName != null) { "call plugin:${call.pluginId} method:${call.method} error" } + val method = plugin.methods[methodName]!! + val webCall = WebCall(call.data, handle) + coroutineScope.launch { + try { + method.invoke(plugin.plugin, webCall) + }catch (e: Exception) { + Log.e(TAG, "web call method:${call.method} error") + Log.e(TAG, e.toString()) + webCall.onError(e) + } + } + } + + fun registerPlugin(id: String, plugin: Any) { + pluginMap[id] = WebPlugin(id, plugin) + + } + + fun removePlugin(id: String, plugin: Any) { + pluginMap.remove(id) + } + + class WebCall(val data: String?, private val handle: JsBridge.Handle) { + + fun onSuccess(data: String?) { + val callbackEvent = CallbackEvent() + callbackEvent.type = CallbackEvent.SuccessType + callbackEvent.data = data + callbackEvent.errorMessage = null + + handle(gson.toJson(callbackEvent), null) + } + + fun onError(err: Exception) { + val callbackEvent = CallbackEvent() + callbackEvent.type = CallbackEvent.ErrorType + callbackEvent.data = null + callbackEvent.errorMessage = err.message + + handle(gson.toJson(callbackEvent), null) + } + } + + class PluginInfoBuilder(val plugin: WebPlugin) { + fun build(): PluginInfo { + return PluginInfo().apply { + pluginId = plugin.id + methods = plugin.methods.keys.toTypedArray() + } + } + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/WebPlugin.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/WebPlugin.kt new file mode 100644 index 000000000..da8deeb82 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/WebPlugin.kt @@ -0,0 +1,17 @@ +package com.aiselp.autojs.codeeditor.web + +import com.aiselp.autojs.codeeditor.web.annotation.WebFunction +import java.lang.reflect.Method + +class WebPlugin(val id: String, val plugin: Any) { + val methods: MutableMap = mutableMapOf() + init { + for (method in plugin.javaClass.methods) { + val webFunction = method.getAnnotation(WebFunction::class.java) + if (webFunction != null) { + val name = if (webFunction.name == "null") method.name else webFunction.name + methods[name] = method + } + } + } +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/annotation/WebFunction.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/annotation/WebFunction.kt new file mode 100644 index 000000000..1b15f59a9 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/annotation/WebFunction.kt @@ -0,0 +1,6 @@ +package com.aiselp.autojs.codeeditor.web.annotation + +@Target(AnnotationTarget.FUNCTION) +annotation class WebFunction( + val name: String = "null" +) diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/CallEvent.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/CallEvent.kt new file mode 100644 index 000000000..8f4c24ec5 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/CallEvent.kt @@ -0,0 +1,13 @@ +package com.aiselp.autojs.codeeditor.web.interfaces + +class CallEvent { + companion object { + const val LoadType = "load" + const val CallType = "call" + } + + var type: String = "init" + var pluginId: String = "null" + var method: String? = null + var data: String? = null +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/CallbackEvent.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/CallbackEvent.kt new file mode 100644 index 000000000..4815ddbc0 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/CallbackEvent.kt @@ -0,0 +1,12 @@ +package com.aiselp.autojs.codeeditor.web.interfaces + +class CallbackEvent { + companion object { + const val SuccessType = "success" + const val ErrorType = "error" + } + + var type: String = "null" + var data: String? = null + var errorMessage: String? = null +} \ No newline at end of file diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/PluginInfo.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/PluginInfo.kt new file mode 100644 index 000000000..c12dedf02 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/interfaces/PluginInfo.kt @@ -0,0 +1,6 @@ +package com.aiselp.autojs.codeeditor.web.interfaces + +class PluginInfo { + var pluginId: String = "null" + var methods: Array = emptyArray() +} \ No newline at end of file diff --git a/codeeditor/src/test/java/com/aiselp/autojs/codeeditor/ExampleUnitTest.kt b/codeeditor/src/test/java/com/aiselp/autojs/codeeditor/ExampleUnitTest.kt new file mode 100644 index 000000000..c6e671723 --- /dev/null +++ b/codeeditor/src/test/java/com/aiselp/autojs/codeeditor/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.aiselp.autojs.codeeditor + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a52659af..10b918b62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,26 @@ [versions] +andserver = "2.1.12" core-ktx = "1.8.0" +google-gson = "2.9.1" junit = "4.13.2" +kotlinx-coroutines-android = "1.6.2" okhttp = "4.10.0" ktor-version = "2.0.3" +androidx-test-ext-junit = "1.1.5" +espresso-core = "3.5.1" +appcompat = "1.6.1" +material = "1.9.0" [libraries] + +commons-io = "commons-io:commons-io:2.6" +eventbus = "org.greenrobot:eventbus:3.3.1" +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" } +nanohttpd-webserver = "org.nanohttpd:nanohttpd-webserver:2.3.1" +androidx-webkit = "androidx.webkit:webkit:1.7.0" core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } documentfile = "androidx.documentfile:documentfile:1.0.1" +google-gson = { module = "com.google.code.gson:gson", version.ref = "google-gson" } junit = { module = "junit:junit", version.ref = "junit" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } # ktor @@ -17,6 +31,12 @@ ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.re ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor-version" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-version" } +androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +andserver-processor = { module = "com.yanzhenjie.andserver:processor", version.ref = "andserver" } +andserver-api = { module = "com.yanzhenjie.andserver:api", version.ref = "andserver" } [plugins] [bundles] diff --git a/settings.gradle b/settings.gradle index d079b4902..6381e8c24 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,3 +7,4 @@ include ':LocalRepo:p7zip' include ':LocalRepo:OpenCV' //include ':LocalRepo:PaddleOCR4Android' include ':paddleocr' +include ':codeeditor'