diff --git a/app/src/main/java/com/aiselp/autox/ui/material3/BuildPage.kt b/app/src/main/java/com/aiselp/autox/ui/material3/BuildPage.kt index 7b780d088..cb32b50a2 100644 --- a/app/src/main/java/com/aiselp/autox/ui/material3/BuildPage.kt +++ b/app/src/main/java/com/aiselp/autox/ui/material3/BuildPage.kt @@ -29,7 +29,6 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -57,6 +56,7 @@ import com.aiselp.autox.ui.material3.components.BaseDialog import com.aiselp.autox.ui.material3.components.BuildCard import com.aiselp.autox.ui.material3.components.CheckboxOption import com.aiselp.autox.ui.material3.components.DialogController +import com.aiselp.autox.ui.material3.components.DialogTitle import com.aiselp.autox.ui.material3.components.InputBox import com.aiselp.autox.ui.material3.components.M3TopAppBar import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -408,10 +408,7 @@ fun DialogController.ChooseSignDialog( BaseDialog( onDismissRequest = { scope.launch { dismiss() } }, title = { - Text( - text = stringResource(R.string.text_sign_choose), - style = MaterialTheme.typography.titleLarge - ) + DialogTitle(title = stringResource(R.string.text_sign_choose)) }, positiveText = "确定", onPositiveClick = { scope.launch { dismiss(); onKeyStoreChange(current) } }, @@ -487,7 +484,7 @@ fun BuildingDialog(show: Boolean, model: BuildViewModel, onDismissRequest: () -> onDismissRequest() model.isShowBuildSuccessfullyDialog = false val outApk = model.outApk - if (outApk?.isFile != true){ + if (outApk?.isFile != true) { return } MaterialAlertDialogBuilder(context) @@ -536,10 +533,7 @@ private fun DialogController.FinishDialog(model: BuildViewModel) { BaseDialog( onDismissRequest = { scope.launch { dismiss() } }, title = { - Text( - text = stringResource(R.string.text_alert), - style = MaterialTheme.typography.titleLarge - ) + DialogTitle(title = stringResource(R.string.text_alert)) }, positiveText = stringResource(id = R.string.text_save_and_exit), onPositiveClick = { diff --git a/app/src/main/java/com/aiselp/autox/ui/material3/DrawerPage.kt b/app/src/main/java/com/aiselp/autox/ui/material3/DrawerPage.kt index cbeb2c925..f8b7484be 100644 --- a/app/src/main/java/com/aiselp/autox/ui/material3/DrawerPage.kt +++ b/app/src/main/java/com/aiselp/autox/ui/material3/DrawerPage.kt @@ -59,6 +59,7 @@ import coil.compose.rememberAsyncImagePainter import com.aiselp.autox.ui.material3.components.AlertDialog import com.aiselp.autox.ui.material3.components.BaseDialog import com.aiselp.autox.ui.material3.components.DialogController +import com.aiselp.autox.ui.material3.components.DialogTitle import com.aiselp.autox.ui.material3.components.SettingOptionSwitch import com.aiselp.autox.ui.material3.components.Watch import com.stardust.app.GlobalAppContext @@ -195,7 +196,7 @@ private fun AccessibilityServiceSwitch() { } ) dialog.BaseDialog(onDismissRequest = { scope.launch { dialog.dismiss() } }, - title = { Text(text = stringResource(R.string.text_need_to_enable_accessibility_service)) }, + title = { DialogTitle(title = stringResource(R.string.text_need_to_enable_accessibility_service)) }, positiveText = stringResource(id = R.string.text_go_to_open), onPositiveClick = { scope.launch { dialog.dismiss() } @@ -497,10 +498,7 @@ private fun DialogController.ConnectComputerDialog() { BaseDialog( onDismissRequest = { scope.launch { dismiss() } }, title = { - Text( - text = stringResource(id = R.string.text_server_address), - style = MaterialTheme.typography.titleLarge - ) + DialogTitle(title = stringResource(id = R.string.text_server_address)) }, positiveText = stringResource(id = R.string.ok), onPositiveClick = { @@ -588,10 +586,7 @@ private fun DialogController.TimedTaskSchedulerDialog() { toast(context, R.string.text_set_successfully) }, title = { - Text( - text = stringResource(id = R.string.text_switch_timed_task_scheduler), - style = MaterialTheme.typography.titleLarge - ) + DialogTitle(title = stringResource(id = R.string.text_switch_timed_task_scheduler)) }, ) { Column { @@ -670,12 +665,11 @@ private fun CheckForUpdate(model: DrawerViewModel = viewModel()) { dialog.BaseDialog( onDismissRequest = { scope.launch { dialog.dismiss() } }, title = { - Text( - text = stringResource( + DialogTitle( + title = stringResource( R.string.text_new_version2, model.githubReleaseInfo!!.name - ), - style = MaterialTheme.typography.titleMedium + ) ) }, positiveText = stringResource(id = R.string.text_download), diff --git a/app/src/main/java/org/autojs/autojs/ui/build/SignKeyCreatePage.kt b/app/src/main/java/org/autojs/autojs/ui/build/SignKeyCreatePage.kt index 19a51604c..82871e81a 100644 --- a/app/src/main/java/org/autojs/autojs/ui/build/SignKeyCreatePage.kt +++ b/app/src/main/java/org/autojs/autojs/ui/build/SignKeyCreatePage.kt @@ -32,6 +32,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.aiselp.autox.apkbuilder.ApkKeyStore import com.aiselp.autox.ui.material3.components.BaseDialog import com.aiselp.autox.ui.material3.components.DialogController +import com.aiselp.autox.ui.material3.components.DialogTitle import com.aiselp.autox.ui.material3.components.DropdownSingleChoiceInputBox import com.aiselp.autox.ui.material3.components.PasswordInputBox import com.mcal.apksigner.CertCreator @@ -54,7 +55,7 @@ fun DialogController.ApkSignDeleteDialog(apkKeyStore: ApkKeyStore) { val signManageModel = viewModel(SignManageModel::class.java) BaseDialog(onDismissRequest = { scope.launch { dismiss() } }, title = { - Text(text = "删除签名", style = MaterialTheme.typography.titleLarge) + DialogTitle(title = "删除签名") }, content = { Column { Text(text = "你确定要删除该文件?") @@ -81,7 +82,7 @@ fun DialogController.ReviseSignKeyDialog(apkKeyStore: ApkKeyStore) { var keyStore: KeyStore? = null BaseDialog(onDismissRequest = { scope.launch { dismiss() } }, title = { - Text(text = "修改签名文件", style = MaterialTheme.typography.titleLarge) + DialogTitle(title = "修改签名文件") }, positiveText = "确定", onPositiveClick = { scope.launch { if (alias.isNullOrEmpty() || keyStorePassword.isNullOrEmpty() || password.isNullOrEmpty()) { @@ -204,7 +205,7 @@ fun DialogController.ApkSignCreateDialog() { val context = LocalContext.current BaseDialog(onDismissRequest = { scope.launch { dismiss() } }, title = { - Text(text = "创建签名文件", style = MaterialTheme.typography.titleLarge) + DialogTitle(title = "创建签名文件") }, content = { SignKeyCreatePage() }, positiveText = "确定", onPositiveClick = { if (!model.checkAll()) { return@BaseDialog diff --git a/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt b/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt index 92c4e0a6a..7802d0a55 100644 --- a/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt +++ b/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt @@ -175,7 +175,7 @@ class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) : private fun initializeModule(file: File): V8Value { val parentFile = file.parentFile ?: File("/") runtime.getNodeModule(NodeModuleProcess::class.java).workingDirectory = parentFile - val nodeModuleResolver = NodeModuleResolver(runtime, parentFile, moduleDirectory) + val nodeModuleResolver = NodeModuleResolver(runtime, file, moduleDirectory) runtime.v8ModuleResolver = nodeModuleResolver runtime.globalObject.delete(NodeModuleModule.PROPERTY_REQUIRE) return if (NodeModuleResolver.isEsModule(file)) { diff --git a/autojs/src/main/java/com/aiselp/autox/module/ModuleFile.kt b/autojs/src/main/java/com/aiselp/autox/module/ModuleFile.kt new file mode 100644 index 000000000..b5c5d03be --- /dev/null +++ b/autojs/src/main/java/com/aiselp/autox/module/ModuleFile.kt @@ -0,0 +1,14 @@ +package com.aiselp.autox.module + +import java.io.File + +class ModuleFile( + val file: File, + val modelType: ModelType, +) { + enum class ModelType { + COMMONJS, + ES_MODULE, + JSON, + } +} \ No newline at end of file diff --git a/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt b/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt index b2987c012..e47695c04 100644 --- a/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt +++ b/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt @@ -15,15 +15,39 @@ import java.net.URI class NodeModuleResolver( val runtime: NodeRuntime, - val workingDirectory: File, + mainFile: File, private val globalModuleDirectory: File ) : IV8ModuleResolver { + val workingDirectory: File = mainFile.parentFile ?: File("/") private val esModuleCache = mutableMapOf() - val require: V8ValueFunction = runtime.getNodeModule(NodeModuleModule::class.java).moduleObject - .invoke( - NodeModuleModule.FUNCTION_CREATE_REQUIRE, - workingDirectory.absolutePath - ) + private val requireCache = mutableMapOf() + val require: V8ValueFunction = createRequire(mainFile.absolutePath) + private val loadFn: V8ValueFunction by lazy { + runtime.getExecutor( + """ + (require,filename)=>{ + const model = require(filename); + if (model instanceof Object) { + Object.defineProperty(model, 'default', { + value: model, + writable: false, + enumerable: true + }) + return model + } else { + return { default:model } + } + } + """.trimIndent() + ).execute() + } + + private fun createRequire(referrer: String): V8ValueFunction { + return requireCache.getOrPut(referrer) { + runtime.getNodeModule(NodeModuleModule::class.java).moduleObject + .invoke(NodeModuleModule.FUNCTION_CREATE_REQUIRE, referrer) + } + } override fun resolve( v8Runtime: V8Runtime, resourceName: String, v8ModuleReferrer: IV8Module @@ -40,11 +64,7 @@ class NodeModuleResolver( return when (uri.scheme) { "node" -> loadNodeModule(resourceName) null -> run { - try { - loadNodeModule("node:$resourceName") - } catch (e: Exception) { - parsingPackageModule(v8Runtime, workingDirectory, resourceName) - } + parsingPackageModule(v8Runtime, resourceName, v8ModuleReferrer.resourceName) } else -> parsingModule(v8Runtime, uri) @@ -66,7 +86,8 @@ class NodeModuleResolver( private fun parsingModule(v8Runtime: V8Runtime, uri: Uri): IV8Module? = checkCacheModule(uri.toString()) { if (uri.scheme == null || uri.scheme == "file") { - return@checkCacheModule parsingModule(v8Runtime, File(uri.path!!)) + val path = uri.path!! + return@checkCacheModule parsingModule(v8Runtime, File(path)) } return@checkCacheModule null } @@ -89,22 +110,47 @@ class NodeModuleResolver( ): IV8Module? { if (directory == null) return null if (!directory.isDirectory) return null - val packageDirectory = File(directory, name) - return PackageJson.create(packageDirectory)?.let { - it.main?.let { file -> parsingModule(v8Runtime, file, it.isEsModule()) } + val split = name.split("/") + val subPath = mutableListOf() + var sufPath = "" + + var packageJson: PackageJson? = null + split.forEach { + if (packageJson != null) { + subPath.add(it) + return@forEach + } + sufPath += "$it/" + packageJson = PackageJson.create(File(directory, sufPath)) + } + return packageJson?.let { + it.resolveSubPath( + if (subPath.isEmpty()) "." + else subPath.joinToString("/"), true + )?.let { modelFile -> + parsingModule( + v8Runtime, modelFile.file, modelFile.modelType == ModuleFile.ModelType.ES_MODULE + ) + } } } + private fun loadNpmPackageModule(require: V8ValueFunction, name: String): IV8Module? = + checkCacheModule(name) { + try { + runtime.createV8Module(name, loadFn.call(null, require, name)) + } catch (e: Exception) { + Log.e(TAG, "loadNpmPackageModule Error:\n ${e.stackTraceToString()}") + null + } + } + private fun parsingPackageModule( - v8Runtime: V8Runtime, workingDirectory: File?, name: String + v8Runtime: V8Runtime, name: String, referrer: String ): IV8Module? { - if (workingDirectory == null) return null - val modulesDirection = File(workingDirectory, "node_modules") - return loadPackageModule(v8Runtime, modulesDirection, name) ?: loadPackageModule( - v8Runtime, - globalModuleDirectory, - name - ) + val require1 = createRequire(referrer) + return loadNpmPackageModule(require1, name) + ?: loadPackageModule(v8Runtime, globalModuleDirectory, name) } fun addCacheModule(module: IV8Module) { diff --git a/autojs/src/main/java/com/aiselp/autox/module/PackageJson.kt b/autojs/src/main/java/com/aiselp/autox/module/PackageJson.kt index 29cd2f4d4..e9237ae2e 100644 --- a/autojs/src/main/java/com/aiselp/autox/module/PackageJson.kt +++ b/autojs/src/main/java/com/aiselp/autox/module/PackageJson.kt @@ -2,35 +2,62 @@ package com.aiselp.autox.module import com.google.gson.Gson import java.io.File +import java.net.URI class PackageJson(val dir: File, private val data: Map) { - val exports: Map? by lazy { + val exports: Map? by lazy { when (data["exports"]) { null -> null is String -> mapOf("." to data["exports"] as String) else -> { - val map = mutableMapOf() - for ((k, v) in data["exports"] as Map) { - if (v is String) map[k] = v - } - map + data["exports"] as Map } } } - val main: File? + val main: ModuleFile? get() { - val main = exports?.let { - (if (isEsModule()) { - it["require"] - } else it["import"]) ?: it["."] - } ?: (data["main"] as? String) ?: (data["module"] as? String) - return main?.let { - File(dir, it.replace(Regex("^\\./"), "")) + return if (exports == null) { + data["main"]?.let { createModuleFile(it as String) } + } else resolveSubPath(".", false) + } + + private fun createModuleFile(subPath: String, import: Boolean = isEsModule()): ModuleFile { + return ModuleFile( + File(URI(dir.path + "/#").resolve(subPath).path), + if (import) ModuleFile.ModelType.ES_MODULE + else ModuleFile.ModelType.COMMONJS + ) + } + + fun resolveSubPath(subPath: String, import: Boolean): ModuleFile? { + val e = exports ?: return main + val c = e[subPath] + + fun select(m: Map<*, *>): ModuleFile? { + val es = (m["import"] as? String)?.let { + createModuleFile(it, true) + } + val commonjs = (m["require"] as? String)?.let { + createModuleFile(it, false) + } + return if (import) { + es ?: commonjs + } else commonjs ?: es + } + if (subPath == ".") { + if (c == null) { + return select(e) } } + if (c is String) return createModuleFile(c) + return if (c is Map<*, *>) { + select(c) + } else null + } + fun isEsModule(): Boolean { return data["type"] == "module" } diff --git a/build.gradle.kts b/build.gradle.kts index abc62a994..4336b8f18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,8 +23,6 @@ buildscript { classpath("com.android.tools.build:gradle:8.2.1") 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") classpath(libs.okhttp) } } diff --git a/codeeditor/build.gradle.kts b/codeeditor/build.gradle.kts index 65efd9237..56beba66a 100644 --- a/codeeditor/build.gradle.kts +++ b/codeeditor/build.gradle.kts @@ -5,7 +5,6 @@ import okio.use plugins { id("com.android.library") id("kotlin-android") - id("com.yanzhenjie.andserver") id("kotlin-kapt") } @@ -44,9 +43,7 @@ android { dependencies { implementation(platform(libs.compose.bom)) - implementation(libs.andserver.api) implementation(libs.androidx.constraintlayout) - kapt(libs.andserver.processor) implementation(libs.kotlinx.coroutines.android) api(libs.androidx.webkit) implementation(libs.google.gson) diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/dialogs/LoadDialog.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/dialogs/LoadDialog.kt index 43b60aff9..b776844d6 100644 --- a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/dialogs/LoadDialog.kt +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/dialogs/LoadDialog.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -18,6 +17,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import com.aiselp.autox.ui.material3.components.BaseDialog import com.aiselp.autox.ui.material3.components.DialogController +import com.aiselp.autox.ui.material3.components.DialogTitle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -37,12 +37,13 @@ class LoadDialog : DialogController( content = text } } + @Composable fun Dialog() { val scope = rememberCoroutineScope() BaseDialog( onDismissRequest = { scope.launch { dismiss() } }, - title = { Text(text = title, style = MaterialTheme.typography.titleLarge) }, + title = { DialogTitle(title = title) }, ) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { CircularProgressIndicator() 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 deleted file mode 100644 index 3591b84e9..000000000 --- a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/AssetWebViewClient.kt +++ /dev/null @@ -1,72 +0,0 @@ -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 index 4d1670531..db7ed5825 100644 --- a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/EditorAppManager.kt +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/EditorAppManager.kt @@ -5,6 +5,7 @@ import android.content.Context import android.util.Log import android.view.ViewGroup import android.webkit.WebView +import androidx.webkit.WebViewAssetLoader import com.aiselp.autojs.codeeditor.dialogs.LoadDialog import com.aiselp.autojs.codeeditor.plugins.AppController import com.aiselp.autojs.codeeditor.plugins.FileSystem @@ -30,32 +31,25 @@ class EditorAppManager(val context: Activity) { private val coroutineScope = CoroutineScope(Dispatchers.Default) 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 openedFile: String? = null val loadDialog = LoadDialog() init { - webView.webViewClient = JsBridge.SuperWebViewClient() + webView.webViewClient = + FileAssetWebViewClient(File(context.filesDir, "$WEB_PUBLIC_PATH/dist")) installPlugin() coroutineScope.launch { loadDialog.show() - fileHttpServer.start() initWebResources() loadDialog.setContent("启动中") delay(500) - fileHttpServer.await() withContext(Dispatchers.Main) { - webView.loadUrl(fileHttpServer.getAddress()) // webView.loadUrl("http://192.168.10.10:8010") + webView.loadUrl("https://${WebViewAssetLoader.DEFAULT_DOMAIN}") loadDialog.dismiss() } } -// webView.loadUrl("http://appassets.androidplatform.net/index.html") jsBridge.registerHandler("app.init", JsBridge.Handle { _, _ -> pluginManager.onWebInit() val file = openedFile @@ -117,7 +111,6 @@ class EditorAppManager(val context: Activity) { fun destroy() { webView.destroy() - fileHttpServer.stop() coroutineScope.cancel() } diff --git a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileAssetWebViewClient.kt b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileAssetWebViewClient.kt new file mode 100644 index 000000000..e552c1885 --- /dev/null +++ b/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileAssetWebViewClient.kt @@ -0,0 +1,41 @@ +package com.aiselp.autojs.codeeditor.web + +import android.net.Uri +import android.util.Log +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import androidx.webkit.WebViewAssetLoader +import com.aiselp.autox.web.FilePathHandler +import java.io.File + +class FileAssetWebViewClient(dir: File) : JsBridge.SuperWebViewClient() { + private val assetLoader: WebViewAssetLoader = + WebViewAssetLoader.Builder() + .setHttpAllowed(true) + .addPathHandler("/", FilePathHandler(dir)) + .build() + + + @Deprecated("Deprecated in Java") + override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? { + return assetLoader.shouldInterceptRequest(Uri.parse(url)) ?: let { + Log.w(TAG, "url request: $url -> $it") + super.shouldInterceptRequest(view, url) + } + } + + override fun shouldInterceptRequest( + view: WebView, + request: WebResourceRequest + ): WebResourceResponse? { + return assetLoader.shouldInterceptRequest(request.url) ?: let { + Log.w(TAG, "url request: ${request.url} -> $it") + super.shouldInterceptRequest(view, request) + } + } + + companion object { + const val TAG = "FileAssetWebViewClient" + } +} \ 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 deleted file mode 100644 index 62ab49fa8..000000000 --- a/codeeditor/src/main/java/com/aiselp/autojs/codeeditor/web/FileHttpServer.kt +++ /dev/null @@ -1,92 +0,0 @@ -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.Server.ServerListener -import com.yanzhenjie.andserver.annotation.Config -import com.yanzhenjie.andserver.framework.config.WebConfig -import com.yanzhenjie.andserver.framework.website.StorageWebsite -import kotlinx.coroutines.CompletableDeferred -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 { - private const val Tag = "FileHttpServer" - fun isPortAvailable(port: Int): Boolean { - try { - ServerSocket(port).use { - return true // 端口可用 - } - } catch (e: IOException) { - return false // 端口已被占用 - } - } - } - - private val port = getPort() - private val status = CompletableDeferred() - val server: Server = AndServer.webServer(context) - .port(port) - .timeout(10, TimeUnit.SECONDS) - .listener(object : ServerListener { - override fun onStarted() { - status.complete(true) - } - - override fun onStopped() {} - override fun onException(e: Exception?) { - status.completeExceptionally(e ?: Exception("$Tag start fail")) - } - }) - .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(Tag, "FileHttpServer init host: 127.0.0.1 port: $port") - server.startup() - } - - suspend fun await() { - status.await() - } - - 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/common/build.gradle.kts b/common/build.gradle.kts index 9c8eaee2a..dd3de42d8 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -46,7 +46,6 @@ android { } dependencies { - androidTestImplementation(libs.espresso.core) implementation(platform(libs.compose.bom)) api(libs.activity.compose) api(libs.compose.ui) @@ -54,8 +53,7 @@ dependencies { api(libs.compose.material3) api(libs.compose.material3.window.size) api(libs.compose.material3.adaptive.navigation.suite) - testImplementation(libs.junit) - api(libs.androidx.annotation) + api(libs.androidx.webkit) api("com.github.hyb1996:settingscompat:1.1.5") implementation(libs.androidx.activity.ktx) implementation(libs.appcompat) diff --git a/common/src/main/java/com/aiselp/autox/ui/material3/components/DialogController.kt b/common/src/main/java/com/aiselp/autox/ui/material3/components/DialogController.kt index 4c83a9f0d..7f8b651b3 100644 --- a/common/src/main/java/com/aiselp/autox/ui/material3/components/DialogController.kt +++ b/common/src/main/java/com/aiselp/autox/ui/material3/components/DialogController.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -24,6 +23,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.DialogProperties import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -79,9 +79,10 @@ fun DialogController.BaseDialog( ) { title() } Spacer(modifier = Modifier.height(16.dp)) Box(modifier = Modifier.heightIn(max = 500.dp)) { content() } - Spacer(modifier = Modifier.height(16.dp)) + + Spacer(modifier = Modifier.height(8.dp)) if (positiveText == null && negativeText == null && neutralText == null) { - Spacer(modifier = Modifier.height(16.dp)) + } else Row(verticalAlignment = Alignment.CenterVertically) { neutralText?.let { TextButton( @@ -132,7 +133,7 @@ fun DialogController.AlertDialog( scope.launch { dismiss() } } BaseDialog(onDismissRequest = { d();onDismiss() }, - title = { Text(text = title, style = MaterialTheme.typography.titleLarge) }, + title = { DialogTitle(title = title) }, positiveText = positiveText, onPositiveClick = onPositiveClick ?: { d();onPositiveClick() }, negativeText = negativeText, @@ -140,4 +141,9 @@ fun DialogController.AlertDialog( neutralText = neutralText, onNeutralClick = onNeutralClick ?: { d();onNegativeClick() }, content = { Text(text = content) }) +} + +@Composable +fun DialogTitle(title: String) { + Text(text = title, fontSize = 20.sp) } \ No newline at end of file diff --git a/common/src/main/java/com/aiselp/autox/web/FilePathHandler.kt b/common/src/main/java/com/aiselp/autox/web/FilePathHandler.kt new file mode 100644 index 000000000..89db24de2 --- /dev/null +++ b/common/src/main/java/com/aiselp/autox/web/FilePathHandler.kt @@ -0,0 +1,28 @@ +package com.aiselp.autox.web + +import android.webkit.MimeTypeMap +import android.webkit.WebResourceResponse +import androidx.webkit.WebViewAssetLoader +import java.io.File + +class FilePathHandler(private val baseFile: File) : WebViewAssetLoader.PathHandler { + + private fun parsePath(path: String): File { + val file = File(baseFile, path.ifEmpty { "index.html" }).let { + if (it.isDirectory) { + File(it, "index.html") + } else it + } + + return file + } + + override fun handle(path: String): WebResourceResponse? { + val file = parsePath(path) + if (!file.isFile) return null + val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( + MimeTypeMap.getFileExtensionFromUrl(path) + ) + return WebResourceResponse(mimeType, null, file.inputStream()) + } +} \ No newline at end of file diff --git a/docs/v2 b/docs/v2 index 434680c4d..d7dd71a49 160000 --- a/docs/v2 +++ b/docs/v2 @@ -1 +1 @@ -Subproject commit 434680c4de5cb8684568c24f93a4f1127f65dfa2 +Subproject commit d7dd71a497eab125cc1f90205d577dc4d3190db0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18ad34a5c..ddda05591 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] activity-compose = "1.8.2" -andserver = "2.1.12" coil-compose-version = "2.0.0-rc03" core-ktx = "1.8.0" google-gson = "2.9.1" @@ -111,8 +110,6 @@ androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } timscriptov-apksigner ="com.github.TimScriptov:apksigner:1.2.0" -andserver-processor = { module = "com.yanzhenjie.andserver:processor", version.ref = "andserver" } -andserver-api = { module = "com.yanzhenjie.andserver:api", version.ref = "andserver" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose-version" } #Deprecated ======================================== #Glide