mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat(scripting): integrated ui
This commit is contained in:
@ -14,7 +14,6 @@ import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||
import me.rhunk.snapenhance.scripting.impl.IPCListeners
|
||||
import me.rhunk.snapenhance.scripting.impl.ManagerIPC
|
||||
import me.rhunk.snapenhance.scripting.impl.ManagerScriptConfig
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.InterfaceManager
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import kotlin.system.exitProcess
|
||||
@ -67,7 +66,6 @@ class RemoteScriptManager(
|
||||
putConst("currentSide", this, BindingSide.MANAGER.key)
|
||||
module.registerBindings(
|
||||
ManagerIPC(ipcListeners),
|
||||
InterfaceManager(),
|
||||
ManagerScriptConfig(this@RemoteScriptManager)
|
||||
)
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
package me.rhunk.snapenhance.scripting.impl.ui.components.impl
|
||||
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.Node
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType
|
||||
|
||||
enum class ActionType {
|
||||
LAUNCHED,
|
||||
DISPOSE
|
||||
}
|
||||
|
||||
class ActionNode(
|
||||
val actionType: ActionType,
|
||||
val key: Any = Unit,
|
||||
val callback: () -> Unit
|
||||
): Node(NodeType.ACTION)
|
@ -20,7 +20,9 @@ import androidx.documentfile.provider.DocumentFile
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.InterfaceManager
|
||||
import me.rhunk.snapenhance.common.scripting.ui.EnumScriptInterface
|
||||
import me.rhunk.snapenhance.common.scripting.ui.InterfaceManager
|
||||
import me.rhunk.snapenhance.common.scripting.ui.ScriptInterface
|
||||
import me.rhunk.snapenhance.ui.manager.Section
|
||||
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||
import me.rhunk.snapenhance.ui.util.chooseFolder
|
||||
@ -140,22 +142,14 @@ class ScriptsSection : Section() {
|
||||
|
||||
@Composable
|
||||
fun ScriptSettings(script: ModuleInfo) {
|
||||
var settingsError by remember {
|
||||
mutableStateOf(null as Throwable?)
|
||||
}
|
||||
|
||||
val settingsInterface = remember {
|
||||
val module = context.scriptManager.runtime.getModuleByName(script.name) ?: return@remember null
|
||||
runCatching {
|
||||
(module.getBinding(InterfaceManager::class))?.buildInterface("settings")
|
||||
}.onFailure {
|
||||
settingsError = it
|
||||
}.getOrNull()
|
||||
(module.getBinding(InterfaceManager::class))?.buildInterface(EnumScriptInterface.SETTINGS)
|
||||
}
|
||||
|
||||
if (settingsInterface == null) {
|
||||
Text(
|
||||
text = settingsError?.message ?: "This module does not have any settings",
|
||||
text = "This module does not have any settings",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
|
@ -703,6 +703,10 @@
|
||||
"name": "Auto Reload",
|
||||
"description": "Automatically reloads scripts when they change"
|
||||
},
|
||||
"integrated_ui": {
|
||||
"name": "Integrated UI",
|
||||
"description": "Allows scripts to add custom UI components to Snapchat"
|
||||
},
|
||||
"disable_log_anonymization": {
|
||||
"name": "Disable Log Anonymization",
|
||||
"description": "Disables the anonymization of logs"
|
||||
|
@ -7,5 +7,6 @@ class Scripting : ConfigContainer() {
|
||||
val developerMode = boolean("developer_mode", false) { requireRestart() }
|
||||
val moduleFolder = string("module_folder", "modules") { addFlags(ConfigFlag.FOLDER); requireRestart() }
|
||||
val autoReload = unique("auto_reload", "snapchat_only", "all")
|
||||
val integratedUI = boolean("integrated_ui", false) { requireRestart() }
|
||||
val disableLogAnonymization = boolean("disable_log_anonymization", false) { requireRestart() }
|
||||
}
|
@ -11,6 +11,7 @@ import me.rhunk.snapenhance.common.scripting.ktx.scriptable
|
||||
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
|
||||
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
|
||||
import me.rhunk.snapenhance.common.scripting.type.Permissions
|
||||
import me.rhunk.snapenhance.common.scripting.ui.InterfaceManager
|
||||
import org.mozilla.javascript.Function
|
||||
import org.mozilla.javascript.NativeJavaObject
|
||||
import org.mozilla.javascript.ScriptableObject
|
||||
@ -53,6 +54,7 @@ class JSModule(
|
||||
|
||||
registerBindings(
|
||||
JavaInterfaces(),
|
||||
InterfaceManager(),
|
||||
)
|
||||
|
||||
moduleObject.putFunction("setField") { args ->
|
||||
|
@ -0,0 +1,11 @@
|
||||
package me.rhunk.snapenhance.common.scripting.ui
|
||||
|
||||
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
|
||||
|
||||
enum class EnumScriptInterface(
|
||||
val key: String,
|
||||
val side: BindingSide
|
||||
) {
|
||||
SETTINGS("settings", BindingSide.MANAGER),
|
||||
FRIEND_FEED_CONTEXT_MENU("friendFeedContextMenu", BindingSide.CORE),
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
package me.rhunk.snapenhance.scripting.impl.ui
|
||||
package me.rhunk.snapenhance.common.scripting.ui
|
||||
|
||||
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
|
||||
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
|
||||
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.Node
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionNode
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionType
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.RowColumnNode
|
||||
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.Node
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.NodeType
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.impl.ActionNode
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.impl.ActionType
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.impl.RowColumnNode
|
||||
import org.mozilla.javascript.Function
|
||||
import org.mozilla.javascript.annotations.JSFunction
|
||||
|
||||
@ -73,24 +74,36 @@ class InterfaceBuilder {
|
||||
|
||||
|
||||
|
||||
class InterfaceManager : AbstractBinding("interface-manager", BindingSide.MANAGER) {
|
||||
private val interfaces = mutableMapOf<String, () -> InterfaceBuilder?>()
|
||||
class InterfaceManager : AbstractBinding("interface-manager", BindingSide.COMMON) {
|
||||
private val interfaces = mutableMapOf<String, (args: Map<String, Any?>) -> InterfaceBuilder?>()
|
||||
|
||||
fun buildInterface(name: String): InterfaceBuilder? {
|
||||
return interfaces[name]?.invoke()
|
||||
fun buildInterface(scriptInterface: EnumScriptInterface, args: Map<String, Any?> = emptyMap()): InterfaceBuilder? {
|
||||
return runCatching {
|
||||
interfaces[scriptInterface.key]?.invoke(args)
|
||||
}.onFailure {
|
||||
context.runtime.logger.error("Failed to build interface ${scriptInterface.key} for ${context.moduleInfo.name}", it)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
override fun onDispose() {
|
||||
interfaces.clear()
|
||||
}
|
||||
|
||||
fun hasInterface(scriptInterfaces: EnumScriptInterface): Boolean {
|
||||
return interfaces.containsKey(scriptInterfaces.key)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JSFunction fun create(name: String, callback: Function) {
|
||||
interfaces[name] = {
|
||||
interfaces[name] = { args ->
|
||||
val interfaceBuilder = InterfaceBuilder()
|
||||
runCatching {
|
||||
contextScope {
|
||||
callback.call(this, callback, callback, arrayOf(interfaceBuilder))
|
||||
callback.call(this, callback, callback, arrayOf(interfaceBuilder, scriptableObject {
|
||||
args.forEach { (key, value) ->
|
||||
putConst(key,this, value)
|
||||
}
|
||||
}))
|
||||
}
|
||||
interfaceBuilder
|
||||
}.onFailure {
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.ui.manager.sections.scripting
|
||||
package me.rhunk.snapenhance.common.scripting.ui
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
@ -13,11 +13,10 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.InterfaceBuilder
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.Node
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionNode
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.impl.ActionType
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.Node
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.NodeType
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.impl.ActionNode
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.impl.ActionType
|
||||
import kotlin.math.abs
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.scripting.impl.ui.components
|
||||
package me.rhunk.snapenhance.common.scripting.ui.components
|
||||
|
||||
open class Node(
|
||||
val type: NodeType,
|
@ -1,4 +1,5 @@
|
||||
package me.rhunk.snapenhance.scripting.impl.ui.components
|
||||
package me.rhunk.snapenhance.common.scripting.ui.components
|
||||
|
||||
enum class NodeType {
|
||||
ROW,
|
||||
COLUMN,
|
@ -0,0 +1,15 @@
|
||||
package me.rhunk.snapenhance.common.scripting.ui.components.impl
|
||||
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.Node
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.NodeType
|
||||
|
||||
enum class ActionType {
|
||||
LAUNCHED,
|
||||
DISPOSE
|
||||
}
|
||||
|
||||
class ActionNode(
|
||||
val actionType: ActionType,
|
||||
val key: Any = Unit,
|
||||
val callback: () -> Unit
|
||||
): Node(NodeType.ACTION)
|
@ -1,9 +1,9 @@
|
||||
package me.rhunk.snapenhance.scripting.impl.ui.components.impl
|
||||
package me.rhunk.snapenhance.common.scripting.ui.components.impl
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.ui.Alignment
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.Node
|
||||
import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.Node
|
||||
import me.rhunk.snapenhance.common.scripting.ui.components.NodeType
|
||||
|
||||
|
||||
class RowColumnNode(
|
@ -25,7 +25,7 @@ import me.rhunk.snapenhance.core.features.impl.tweaks.UnsaveableMessages
|
||||
import me.rhunk.snapenhance.core.features.impl.ui.*
|
||||
import me.rhunk.snapenhance.core.logger.CoreLogger
|
||||
import me.rhunk.snapenhance.core.manager.Manager
|
||||
import me.rhunk.snapenhance.core.ui.menu.impl.MenuViewInjector
|
||||
import me.rhunk.snapenhance.core.ui.menu.MenuViewInjector
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.core.ui.menu.impl
|
||||
package me.rhunk.snapenhance.core.ui.menu
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.Gravity
|
||||
@ -6,12 +6,13 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import me.rhunk.snapenhance.core.event.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
|
||||
import me.rhunk.snapenhance.core.ui.ViewTagState
|
||||
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
|
||||
import me.rhunk.snapenhance.core.ui.menu.impl.*
|
||||
import me.rhunk.snapenhance.core.util.ktx.getIdentifier
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.reflect.KClass
|
||||
@ -27,14 +28,18 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
override fun asyncOnActivityCreate() {
|
||||
menuMap[OperaContextActionMenu::class] = OperaContextActionMenu()
|
||||
menuMap[OperaDownloadIconMenu::class] = OperaDownloadIconMenu()
|
||||
menuMap[SettingsGearInjector::class] = SettingsGearInjector()
|
||||
menuMap[FriendFeedInfoMenu::class] = FriendFeedInfoMenu()
|
||||
menuMap[ChatActionMenu::class] = ChatActionMenu()
|
||||
menuMap[SettingsMenu::class] = SettingsMenu()
|
||||
|
||||
menuMap.values.forEach { it.context = context; it.init() }
|
||||
arrayOf(
|
||||
OperaContextActionMenu(),
|
||||
OperaDownloadIconMenu(),
|
||||
SettingsGearInjector(),
|
||||
FriendFeedInfoMenu(),
|
||||
ChatActionMenu(),
|
||||
SettingsMenu()
|
||||
).forEach {
|
||||
menuMap[it::class] = it.also {
|
||||
it.context = context; it.init()
|
||||
}
|
||||
}
|
||||
|
||||
val messaging = context.feature(Messaging::class)
|
||||
|
||||
@ -90,8 +95,18 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
})
|
||||
}
|
||||
|
||||
val viewList = mutableListOf<View>()
|
||||
context.runOnUiThread {
|
||||
injectedLayout.addView(ScrollView(injectedLayout.context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
weight = 1f;
|
||||
setMargins(0, 100, 0, 0)
|
||||
}
|
||||
|
||||
addView(LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
menuMap[FriendFeedInfoMenu::class]?.inject(event.parent, injectedLayout) { view ->
|
||||
view.layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
@ -99,17 +114,10 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
).apply {
|
||||
setMargins(0, 5, 0, 5)
|
||||
}
|
||||
viewList.add(view)
|
||||
addView(view)
|
||||
}
|
||||
|
||||
viewList.add(View(injectedLayout.context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
30
|
||||
)
|
||||
})
|
||||
|
||||
viewList.reversed().forEach { injectedLayout.addView(it, 0) }
|
||||
}, 0)
|
||||
}
|
||||
|
||||
event.view = injectedLayout
|
@ -9,8 +9,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Switch
|
||||
import androidx.compose.runtime.remember
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
@ -21,6 +23,10 @@ import me.rhunk.snapenhance.common.data.MessageUpdate
|
||||
import me.rhunk.snapenhance.common.database.impl.ConversationMessage
|
||||
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
||||
import me.rhunk.snapenhance.common.database.impl.UserConversationLink
|
||||
import me.rhunk.snapenhance.common.scripting.ui.EnumScriptInterface
|
||||
import me.rhunk.snapenhance.common.scripting.ui.InterfaceManager
|
||||
import me.rhunk.snapenhance.common.scripting.ui.ScriptInterface
|
||||
import me.rhunk.snapenhance.common.ui.createComposeView
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie
|
||||
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
|
||||
@ -326,5 +332,33 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (context.config.scripting.integratedUI.get()) {
|
||||
context.scriptRuntime.eachModule {
|
||||
val interfaceManager = getBinding(InterfaceManager::class)
|
||||
?.takeIf {
|
||||
it.hasInterface(EnumScriptInterface.FRIEND_FEED_CONTEXT_MENU)
|
||||
} ?: return@eachModule
|
||||
|
||||
viewConsumer(LinearLayout(view.context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
applyTheme(view.width, hasRadius = true)
|
||||
|
||||
orientation = LinearLayout.VERTICAL
|
||||
addView(createComposeView(view.context) {
|
||||
ScriptInterface(interfaceBuilder = remember {
|
||||
interfaceManager.buildInterface(EnumScriptInterface.FRIEND_FEED_CONTEXT_MENU, mapOf(
|
||||
"conversationId" to conversationId,
|
||||
"userId" to targetUser
|
||||
))
|
||||
} ?: return@createComposeView)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user