You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
5 Commits
v1.0.8
...
patch-new-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d990276a21 | ||
|
|
b82f50685d | ||
|
|
18219a40b0 | ||
|
|
1eb6eceb2d | ||
|
|
c09fecfb1a |
@@ -108,6 +108,7 @@ dependencies {
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
implementation(libs.androidx.swiperefreshlayout)
|
||||
|
||||
implementation(libs.compose.destinations.core)
|
||||
ksp(libs.compose.destinations.ksp)
|
||||
|
||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import com.rifsxd.ksunext.ui.webui.initPlatform
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
|
||||
lateinit var ksuApp: KernelSUApplication
|
||||
|
||||
@@ -61,6 +62,7 @@ class KernelSUApplication : Application() {
|
||||
}.build()
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private fun launchPlatformInit() {
|
||||
// Use a coroutine to avoid blocking the main thread
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
|
||||
@@ -71,12 +71,7 @@ class MainActivity : ComponentActivity() {
|
||||
if (isManager) install()
|
||||
|
||||
setContent {
|
||||
// Read AMOLED mode preference
|
||||
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val amoledMode = prefs.getBoolean("enable_amoled", false)
|
||||
|
||||
KernelSUTheme (
|
||||
amoledMode = amoledMode
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val snackBarHostState = remember { SnackbarHostState() }
|
||||
|
||||
@@ -311,6 +311,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
.putExtra("id", id)
|
||||
.putExtra("name", name)
|
||||
|
||||
|
||||
webUILauncher.launch(
|
||||
if (prefs.getBoolean("use_webuix", true) && Platform.isAlive) {
|
||||
wxEngine
|
||||
@@ -318,6 +319,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
ksuEngine
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
},
|
||||
context = context,
|
||||
@@ -660,7 +662,9 @@ fun ModuleItem(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
LabelItem(
|
||||
text = if (module.enabled) stringResource(R.string.enabled) else stringResource(R.string.disabled),
|
||||
text = if (module.enabled) stringResource(R.string.enabled) else stringResource(
|
||||
R.string.disabled
|
||||
),
|
||||
style = if (module.enabled)
|
||||
com.dergoogler.mmrl.ui.component.LabelItemDefaults.style.copy()
|
||||
else
|
||||
@@ -798,7 +802,7 @@ fun ModuleItem(
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
|
||||
if (module.hasWebUi) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.webui)) },
|
||||
@@ -819,7 +823,7 @@ fun ModuleItem(
|
||||
)
|
||||
}
|
||||
|
||||
if (module.hasWebUi || module.hasActionScript ) {
|
||||
if (module.hasWebUi || module.hasActionScript) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package com.rifsxd.ksunext.ui.theme
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.dergoogler.mmrl.ui.component.StatusBarStyle
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = PRIMARY,
|
||||
@@ -38,38 +37,58 @@ fun Color.blend(other: Color, ratio: Float): Color {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KernelSUTheme(
|
||||
/**
|
||||
* AMOLED colors are handled through the context
|
||||
*/
|
||||
fun Context.getColorScheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
): ColorScheme {
|
||||
// Read AMOLED mode preference
|
||||
val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val amoledMode = prefs.getBoolean("enable_amoled", false)
|
||||
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
amoledMode: Boolean = false,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
val dynamicColor = true
|
||||
|
||||
return when {
|
||||
amoledMode && darkTheme && dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
val dynamicScheme = dynamicDarkColorScheme(context)
|
||||
val dynamicScheme = dynamicDarkColorScheme(this)
|
||||
dynamicScheme.copy(
|
||||
background = AMOLED_BLACK,
|
||||
surface = AMOLED_BLACK,
|
||||
surfaceVariant = dynamicScheme.surfaceVariant.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainer = dynamicScheme.surfaceContainer.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLow = dynamicScheme.surfaceContainerLow.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLowest = dynamicScheme.surfaceContainerLowest.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerLowest = dynamicScheme.surfaceContainerLowest.blend(
|
||||
AMOLED_BLACK,
|
||||
0.6f
|
||||
),
|
||||
surfaceContainerHigh = dynamicScheme.surfaceContainerHigh.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerHighest = dynamicScheme.surfaceContainerHighest.blend(AMOLED_BLACK, 0.6f),
|
||||
surfaceContainerHighest = dynamicScheme.surfaceContainerHighest.blend(
|
||||
AMOLED_BLACK,
|
||||
0.6f
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
if (darkTheme) dynamicDarkColorScheme(this) else dynamicLightColorScheme(this)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
}
|
||||
|
||||
SystemBarStyle(
|
||||
@Composable
|
||||
fun KernelSUTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val colorScheme = context.getColorScheme(darkTheme)
|
||||
|
||||
StatusBarStyle(
|
||||
darkMode = darkTheme
|
||||
)
|
||||
|
||||
@@ -80,31 +99,7 @@ fun KernelSUTheme(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SystemBarStyle(
|
||||
darkMode: Boolean,
|
||||
statusBarScrim: Color = Color.Transparent,
|
||||
navigationBarScrim: Color = Color.Transparent,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val activity = context as ComponentActivity
|
||||
|
||||
SideEffect {
|
||||
activity.enableEdgeToEdge(
|
||||
statusBarStyle = SystemBarStyle.auto(
|
||||
statusBarScrim.toArgb(),
|
||||
statusBarScrim.toArgb(),
|
||||
) { darkMode },
|
||||
navigationBarStyle = when {
|
||||
darkMode -> SystemBarStyle.dark(
|
||||
navigationBarScrim.toArgb()
|
||||
)
|
||||
|
||||
else -> SystemBarStyle.light(
|
||||
navigationBarScrim.toArgb(),
|
||||
navigationBarScrim.toArgb(),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
internal fun isSystemInDarkTheme(): Boolean {
|
||||
val uiMode = Resources.getSystem().configuration.uiMode
|
||||
return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
@@ -17,14 +17,15 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.webkit.WebViewAssetLoader
|
||||
import com.dergoogler.mmrl.platform.model.ModId
|
||||
import com.dergoogler.mmrl.webui.interfaces.WXOptions
|
||||
import com.dergoogler.mmrl.webui.util.WebUIOptions
|
||||
import com.dergoogler.mmrl.webui.view.WXView
|
||||
import com.dergoogler.mmrl.webui.view.WebUIView
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.rifsxd.ksunext.ui.util.createRootShell
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
class WebUIActivity : ComponentActivity() {
|
||||
private lateinit var webviewInterface: WebViewInterface
|
||||
|
||||
private var rootShell: Shell? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -71,7 +72,12 @@ class WebUIActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val webView = WebView(this).apply {
|
||||
val options = WebUIOptions(
|
||||
modId = ModId(moduleId),
|
||||
context = this,
|
||||
)
|
||||
|
||||
val webView = WebUIView(options).apply {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->
|
||||
val inset = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.updateLayoutParams<MarginLayoutParams> {
|
||||
@@ -82,15 +88,15 @@ class WebUIActivity : ComponentActivity() {
|
||||
}
|
||||
return@setOnApplyWindowInsetsListener insets
|
||||
}
|
||||
|
||||
val factory = WebViewInterface.factory()
|
||||
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
settings.allowFileAccess = false
|
||||
webviewInterface = WebViewInterface(
|
||||
WXOptions(this@WebUIActivity, this, ModId(moduleId))
|
||||
)
|
||||
addJavascriptInterface(webviewInterface, "ksu")
|
||||
addJavascriptInterface(factory)
|
||||
setWebViewClient(webViewClient)
|
||||
loadUrl("https://mui.kernelsu.org/index.html")
|
||||
loadDomain()
|
||||
}
|
||||
|
||||
setContentView(webView)
|
||||
|
||||
@@ -1,32 +1,19 @@
|
||||
package com.rifsxd.ksunext.ui.webui
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.dergoogler.mmrl.platform.Platform
|
||||
import com.dergoogler.mmrl.platform.model.ModId
|
||||
import com.dergoogler.mmrl.ui.component.Loading
|
||||
import com.dergoogler.mmrl.webui.screen.WebUIScreen
|
||||
import com.dergoogler.mmrl.webui.util.rememberWebUIOptions
|
||||
import com.dergoogler.mmrl.webui.activity.WXActivity
|
||||
import com.dergoogler.mmrl.webui.util.WebUIOptions
|
||||
import com.dergoogler.mmrl.webui.view.WebUIXView
|
||||
import com.rifsxd.ksunext.BuildConfig
|
||||
import com.rifsxd.ksunext.ui.theme.KernelSUTheme
|
||||
import kotlinx.coroutines.delay
|
||||
import com.rifsxd.ksunext.ui.theme.getColorScheme
|
||||
import com.rifsxd.ksunext.ui.theme.isSystemInDarkTheme
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class WebUIXActivity : ComponentActivity() {
|
||||
private lateinit var webView: WebView
|
||||
|
||||
class WebUIXActivity : WXActivity() {
|
||||
private val userAgent
|
||||
get(): String {
|
||||
val ksuVersion = BuildConfig.VERSION_CODE
|
||||
@@ -45,69 +32,59 @@ class WebUIXActivity : ComponentActivity() {
|
||||
return "KernelSU Next/$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
override fun onRender(savedInstanceState: Bundle?) {
|
||||
super.onRender(savedInstanceState)
|
||||
|
||||
webView = WebView(this)
|
||||
|
||||
lifecycleScope.launch {
|
||||
initPlatform()
|
||||
if (this.modId == null) {
|
||||
val msg = "ModId cannot be null"
|
||||
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
throw IllegalArgumentException(msg)
|
||||
}
|
||||
|
||||
val moduleId = intent.getStringExtra("id")!!
|
||||
val name = intent.getStringExtra("name")!!
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@Suppress("DEPRECATION")
|
||||
setTaskDescription(ActivityManager.TaskDescription("KernelSU Next - $name"))
|
||||
} else {
|
||||
val taskDescription =
|
||||
ActivityManager.TaskDescription.Builder().setLabel("KernelSU Next - $name").build()
|
||||
setTaskDescription(taskDescription)
|
||||
}
|
||||
// Cast since we check it
|
||||
val modId = this.modId!!
|
||||
|
||||
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
|
||||
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
|
||||
|
||||
setContent {
|
||||
KernelSUTheme {
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(Platform.isAlive) {
|
||||
while (!Platform.isAlive) {
|
||||
delay(1000)
|
||||
}
|
||||
val options = WebUIOptions(
|
||||
modId = modId,
|
||||
context = this,
|
||||
debug = webDebugging,
|
||||
appVersionCode = BuildConfig.VERSION_CODE,
|
||||
isDarkMode = isSystemInDarkTheme(),
|
||||
enableEruda = erudaInject,
|
||||
cls = WebUIXActivity::class.java,
|
||||
userAgentString = userAgent,
|
||||
colorScheme = getColorScheme()
|
||||
)
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
val view = WebUIXView(options).apply {
|
||||
wx.addJavascriptInterface(WebViewInterface.factory())
|
||||
wx.loadDomain()
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
Loading()
|
||||
this.options = options
|
||||
this.view = view
|
||||
|
||||
return@KernelSUTheme
|
||||
}
|
||||
|
||||
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
|
||||
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
|
||||
val dark = isSystemInDarkTheme()
|
||||
// Ensure type safety
|
||||
val name = intent.getStringExtra("name")
|
||||
if (name != null) {
|
||||
setActivityTitle("KernelSU Next - $name")
|
||||
}
|
||||
|
||||
val options = rememberWebUIOptions(
|
||||
modId = ModId(moduleId),
|
||||
debug = webDebugging,
|
||||
appVersionCode = BuildConfig.VERSION_CODE,
|
||||
isDarkMode = dark,
|
||||
enableEruda = erudaInject,
|
||||
cls = WebUIXActivity::class.java,
|
||||
userAgentString = userAgent
|
||||
)
|
||||
val loading = createLoadingRenderer()
|
||||
setContentView(loading)
|
||||
|
||||
WebUIScreen(
|
||||
webView = webView,
|
||||
options = options,
|
||||
interfaces = listOf(
|
||||
WebViewInterface.factory()
|
||||
)
|
||||
)
|
||||
lifecycleScope.launch {
|
||||
val deferred = Platform.getAsyncDeferred(this, null) {
|
||||
view
|
||||
}
|
||||
|
||||
setContentView(deferred.await())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package com.rifsxd.ksunext.ui.webui
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.TextUtils
|
||||
import android.view.Window
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.dergoogler.mmrl.platform.model.ModId.Companion.moduleDir
|
||||
import com.dergoogler.mmrl.webui.interfaces.WXInterface
|
||||
import com.dergoogler.mmrl.webui.interfaces.WXOptions
|
||||
import com.dergoogler.mmrl.webui.model.JavaScriptInterface
|
||||
@@ -20,20 +18,19 @@ import com.rifsxd.ksunext.ui.util.listModules
|
||||
import com.rifsxd.ksunext.ui.util.withNewRootShell
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class WebViewInterface(
|
||||
wxOptions: WXOptions,
|
||||
) : WXInterface(wxOptions) {
|
||||
override var name: String = "ksu"
|
||||
// Add logging tag for console.log
|
||||
override var tag: String = "KernelSUInterface"
|
||||
|
||||
companion object {
|
||||
fun factory() = JavaScriptInterface(WebViewInterface::class.java)
|
||||
}
|
||||
|
||||
private val modDir get() = "/data/adb/modules/${modId.id}"
|
||||
|
||||
@JavascriptInterface
|
||||
fun exec(cmd: String): String {
|
||||
return withNewRootShell(true) { ShellUtils.fastCmd(this, cmd) }
|
||||
@@ -171,23 +168,22 @@ class WebViewInterface(
|
||||
|
||||
@JavascriptInterface
|
||||
fun fullScreen(enable: Boolean) {
|
||||
if (context is Activity) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (enable) {
|
||||
hideSystemUI(activity.window)
|
||||
} else {
|
||||
showSystemUI(activity.window)
|
||||
}
|
||||
runMainLooperPost {
|
||||
if (enable) {
|
||||
hideSystemUI(window)
|
||||
} else {
|
||||
showSystemUI(window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
fun moduleInfo(): String {
|
||||
val modDir = modId.moduleDir
|
||||
val moduleInfos = JSONArray(listModules())
|
||||
val currentModuleInfo = JSONObject()
|
||||
currentModuleInfo.put("moduleDir", modDir)
|
||||
val moduleId = File(modDir).getName()
|
||||
val moduleId = modDir.getName()
|
||||
for (i in 0 until moduleInfos.length()) {
|
||||
val currentInfo = moduleInfos.getJSONObject(i)
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ parcelablelist = "2.0.1"
|
||||
libsu = "6.0.0"
|
||||
apksign = "1.4"
|
||||
cmaker = "1.2"
|
||||
mmrl = "2bb00b3c2b"
|
||||
mmrl = "fcb3a1fb76"
|
||||
swiperefreshlayout = "1.1.0"
|
||||
|
||||
[plugins]
|
||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||
@@ -36,6 +37,8 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp
|
||||
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
|
||||
|
||||
androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||
|
||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
|
||||
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||
androidx-compose-material = { group = "androidx.compose.material", name = "material" }
|
||||
|
||||
Reference in New Issue
Block a user