开发编辑器插件步骤如下:
-
在 build.gradle.kts 文件中添加 m8test sdk 依赖 , 为了减小插件apk大小, 如果是 M8Test Version Catalog 中存在的依赖库请使用 compileOnly 来依赖项目
import com.m8test.util.VersionUtilsplugins {alias(m8test.plugins.android.application)alias(m8test.plugins.kotlin.android)alias(m8test.plugins.kotlin.compose)
}android {namespace = "com.m8test.editor.language"compileSdk = m8test.versions.compileSdk.get().toInt()defaultConfig {minSdk = m8test.versions.minSdk.get().toInt()targetSdk = m8test.versions.targetSdk.get().toInt()versionName = libs.versions.versionName.get()versionCode = VersionUtils.getCode(versionName!!)testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),"proguard-rules.pro")}}compileOptions {sourceCompatibility = JavaVersion.toVersion(m8test.versions.sourceCompatibility.get())targetCompatibility = JavaVersion.toVersion(m8test.versions.targetCompatibility.get())}kotlinOptions {jvmTarget = m8test.versions.jvmTarget.get()}buildFeatures {compose = true}
}dependencies {compileOnly(platform(m8test.androidx.compose.bom))compileOnly(m8test.bundles.compose)compileOnly(m8test.m8test.sdk)compileOnly(m8test.gson)implementation(libs.m8test.compose.widget)implementation(libs.bundles.editor.kit)
}
https://raw.githubusercontent.com/m8test/Plugins/refs/heads/main/editor/language/build.gradle.kts
-
编写插件继承 AbstractComposableEditorPlugin 并重写
Content
方法
package com.m8test.editor.languageimport android.content.res.Resources
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.dp
import com.blacksquircle.ui.language.css.CssLanguage
import com.blacksquircle.ui.language.groovy.GroovyLanguage
import com.blacksquircle.ui.language.html.HtmlLanguage
import com.blacksquircle.ui.language.ini.IniLanguage
import com.blacksquircle.ui.language.java.JavaLanguage
import com.blacksquircle.ui.language.javascript.JavaScriptLanguage
import com.blacksquircle.ui.language.json.JsonLanguage
import com.blacksquircle.ui.language.kotlin.KotlinLanguage
import com.blacksquircle.ui.language.lua.LuaLanguage
import com.blacksquircle.ui.language.markdown.MarkdownLanguage
import com.blacksquircle.ui.language.php.PhpLanguage
import com.blacksquircle.ui.language.python.PythonLanguage
import com.blacksquircle.ui.language.ruby.RubyLanguage
import com.blacksquircle.ui.language.toml.TomlLanguage
import com.blacksquircle.ui.language.typescript.TypeScriptLanguage
import com.blacksquircle.ui.language.xml.XmlLanguage
import com.blacksquircle.ui.language.yaml.YamlLanguage
import com.google.accompanist.imageloading.rememberDrawablePainter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.m8test.compose.widget.AutoResizeSurfaceDialog
import com.m8test.compose.widget.BackTopBarScaffold
import com.m8test.editor.plugin.AbstractComposableEditorPlugin
import com.m8test.editor.ui.Editor
import com.m8test.plugin.api.ApkPluginProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch/*** Description TODO** @date 2024/12/18 15:43:44* @author M8Test, [email protected], https://m8test.com*/
class LanguageEditorPlugin(apkPluginProvider: ApkPluginProvider) :AbstractComposableEditorPlugin(apkPluginProvider = apkPluginProvider) {private val gson = Gson()private val defaultLanguages = listOf(LanguageWrapper(language = "groovy", extensions = mutableSetOf("groovy")),LanguageWrapper(language = "java", extensions = mutableSetOf("java", "javas")),LanguageWrapper(language = "javascript", extensions = mutableSetOf("js")),LanguageWrapper(language = "kotlin", extensions = mutableSetOf("kt", "kts")),LanguageWrapper(language = "lua", extensions = mutableSetOf("lua")),LanguageWrapper(language = "php", extensions = mutableSetOf("php")),LanguageWrapper(language = "python", extensions = mutableSetOf("py")),LanguageWrapper(language = "ruby", extensions = mutableSetOf("rb")),LanguageWrapper(language = "yaml", extensions = mutableSetOf("yaml", "yml")),LanguageWrapper(language = "json", extensions = mutableSetOf("json")),LanguageWrapper(language = "xml", extensions = mutableSetOf("xml")),LanguageWrapper(language = "typescript", extensions = mutableSetOf("ts")),LanguageWrapper(language = "markdown", extensions = mutableSetOf("md")),LanguageWrapper(language = "css", extensions = mutableSetOf("css")),LanguageWrapper(language = "html", extensions = mutableSetOf("html")),LanguageWrapper(language = "ini", extensions = mutableSetOf("ini")),LanguageWrapper(language = "toml", extensions = mutableSetOf("toml")))private val languages = listOf(GroovyLanguage(),JavaLanguage(),JavaScriptLanguage(),KotlinLanguage(),LuaLanguage(),PhpLanguage(),PythonLanguage(),RubyLanguage(),YamlLanguage(),JsonLanguage(),XmlLanguage(),TypeScriptLanguage(),MarkdownLanguage(),CssLanguage(),HtmlLanguage(),IniLanguage(),TomlLanguage(),)@Composableprivate fun RestoreDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) {AutoResizeSurfaceDialog(onDismissRequest = onDismiss,shape = RoundedCornerShape(20.dp),content = {Column(modifier = Modifier.padding(20.dp),horizontalAlignment = Alignment.CenterHorizontally) {val r = [email protected]()Text(text = r.getString(R.string.text_warn),style = MaterialTheme.typography.titleLarge)Text(text = r.getString(R.string.text_restore_language_warning))Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceEvenly) {TextButton(onClick = onDismiss) {Text(text = r.getString(R.string.text_cancel))}TextButton(onClick = {onDismiss()onConfirm()}) {Text(text = r.getString(R.string.text_confirm))}}}})}@Composableoverride fun Content() {val languages = remember {mutableStateListOf<LanguageWrapper>().apply {addAll(getLanguagesFromSetting())}}val onBackPressedDispatcher =LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatchervar canSave by remember { mutableStateOf(false) }var showRestoreDialog by remember { mutableStateOf(false) }val scope = rememberCoroutineScope()if (showRestoreDialog) {RestoreDialog(onConfirm = {getSettings().remove("languages")scope.launch(Dispatchers.IO) {languages.clear()delay(100)languages.addAll(getLanguagesFromSetting())}}) { showRestoreDialog = false }}BackTopBarScaffold(onBackPress = { onBackPressedDispatcher?.onBackPressed() },titleText = [email protected]().getString(R.string.text_editor_language_setting),actions = {IconButton(enabled = canSave, onClick = {getSettings().set("languages", gson.toJson(languages))canSave = false}) {Icon(painter = painterResource([email protected](),R.drawable.ic_baseline_save_24),contentDescription = null)}IconButton(onClick = { showRestoreDialog = true }) {Icon(painter = painterResource(resources = [email protected](),R.drawable.ic_baseline_restore_24),contentDescription = null)}}) { paddingValues ->LazyColumn(modifier = Modifier.fillMaxSize().padding(paddingValues)) {items(languages) { language ->LanguageItem(language = language,onValueChange = { canSave = true },modifier = Modifier.fillMaxWidth())}}}}private fun getLanguagesFromSetting(): List<LanguageWrapper> {return getSettings().get("languages")?.let { gson.fromJson(it, object : TypeToken<List<LanguageWrapper>>() {}.type) }?: defaultLanguages.map {LanguageWrapper(language = it.language,extensions = mutableSetOf<String>().apply { addAll(it.extensions) })}}@Composableprivate fun LanguageItem(language: LanguageWrapper,onValueChange: (String) -> Unit,modifier: Modifier = Modifier,) {Row(modifier = modifier) {var value by remember { mutableStateOf(language.extensions.joinToString()) }OutlinedTextField(modifier = Modifier.fillMaxWidth(),label = { Text(text = language.language) },placeholder = {val r = [email protected]()Text(text = r.getString(R.string.text_extension_placeholder))},value = value,onValueChange = {value = itlanguage.extensions.clear()language.extensions.addAll(value.split(",").map { it.trim() }.filter { it.isNotBlank() })onValueChange(it)})}}override fun onAttached(editor: Editor) {super.onAttached(editor)val settingLanguages = getLanguagesFromSetting()editor.language = editor.file.extension.let { extension ->languages.firstOrNull { l ->settingLanguages.firstOrNull { it.language == l.languageName }?.extensions?.contains(extension) == true}} ?: editor.scriptLanguage?.getName()?.lowercase()?.let { name -> languages.firstOrNull { it.languageName == name } }}
}@Composable
private fun painterResource(resources: Resources, id: Int): Painter {return rememberDrawablePainter(drawable = resources.getDrawable(id, null))
}
https://raw.githubusercontent.com/m8test/Plugins/refs/heads/main/editor/language/src/main/java/com/m8test/editor/language/LanguageEditorPlugin.kt
-
在 AndroidManifest.xml 中配置插件信息
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><application android:icon="@drawable/ic_launcher"><meta-dataandroid:name="com.m8test.plugin.type"android:value="editor" /><meta-dataandroid:name="com.m8test.plugin.name"android:value="language-editor" /><meta-dataandroid:name="com.m8test.plugin.className"android:value="com.m8test.editor.language.LanguageEditorPlugin" /></application>
</manifest>
https://raw.githubusercontent.com/m8test/Plugins/refs/heads/main/editor/language/src/main/AndroidManifest.xml
-
com.m8test.plugin.type: 插件类型, 此处为
common
-
com.m8test.plugin.name: 插件名称, 可以为任意字符串
-
com.m8test.plugin.className: 实现了插件的全类名
示例代码