Add option to use WebUI X
- Added WebUI X from MMRL Co-authored-by:Der_Googler <54764558+DerGoogler@users.noreply.github.com> Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Signed-off-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
@@ -154,4 +154,9 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.com.github.topjohnwu.libsu.core)
|
implementation(libs.com.github.topjohnwu.libsu.core)
|
||||||
|
|
||||||
|
implementation(libs.mmrl.platform)
|
||||||
|
compileOnly(libs.mmrl.hidden.api)
|
||||||
|
implementation(libs.mmrl.webui)
|
||||||
|
implementation(libs.mmrl.ui)
|
||||||
|
|
||||||
}
|
}
|
||||||
47
manager/app/proguard-rules.pro
vendored
47
manager/app/proguard-rules.pro
vendored
@@ -0,0 +1,47 @@
|
|||||||
|
-verbose
|
||||||
|
-optimizationpasses 5
|
||||||
|
|
||||||
|
-dontwarn org.conscrypt.**
|
||||||
|
-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
|
||||||
|
-keep class com.dergoogler.mmrl.webui.model.ModId { *; }
|
||||||
|
-keep class com.dergoogler.mmrl.webui.interfaces.** { *; }
|
||||||
|
-keep class com.sukisu.ultra.ui.webui.WebViewInterface { *; }
|
||||||
|
|
||||||
|
-keep,allowobfuscation class * extends com.dergoogler.mmrl.platform.content.IService { *; }
|
||||||
@@ -39,6 +39,13 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.KernelSU.WebUI" />
|
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.webui.WebUIXActivity"
|
||||||
|
android:autoRemoveFromRecents="true"
|
||||||
|
android:documentLaunchMode="intoExisting"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.KernelSU.WebUI" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.sukisu.ultra
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
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
|
||||||
@@ -15,6 +16,8 @@ class KernelSUApplication : Application() {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
ksuApp = this
|
ksuApp = this
|
||||||
|
|
||||||
|
Platform.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)
|
||||||
Coil.setImageLoader(
|
Coil.setImageLoader(
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package com.sukisu.ultra.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.os.UserManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.ipc.RootService;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.sukisu.zako.IKsuInterface;
|
|
||||||
import rikka.parcelablelist.ParcelableListSlice;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author weishu
|
|
||||||
* @date 2023/4/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class KsuService extends RootService {
|
|
||||||
|
|
||||||
private static final String TAG = "KsuService";
|
|
||||||
|
|
||||||
class Stub extends IKsuInterface.Stub {
|
|
||||||
@Override
|
|
||||||
public ParcelableListSlice<PackageInfo> getPackages(int flags) {
|
|
||||||
List<PackageInfo> list = getInstalledPackagesAll(flags);
|
|
||||||
Log.i(TAG, "getPackages: " + list.size());
|
|
||||||
return new ParcelableListSlice<>(list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(@NonNull Intent intent) {
|
|
||||||
return new Stub();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> getUserIds() {
|
|
||||||
List<Integer> result = new ArrayList<>();
|
|
||||||
UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
|
|
||||||
List<UserHandle> userProfiles = um.getUserProfiles();
|
|
||||||
for (UserHandle userProfile : userProfiles) {
|
|
||||||
int userId = userProfile.hashCode();
|
|
||||||
result.add(userProfile.hashCode());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<PackageInfo> getInstalledPackagesAll(int flags) {
|
|
||||||
ArrayList<PackageInfo> packages = new ArrayList<>();
|
|
||||||
for (Integer userId : getUserIds()) {
|
|
||||||
Log.i(TAG, "getInstalledPackagesAll: " + userId);
|
|
||||||
packages.addAll(getInstalledPackagesAsUser(flags, userId));
|
|
||||||
}
|
|
||||||
return packages;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
|
|
||||||
try {
|
|
||||||
PackageManager pm = getPackageManager();
|
|
||||||
Method getInstalledPackagesAsUser = pm.getClass().getDeclaredMethod("getInstalledPackagesAsUser", int.class, int.class);
|
|
||||||
return (List<PackageInfo>) getInstalledPackagesAsUser.invoke(pm, flags, userId);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Log.e(TAG, "err", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,6 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
@@ -34,6 +33,7 @@ import com.sukisu.ultra.ui.theme.CardConfig.cardAlpha
|
|||||||
import com.sukisu.ultra.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
|
import com.sukisu.ultra.ui.webui.initPlatform
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private inner class ThemeChangeContentObserver(
|
private inner class ThemeChangeContentObserver(
|
||||||
@@ -104,6 +104,11 @@ class MainActivity : ComponentActivity() {
|
|||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val snackBarHostState = remember { SnackbarHostState() }
|
val snackBarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
// pre-init platform to faster start WebUI X activities
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
initPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = { BottomBar(navController) },
|
bottomBar = { BottomBar(navController) },
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
@@ -131,7 +136,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
CardConfig.save(applicationContext)
|
CardConfig.save(applicationContext)
|
||||||
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit() {
|
getSharedPreferences("theme_prefs", MODE_PRIVATE).edit {
|
||||||
putBoolean("prevent_background_refresh", true)
|
putBoolean("prevent_background_refresh", true)
|
||||||
}
|
}
|
||||||
ThemeConfig.preventBackgroundRefresh = true
|
ThemeConfig.preventBackgroundRefresh = true
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import com.dergoogler.mmrl.ui.component.LabelItem
|
||||||
|
import com.dergoogler.mmrl.ui.component.text.TextRow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchItem(
|
fun SwitchItem(
|
||||||
@@ -21,9 +24,11 @@ fun SwitchItem(
|
|||||||
summary: String? = null,
|
summary: String? = null,
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
beta: Boolean = false,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val stateAlpha = remember(checked, enabled) { Modifier.alpha(if (enabled) 1f else 0.5f) }
|
||||||
|
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -36,10 +41,30 @@ fun SwitchItem(
|
|||||||
onValueChange = onCheckedChange
|
onValueChange = onCheckedChange
|
||||||
),
|
),
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(title)
|
TextRow(
|
||||||
|
leadingContent = if (beta) {
|
||||||
|
{
|
||||||
|
LabelItem(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = "Beta"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = title,
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
leadingContent = icon?.let {
|
leadingContent = icon?.let {
|
||||||
{ Icon(icon, title) }
|
{
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
@@ -51,7 +76,10 @@ fun SwitchItem(
|
|||||||
},
|
},
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
if (summary != null) {
|
if (summary != null) {
|
||||||
Text(summary)
|
Text(
|
||||||
|
modifier = Modifier.then(stateAlpha),
|
||||||
|
text = summary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -71,4 +99,4 @@ fun RadioItem(
|
|||||||
RadioButton(selected = selected, onClick = onClick)
|
RadioButton(selected = selected, onClick = onClick)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -68,9 +68,11 @@ import java.io.BufferedReader
|
|||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
|
import com.sukisu.ultra.ui.webui.WebUIXActivity
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -394,10 +396,17 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
onClickModule = { id, name, hasWebUi ->
|
onClickModule = { id, name, hasWebUi ->
|
||||||
if (hasWebUi) {
|
if (hasWebUi) {
|
||||||
webUILauncher.launch(
|
webUILauncher.launch(
|
||||||
Intent(context, WebUIActivity::class.java)
|
if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
|
||||||
.setData("kernelsu://webui/$id".toUri())
|
Intent(context, WebUIXActivity::class.java)
|
||||||
.putExtra("id", id)
|
.setData("kernelsu://webuix/$id".toUri())
|
||||||
.putExtra("name", name)
|
.putExtra("id", id)
|
||||||
|
.putExtra("name", name)
|
||||||
|
} else {
|
||||||
|
Intent(context, WebUIActivity::class.java)
|
||||||
|
.setData("kernelsu://webui/$id".toUri())
|
||||||
|
.putExtra("id", id)
|
||||||
|
.putExtra("name", name)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import com.sukisu.ultra.ui.util.getBugreportFile
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import com.sukisu.ultra.ui.component.KsuIsValid
|
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -239,6 +240,43 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var useWebUIX by rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("use_webuix", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
SwitchItem(
|
||||||
|
beta = true,
|
||||||
|
enabled = Platform.isAlive,
|
||||||
|
icon = Icons.Filled.WebAsset,
|
||||||
|
title = stringResource(id = R.string.use_webuix),
|
||||||
|
summary = stringResource(id = R.string.use_webuix_summary),
|
||||||
|
checked = useWebUIX
|
||||||
|
) {
|
||||||
|
prefs.edit { putBoolean("use_webuix", it) }
|
||||||
|
useWebUIX = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var useWebUIXEruda by rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("use_webuix_eruda", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
SwitchItem(
|
||||||
|
beta = true,
|
||||||
|
enabled = Platform.isAlive && useWebUIX && enableWebDebugging,
|
||||||
|
icon = Icons.Filled.FormatListNumbered,
|
||||||
|
title = stringResource(id = R.string.use_webuix_eruda),
|
||||||
|
summary = stringResource(id = R.string.use_webuix_eruda_summary),
|
||||||
|
checked = useWebUIXEruda
|
||||||
|
) {
|
||||||
|
prefs.edit().putBoolean("use_webuix_eruda", it).apply()
|
||||||
|
useWebUIXEruda = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更多设置
|
// 更多设置
|
||||||
SettingItem(
|
SettingItem(
|
||||||
icon = Icons.Filled.Settings,
|
icon = Icons.Filled.Settings,
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ import androidx.core.content.edit
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题配置对象,管理应用的主题相关状态
|
* 主题配置对象,管理应用的主题相关状态
|
||||||
@@ -110,6 +115,10 @@ fun KernelSUTheme(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SystemBarStyle(
|
||||||
|
darkMode = darkTheme
|
||||||
|
)
|
||||||
|
|
||||||
// 初始加载配置
|
// 初始加载配置
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
context.loadThemeMode()
|
context.loadThemeMode()
|
||||||
@@ -535,4 +544,36 @@ fun Context.loadDynamicColorState() {
|
|||||||
.getBoolean("use_dynamic_color", true)
|
.getBoolean("use_dynamic_color", true)
|
||||||
|
|
||||||
ThemeConfig.useDynamicColor = enabled
|
ThemeConfig.useDynamicColor = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* webui X样式
|
||||||
|
*/
|
||||||
|
@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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
package com.sukisu.ultra.ui.viewmodel
|
package com.sukisu.ultra.ui.viewmodel
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.IBinder
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -14,22 +10,23 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.sukisu.zako.IKsuInterface
|
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import com.sukisu.ultra.ui.KsuService
|
|
||||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import com.sukisu.ultra.ui.util.KsuCli
|
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
|
||||||
|
import com.sukisu.ultra.ui.webui.packageManager
|
||||||
|
import com.sukisu.ultra.ui.webui.userManager
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
|
val isPlatformAlive get() = Platform.isAlive
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "SuperUserViewModel"
|
private const val TAG = "SuperUserViewModel"
|
||||||
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||||
@@ -142,55 +139,28 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
fetchAppList() // 刷新列表以显示最新状态
|
fetchAppList() // 刷新列表以显示最新状态
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun connectKsuService(
|
|
||||||
onDisconnect: () -> Unit = {}
|
|
||||||
): Pair<IBinder, ServiceConnection> = suspendCoroutine { continuation ->
|
|
||||||
val connection = object : ServiceConnection {
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
|
||||||
onDisconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
|
|
||||||
continuation.resume(binder as IBinder to this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val intent = Intent(ksuApp, KsuService::class.java)
|
|
||||||
|
|
||||||
val task = KsuService.bindOrTask(
|
|
||||||
intent,
|
|
||||||
Shell.EXECUTOR,
|
|
||||||
connection,
|
|
||||||
)
|
|
||||||
val shell = KsuCli.SHELL
|
|
||||||
task?.let { it1 -> shell.execTask(it1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopKsuService() {
|
|
||||||
val intent = Intent(ksuApp, KsuService::class.java)
|
|
||||||
KsuService.stop(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun fetchAppList() {
|
suspend fun fetchAppList() {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
|
|
||||||
val result = connectKsuService {
|
|
||||||
Log.w(TAG, "KsuService disconnected")
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
withTimeoutOrNull(TIMEOUT_MILLIS) {
|
||||||
|
while (!isPlatformAlive) {
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
} ?: return@withContext // Exit early if timeout
|
||||||
val pm = ksuApp.packageManager
|
val pm = ksuApp.packageManager
|
||||||
val start = SystemClock.elapsedRealtime()
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
val binder = result.first
|
val userInfos = Platform.userManager.getUsers()
|
||||||
val allPackages = IKsuInterface.Stub.asInterface(binder).getPackages(0)
|
val packages = mutableListOf<PackageInfo>()
|
||||||
|
val packageManager = Platform.packageManager
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
for (userInfo in userInfos) {
|
||||||
stopKsuService()
|
Log.i(TAG, "fetchAppList: ${userInfo.id}")
|
||||||
|
packages.addAll(packageManager.getInstalledPackages(0, userInfo.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
val packages = allPackages.list
|
|
||||||
|
|
||||||
apps = packages.map {
|
apps = packages.map {
|
||||||
val appInfo = it.applicationInfo
|
val appInfo = it.applicationInfo
|
||||||
val uid = appInfo!!.uid
|
val uid = appInfo!!.uid
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.util.Log
|
||||||
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
|
import com.dergoogler.mmrl.platform.hiddenApi.HiddenPackageManager
|
||||||
|
import com.dergoogler.mmrl.platform.hiddenApi.HiddenUserManager
|
||||||
|
import com.dergoogler.mmrl.platform.model.IProvider
|
||||||
|
import com.dergoogler.mmrl.platform.model.PlatformIntent
|
||||||
|
import com.sukisu.ultra.ksuApp
|
||||||
|
import com.sukisu.ultra.Natives
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class KsuLibSuProvider : IProvider {
|
||||||
|
override val name = "KsuLibSu"
|
||||||
|
|
||||||
|
override fun isAvailable() = true
|
||||||
|
|
||||||
|
override suspend fun isAuthorized() = Natives.becomeManager(ksuApp.packageName)
|
||||||
|
|
||||||
|
private val serviceIntent
|
||||||
|
get() = PlatformIntent(
|
||||||
|
ksuApp,
|
||||||
|
Platform.KsuNext,
|
||||||
|
SuService::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun bind(connection: ServiceConnection) {
|
||||||
|
RootService.bind(serviceIntent.intent, connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unbind(connection: ServiceConnection) {
|
||||||
|
RootService.stop(serviceIntent.intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// webui x
|
||||||
|
suspend fun initPlatform() = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val active = Platform.init {
|
||||||
|
this.context = ksuApp
|
||||||
|
this.platform = Platform.KsuNext
|
||||||
|
this.provider = from(KsuLibSuProvider())
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!active) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext active
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("KsuLibSu", "Failed to initialize platform", e)
|
||||||
|
return@withContext false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Platform.Companion.packageManager get(): HiddenPackageManager = HiddenPackageManager(this.mService)
|
||||||
|
val Platform.Companion.userManager get(): HiddenUserManager = HiddenUserManager(this.mService)
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import com.dergoogler.mmrl.platform.model.PlatformIntent.Companion.getPlatform
|
||||||
|
import com.dergoogler.mmrl.platform.service.ServiceManager
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
|
||||||
|
class SuService : RootService() {
|
||||||
|
override fun onBind(intent: Intent): IBinder {
|
||||||
|
val mode = intent.getPlatform()
|
||||||
|
return ServiceManager(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,11 @@ 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.webkit.WebViewAssetLoader
|
import androidx.webkit.WebViewAssetLoader
|
||||||
|
import com.dergoogler.mmrl.platform.model.ModId
|
||||||
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 java.io.File
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WXOptions
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
class WebUIActivity : ComponentActivity() {
|
class WebUIActivity : ComponentActivity() {
|
||||||
@@ -41,7 +43,8 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
setTaskDescription(ActivityManager.TaskDescription("KernelSU - $name"))
|
||||||
} else {
|
} else {
|
||||||
val taskDescription = ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
val taskDescription =
|
||||||
|
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
||||||
setTaskDescription(taskDescription)
|
setTaskDescription(taskDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +85,7 @@ class WebUIActivity : ComponentActivity() {
|
|||||||
settings.javaScriptEnabled = true
|
settings.javaScriptEnabled = true
|
||||||
settings.domStorageEnabled = true
|
settings.domStorageEnabled = true
|
||||||
settings.allowFileAccess = false
|
settings.allowFileAccess = false
|
||||||
webviewInterface = WebViewInterface(this@WebUIActivity, this, moduleDir)
|
webviewInterface = WebViewInterface(WXOptions(this@WebUIActivity, this, ModId(moduleId)))
|
||||||
addJavascriptInterface(webviewInterface, "ksu")
|
addJavascriptInterface(webviewInterface, "ksu")
|
||||||
setWebViewClient(webViewClient)
|
setWebViewClient(webViewClient)
|
||||||
loadUrl("https://mui.kernelsu.org/index.html")
|
loadUrl("https://mui.kernelsu.org/index.html")
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.sukisu.ultra.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 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.sukisu.ultra.BuildConfig
|
||||||
|
import com.sukisu.ultra.ui.theme.KernelSUTheme
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class WebUIXActivity : ComponentActivity() {
|
||||||
|
private lateinit var webView: WebView
|
||||||
|
|
||||||
|
private val userAgent
|
||||||
|
get(): String {
|
||||||
|
val ksuVersion = BuildConfig.VERSION_CODE
|
||||||
|
|
||||||
|
val platform = Platform.get("Unknown") {
|
||||||
|
platform.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val platformVersion = Platform.get(-1) {
|
||||||
|
moduleManager.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
val osVersion = Build.VERSION.RELEASE
|
||||||
|
val deviceModel = Build.MODEL
|
||||||
|
|
||||||
|
return "KernelSU Next/$ksuVersion (Linux; Android $osVersion; $deviceModel; $platform/$platformVersion)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
webView = WebView(this)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
initPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 - $name"))
|
||||||
|
} else {
|
||||||
|
val taskDescription =
|
||||||
|
ActivityManager.TaskDescription.Builder().setLabel("KernelSU - $name").build()
|
||||||
|
setTaskDescription(taskDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
KernelSUTheme {
|
||||||
|
var isLoading by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
LaunchedEffect(Platform.isAlive) {
|
||||||
|
while (!Platform.isAlive) {
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
Loading()
|
||||||
|
|
||||||
|
return@KernelSUTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
val webDebugging = prefs.getBoolean("enable_web_debugging", false)
|
||||||
|
val erudaInject = prefs.getBoolean("use_webuix_eruda", false)
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
val options = rememberWebUIOptions(
|
||||||
|
modId = ModId(moduleId),
|
||||||
|
debug = webDebugging,
|
||||||
|
appVersionCode = BuildConfig.VERSION_CODE,
|
||||||
|
isDarkMode = dark,
|
||||||
|
enableEruda = erudaInject,
|
||||||
|
cls = WebUIXActivity::class.java,
|
||||||
|
userAgentString = userAgent
|
||||||
|
)
|
||||||
|
|
||||||
|
WebUIScreen(
|
||||||
|
webView = webView,
|
||||||
|
options = options,
|
||||||
|
interfaces = listOf(
|
||||||
|
WebViewInterface.factory()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
package com.sukisu.ultra.ui.webui
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
import android.webkit.WebView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WXOptions
|
||||||
|
import com.dergoogler.mmrl.webui.interfaces.WebUIInterface
|
||||||
|
import com.dergoogler.mmrl.webui.model.JavaScriptInterface
|
||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
@@ -25,10 +26,15 @@ import java.io.File
|
|||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
class WebViewInterface(
|
class WebViewInterface(
|
||||||
val context: Context,
|
wxOptions: WXOptions,
|
||||||
private val webView: WebView,
|
) : WebUIInterface(wxOptions) {
|
||||||
private val modDir: String
|
override var name: String = "ksu"
|
||||||
) {
|
|
||||||
|
companion object {
|
||||||
|
fun factory() = JavaScriptInterface(WebViewInterface::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val modDir get() = "/data/adb/modules/${modId.id}"
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun exec(cmd: String): String {
|
fun exec(cmd: String): String {
|
||||||
@@ -170,9 +176,9 @@ class WebViewInterface(
|
|||||||
if (context is Activity) {
|
if (context is Activity) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
hideSystemUI(context.window)
|
hideSystemUI(activity.window)
|
||||||
} else {
|
} else {
|
||||||
showSystemUI(context.window)
|
showSystemUI(activity.window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,4 +320,8 @@
|
|||||||
<string name="background_set_success">背景设置成功</string>
|
<string name="background_set_success">背景设置成功</string>
|
||||||
<string name="background_removed">已移除自定义背景</string>
|
<string name="background_removed">已移除自定义背景</string>
|
||||||
<string name="root_require_for_install">需要 root 权限</string>
|
<string name="root_require_for_install">需要 root 权限</string>
|
||||||
|
<string name="use_webuix">使用 WebUI X</string>
|
||||||
|
<string name="use_webuix_summary">使用支持更多 API 的 WebUI X 而不是 WebUI</string>
|
||||||
|
<string name="use_webuix_eruda">将 Eruda 注入 WebUI X</string>
|
||||||
|
<string name="use_webuix_eruda_summary">在 WebUI X 中注入调试控制台,使调试更容易,需要启用 WebView 调试</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -324,4 +324,8 @@
|
|||||||
<string name="background_set_success">Background set successfully</string>
|
<string name="background_set_success">Background set successfully</string>
|
||||||
<string name="background_removed">Removed custom backgrounds</string>
|
<string name="background_removed">Removed custom backgrounds</string>
|
||||||
<string name="root_require_for_install">Requires root privileges</string>
|
<string name="root_require_for_install">Requires root privileges</string>
|
||||||
|
<string name="use_webuix">Use WebUI X</string>
|
||||||
|
<string name="use_webuix_summary">Use WebUI X instead of WebUI which supports more API\'s</string>
|
||||||
|
<string name="use_webuix_eruda">Inject Eruda into WebUI X</string>
|
||||||
|
<string name="use_webuix_eruda_summary">Inject a debug console into WebUI X to make debugging easier. Requires web debugging to be on.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ compose-material3 = "1.3.2"
|
|||||||
compose-ui = "1.8.0"
|
compose-ui = "1.8.0"
|
||||||
compose-foundation = "1.7.8"
|
compose-foundation = "1.7.8"
|
||||||
documentfile = "1.0.1"
|
documentfile = "1.0.1"
|
||||||
|
mmrl = "v33560"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||||
@@ -81,4 +82,10 @@ sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs"
|
|||||||
markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" }
|
markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" }
|
||||||
|
|
||||||
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
|
lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
|
||||||
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
|
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
|
||||||
|
|
||||||
|
|
||||||
|
mmrl-webui = { group = "com.github.MMRLApp.MMRL", name = "webui", 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-hidden-api = { group = "com.github.MMRLApp.MMRL", name = "hidden-api", version.ref = "mmrl" }
|
||||||
Reference in New Issue
Block a user