diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/DynamicManagerCard.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/DynamicManagerCard.kt new file mode 100644 index 00000000..7cc9b277 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/DynamicManagerCard.kt @@ -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, + 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() + ) + } + } +} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt index fe67825c..6098236f 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Home.kt @@ -2,7 +2,6 @@ package com.sukisu.ultra.ui.screen import android.content.Context import android.os.Build -import android.os.Process.myUid import android.system.Os import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility @@ -649,7 +648,7 @@ private fun InfoCard() { val dynamicValid = remember { Natives.getDynamicManager()?.isValid() == true } if (dynamicValid && managersList != null) { 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) { val managersText = buildString { signatureMap.toSortedMap().forEach { (idx, list) -> diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt index 197ec4eb..8f5b95c9 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Settings.kt @@ -1,6 +1,8 @@ package com.sukisu.ultra.ui.screen 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.WindowInsets 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.padding import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons 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.RemoveModerator 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.UploadFile import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf 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 @@ -56,18 +62,27 @@ import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import com.sukisu.ultra.Natives 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.SendLogDialog import com.sukisu.ultra.ui.component.SuperDropdown import com.sukisu.ultra.ui.component.UninstallDialog import com.sukisu.ultra.ui.component.rememberLoadingDialog 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.Icon import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior 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.extra.SuperArrow +import top.yukonga.miuix.kmp.extra.SuperDialog import top.yukonga.miuix.kmp.extra.SuperSwitch import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme import top.yukonga.miuix.kmp.utils.getWindowSize @@ -475,13 +490,17 @@ fun SettingPager( } KsuIsValid { - Card( - modifier = Modifier - .padding(top = 12.dp) - .fillMaxWidth(), - ) { - val lkmMode = Natives.isLkmMode - if (lkmMode) { + DynamicManagerCard() + } + + KsuIsValid { + val lkmMode = Natives.isLkmMode + if (lkmMode) { + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { val uninstall = stringResource(id = R.string.settings_uninstall) SuperArrow( title = uninstall, diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index f12fd138..e6882ab3 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -193,4 +193,17 @@ 设置 收起 展开 + + 动态管理器配置 + 启用(大小:%s) + 已禁用 + 启用动态管理器 + 动态管理器签名大小 + 动态管理器签名哈希 + 哈希必须是 64 个 16 进制字符 + 动态管理器配置设置成功 + 设置动态管理器配置失败 + 无效的管理器配置 + 动态管理器已禁用 + 清空动态管理器配置失败 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 396ff26c..737199f1 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -195,4 +195,17 @@ Settings Collapse Expand + + Dynamic Manager Configuration + Enabled (Size: %s) + Disabled + Enable Dynamic Manager + Dynamic Manager Signature Size + Dynamic Manager Signature Hash + Hash must be 64 hexadecimal characters + Dynamic Manager configuration set successfully + Failed to set dynamic Manager configuration + Invalid Manager configuration + Dynamic Manager disabled + Failed to clear dynamic Manager