1 Commits
main ... WX-new

Author SHA1 Message Date
Der_Googler
c5ed6e1e8c manager: Update WebUI X to the latest version (#345)
* manager: bump WebUI X

* manager: forgot to add interface name back

* manager: use compose set content for webuix

* manager: fix :jna library and reflections in prod
2025-08-21 07:21:43 +08:00
13 changed files with 511 additions and 425 deletions

View File

@@ -28,12 +28,12 @@ apksign {
android { android {
/**signingConfigs { /**signingConfigs {
create("Debug") { create("Debug") {
storeFile = file("D:\\other\\AndroidTool\\android_key\\keystore\\release-key.keystore") storeFile = file("D:\\other\\AndroidTool\\android_key\\keystore\\release-key.keystore")
storePassword = "" storePassword = ""
keyAlias = "" keyAlias = ""
keyPassword = "" keyPassword = ""
} }
}**/ }**/
namespace = "com.sukisu.ultra" namespace = "com.sukisu.ultra"
@@ -41,10 +41,13 @@ android {
release { release {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
} }
/**debug { /**debug {
signingConfig = signingConfigs.named("Debug").get() as ApkSigningConfig signingConfig = signingConfigs.named("Debug").get() as ApkSigningConfig
}**/ }**/
} }
@@ -131,6 +134,8 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.swiperefreshlayout)
implementation(libs.compose.destinations.core) implementation(libs.compose.destinations.core)
ksp(libs.compose.destinations.ksp) ksp(libs.compose.destinations.ksp)
@@ -159,7 +164,16 @@ dependencies {
implementation(libs.mmrl.platform) implementation(libs.mmrl.platform)
compileOnly(libs.mmrl.hidden.api) compileOnly(libs.mmrl.hidden.api)
implementation(libs.mmrl.webui) /**
* Compile only `Java-Native-Access` since plugins are disabled in both WebUI X and KSU WebUI
* Avoid using:
* - fun WXInterface.registerLibrary(clazz: Class<*>, name: String): Unit
* - fun WXInterface.unregisterLibrary(clazz: Class<*>): Unit
* - fun WXInterface.isLibraryRegistered(libName: String): Boolean
*/
compileOnly(libs.mmrl.webui.jna)
implementation(libs.mmrl.webui.portable)
implementation(libs.mmrl.ui) implementation(libs.mmrl.ui)
implementation(libs.accompanist.drawablepainter) implementation(libs.accompanist.drawablepainter)

View File

@@ -4,43 +4,14 @@
-dontwarn org.conscrypt.** -dontwarn org.conscrypt.**
-dontwarn kotlinx.serialization.** -dontwarn kotlinx.serialization.**
# Please add these rules to your existing keep rules in order to suppress warnings.
# This is generated automatically by the Android Gradle plugin.
-dontwarn com.google.auto.service.AutoService
-dontwarn com.google.j2objc.annotations.RetainedWith
-dontwarn javax.lang.model.SourceVersion
-dontwarn javax.lang.model.element.AnnotationMirror
-dontwarn javax.lang.model.element.AnnotationValue
-dontwarn javax.lang.model.element.Element
-dontwarn javax.lang.model.element.ElementKind
-dontwarn javax.lang.model.element.ElementVisitor
-dontwarn javax.lang.model.element.ExecutableElement
-dontwarn javax.lang.model.element.Modifier
-dontwarn javax.lang.model.element.Name
-dontwarn javax.lang.model.element.PackageElement
-dontwarn javax.lang.model.element.TypeElement
-dontwarn javax.lang.model.element.TypeParameterElement
-dontwarn javax.lang.model.element.VariableElement
-dontwarn javax.lang.model.type.ArrayType
-dontwarn javax.lang.model.type.DeclaredType
-dontwarn javax.lang.model.type.ExecutableType
-dontwarn javax.lang.model.type.TypeKind
-dontwarn javax.lang.model.type.TypeMirror
-dontwarn javax.lang.model.type.TypeVariable
-dontwarn javax.lang.model.type.TypeVisitor
-dontwarn javax.lang.model.util.AbstractAnnotationValueVisitor8
-dontwarn javax.lang.model.util.AbstractTypeVisitor8
-dontwarn javax.lang.model.util.ElementFilter
-dontwarn javax.lang.model.util.Elements
-dontwarn javax.lang.model.util.SimpleElementVisitor8
-dontwarn javax.lang.model.util.SimpleTypeVisitor7
-dontwarn javax.lang.model.util.SimpleTypeVisitor8
-dontwarn javax.lang.model.util.Types
-dontwarn javax.tools.Diagnostic$Kind
# MMRL:webui reflection # MMRL:webui reflection
-keep class com.dergoogler.mmrl.webui.model.ModId { *; } -keep class androidx.compose.ui.graphics.Color { *; }
-keep class androidx.compose.material3.ButtonColors { *; }
-keep class androidx.compose.material3.CardColors { *; }
-keep class androidx.compose.material3.ColorScheme { *; }
-keep class com.dergoogler.mmrl.platform.model.ModId { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.WXOptions { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.WXInterface { *; }
-keep class com.dergoogler.mmrl.webui.interfaces.** { *; } -keep class com.dergoogler.mmrl.webui.interfaces.** { *; }
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; } -keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }

View File

@@ -11,7 +11,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import coil.Coil import coil.Coil
import coil.ImageLoader import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform import com.dergoogler.mmrl.platform.PlatformManager
import me.zhanghai.android.appiconloader.coil.AppIconFetcher import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File import java.io.File
@@ -90,7 +90,7 @@ class KernelSUApplication : Application() {
// 注册Activity生命周期回调 // 注册Activity生命周期回调
registerActivityLifecycleCallbacks(activityLifecycleCallbacks) registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
Platform.setHiddenApiExemptions() PlatformManager.setHiddenApiExemptions()
val context = this val context = this
val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size) val iconSize = resources.getDimensionPixelSize(android.R.dimen.app_icon_size)

View File

@@ -84,8 +84,8 @@ import java.util.concurrent.TimeUnit
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ui.webui.WebUIXActivity import com.sukisu.ultra.ui.webui.WebUIXActivity
import com.dergoogler.mmrl.platform.Platform
import androidx.core.net.toUri import androidx.core.net.toUri
import com.dergoogler.mmrl.platform.PlatformManager
import com.dergoogler.mmrl.platform.model.ModuleConfig import com.dergoogler.mmrl.platform.model.ModuleConfig
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
import com.sukisu.ultra.ui.component.AnimatedFab import com.sukisu.ultra.ui.component.AnimatedFab
@@ -460,7 +460,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
"wx" -> wxEngine "wx" -> wxEngine
"ksu" -> ksuEngine "ksu" -> ksuEngine
else -> { else -> {
if (Platform.isAlive) { if (PlatformManager.isAlive) {
wxEngine wxEngine
} else { } else {
ksuEngine ksuEngine

View File

@@ -2,6 +2,7 @@ package com.sukisu.ultra.ui.theme
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
@@ -48,8 +49,10 @@ import androidx.activity.SystemBarStyle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalConfiguration
/** /**
* 主题配置对象,管理应用的主题相关状态 * 主题配置对象,管理应用的主题相关状态
@@ -84,13 +87,13 @@ object ThemeConfig {
*/ */
@Composable @Composable
fun KernelSUTheme( fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) { darkTheme: Boolean = when (ThemeConfig.forceDarkMode) {
true -> true true -> true
false -> false false -> false
null -> isSystemInDarkTheme() null -> isSystemInDarkTheme()
}, },
dynamicColor: Boolean = ThemeConfig.useDynamicColor, dynamicColor: Boolean = ThemeConfig.useDynamicColor,
content: @Composable () -> Unit content: @Composable () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val systemIsDark = isSystemInDarkTheme() val systemIsDark = isSystemInDarkTheme()
@@ -135,18 +138,17 @@ fun KernelSUTheme(
ThemeConfig.backgroundImageLoaded = false ThemeConfig.backgroundImageLoaded = false
} }
ThemeConfig.preventBackgroundRefresh = context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) ThemeConfig.preventBackgroundRefresh =
.getBoolean("prevent_background_refresh", true) context.getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getBoolean("prevent_background_refresh", true)
} }
// 创建颜色方案 // 创建颜色方案
val colorScheme = when { val colorScheme = createColorScheme(
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { context = context,
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(context) darkTheme = darkTheme,
} dynamicColor = dynamicColor
darkTheme -> createDarkColorScheme() )
else -> createLightColorScheme()
}
// 根据暗色模式和自定义背景调整卡片配置 // 根据暗色模式和自定义背景调整卡片配置
val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null val isDarkModeWithCustomBackground = darkTheme && ThemeConfig.customBackgroundUri != null
@@ -217,8 +219,18 @@ fun KernelSUTheme(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.zIndex(-2f) .zIndex(-2f)
.background(if (darkTheme) if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background } .background(
else if (CardConfig.isCustomBackgroundEnabled) { colorScheme.surfaceContainerLow } else { colorScheme.background }) if (darkTheme) if (CardConfig.isCustomBackgroundEnabled) {
colorScheme.surfaceContainerLow
} else {
colorScheme.background
}
else if (CardConfig.isCustomBackgroundEnabled) {
colorScheme.surfaceContainerLow
} else {
colorScheme.background
}
)
) )
// 自定义背景层 // 自定义背景层
@@ -239,7 +251,9 @@ fun KernelSUTheme(
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
.graphicsLayer { .graphicsLayer {
alpha = (painter.state as? AsyncImagePainter.State.Success)?.let { 1f } ?: 0f alpha =
(painter.state as? AsyncImagePainter.State.Success)?.let { 1f }
?: 0f
} }
) )
} }
@@ -288,7 +302,6 @@ fun KernelSUTheme(
* 创建动态深色颜色方案 * 创建动态深色颜色方案
*/ */
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
@Composable
private fun createDynamicDarkColorScheme(context: Context): ColorScheme { private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
val scheme = dynamicDarkColorScheme(context) val scheme = dynamicDarkColorScheme(context)
return scheme.copy( return scheme.copy(
@@ -303,7 +316,6 @@ private fun createDynamicDarkColorScheme(context: Context): ColorScheme {
* 创建动态浅色颜色方案 * 创建动态浅色颜色方案
*/ */
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
@Composable
private fun createDynamicLightColorScheme(context: Context): ColorScheme { private fun createDynamicLightColorScheme(context: Context): ColorScheme {
val scheme = dynamicLightColorScheme(context) val scheme = dynamicLightColorScheme(context)
return scheme.copy( return scheme.copy(
@@ -314,12 +326,35 @@ private fun createDynamicLightColorScheme(context: Context): ColorScheme {
) )
} }
internal fun createColorScheme(
context: Context,
darkTheme: Boolean = when (ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> _isSystemInDarkTheme(context)
},
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
) = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) createDynamicDarkColorScheme(context) else createDynamicLightColorScheme(
context
)
}
darkTheme -> createDarkColorScheme()
else -> createLightColorScheme()
}
@Suppress("FunctionName")
internal fun _isSystemInDarkTheme(context: Context): Boolean {
val configuration = context.resources.configuration
val uiMode = configuration.uiMode
return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}
/** /**
* 创建深色颜色方案 * 创建深色颜色方案
*/ */
@Composable
private fun createDarkColorScheme() = darkColorScheme( private fun createDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.primaryDark, primary = ThemeConfig.currentTheme.primaryDark,
onPrimary = ThemeConfig.currentTheme.onPrimaryDark, onPrimary = ThemeConfig.currentTheme.onPrimaryDark,
@@ -361,7 +396,6 @@ private fun createDarkColorScheme() = darkColorScheme(
/** /**
* 创建浅色颜色方案 * 创建浅色颜色方案
*/ */
@Composable
private fun createLightColorScheme() = lightColorScheme( private fun createLightColorScheme() = lightColorScheme(
primary = ThemeConfig.currentTheme.primaryLight, primary = ThemeConfig.currentTheme.primaryLight,
onPrimary = ThemeConfig.currentTheme.onPrimaryLight, onPrimary = ThemeConfig.currentTheme.onPrimaryLight,
@@ -440,7 +474,10 @@ private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
/** /**
* 保存并应用自定义背景 * 保存并应用自定义背景
*/ */
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) { fun Context.saveAndApplyCustomBackground(
uri: Uri,
transformation: BackgroundTransformation? = null,
) {
val finalUri = if (transformation != null) { val finalUri = if (transformation != null) {
saveTransformedBackground(uri, transformation) saveTransformedBackground(uri, transformation)
} else { } else {
@@ -536,7 +573,7 @@ fun Context.loadThemeMode() {
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_mode", "system") .getString("theme_mode", "system")
ThemeConfig.forceDarkMode = when(mode) { ThemeConfig.forceDarkMode = when (mode) {
"dark" -> true "dark" -> true
"light" -> false "light" -> false
else -> null else -> null

View File

@@ -7,7 +7,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.dergoogler.mmrl.platform.Platform.Companion.context import com.dergoogler.mmrl.platform.PlatformManager.context
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell

View File

@@ -10,7 +10,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.dergoogler.mmrl.platform.Platform.Companion.context import com.dergoogler.mmrl.platform.PlatformManager.context
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.sukisu.ultra.KernelVersion import com.sukisu.ultra.KernelVersion

View File

@@ -1,60 +1,57 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.content.Context
import android.content.ServiceConnection import android.content.ServiceConnection
import android.util.Log import android.util.Log
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import com.dergoogler.mmrl.platform.Platform import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.Platform.Companion.createPlatformIntent
import com.dergoogler.mmrl.platform.PlatformManager
import com.dergoogler.mmrl.platform.PlatformManager.packageManager
import com.dergoogler.mmrl.platform.PlatformManager.userManager
import com.dergoogler.mmrl.platform.model.IProvider import com.dergoogler.mmrl.platform.model.IProvider
import com.dergoogler.mmrl.platform.model.PlatformIntent
import com.sukisu.ultra.ksuApp import com.sukisu.ultra.ksuApp
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.topjohnwu.superuser.ipc.RootService import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.Deferred
class KsuLibSuProvider : IProvider { class KsuLibSuProvider(
override val name = "KsuLibSu" private val context: Context,
) : IProvider {
override val name = "SukiLibSu"
override fun isAvailable() = true override fun isAvailable() = true
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName) override suspend fun isAuthorized() = Natives.becomeManager(context.packageName)
private val serviceIntent private val intent by lazy {
get() = PlatformIntent( context.createPlatformIntent<SuService>(Platform.SukiSU)
ksuApp, }
Platform.KsuNext,
SuService::class.java
)
override fun bind(connection: ServiceConnection) { override fun bind(connection: ServiceConnection) {
RootService.bind(serviceIntent.intent, connection) RootService.bind(intent, connection)
} }
override fun unbind(connection: ServiceConnection) { override fun unbind(connection: ServiceConnection) {
RootService.stop(serviceIntent.intent) RootService.stop(intent)
} }
} }
// webui x // webui x
suspend fun initPlatform() = withContext(Dispatchers.IO) { suspend fun CoroutineScope.initPlatform(context: Context = ksuApp): Deferred<Boolean> =
try { try {
val active = Platform.init { val active = PlatformManager.init(this) {
this.context = ksuApp from(KsuLibSuProvider(context))
this.platform = Platform.KsuNext
this.provider = from(KsuLibSuProvider())
} }
while (!active) { active
delay(1000)
}
return@withContext active
} catch (e: Exception) { } catch (e: Exception) {
Log.e("KsuLibSu", "Failed to initialize platform", e) Log.e("KsuLibSu", "Failed to initialize platform", e)
return@withContext false CompletableDeferred(false)
} }
}
fun Platform.Companion.getInstalledPackagesAll(catch: (Exception) -> Unit = {}): List<PackageInfo> = fun Platform.Companion.getInstalledPackagesAll(catch: (Exception) -> Unit = {}): List<PackageInfo> =
try { try {

View File

@@ -2,13 +2,13 @@ package com.sukisu.ultra.ui.webui
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import com.dergoogler.mmrl.platform.model.PlatformIntent.Companion.getPlatform import com.dergoogler.mmrl.platform.Platform.Companion.getPlatform
import com.dergoogler.mmrl.platform.service.ServiceManager import com.dergoogler.mmrl.platform.service.ServiceManager
import com.topjohnwu.superuser.ipc.RootService import com.topjohnwu.superuser.ipc.RootService
class SuService : RootService() { class SuService : RootService() {
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder {
val mode = intent.getPlatform() val mode = intent.getPlatform() ?: throw Exception("Platform not found")
return ServiceManager(mode) return ServiceManager(mode)
} }
} }

View File

@@ -1,10 +1,10 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.annotation.SuppressLint import android.content.Context
import android.app.ActivityManager import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
@@ -14,21 +14,30 @@ import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader
import com.dergoogler.mmrl.platform.model.ModId import com.dergoogler.mmrl.platform.model.ModId.Companion.getModId
import com.dergoogler.mmrl.platform.model.ModId.Companion.webrootDir
import com.dergoogler.mmrl.ui.component.dialog.ConfirmData
import com.dergoogler.mmrl.ui.component.dialog.confirm
import com.dergoogler.mmrl.webui.activity.WXActivity.Companion.createLoadingRenderer
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.ui.util.createRootShell import com.sukisu.ultra.ui.util.createRootShell
import java.io.File import com.dergoogler.mmrl.webui.util.WebUIOptions
import com.dergoogler.mmrl.webui.interfaces.WXOptions import com.dergoogler.mmrl.webui.view.WebUIView
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme._isSystemInDarkTheme
import com.sukisu.ultra.ui.theme.createColorScheme
import kotlinx.coroutines.launch
@SuppressLint("SetJavaScriptEnabled")
class WebUIActivity : ComponentActivity() { class WebUIActivity : ComponentActivity() {
private lateinit var webviewInterface: WebViewInterface val modId get() = intent.getModId() ?: throw IllegalArgumentException("Invalid Module ID")
val prefs: SharedPreferences get() = getSharedPreferences("settings", MODE_PRIVATE)
val context: Context get() = this
private var rootShell: Shell? = null private var rootShell: Shell? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// Enable edge to edge // Enable edge to edge
enableEdgeToEdge() enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -37,44 +46,76 @@ class WebUIActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val moduleId = intent.getStringExtra("id")!! val darkTheme = when (ThemeConfig.forceDarkMode) {
val name = intent.getStringExtra("name")!! true -> true
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { false -> false
@Suppress("DEPRECATION") null -> _isSystemInDarkTheme(context)
setTaskDescription(ActivityManager.TaskDescription("SukiSU-Ultra - $name"))
} else {
val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("SukiSU-Ultra - $name").build()
setTaskDescription(taskDescription)
} }
val prefs = getSharedPreferences("settings", MODE_PRIVATE) val colorScheme = createColorScheme(
WebView.setWebContentsDebuggingEnabled(prefs.getBoolean("enable_web_debugging", false)) context = context,
darkTheme = darkTheme
)
val loading = createLoadingRenderer(colorScheme)
setContentView(loading)
lifecycleScope.launch {
val ready = initPlatform(context)
if (ready.await()) {
init()
return@launch
}
confirm(
ConfirmData(
title = "Failed!",
description = "Failed to initialize platform. Please try again.",
confirmText = "Close",
onConfirm = {
finish()
},
),
colorScheme = colorScheme
)
}
}
private fun init() {
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
val options = WebUIOptions(
modId = modId,
debug = webDebugging,
// keep plugins disabled for security reasons
pluginsEnabled = false,
context = context,
)
val moduleDir = "/data/adb/modules/${moduleId}"
val webRoot = File("${moduleDir}/webroot")
val rootShell = createRootShell(true).also { this.rootShell = it } val rootShell = createRootShell(true).also { this.rootShell = it }
val webViewAssetLoader = WebViewAssetLoader.Builder() val webViewAssetLoader = WebViewAssetLoader.Builder()
.setDomain("mui.kernelsu.org") .setDomain("mui.kernelsu.org")
.addPathHandler( .addPathHandler(
"/", "/",
SuFilePathHandler(this, webRoot, rootShell) SuFilePathHandler(this, modId.webrootDir, rootShell)
) )
.build() .build()
val webViewClient = object : WebViewClient() { val webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest( override fun shouldInterceptRequest(
view: WebView, view: WebView,
request: WebResourceRequest request: WebResourceRequest,
): WebResourceResponse? { ): WebResourceResponse? {
return webViewAssetLoader.shouldInterceptRequest(request.url) return webViewAssetLoader.shouldInterceptRequest(request.url)
} }
} }
val webView = WebView(this).apply { val webView = WebUIView(options).apply {
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets -> ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updateLayoutParams<MarginLayoutParams> { view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = inset.left leftMargin = inset.left
rightMargin = inset.right rightMargin = inset.right
topMargin = inset.top topMargin = inset.top
@@ -82,20 +123,17 @@ class WebUIActivity : ComponentActivity() {
} }
return@setOnApplyWindowInsetsListener insets return@setOnApplyWindowInsetsListener insets
} }
settings.javaScriptEnabled = true
settings.domStorageEnabled = true addJavascriptInterface<WebViewInterface>()
settings.allowFileAccess = false
webviewInterface = WebViewInterface(WXOptions(this@WebUIActivity, this, ModId(moduleId)))
addJavascriptInterface(webviewInterface, "ksu")
setWebViewClient(webViewClient) setWebViewClient(webViewClient)
loadUrl("https://mui.kernelsu.org/index.html") loadDomain()
} }
setContentView(webView) setContentView(webView)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy()
runCatching { rootShell?.close() } runCatching { rootShell?.close() }
super.onDestroy()
} }
} }

View File

@@ -1,110 +1,126 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.app.ActivityManager import android.content.Context
import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.os.Bundle
import android.webkit.WebView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.lifecycleScope import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.dergoogler.mmrl.platform.Platform import com.dergoogler.mmrl.platform.Platform
import com.dergoogler.mmrl.platform.model.ModId import com.dergoogler.mmrl.platform.PlatformManager
import com.dergoogler.mmrl.ui.component.Loading import com.dergoogler.mmrl.webui.activity.WXActivity
import com.dergoogler.mmrl.webui.screen.WebUIScreen import com.dergoogler.mmrl.webui.util.WebUIOptions
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions import com.dergoogler.mmrl.webui.view.WebUIXView
import com.sukisu.ultra.BuildConfig import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.ui.theme.KernelSUTheme import com.sukisu.ultra.ui.theme.KernelSUTheme
import kotlinx.coroutines.delay import com.sukisu.ultra.ui.theme.ThemeConfig
import kotlinx.coroutines.launch import com.sukisu.ultra.ui.theme._isSystemInDarkTheme
import kotlinx.coroutines.CoroutineScope
class WebUIXActivity : ComponentActivity() { import kotlin.jvm.java
private lateinit var webView: WebView
class WebUIXActivity : WXActivity() {
private val userAgent private val userAgent
get(): String { get(): String {
val ksuVersion = BuildConfig.VERSION_CODE val ksuVersion = BuildConfig.VERSION_CODE
val platform = Platform.get("Unknown") { val platform = PlatformManager.get(Platform.Unknown) {
platform.name platform
} }
val platformVersion = Platform.get(-1) { val platformVersion = PlatformManager.get(-1) {
moduleManager.versionCode moduleManager.versionCode
} }
val osVersion = Build.VERSION.RELEASE val osVersion = Build.VERSION.RELEASE
val deviceModel = Build.MODEL val deviceModel = Build.MODEL
return "SukiSU-Ultra /$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)" return "SukiSU-Ultra/$ksuVersion (Linux; Android $osVersion; $deviceModel; ${platform.name}/$platformVersion)"
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
webView = WebView(this) val prefs: SharedPreferences get() = getSharedPreferences("settings", MODE_PRIVATE)
val context: Context get() = this
lifecycleScope.launch { override suspend fun onRender(scope: CoroutineScope) {
initPlatform() super.onRender(scope)
}
val moduleId = intent.getStringExtra("id")!! val modId =
val name = intent.getStringExtra("name")!! this.modId
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { ?: throw IllegalArgumentException("modId cannot be null or empty")
@Suppress("DEPRECATION")
setTaskDescription(ActivityManager.TaskDescription("SukiSU-Ultra - $name"))
} else {
val taskDescription =
ActivityManager.TaskDescription.Builder().setLabel("SukiSU-Ultra - $name").build()
setTaskDescription(taskDescription)
}
val prefs = getSharedPreferences("settings", MODE_PRIVATE) val webDebugging = prefs.getBoolean("enable_web_debugging", false)
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
setContent { setContent {
// keep the compose logic so custom background continue to work
KernelSUTheme { KernelSUTheme {
var isLoading by remember { mutableStateOf(true) } var ready by remember { mutableStateOf(false) }
LaunchedEffect(Platform.isAlive) { LaunchedEffect(Unit) {
while (!Platform.isAlive) { val init = initPlatform(context)
delay(1000) ready = init.await()
}
isLoading = false
} }
if (isLoading) { if (!ready) {
Loading() Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
return@KernelSUTheme return@KernelSUTheme
} }
val webDebugging = prefs.getBoolean("enable_web_debugging", false) val darkTheme = remember(ThemeConfig) {
val erudaInject = prefs.getBoolean("use_webuix_eruda", false) when (ThemeConfig.forceDarkMode) {
val dark = isSystemInDarkTheme() true -> true
false -> false
null -> _isSystemInDarkTheme(context)
}
}
val options = rememberWebUIOptions( val options = WebUIOptions(
modId = ModId(moduleId), modId = modId,
context = context,
debug = webDebugging, debug = webDebugging,
appVersionCode = BuildConfig.VERSION_CODE, isDarkMode = darkTheme,
isDarkMode = dark, // keep plugins disabled for security reasons
pluginsEnabled = false,
enableEruda = erudaInject, enableEruda = erudaInject,
cls = WebUIXActivity::class.java, cls = WebUIXActivity::class.java,
userAgentString = userAgent userAgentString = userAgent,
colorScheme = MaterialTheme.colorScheme
) )
WebUIScreen( // Activity Title
webView = webView, config {
options = options, if (title != null) {
interfaces = listOf( setActivityTitle("SukiSU-Ultra - $title")
WebViewInterface.factory() }
) }
AndroidView(
factory = { WebUIXView(options) },
update = { view ->
val v = view.apply {
wx.addJavascriptInterface<WebViewInterface>()
}
// pass it for the activity
this.view = v
}
) )
} }
} }

View File

@@ -1,228 +1,237 @@
package com.sukisu.ultra.ui.webui package com.sukisu.ultra.ui.webui
import android.app.Activity import android.app.Activity
import android.os.Handler import android.content.Context
import android.os.Looper import android.webkit.WebView
import android.text.TextUtils import com.dergoogler.mmrl.platform.model.ModId
import android.view.Window import android.os.Handler
import android.webkit.JavascriptInterface import android.os.Looper
import android.widget.Toast import android.text.TextUtils
import androidx.core.view.WindowInsetsCompat import android.util.Log
import androidx.core.view.WindowInsetsControllerCompat import android.view.Window
import com.dergoogler.mmrl.webui.interfaces.WXInterface import android.webkit.JavascriptInterface
import com.dergoogler.mmrl.webui.interfaces.WXOptions import android.widget.Toast
import com.dergoogler.mmrl.webui.model.JavaScriptInterface import androidx.core.view.WindowInsetsCompat
import com.topjohnwu.superuser.CallbackList import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.ShellUtils import com.dergoogler.mmrl.platform.file.ExtFile
import com.topjohnwu.superuser.internal.UiThreadHandler import com.dergoogler.mmrl.platform.model.ModId.Companion.moduleDir
import com.sukisu.ultra.ui.util.createRootShell import com.dergoogler.mmrl.webui.interfaces.WXInterface
import com.sukisu.ultra.ui.util.listModules import com.dergoogler.mmrl.webui.interfaces.WXOptions
import com.sukisu.ultra.ui.util.withNewRootShell import com.dergoogler.mmrl.webui.util.WebUIOptions
import org.json.JSONArray import com.topjohnwu.superuser.CallbackList
import org.json.JSONObject import com.topjohnwu.superuser.ShellUtils
import com.sukisu.ultra.ui.util.* import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File import com.sukisu.ultra.ui.util.createRootShell
import java.util.concurrent.CompletableFuture import com.sukisu.ultra.ui.util.listModules
import com.sukisu.ultra.ui.util.withNewRootShell
class WebViewInterface( import org.json.JSONArray
wxOptions: WXOptions, import org.json.JSONObject
) : WXInterface(wxOptions) { import com.sukisu.ultra.ui.util.*
override var name: String = "ksu" import java.util.concurrent.CompletableFuture
import kotlin.collections.iterator
companion object {
fun factory() = JavaScriptInterface(WebViewInterface::class.java) internal class WebViewInterface(wxOptions: WXOptions) : WXInterface(wxOptions) {
} // `ExtFile` to make sure that the platform won't get called when it is used within KSU WebUI
private val modDir: ExtFile get() = modId.moduleDir.toExtFile()
private val modDir get() = "/data/adb/modules/${modId.id}"
override var name = "ksu"
@JavascriptInterface
fun exec(cmd: String): String { @JavascriptInterface
return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) } fun exec(cmd: String): String {
} return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) }
}
@JavascriptInterface
fun exec(cmd: String, callbackFunc: String) { @JavascriptInterface
exec(cmd, null, callbackFunc) fun exec(cmd: String, callbackFunc: String) {
} exec(cmd, null, callbackFunc)
}
private fun processOptions(sb: StringBuilder, options: String?) {
val opts = if (options == null) JSONObject() else { private fun processOptions(sb: StringBuilder, options: String?) {
JSONObject(options) val opts = if (options == null) JSONObject() else {
} JSONObject(options)
}
val cwd = opts.optString("cwd")
if (!TextUtils.isEmpty(cwd)) { val cwd = opts.optString("cwd")
sb.append("cd ${cwd};") if (!TextUtils.isEmpty(cwd)) {
} sb.append("cd ${cwd};")
}
opts.optJSONObject("env")?.let { env ->
env.keys().forEach { key -> opts.optJSONObject("env")?.let { env ->
sb.append("export ${key}=${env.getString(key)};") env.keys().forEach { key ->
} sb.append("export ${key}=${env.getString(key)};")
} }
} }
}
@JavascriptInterface
fun exec( @JavascriptInterface
cmd: String, fun exec(
options: String?, cmd: String,
callbackFunc: String options: String?,
) { callbackFunc: String,
val finalCommand = StringBuilder() ) {
processOptions(finalCommand, options) val finalCommand = StringBuilder()
finalCommand.append(cmd) processOptions(finalCommand, options)
finalCommand.append(cmd)
val result = withNewRootShell(true) {
newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec() val result = withNewRootShell(true) {
} newJob().add(finalCommand.toString()).to(ArrayList(), ArrayList()).exec()
val stdout = result.out.joinToString(separator = "\n") }
val stderr = result.err.joinToString(separator = "\n") val stdout = result.out.joinToString(separator = "\n")
val stderr = result.err.joinToString(separator = "\n")
val jsCode =
"javascript: (function() { try { ${callbackFunc}(${result.code}, ${ val jsCode =
JSONObject.quote( "javascript: (function() { try { ${callbackFunc}(${result.code}, ${
stdout JSONObject.quote(
) stdout
}, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();" )
webView.post { }, ${JSONObject.quote(stderr)}); } catch(e) { console.error(e); } })();"
webView.loadUrl(jsCode) webView.post {
} webView.loadUrl(jsCode)
} }
}
@JavascriptInterface
fun spawn(command: String, args: String, options: String?, callbackFunc: String) { @JavascriptInterface
val finalCommand = StringBuilder() fun spawn(command: String, args: String, options: String?, callbackFunc: String) {
val finalCommand = StringBuilder()
processOptions(finalCommand, options)
processOptions(finalCommand, options)
if (!TextUtils.isEmpty(args)) {
finalCommand.append(command).append(" ") if (!TextUtils.isEmpty(args)) {
JSONArray(args).let { argsArray -> finalCommand.append(command).append(" ")
for (i in 0 until argsArray.length()) { JSONArray(args).let { argsArray ->
finalCommand.append(argsArray.getString(i)) for (i in 0 until argsArray.length()) {
finalCommand.append(" ") finalCommand.append(argsArray.getString(i))
} finalCommand.append(" ")
} }
} else { }
finalCommand.append(command) } else {
} finalCommand.append(command)
}
val shell = createRootShell(true)
val shell = createRootShell(true)
val emitData = fun(name: String, data: String) {
val jsCode = val emitData = fun(name: String, data: String) {
"javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${ val jsCode =
JSONObject.quote( "javascript: (function() { try { ${callbackFunc}.${name}.emit('data', ${
data JSONObject.quote(
) data
}); } catch(e) { console.error('emitData', e); } })();" )
webView.post { }); } catch(e) { console.error('emitData', e); } })();"
webView.loadUrl(jsCode) webView.post {
} webView.loadUrl(jsCode)
} }
}
val stdout = object : CallbackList<String>(UiThreadHandler::runAndWait) {
override fun onAddElement(s: String) { val stdout = object : CallbackList<String>(UiThreadHandler::runAndWait) {
emitData("stdout", s) override fun onAddElement(s: String) {
} emitData("stdout", s)
} }
}
val stderr = object : CallbackList<String>(UiThreadHandler::runAndWait) {
override fun onAddElement(s: String) { val stderr = object : CallbackList<String>(UiThreadHandler::runAndWait) {
emitData("stderr", s) override fun onAddElement(s: String) {
} emitData("stderr", s)
} }
}
val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()
val completableFuture = CompletableFuture.supplyAsync { val future = shell.newJob().add(finalCommand.toString()).to(stdout, stderr).enqueue()
future.get() val completableFuture = CompletableFuture.supplyAsync {
} future.get()
}
completableFuture.thenAccept { result ->
val emitExitCode = completableFuture.thenAccept { result ->
"javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();" val emitExitCode =
webView.post { "javascript: (function() { try { ${callbackFunc}.emit('exit', ${result.code}); } catch(e) { console.error(`emitExit error: \${e}`); } })();"
webView.loadUrl(emitExitCode) webView.post {
} webView.loadUrl(emitExitCode)
}
if (result.code != 0) {
val emitErrCode = if (result.code != 0) {
"javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${ val emitErrCode =
JSONObject.quote( "javascript: (function() { try { var err = new Error(); err.exitCode = ${result.code}; err.message = ${
result.err.joinToString( JSONObject.quote(
"\n" result.err.joinToString(
) "\n"
) )
};${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();" )
webView.post { };${callbackFunc}.emit('error', err); } catch(e) { console.error('emitErr', e); } })();"
webView.loadUrl(emitErrCode) webView.post {
} webView.loadUrl(emitErrCode)
} }
}.whenComplete { _, _ -> }
runCatching { shell.close() } }.whenComplete { _, _ ->
} runCatching { shell.close() }
} }
}
@JavascriptInterface
fun toast(msg: String) { @JavascriptInterface
webView.post { fun toast(msg: String) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() webView.post {
} Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
} }
}
@JavascriptInterface
fun fullScreen(enable: Boolean) { @JavascriptInterface
if (context is Activity) { fun fullScreen(enable: Boolean) {
Handler(Looper.getMainLooper()).post { if (activity == null) return
if (enable) {
hideSystemUI(activity.window) try {
} else { Handler(Looper.getMainLooper()).post {
showSystemUI(activity.window) if (enable) {
} hideSystemUI(activity!!.window)
} } else {
} showSystemUI(activity!!.window)
} }
}
@JavascriptInterface } catch (e: Exception) {
fun moduleInfo(): String { Log.e("WebViewInterface", "fullScreen", e)
val moduleInfos = JSONArray(listModules()) }
val currentModuleInfo = JSONObject() }
currentModuleInfo.put("moduleDir", modDir)
val moduleId = File(modDir).getName() @JavascriptInterface
for (i in 0 until moduleInfos.length()) { fun moduleInfo(): String {
val currentInfo = moduleInfos.getJSONObject(i) val moduleInfos = JSONArray(listModules())
val currentModuleInfo = JSONObject()
if (currentInfo.getString("id") != moduleId) { currentModuleInfo.put("moduleDir", modDir.path)
continue val moduleId = modDir.getName()
} for (i in 0 until moduleInfos.length()) {
val currentInfo = moduleInfos.getJSONObject(i)
val keys = currentInfo.keys()
for (key in keys) { if (currentInfo.getString("id") != moduleId) {
currentModuleInfo.put(key, currentInfo.get(key)) continue
} }
break
} val keys = currentInfo.keys()
return currentModuleInfo.toString() for (key in keys) {
} currentModuleInfo.put(key, currentInfo.get(key))
}
// =================== KPM支持 ============================= break
}
@JavascriptInterface return currentModuleInfo.toString()
fun listAllKpm() : String { }
return listKpmModules()
} // =================== KPM支持 =============================
@JavascriptInterface @JavascriptInterface
fun controlKpm(name: String, args: String) : Int { fun listAllKpm(): String {
return controlKpmModule(name, args) return listKpmModules()
} }
}
@JavascriptInterface
fun hideSystemUI(window: Window) = fun controlKpm(name: String, args: String): Int {
WindowInsetsControllerCompat(window, window.decorView).let { controller -> return controlKpmModule(name, args)
controller.hide(WindowInsetsCompat.Type.systemBars()) }
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE }
}
private fun hideSystemUI(window: Window) =
fun showSystemUI(window: Window) = WindowInsetsControllerCompat(window, window.decorView).let { controller ->
WindowInsetsControllerCompat(window, window.decorView).show(WindowInsetsCompat.Type.systemBars()) controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
private fun showSystemUI(window: Window) =
WindowInsetsControllerCompat(
window,
window.decorView
).show(WindowInsetsCompat.Type.systemBars())

View File

@@ -23,7 +23,9 @@ compose-material = "1.8.3"
compose-material3 = "1.3.2" compose-material3 = "1.3.2"
compose-ui = "1.8.3" compose-ui = "1.8.3"
documentfile = "1.1.0" documentfile = "1.1.0"
mmrl = "2bb00b3c2b" mmrl = "v33953"
webui-x-portable = "953fad192a"
swiperefreshlayout = "1.1.0"
[plugins] [plugins]
agp-app = { id = "com.android.application", version.ref = "agp" } agp-app = { id = "com.android.application", version.ref = "agp" }
@@ -57,6 +59,8 @@ androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecyc
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" }
@@ -84,8 +88,8 @@ markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "29.0.13599879-beta2" } lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "29.0.13599879-beta2" }
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
mmrl-webui-portable = { group = "com.github.MMRLApp.WebUI-X-Portable", name = "webui", version.ref = "webui-x-portable" }
mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", version.ref = "mmrl" } mmrl-webui-jna = { group = "com.github.MMRLApp.WebUI-X-Portable", name = "jna", version.ref = "webui-x-portable" }
mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" } mmrl-platform = { group = "com.github.MMRLApp.MMRL", name = "platform", version.ref = "mmrl" }
mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" } mmrl-ui = { group = "com.github.MMRLApp.MMRL", name = "ui", version.ref = "mmrl" }
mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" } mmrl-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" }