Step 2: Add the remaining dynamic manager configurations

This commit is contained in:
ShirkNeko
2025-11-19 21:10:34 +08:00
parent a8acea9180
commit 3c501295b7
5 changed files with 317 additions and 9 deletions

View File

@@ -0,0 +1,264 @@
package com.sukisu.ultra.ui.component
import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Security
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.yukonga.miuix.kmp.basic.ButtonDefaults
import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextButton
import top.yukonga.miuix.kmp.basic.TextField
import top.yukonga.miuix.kmp.extra.SuperArrow
import top.yukonga.miuix.kmp.extra.SuperDialog
import top.yukonga.miuix.kmp.extra.SuperSwitch
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
@Composable
fun DynamicManagerCard() {
Card(
modifier = Modifier
.padding(top = 12.dp)
.fillMaxWidth(),
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
var isDynEnabled by rememberSaveable {
mutableStateOf(
Natives.getDynamicManager()?.isValid() == true
)
}
var dynSize by rememberSaveable {
mutableStateOf(
Natives.getDynamicManager()?.size?.toString() ?: ""
)
}
var dynHash by rememberSaveable {
mutableStateOf(
Natives.getDynamicManager()?.hash ?: ""
)
}
val showDynDialog = rememberSaveable { mutableStateOf(false) }
SuperArrow(
title = stringResource(R.string.dynamic_manager_title),
summary = if (isDynEnabled) {
stringResource(R.string.dynamic_manager_enabled_summary, dynSize)
} else {
stringResource(R.string.dynamic_manager_disabled)
},
leftAction = {
Icon(
Icons.Rounded.Security,
modifier = Modifier.padding(end = 16.dp),
contentDescription = stringResource(R.string.dynamic_manager_title),
tint = colorScheme.onBackground
)
},
onClick = {
showDynDialog.value = true
}
)
DynamicManagerDialog(
show = showDynDialog,
initialEnabled = isDynEnabled,
initialSize = dynSize,
initialHash = dynHash,
onConfirm = { enabled, size, hash ->
scope.launch(Dispatchers.IO) {
if (enabled) {
val newSize = try {
when {
size.startsWith("0x", true) ->
size.substring(2).toInt(16)
else -> size.toInt()
}
} catch (_: Exception) {
-1
}
if (newSize <= 0 || hash.length != 64) {
withContext(Dispatchers.Main) {
android.widget.Toast.makeText(
context,
R.string.invalid_sign_config,
android.widget.Toast.LENGTH_SHORT
).show()
}
return@launch
}
val ok = Natives.setDynamicManager(newSize, hash)
withContext(Dispatchers.Main) {
if (ok) {
dynSize = size
dynHash = hash
isDynEnabled = true
prefs.edit().apply {
putBoolean("dm_enabled", true)
putString("dm_size", dynSize)
putString("dm_hash", dynHash)
apply()
}
android.widget.Toast.makeText(
context,
R.string.dynamic_manager_set_success,
android.widget.Toast.LENGTH_SHORT
).show()
} else {
android.widget.Toast.makeText(
context,
R.string.dynamic_manager_set_failed,
android.widget.Toast.LENGTH_SHORT
).show()
}
}
} else {
val ok = Natives.clearDynamicManager()
withContext(Dispatchers.Main) {
if (ok) {
isDynEnabled = false
prefs.edit().apply {
putBoolean("dm_enabled", false)
apply()
}
android.widget.Toast.makeText(
context,
R.string.dynamic_manager_disabled_success,
android.widget.Toast.LENGTH_SHORT
).show()
} else {
android.widget.Toast.makeText(
context,
R.string.dynamic_manager_clear_failed,
android.widget.Toast.LENGTH_SHORT
).show()
}
}
}
}
}
)
}
}
@Composable
private fun DynamicManagerDialog(
show: MutableState<Boolean>,
initialEnabled: Boolean,
initialSize: String,
initialHash: String,
onConfirm: (enabled: Boolean, size: String, hash: String) -> Unit
) {
var tempDynEnabled by remember { mutableStateOf(initialEnabled) }
var tempDynSize by remember { mutableStateOf(initialSize) }
var tempDynHash by remember { mutableStateOf(initialHash) }
if (show.value) {
tempDynEnabled = initialEnabled
tempDynSize = initialSize
tempDynHash = initialHash
}
SuperDialog(
title = stringResource(R.string.dynamic_manager_title),
show = show,
onDismissRequest = {
show.value = false
}
) {
Column(
modifier = Modifier.padding(bottom = 16.dp)
) {
SuperSwitch(
title = stringResource(R.string.enable_dynamic_manager),
checked = tempDynEnabled,
onCheckedChange = { tempDynEnabled = it }
)
Spacer(Modifier.height(16.dp))
TextField(
value = tempDynSize,
onValueChange = { value ->
// 只允许输入十六进制字符
if (value.all { it in "0123456789xXaAbBcCdDeEfF" }) {
tempDynSize = value
}
},
label = stringResource(R.string.signature_size),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
)
TextField(
value = tempDynHash,
onValueChange = { value ->
// 只允许输入十六进制字符最多64个
if (value.all { it in "0123456789aAbBcCdDeEfF" } && value.length <= 64) {
tempDynHash = value
}
},
label = stringResource(R.string.signature_hash),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
)
Text(
text = "${tempDynHash.length} / 64",
modifier = Modifier.padding(start = 12.dp, top = 4.dp),
color = colorScheme.onSurfaceVariantSummary
)
}
Row(
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween
) {
TextButton(
text = stringResource(android.R.string.cancel),
onClick = {
show.value = false
},
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(20.dp))
TextButton(
text = stringResource(android.R.string.ok),
onClick = {
show.value = false
onConfirm(tempDynEnabled, tempDynSize.trim(), tempDynHash.trim())
},
modifier = Modifier.weight(1f),
colors = ButtonDefaults.textButtonColorsPrimary()
)
}
}
}

View File

@@ -2,7 +2,6 @@ package com.sukisu.ultra.ui.screen
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Process.myUid
import android.system.Os import android.system.Os
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@@ -649,7 +648,7 @@ private fun InfoCard() {
val dynamicValid = remember { Natives.getDynamicManager()?.isValid() == true } val dynamicValid = remember { Natives.getDynamicManager()?.isValid() == true }
if (dynamicValid && managersList != null) { if (dynamicValid && managersList != null) {
val signatureMap = managersList.managers.groupBy { it.signatureIndex } val signatureMap = managersList.managers.groupBy { it.signatureIndex }
val showDetailed = signatureMap.size > 1 || signatureMap.keys.firstOrNull() != 0 val showDetailed = signatureMap.isNotEmpty() || signatureMap.keys.firstOrNull() != 0
if (showDetailed) { if (showDetailed) {
val managersText = buildString { val managersText = buildString {
signatureMap.toSortedMap().forEach { (idx, list) -> signatureMap.toSortedMap().forEach { (idx, list) ->

View File

@@ -1,6 +1,8 @@
package com.sukisu.ultra.ui.screen package com.sukisu.ultra.ui.screen
import android.content.Context import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
@@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Adb import androidx.compose.material.icons.rounded.Adb
@@ -25,13 +28,16 @@ import androidx.compose.material.icons.rounded.FolderDelete
import androidx.compose.material.icons.rounded.RemoveCircle import androidx.compose.material.icons.rounded.RemoveCircle
import androidx.compose.material.icons.rounded.RemoveModerator import androidx.compose.material.icons.rounded.RemoveModerator
import androidx.compose.material.icons.rounded.RestartAlt import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Security
import androidx.compose.material.icons.rounded.Update import androidx.compose.material.icons.rounded.Update
import androidx.compose.material.icons.rounded.UploadFile import androidx.compose.material.icons.rounded.UploadFile
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -56,18 +62,27 @@ import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
import com.sukisu.ultra.Natives import com.sukisu.ultra.Natives
import com.sukisu.ultra.R import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.DynamicManagerCard
import com.sukisu.ultra.ui.component.KsuIsValid import com.sukisu.ultra.ui.component.KsuIsValid
import com.sukisu.ultra.ui.component.SendLogDialog import com.sukisu.ultra.ui.component.SendLogDialog
import com.sukisu.ultra.ui.component.SuperDropdown import com.sukisu.ultra.ui.component.SuperDropdown
import com.sukisu.ultra.ui.component.UninstallDialog import com.sukisu.ultra.ui.component.UninstallDialog
import com.sukisu.ultra.ui.component.rememberLoadingDialog import com.sukisu.ultra.ui.component.rememberLoadingDialog
import com.sukisu.ultra.ui.util.execKsud import com.sukisu.ultra.ui.util.execKsud
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.yukonga.miuix.kmp.basic.ButtonDefaults
import top.yukonga.miuix.kmp.basic.Card import top.yukonga.miuix.kmp.basic.Card
import top.yukonga.miuix.kmp.basic.Icon import top.yukonga.miuix.kmp.basic.Icon
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
import top.yukonga.miuix.kmp.basic.Scaffold import top.yukonga.miuix.kmp.basic.Scaffold
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextButton
import top.yukonga.miuix.kmp.basic.TextField
import top.yukonga.miuix.kmp.basic.TopAppBar import top.yukonga.miuix.kmp.basic.TopAppBar
import top.yukonga.miuix.kmp.extra.SuperArrow import top.yukonga.miuix.kmp.extra.SuperArrow
import top.yukonga.miuix.kmp.extra.SuperDialog
import top.yukonga.miuix.kmp.extra.SuperSwitch import top.yukonga.miuix.kmp.extra.SuperSwitch
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
import top.yukonga.miuix.kmp.utils.getWindowSize import top.yukonga.miuix.kmp.utils.getWindowSize
@@ -475,13 +490,17 @@ fun SettingPager(
} }
KsuIsValid { KsuIsValid {
Card( DynamicManagerCard()
modifier = Modifier }
.padding(top = 12.dp)
.fillMaxWidth(), KsuIsValid {
) { val lkmMode = Natives.isLkmMode
val lkmMode = Natives.isLkmMode if (lkmMode) {
if (lkmMode) { Card(
modifier = Modifier
.padding(top = 12.dp)
.fillMaxWidth(),
) {
val uninstall = stringResource(id = R.string.settings_uninstall) val uninstall = stringResource(id = R.string.settings_uninstall)
SuperArrow( SuperArrow(
title = uninstall, title = uninstall,

View File

@@ -193,4 +193,17 @@
<string name="log_viewer_settings">设置</string> <string name="log_viewer_settings">设置</string>
<string name="log_viewer_collapse">收起</string> <string name="log_viewer_collapse">收起</string>
<string name="log_viewer_expand">展开</string> <string name="log_viewer_expand">展开</string>
<!-- dynamic Manager -->
<string name="dynamic_manager_title">动态管理器配置</string>
<string name="dynamic_manager_enabled_summary">启用(大小:%s</string>
<string name="dynamic_manager_disabled">已禁用</string>
<string name="enable_dynamic_manager">启用动态管理器</string>
<string name="signature_size">动态管理器签名大小</string>
<string name="signature_hash">动态管理器签名哈希</string>
<string name="hash_must_be_64_chars">哈希必须是 64 个 16 进制字符</string>
<string name="dynamic_manager_set_success">动态管理器配置设置成功</string>
<string name="dynamic_manager_set_failed">设置动态管理器配置失败</string>
<string name="invalid_sign_config">无效的管理器配置</string>
<string name="dynamic_manager_disabled_success">动态管理器已禁用</string>
<string name="dynamic_manager_clear_failed">清空动态管理器配置失败</string>
</resources> </resources>

View File

@@ -195,4 +195,17 @@
<string name="log_viewer_settings">Settings</string> <string name="log_viewer_settings">Settings</string>
<string name="log_viewer_collapse">Collapse</string> <string name="log_viewer_collapse">Collapse</string>
<string name="log_viewer_expand">Expand</string> <string name="log_viewer_expand">Expand</string>
<!-- dynamic Manager -->
<string name="dynamic_manager_title">Dynamic Manager Configuration</string>
<string name="dynamic_manager_enabled_summary">Enabled (Size: %s)</string>
<string name="dynamic_manager_disabled">Disabled</string>
<string name="enable_dynamic_manager">Enable Dynamic Manager</string>
<string name="signature_size">Dynamic Manager Signature Size</string>
<string name="signature_hash">Dynamic Manager Signature Hash</string>
<string name="hash_must_be_64_chars">Hash must be 64 hexadecimal characters</string>
<string name="dynamic_manager_set_success">Dynamic Manager configuration set successfully</string>
<string name="dynamic_manager_set_failed">Failed to set dynamic Manager configuration</string>
<string name="invalid_sign_config">Invalid Manager configuration</string>
<string name="dynamic_manager_disabled_success">Dynamic Manager disabled</string>
<string name="dynamic_manager_clear_failed">Failed to clear dynamic Manager</string>
</resources> </resources>