feat(ui): script list

- add module state
- add onSnapActivity and onManagerActivity test functions
This commit is contained in:
rhunk
2023-09-16 23:34:28 +02:00
parent 08614ef454
commit 6e0e6d3339
11 changed files with 314 additions and 62 deletions

View File

@@ -11,6 +11,7 @@ import me.rhunk.snapenhance.core.util.SQLiteDatabaseHelper
import me.rhunk.snapenhance.core.util.ktx.getInteger
import me.rhunk.snapenhance.core.util.ktx.getLongOrNull
import me.rhunk.snapenhance.core.util.ktx.getStringOrNull
import me.rhunk.snapenhance.scripting.type.ModuleInfo
import java.util.concurrent.Executors
@@ -60,17 +61,12 @@ class ModDatabase(
"expirationTimestamp BIGINT",
"length INTEGER"
),
"analytics_config" to listOf(
"userId VARCHAR PRIMARY KEY",
"modes VARCHAR"
),
"analytics" to listOf(
"hash VARCHAR PRIMARY KEY",
"userId VARCHAR",
"conversationId VARCHAR",
"timestamp BIGINT",
"eventName VARCHAR",
"eventData VARCHAR"
"scripts" to listOf(
"name VARCHAR PRIMARY KEY",
"version VARCHAR NOT NULL",
"description VARCHAR",
"author VARCHAR NOT NULL",
"enabled BOOLEAN"
)
))
}
@@ -251,4 +247,64 @@ class ModDatabase(
ruleIds
}
}
fun getScripts(): List<ModuleInfo> {
return database.rawQuery("SELECT * FROM scripts", null).use { cursor ->
val scripts = mutableListOf<ModuleInfo>()
while (cursor.moveToNext()) {
scripts.add(
ModuleInfo(
name = cursor.getStringOrNull("name")!!,
version = cursor.getStringOrNull("version")!!,
description = cursor.getStringOrNull("description"),
author = cursor.getStringOrNull("author"),
grantPermissions = null
)
)
}
scripts
}
}
fun setScriptEnabled(name: String, enabled: Boolean) {
executeAsync {
database.execSQL("UPDATE scripts SET enabled = ? WHERE name = ?", arrayOf(
if (enabled) 1 else 0,
name
))
}
}
fun isScriptEnabled(name: String): Boolean {
return database.rawQuery("SELECT enabled FROM scripts WHERE name = ?", arrayOf(name)).use { cursor ->
if (!cursor.moveToFirst()) return@use false
cursor.getInteger("enabled") == 1
}
}
fun syncScripts(availableScripts: List<ModuleInfo>) {
executeAsync {
val enabledScripts = getScripts()
val enabledScriptPaths = enabledScripts.map { it.name }
val availableScriptPaths = availableScripts.map { it.name }
enabledScripts.forEach { script ->
if (!availableScriptPaths.contains(script.name)) {
database.execSQL("DELETE FROM scripts WHERE name = ?", arrayOf(script.name))
}
}
availableScripts.forEach { script ->
if (!enabledScriptPaths.contains(script.name)) {
database.execSQL("INSERT OR REPLACE INTO scripts (name, version, description, author, enabled) VALUES (?, ?, ?, ?, ?)", arrayOf(
script.name,
script.version,
script.description,
script.author,
0
))
}
}
}
}
}

View File

@@ -5,36 +5,63 @@ import androidx.documentfile.provider.DocumentFile
import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.bridge.scripting.IScripting
import me.rhunk.snapenhance.bridge.scripting.ReloadListener
import me.rhunk.snapenhance.scripting.type.ModuleInfo
import java.io.InputStream
class RemoteScriptManager(
private val context: RemoteSideContext,
) : IScripting.Stub() {
private val scriptRuntime = ScriptRuntime(context.log)
val runtime = ScriptRuntime(context.log)
private fun getScriptFolder()
= DocumentFile.fromTreeUri(context.androidContext, Uri.parse(context.config.root.scripting.moduleFolder.get()))
private fun hasHotReload() = context.config.root.scripting.hotReload.get()
//private fun hasHotReload() = context.config.root.scripting.hotReload.get()
private val reloadListeners = mutableListOf<ReloadListener>()
private val cachedModuleInfo = mutableMapOf<String, ModuleInfo>()
fun sync() {
getScriptFileNames().forEach { name ->
runCatching {
getScriptInputStream(name) { stream ->
runtime.getModuleInfo(stream!!).also { info ->
cachedModuleInfo[name] = info
}
}
}.onFailure {
context.log.error("Failed to load module info for $name", it)
}
}
context.modDatabase.syncScripts(cachedModuleInfo.values.toList())
}
fun init() {
enabledScriptPaths.forEach { path ->
val content = getScriptContent(path)
scriptRuntime.load(path, content)
sync()
enabledScripts.forEach { path ->
val content = getScriptContent(path) ?: return@forEach
runtime.load(path, content)
}
}
override fun getEnabledScriptPaths(): List<String> {
val folder = getScriptFolder() ?: return emptyList()
return folder.listFiles().filter { it.name?.endsWith(".js") ?: false }.map { it.name!! }
private fun <R> getScriptInputStream(name: String, callback: (InputStream?) -> R): R {
val file = getScriptFolder()?.findFile(name) ?: return callback(null)
return context.androidContext.contentResolver.openInputStream(file.uri)?.use(callback) ?: callback(null)
}
override fun getScriptContent(path: String): String {
val folder = getScriptFolder() ?: return ""
val file = folder.findFile(path) ?: return ""
return context.androidContext.contentResolver.openInputStream(file.uri)?.use {
it.readBytes().toString(Charsets.UTF_8)
} ?: ""
private fun getScriptFileNames(): List<String> {
return (getScriptFolder() ?: return emptyList()).listFiles().filter { it.name?.endsWith(".js") ?: false }.map { it.name!! }
}
override fun getEnabledScripts(): List<String> {
return getScriptFileNames().filter {
context.modDatabase.isScriptEnabled(cachedModuleInfo[it]?.name ?: return@filter false)
}
}
override fun getScriptContent(name: String): String? {
return getScriptInputStream(name) { it?.bufferedReader()?.readText() }
}
override fun registerReloadListener(listener: ReloadListener) {

View File

@@ -46,6 +46,10 @@ class MainActivity : ComponentActivity() {
checkForRequirements()
}
managerContext.scriptManager.runtime.eachModule {
callOnManagerLoad(this@MainActivity)
}
sections = EnumSection.values().toList().associateWith {
it.section.constructors.first().call()
}.onEach { (section, instance) ->

View File

@@ -17,6 +17,7 @@ import me.rhunk.snapenhance.ui.manager.sections.NotImplemented
import me.rhunk.snapenhance.ui.manager.sections.downloads.DownloadsSection
import me.rhunk.snapenhance.ui.manager.sections.features.FeaturesSection
import me.rhunk.snapenhance.ui.manager.sections.home.HomeSection
import me.rhunk.snapenhance.ui.manager.sections.scripting.ScriptsSection
import me.rhunk.snapenhance.ui.manager.sections.social.SocialSection
import kotlin.reflect.KClass
@@ -47,7 +48,8 @@ enum class EnumSection(
),
SCRIPTS(
route = "scripts",
icon = Icons.Filled.DataObject
icon = Icons.Filled.DataObject,
section = ScriptsSection::class
);
companion object {

View File

@@ -0,0 +1,91 @@
package me.rhunk.snapenhance.ui.manager.sections.scripting
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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 me.rhunk.snapenhance.scripting.type.ModuleInfo
import me.rhunk.snapenhance.ui.manager.Section
class ScriptsSection : Section() {
@Composable
fun ModuleItem(script: ModuleInfo) {
var enabled by remember {
mutableStateOf(context.modDatabase.isScriptEnabled(script.name))
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
elevation = CardDefaults.cardElevation()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.weight(1f)
.padding(end = 8.dp)
) {
Text(
text = script.name,
fontSize = 20.sp,
)
Text(
text = script.description ?: "No description",
fontSize = 14.sp,
)
}
Switch(
checked = enabled,
onCheckedChange = {
context.modDatabase.setScriptEnabled(script.name, it)
enabled = it
}
)
}
}
}
@Composable
override fun Content() {
val scriptModules = remember {
context.modDatabase.getScripts()
}
LazyColumn {
item {
if (scriptModules.isEmpty()) {
Text(
text = "No scripts found",
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(8.dp)
)
}
}
items(scriptModules.size) { index ->
ModuleItem(scriptModules[index])
}
}
}
}