Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d0d87cb0c | ||
|
|
6b66d9b3f8 | ||
|
|
a301d94858 | ||
|
|
01199470f2 | ||
|
|
9e7ea19567 | ||
|
|
cdc6a6cb4a |
@@ -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<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sukisu.ultra.ui
|
package com.sukisu.ultra.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.database.ContentObserver
|
import android.database.ContentObserver
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -13,8 +14,8 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.material3.*
|
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.platform.LocalContext
|
||||||
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 +35,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(
|
||||||
@@ -47,6 +49,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
// 应用DPI设置(仅对当前应用生效)
|
||||||
|
applyCustomDpi()
|
||||||
|
|
||||||
// Enable edge to edge
|
// Enable edge to edge
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
@@ -104,6 +109,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)
|
||||||
@@ -128,10 +138,30 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用自定义DPI设置(仅对当前应用生效)
|
||||||
|
private fun applyCustomDpi() {
|
||||||
|
val prefs = getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
val customDpi = prefs.getInt("app_dpi", 0)
|
||||||
|
|
||||||
|
if (customDpi > 0) {
|
||||||
|
try {
|
||||||
|
val resources = resources
|
||||||
|
val metrics = resources.displayMetrics
|
||||||
|
|
||||||
|
// 仅更新应用内显示,不影响系统状态栏
|
||||||
|
metrics.density = customDpi / 160f
|
||||||
|
metrics.scaledDensity = customDpi / 160f
|
||||||
|
metrics.densityDpi = customDpi
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -162,6 +192,10 @@ private fun BottomBar(navController: NavHostController) {
|
|||||||
val containerColor = MaterialTheme.colorScheme.surfaceVariant
|
val containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
val cardColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
|
||||||
|
// 检查是否显示KPM
|
||||||
|
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("show_kpm_info", true)
|
||||||
|
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
modifier = Modifier.windowInsetsPadding(
|
modifier = Modifier.windowInsetsPadding(
|
||||||
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
|
||||||
@@ -174,7 +208,7 @@ private fun BottomBar(navController: NavHostController) {
|
|||||||
) {
|
) {
|
||||||
BottomBarDestination.entries.forEach { destination ->
|
BottomBarDestination.entries.forEach { destination ->
|
||||||
if (destination == BottomBarDestination.Kpm) {
|
if (destination == BottomBarDestination.Kpm) {
|
||||||
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo) {
|
||||||
if (!fullFeatured && destination.rootRequired) return@forEach
|
if (!fullFeatured && destination.rootRequired) return@forEach
|
||||||
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
val isCurrentDestOnBackStack by navController.isRouteOnBackStackAsState(destination.direction)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,11 +47,7 @@ fun SlotSelectionDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
val cardColor = if (!ThemeConfig.useDynamicColor) {
|
val cardColor = MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
ThemeConfig.currentTheme.ButtonContrast
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.surfaceContainerHigh
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
|
var isHideOtherInfo by rememberSaveable { mutableStateOf(false) }
|
||||||
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
|
var isHideSusfsStatus by rememberSaveable { mutableStateOf(false) }
|
||||||
var isHideLinkCard by rememberSaveable { mutableStateOf(false) }
|
var isHideLinkCard by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var showKpmInfo by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
// 从 SharedPreferences 加载简洁模式状态
|
// 从 SharedPreferences 加载简洁模式状态
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -150,6 +151,9 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
|
|
||||||
isHideLinkCard = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
isHideLinkCard = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("is_hide_link_card", false)
|
.getBoolean("is_hide_link_card", false)
|
||||||
|
|
||||||
|
showKpmInfo = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("show_kpm_info", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val kernelVersion = getKernelVersion()
|
val kernelVersion = getKernelVersion()
|
||||||
@@ -487,6 +491,9 @@ private fun StatusCard(
|
|||||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("is_hide_susfs_status", false)
|
.getBoolean("is_hide_susfs_status", false)
|
||||||
|
|
||||||
|
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("show_kpm_info", true)
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.CheckCircle,
|
Icons.Outlined.CheckCircle,
|
||||||
contentDescription = stringResource(R.string.home_working),
|
contentDescription = stringResource(R.string.home_working),
|
||||||
@@ -526,7 +533,7 @@ private fun StatusCard(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val kpmVersion = getKpmVersion()
|
val kpmVersion = getKpmVersion()
|
||||||
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error")) {
|
if (kpmVersion.isNotEmpty() && !kpmVersion.startsWith("Error") && showKpmInfo) {
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.home_kpm_module, getKpmModuleCount()),
|
text = stringResource(R.string.home_kpm_module, getKpmModuleCount()),
|
||||||
@@ -790,6 +797,8 @@ private fun InfoCard() {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("is_simple_mode", false)
|
.getBoolean("is_simple_mode", false)
|
||||||
|
val showKpmInfo = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
.getBoolean("show_kpm_info", true)
|
||||||
|
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHighest),
|
colors = getCardColors(MaterialTheme.colorScheme.surfaceContainerHighest),
|
||||||
@@ -891,6 +900,8 @@ private fun InfoCard() {
|
|||||||
val kpmVersion = getKpmVersion()
|
val kpmVersion = getKpmVersion()
|
||||||
val isKpmConfigured = checkKpmConfigured()
|
val isKpmConfigured = checkKpmConfigured()
|
||||||
|
|
||||||
|
// 根据showKpmInfo决定是否显示KPM信息
|
||||||
|
if (showKpmInfo) {
|
||||||
val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
|
val displayVersion = if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
|
||||||
val statusText = if (isKpmConfigured) {
|
val statusText = if (isKpmConfigured) {
|
||||||
stringResource(R.string.kernel_patched)
|
stringResource(R.string.kernel_patched)
|
||||||
@@ -909,6 +920,7 @@ private fun InfoCard() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("is_hide_susfs_status", false)
|
.getBoolean("is_hide_susfs_status", false)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
if (prefs.getBoolean("use_webuix", false) && Platform.isAlive) {
|
||||||
|
Intent(context, WebUIXActivity::class.java)
|
||||||
|
.setData("kernelsu://webuix/$id".toUri())
|
||||||
|
.putExtra("id", id)
|
||||||
|
.putExtra("name", name)
|
||||||
|
} else {
|
||||||
Intent(context, WebUIActivity::class.java)
|
Intent(context, WebUIActivity::class.java)
|
||||||
.setData("kernelsu://webui/$id".toUri())
|
.setData("kernelsu://webui/$id".toUri())
|
||||||
.putExtra("id", id)
|
.putExtra("id", id)
|
||||||
.putExtra("name", name)
|
.putExtra("name", name)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -29,6 +30,7 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.NavigateNext
|
import androidx.compose.material.icons.automirrored.filled.NavigateNext
|
||||||
|
import androidx.compose.material.icons.filled.AcUnit
|
||||||
import androidx.compose.material.icons.filled.Brush
|
import androidx.compose.material.icons.filled.Brush
|
||||||
import androidx.compose.material.icons.filled.ColorLens
|
import androidx.compose.material.icons.filled.ColorLens
|
||||||
import androidx.compose.material.icons.filled.DarkMode
|
import androidx.compose.material.icons.filled.DarkMode
|
||||||
@@ -81,9 +83,7 @@ import androidx.core.content.edit
|
|||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.sukisu.ultra.Natives
|
|
||||||
import com.sukisu.ultra.R
|
import com.sukisu.ultra.R
|
||||||
import com.sukisu.ultra.ksuApp
|
|
||||||
import com.sukisu.ultra.ui.component.ImageEditorDialog
|
import com.sukisu.ultra.ui.component.ImageEditorDialog
|
||||||
import com.sukisu.ultra.ui.component.KsuIsValid
|
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||||
import com.sukisu.ultra.ui.component.SwitchItem
|
import com.sukisu.ultra.ui.component.SwitchItem
|
||||||
@@ -176,6 +176,17 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
isHideOtherInfo = newValue
|
isHideOtherInfo = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示KPM开关状态
|
||||||
|
var isShowKpmInfo by remember {
|
||||||
|
mutableStateOf(prefs.getBoolean("show_kpm_info", true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新显示KPM开关状态
|
||||||
|
val onShowKpmInfoChange = { newValue: Boolean ->
|
||||||
|
prefs.edit { putBoolean("show_kpm_info", newValue) }
|
||||||
|
isShowKpmInfo = newValue
|
||||||
|
}
|
||||||
|
|
||||||
// 隐藏SuSFS状态开关状态
|
// 隐藏SuSFS状态开关状态
|
||||||
var isHideSusfsStatus by remember {
|
var isHideSusfsStatus by remember {
|
||||||
mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false))
|
mutableStateOf(prefs.getBoolean("is_hide_susfs_status", false))
|
||||||
@@ -216,6 +227,36 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
var isCustomizeExpanded by remember { mutableStateOf(false) }
|
var isCustomizeExpanded by remember { mutableStateOf(false) }
|
||||||
var isAppearanceExpanded by remember { mutableStateOf(true) }
|
var isAppearanceExpanded by remember { mutableStateOf(true) }
|
||||||
var isAdvancedExpanded by remember { mutableStateOf(false) }
|
var isAdvancedExpanded by remember { mutableStateOf(false) }
|
||||||
|
var isDpiExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// DPI 设置
|
||||||
|
val systemDpi = remember { context.resources.displayMetrics.densityDpi }
|
||||||
|
var currentDpi by remember {
|
||||||
|
mutableIntStateOf(prefs.getInt("app_dpi", systemDpi))
|
||||||
|
}
|
||||||
|
var tempDpi by remember { mutableIntStateOf(currentDpi) }
|
||||||
|
var isDpiCustom by remember { mutableStateOf(true) }
|
||||||
|
var showDpiConfirmDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 预设 DPI 选项
|
||||||
|
val dpiPresets = mapOf(
|
||||||
|
stringResource(R.string.dpi_size_small) to 240,
|
||||||
|
stringResource(R.string.dpi_size_medium) to 320,
|
||||||
|
stringResource(R.string.dpi_size_large) to 420,
|
||||||
|
stringResource(R.string.dpi_size_extra_large) to 560
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取DPI大小
|
||||||
|
@Composable
|
||||||
|
fun getDpiFriendlyName(dpi: Int): String {
|
||||||
|
return when (dpi) {
|
||||||
|
240 -> stringResource(R.string.dpi_size_small)
|
||||||
|
320 -> stringResource(R.string.dpi_size_medium)
|
||||||
|
420 -> stringResource(R.string.dpi_size_large)
|
||||||
|
560 -> stringResource(R.string.dpi_size_extra_large)
|
||||||
|
else -> stringResource(R.string.dpi_size_custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化卡片配置
|
// 初始化卡片配置
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -252,10 +293,37 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
CardConfig.setDarkModeDefaults()
|
CardConfig.setDarkModeDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存设置
|
currentDpi = prefs.getInt("app_dpi", systemDpi)
|
||||||
|
tempDpi = currentDpi
|
||||||
|
|
||||||
CardConfig.save(context)
|
CardConfig.save(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用 DPI 设置
|
||||||
|
val applyDpiSetting = { dpi: Int ->
|
||||||
|
if (dpi != currentDpi) {
|
||||||
|
// 保存到 SharedPreferences
|
||||||
|
prefs.edit {
|
||||||
|
putInt("app_dpi", dpi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只修改应用级别的DPI设置
|
||||||
|
currentDpi = dpi
|
||||||
|
tempDpi = dpi
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.dpi_applied_success, dpi),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
|
||||||
|
val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
||||||
|
restartIntent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(restartIntent)
|
||||||
|
|
||||||
|
showDpiConfirmDialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 主题色选项
|
// 主题色选项
|
||||||
val themeColorOptions = listOf(
|
val themeColorOptions = listOf(
|
||||||
stringResource(R.string.color_default) to ThemeColors.Default,
|
stringResource(R.string.color_default) to ThemeColors.Default,
|
||||||
@@ -564,6 +632,114 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DPI 设置部分
|
||||||
|
SectionHeader(
|
||||||
|
title = stringResource(R.string.dpi_settings),
|
||||||
|
expanded = isDpiExpanded,
|
||||||
|
onToggle = { isDpiExpanded = !isDpiExpanded }
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isDpiExpanded,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
tonalElevation = 1.dp,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(stringResource(R.string.app_dpi_title)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.app_dpi_summary)) },
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.AcUnit,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Text(
|
||||||
|
text = getDpiFriendlyName(tempDpi),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = ListItemDefaults.colors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DPI 滑动条
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 32.dp, vertical = 8.dp)) {
|
||||||
|
Slider(
|
||||||
|
value = tempDpi.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
tempDpi = it.toInt()
|
||||||
|
isDpiCustom = !dpiPresets.containsValue(tempDpi)
|
||||||
|
},
|
||||||
|
valueRange = 160f..600f,
|
||||||
|
steps = 11,
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
thumbColor = MaterialTheme.colorScheme.primary,
|
||||||
|
activeTrackColor = MaterialTheme.colorScheme.primary,
|
||||||
|
inactiveTrackColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp),
|
||||||
|
) {
|
||||||
|
dpiPresets.forEach { (name, dpi) ->
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
tempDpi = dpi
|
||||||
|
isDpiCustom = false
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
color = if (tempDpi == dpi)
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
if (tempDpi != currentDpi) {
|
||||||
|
showDpiConfirmDialog = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.dpi_apply_settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = if (isDpiCustom)
|
||||||
|
"${stringResource(R.string.dpi_size_custom)}: $tempDpi"
|
||||||
|
else
|
||||||
|
"${getDpiFriendlyName(tempDpi)}: $tempDpi",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 自定义设置部分
|
// 自定义设置部分
|
||||||
SectionHeader(
|
SectionHeader(
|
||||||
title = stringResource(R.string.custom_settings),
|
title = stringResource(R.string.custom_settings),
|
||||||
@@ -642,6 +818,21 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
color = MaterialTheme.colorScheme.outlineVariant
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 显示KPM开关
|
||||||
|
SwitchItem(
|
||||||
|
icon = Icons.Filled.VisibilityOff,
|
||||||
|
title = stringResource(R.string.show_kpm_info),
|
||||||
|
summary = stringResource(R.string.show_kpm_info_summary),
|
||||||
|
checked = isShowKpmInfo
|
||||||
|
) {
|
||||||
|
onShowKpmInfoChange(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.outlineVariant
|
||||||
|
)
|
||||||
|
|
||||||
// 隐藏链接信息
|
// 隐藏链接信息
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
icon = Icons.Filled.VisibilityOff,
|
icon = Icons.Filled.VisibilityOff,
|
||||||
@@ -837,6 +1028,42 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DPI 设置确认对话框
|
||||||
|
if (showDpiConfirmDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showDpiConfirmDialog = false },
|
||||||
|
title = { Text(stringResource(R.string.dpi_confirm_title)) },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text(stringResource(R.string.dpi_confirm_message, currentDpi, tempDpi))
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.dpi_confirm_summary),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { applyDpiSetting(tempDpi) }
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showDpiConfirmDialog = false
|
||||||
|
tempDpi = currentDpi
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 主题色选择对话框
|
// 主题色选择对话框
|
||||||
if (showThemeColorDialog) {
|
if (showThemeColorDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import android.net.Uri
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
@@ -58,6 +63,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)
|
||||||
@@ -80,7 +86,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
AboutDialog(it)
|
AboutDialog(it)
|
||||||
}
|
}
|
||||||
val loadingDialog = rememberLoadingDialog()
|
val loadingDialog = rememberLoadingDialog()
|
||||||
// endregion
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -88,12 +93,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
// region 上下文与协程
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region 日志导出功能
|
|
||||||
val exportBugreportLauncher = rememberLauncherForActivityResult(
|
val exportBugreportLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.CreateDocument("application/gzip")
|
ActivityResultContracts.CreateDocument("application/gzip")
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
@@ -183,7 +184,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置分组卡片 - 应用设置
|
// 应用设置
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -239,6 +240,52 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web X 开关
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web X Eruda 开关
|
||||||
|
var useWebUIXEruda by rememberSaveable {
|
||||||
|
mutableStateOf(
|
||||||
|
prefs.getBoolean("use_webuix_eruda", false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
KsuIsValid {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = useWebUIX && enableWebDebugging,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
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) }
|
||||||
|
useWebUIXEruda = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更多设置
|
// 更多设置
|
||||||
SettingItem(
|
SettingItem(
|
||||||
icon = Icons.Filled.Settings,
|
icon = Icons.Filled.Settings,
|
||||||
@@ -251,7 +298,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置分组卡片 - 工具
|
// 工具
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -536,3 +545,35 @@ fun Context.loadDynamicColorState() {
|
|||||||
|
|
||||||
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,113 @@
|
|||||||
|
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 "SukiSU /$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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,6 @@
|
|||||||
<string name="batch_cancel_authorization">認証を一括でキャンセル</string>
|
<string name="batch_cancel_authorization">認証を一括でキャンセル</string>
|
||||||
<string name="backup">バックアップ</string>
|
<string name="backup">バックアップ</string>
|
||||||
<string name="color_yellow">イエロー</string>
|
<string name="color_yellow">イエロー</string>
|
||||||
<string name="kpm">カーネルモジュール</string>
|
|
||||||
<string name="kpm_title">カーネルモジュール</string>
|
<string name="kpm_title">カーネルモジュール</string>
|
||||||
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
|
<string name="kpm_empty">カーネルモジュールは現在インストールされていません</string>
|
||||||
<string name="kpm_version">バージョン</string>
|
<string name="kpm_version">バージョン</string>
|
||||||
|
|||||||
@@ -222,7 +222,6 @@
|
|||||||
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
|
<string name="batch_cancel_authorization">Hủy ủy quyền hàng loạt</string>
|
||||||
<string name="backup">Sao lưu</string>
|
<string name="backup">Sao lưu</string>
|
||||||
<string name="color_yellow">Vàng</string>
|
<string name="color_yellow">Vàng</string>
|
||||||
<string name="kpm">Mô-đun kpm</string>
|
|
||||||
<string name="kpm_title">Mô-đun KPM</string>
|
<string name="kpm_title">Mô-đun KPM</string>
|
||||||
<string name="kpm_empty">Không có mô-đun nào được cài đặt.</string>
|
<string name="kpm_empty">Không có mô-đun nào được cài đặt.</string>
|
||||||
<string name="kpm_version">Phát hành</string>
|
<string name="kpm_version">Phát hành</string>
|
||||||
|
|||||||
@@ -320,4 +320,26 @@
|
|||||||
<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>
|
||||||
|
<!-- KPM 显示设置相关 -->
|
||||||
|
<string name="show_kpm_info">显示 KPM 功能</string>
|
||||||
|
<string name="show_kpm_info_summary">在主页和底栏显示 KPM 相关功能和信息 (需要重新打开应用)</string>
|
||||||
|
<!-- Webui X 设置相关 -->
|
||||||
|
<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>
|
||||||
|
<!-- DPI设置相关字符串 -->
|
||||||
|
<string name="dpi_settings">DPI 设置</string>
|
||||||
|
<string name="app_dpi_title">应用DPI</string>
|
||||||
|
<string name="app_dpi_summary">仅调整当前应用的屏幕显示密度</string>
|
||||||
|
<string name="dpi_size_small">小</string>
|
||||||
|
<string name="dpi_size_medium">中</string>
|
||||||
|
<string name="dpi_size_large">大</string>
|
||||||
|
<string name="dpi_size_extra_large">超大</string>
|
||||||
|
<string name="dpi_size_custom">自定义</string>
|
||||||
|
<string name="dpi_apply_settings">应用DPI设置</string>
|
||||||
|
<string name="dpi_confirm_title">确认更改DPI</string>
|
||||||
|
<string name="dpi_confirm_message">你确定要将应用DPI从 %1$d 更改为 %2$d 吗?</string>
|
||||||
|
<string name="dpi_confirm_summary">应用需要重启以应用新的DPI设置,不会影响系统状态栏或其他应用</string>
|
||||||
|
<string name="dpi_applied_success">DPI 已设置为 %1$d,重启应用后生效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -324,4 +324,26 @@
|
|||||||
<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>
|
||||||
|
<!-- KPM display settings -->
|
||||||
|
<string name="show_kpm_info">Display KPM Function</string>
|
||||||
|
<string name="show_kpm_info_summary">Display KPM information and Function in home and bottom bar (Need to reopen the app)</string>
|
||||||
|
<!-- Webui X settings -->
|
||||||
|
<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>
|
||||||
|
<!-- DPI setting related strings -->
|
||||||
|
<string name="dpi_settings">DPI setting</string>
|
||||||
|
<string name="app_dpi_title">Applied DPI</string>
|
||||||
|
<string name="app_dpi_summary">Adjust the screen display density for the current application only</string>
|
||||||
|
<string name="dpi_size_small">Small </string>
|
||||||
|
<string name="dpi_size_medium">Medium </string>
|
||||||
|
<string name="dpi_size_large">Big</string>
|
||||||
|
<string name="dpi_size_extra_large">oversize</string>
|
||||||
|
<string name="dpi_size_custom">customizable</string>
|
||||||
|
<string name="dpi_apply_settings">Applying DPI settings</string>
|
||||||
|
<string name="dpi_confirm_title">Confirm DPI change</string>
|
||||||
|
<string name="dpi_confirm_message">Are you sure you want to change the application DPI from %1$d to %2$d?</string>
|
||||||
|
<string name="dpi_confirm_summary">Application needs to be restarted to apply the new DPI settings, does not affect the system status bar or other applications</string>
|
||||||
|
<string name="dpi_applied_success">DPI has been set to %1$d, effective after restarting the application</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import com.android.build.api.dsl.ApplicationDefaultConfig
|
import com.android.build.api.dsl.ApplicationDefaultConfig
|
||||||
import com.android.build.api.dsl.CommonExtension
|
import com.android.build.api.dsl.CommonExtension
|
||||||
import com.android.build.gradle.api.AndroidBasePlugin
|
import com.android.build.gradle.api.AndroidBasePlugin
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.agp.app) apply false
|
alias(libs.plugins.agp.app) apply false
|
||||||
@@ -48,18 +47,6 @@ fun getGitDescribe(): String {
|
|||||||
}.standardOutput.asText.get().trim()
|
}.standardOutput.asText.get().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun getVersionCode(): Int {
|
|
||||||
val commitCount = getGitCommitCount()
|
|
||||||
val major = 1
|
|
||||||
return major * 10000 + commitCount + 606
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getVersionName(): String {
|
|
||||||
return getGitDescribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
plugins.withType(AndroidBasePlugin::class.java) {
|
plugins.withType(AndroidBasePlugin::class.java) {
|
||||||
extensions.configure(CommonExtension::class.java) {
|
extensions.configure(CommonExtension::class.java) {
|
||||||
|
|||||||
@@ -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" }
|
||||||
@@ -82,3 +83,9 @@ 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