From 6898d82dafd28cf5f92001d618d04bd2a401df61 Mon Sep 17 00:00:00 2001 From: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com> Date: Sat, 22 Nov 2025 16:56:51 +0800 Subject: [PATCH] Step 7-1: Optimize the susfs interface format and refactor the code - Remove unused resources --- .../com/sukisu/ultra/ui/screen/Settings.kt | 49 +- .../ultra/ui/screen/settings/ToolsScreen.kt | 17 +- .../com/sukisu/ultra/ui/susfs/SuSFSConfig.kt | 738 +------- .../ultra/ui/susfs/component/ActionButtons.kt | 112 ++ .../susfs/component/BackupRestoreComponent.kt | 404 +++++ .../ui/susfs/component/SuSFSConfigDialogs.kt | 1548 +++++++---------- .../ui/susfs/component/SuSFSConfigTabs.kt | 1183 ------------- .../ui/susfs/component/UniversalDialog.kt | 351 ++++ .../ui/susfs/content/BasicSettingsContent.kt | 313 ++++ .../susfs/content/EnabledFeaturesContent.kt | 79 + .../ui/susfs/content/KstatConfigContent.kt | 99 ++ .../ui/susfs/content/PathSettingsContent.kt | 80 + .../ui/susfs/content/SusLoopPathsContent.kt | 77 + .../ultra/ui/susfs/content/SusMapsContent.kt | 78 + .../ui/susfs/content/SusMountsContent.kt | 70 + .../ultra/ui/susfs/content/SusPathsContent.kt | 192 ++ .../ui/susfs/content/TryUmountContent.kt | 99 ++ .../ultra/ui/susfs/util/SuSFSManager.kt | 245 +-- .../ultra/ui/susfs/util/SuSFSModuleManager.kt | 74 + .../ultra/ui/susfs/util/SuSFSModuleScripts.kt | 137 +- .../app/src/main/res/values-ar/strings.xml | 7 - .../app/src/main/res/values-az/strings.xml | 7 - .../src/main/res/values-bn-rBD/strings.xml | 6 - .../app/src/main/res/values-bn/strings.xml | 5 - .../app/src/main/res/values-bs/strings.xml | 6 - .../app/src/main/res/values-da/strings.xml | 7 - .../app/src/main/res/values-de/strings.xml | 7 - .../app/src/main/res/values-es/strings.xml | 7 - .../app/src/main/res/values-et/strings.xml | 7 - .../app/src/main/res/values-fa/strings.xml | 6 - .../app/src/main/res/values-fil/strings.xml | 7 - .../app/src/main/res/values-fr/strings.xml | 7 - .../app/src/main/res/values-hi/strings.xml | 6 - .../app/src/main/res/values-hr/strings.xml | 7 - .../app/src/main/res/values-hu/strings.xml | 7 - .../app/src/main/res/values-in/strings.xml | 7 - .../app/src/main/res/values-it/strings.xml | 7 - .../app/src/main/res/values-iw/strings.xml | 6 - .../app/src/main/res/values-ja/strings.xml | 7 - .../app/src/main/res/values-kn/strings.xml | 6 - .../app/src/main/res/values-ko/strings.xml | 7 - .../app/src/main/res/values-lt/strings.xml | 6 - .../app/src/main/res/values-lv/strings.xml | 7 - .../app/src/main/res/values-mr/strings.xml | 6 - .../app/src/main/res/values-ms/strings.xml | 2 - .../app/src/main/res/values-nl/strings.xml | 7 - .../app/src/main/res/values-pl/strings.xml | 7 - .../src/main/res/values-pt-rBR/strings.xml | 7 - .../app/src/main/res/values-pt/strings.xml | 6 - .../app/src/main/res/values-ro/strings.xml | 7 - .../app/src/main/res/values-ru/strings.xml | 7 - .../app/src/main/res/values-sl/strings.xml | 7 - .../app/src/main/res/values-sr/strings.xml | 2 - .../app/src/main/res/values-te/strings.xml | 2 - .../app/src/main/res/values-th/strings.xml | 7 - .../app/src/main/res/values-tr/strings.xml | 7 - .../app/src/main/res/values-uk/strings.xml | 7 - .../app/src/main/res/values-vi/strings.xml | 7 - .../src/main/res/values-zh-rCN/strings.xml | 48 - .../src/main/res/values-zh-rHK/strings.xml | 7 - .../src/main/res/values-zh-rTW/strings.xml | 7 - manager/app/src/main/res/values/strings.xml | 48 - 62 files changed, 2893 insertions(+), 3402 deletions(-) create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/ActionButtons.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/BackupRestoreComponent.kt delete mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/UniversalDialog.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/BasicSettingsContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/EnabledFeaturesContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/KstatConfigContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/PathSettingsContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusLoopPathsContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMapsContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMountsContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusPathsContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/TryUmountContent.kt create mode 100644 manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleManager.kt 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 a702f716..20bc336f 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 @@ -70,6 +70,7 @@ import com.sukisu.ultra.ui.component.UninstallDialog import com.sukisu.ultra.ui.component.rememberLoadingDialog import com.sukisu.ultra.ui.util.execKsud import com.sukisu.ultra.ui.util.getFeatureStatus +import com.sukisu.ultra.ui.util.getSuSFSFeatures import com.sukisu.ultra.ui.util.rememberKpmAvailable import top.yukonga.miuix.kmp.basic.Card import top.yukonga.miuix.kmp.basic.Icon @@ -302,29 +303,33 @@ fun SettingPager( } KsuIsValid { - Card( - modifier = Modifier - .padding(top = 12.dp) - .fillMaxWidth(), - ) { - val susfsTitle = stringResource(id = R.string.susfs_config_title) - SuperArrow( - title = susfsTitle, - summary = stringResource(id = R.string.susfs_config_summary), - leftAction = { - Icon( - Icons.Rounded.Settings, - modifier = Modifier.padding(end = 16.dp), - contentDescription = susfsTitle, - tint = colorScheme.onBackground - ) - }, - onClick = { - navigator.navigate(SuSFSConfigScreenDestination) { - launchSingleTop = true + val rawFeature = getSuSFSFeatures() + val supported = rawFeature.isNotEmpty() && !rawFeature.startsWith("[-]") + if (supported) { + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + val susfsTitle = stringResource(id = R.string.susfs_config_title) + SuperArrow( + title = susfsTitle, + summary = stringResource(id = R.string.susfs_config_summary), + leftAction = { + Icon( + Icons.Rounded.Settings, + modifier = Modifier.padding(end = 16.dp), + contentDescription = susfsTitle, + tint = colorScheme.onBackground + ) + }, + onClick = { + navigator.navigate(SuSFSConfigScreenDestination) { + launchSingleTop = true + } } - } - ) + ) + } } } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/settings/ToolsScreen.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/settings/ToolsScreen.kt index edeb290a..a199d03d 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/settings/ToolsScreen.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/settings/ToolsScreen.kt @@ -149,13 +149,13 @@ fun Tools( DynamicManagerCard() - Card( - modifier = Modifier - .padding(top = 12.dp) - .fillMaxWidth(), - ) { - val lkmMode = Natives.isLkmMode - if (lkmMode) { + val lkmMode = Natives.isLkmMode + if (lkmMode) { + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { val umontManager = stringResource(id = R.string.umount_path_manager) SuperArrow( title = umontManager, @@ -168,8 +168,7 @@ fun Tools( ) }, onClick = { - navigator.navigate(UmountManagerDestination) { - } + navigator.navigate(UmountManagerDestination) } ) } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt index 7ab4849b..f6d62f5b 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/SuSFSConfig.kt @@ -1,9 +1,6 @@ package com.sukisu.ultra.ui.susfs import android.annotation.SuppressLint -import android.content.Context -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -15,6 +12,7 @@ import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext @@ -38,16 +36,20 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.sukisu.ultra.R import com.sukisu.ultra.ui.susfs.component.* +import com.sukisu.ultra.ui.susfs.content.BasicSettingsContent +import com.sukisu.ultra.ui.susfs.content.EnabledFeaturesContent +import com.sukisu.ultra.ui.susfs.content.KstatConfigContent +import com.sukisu.ultra.ui.susfs.content.PathSettingsContent +import com.sukisu.ultra.ui.susfs.content.SusLoopPathsContent +import com.sukisu.ultra.ui.susfs.content.SusMapsContent +import com.sukisu.ultra.ui.susfs.content.SusMountsContent +import com.sukisu.ultra.ui.susfs.content.SusPathsContent +import com.sukisu.ultra.ui.susfs.content.TryUmountContent import com.sukisu.ultra.ui.susfs.util.SuSFSManager import com.sukisu.ultra.ui.util.isAbDevice import kotlinx.coroutines.launch -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.coroutines.delay import top.yukonga.miuix.kmp.basic.* import top.yukonga.miuix.kmp.extra.SuperDialog -import top.yukonga.miuix.kmp.extra.SuperDropdown -import top.yukonga.miuix.kmp.extra.SuperSwitch import top.yukonga.miuix.kmp.icon.MiuixIcons import top.yukonga.miuix.kmp.icon.icons.useful.Back import top.yukonga.miuix.kmp.theme.MiuixTheme @@ -55,13 +57,7 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme import top.yukonga.miuix.kmp.utils.getWindowSize import top.yukonga.miuix.kmp.utils.overScrollVertical import top.yukonga.miuix.kmp.utils.scrollEndHaptic -import java.io.File -import java.text.SimpleDateFormat -import java.util.* -/** - * 标签页枚举类 - */ enum class SuSFSTab(val displayNameRes: Int) { BASIC_SETTINGS(R.string.susfs_tab_basic_settings), SUS_PATHS(R.string.susfs_tab_sus_paths), @@ -80,9 +76,6 @@ enum class SuSFSTab(val displayNameRes: Int) { } } -/** - * SuSFS配置界面 - */ @SuppressLint("SdCardPath", "AutoboxingStateCreation") @Destination @Composable @@ -167,12 +160,6 @@ fun SuSFSConfigScreen( var showResetUmountsDialog by remember { mutableStateOf(false) } var showResetKstatDialog by remember { mutableStateOf(false) } - // 备份还原相关状态 - var showBackupDialog by remember { mutableStateOf(false) } - var showRestoreDialog by remember { mutableStateOf(false) } - var showRestoreConfirmDialog by remember { mutableStateOf(false) } - var selectedBackupFile by remember { mutableStateOf(null) } - var backupInfo by remember { mutableStateOf(null) } var isNavigating by remember { mutableStateOf(false) } @@ -186,62 +173,6 @@ fun SuSFSConfigScreen( } - // 文件选择器 - val backupFileLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument("application/json") - ) { uri -> - uri?.let { fileUri -> - val fileName = SuSFSManager.getDefaultBackupFileName() - val tempFile = File(context.cacheDir, fileName) - coroutineScope.launch { - isLoading = true - val success = SuSFSManager.createBackup(context, tempFile.absolutePath) - if (success) { - try { - context.contentResolver.openOutputStream(fileUri)?.use { outputStream -> - tempFile.inputStream().use { inputStream -> - inputStream.copyTo(outputStream) - } - } - } catch (e: Exception) { - e.printStackTrace() - } - tempFile.delete() - } - isLoading = false - showBackupDialog = false - } - } - } - - val restoreFileLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.OpenDocument() - ) { uri -> - uri?.let { fileUri -> - coroutineScope.launch { - try { - val tempFile = File(context.cacheDir, "temp_restore.susfs_backup") - context.contentResolver.openInputStream(fileUri)?.use { inputStream -> - tempFile.outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - } - - // 验证备份文件 - val backup = SuSFSManager.validateBackupFile(tempFile.absolutePath) - if (backup != null) { - selectedBackupFile = tempFile.absolutePath - backupInfo = backup - showRestoreConfirmDialog = true - } - tempFile.deleteOnExit() - } catch (e: Exception) { - e.printStackTrace() - } - showRestoreDialog = false - } - } - } // 加载启用功能状态 fun loadEnabledFeatures() { @@ -311,256 +242,6 @@ fun SuSFSConfigScreen( } } - // 备份对话框 - val showBackupDialogState = remember { mutableStateOf(showBackupDialog) } - LaunchedEffect(showBackupDialog) { - showBackupDialogState.value = showBackupDialog - } - if (showBackupDialog) { - SuperDialog( - show = showBackupDialogState, - title = stringResource(R.string.susfs_backup_title), - onDismissRequest = { showBackupDialog = false }, - content = { - Text(stringResource(R.string.susfs_backup_description)) - Spacer(modifier = Modifier.height(12.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(R.string.cancel), - onClick = { showBackupDialog = false }, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - TextButton( - text = stringResource(R.string.susfs_backup_create), - onClick = { - val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) - val timestamp = dateFormat.format(Date()) - backupFileLauncher.launch("SuSFS_Config_$timestamp.susfs_backup") - }, - enabled = !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - colors = ButtonDefaults.textButtonColorsPrimary() - ) - } - } - ) - } - - // 还原对话框 - val showRestoreDialogState = remember { mutableStateOf(showRestoreDialog) } - LaunchedEffect(showRestoreDialog) { - showRestoreDialogState.value = showRestoreDialog - } - if (showRestoreDialog) { - SuperDialog( - show = showRestoreDialogState, - title = stringResource(R.string.susfs_restore_title), - onDismissRequest = { showRestoreDialog = false }, - content = { - Text(stringResource(R.string.susfs_restore_description)) - Spacer(modifier = Modifier.height(12.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(R.string.cancel), - onClick = { showRestoreDialog = false }, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - TextButton( - text = stringResource(R.string.susfs_restore_select_file), - onClick = { - restoreFileLauncher.launch(arrayOf("application/json", "*/*")) - }, - enabled = !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - colors = ButtonDefaults.textButtonColorsPrimary() - ) - } - } - ) - } - - // 还原确认对话框 - val showRestoreConfirmDialogState = remember { mutableStateOf(showRestoreConfirmDialog && backupInfo != null) } - LaunchedEffect(showRestoreConfirmDialog, backupInfo) { - showRestoreConfirmDialogState.value = showRestoreConfirmDialog && backupInfo != null - } - if (showRestoreConfirmDialog && backupInfo != null) { - SuperDialog( - show = showRestoreConfirmDialogState, - title = stringResource(R.string.susfs_restore_confirm_title), - onDismissRequest = { - showRestoreConfirmDialog = false - selectedBackupFile = null - backupInfo = null - }, - content = { - Column( - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text(stringResource(R.string.susfs_restore_confirm_description)) - - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) - Text( - text = stringResource(R.string.susfs_backup_info_date, - dateFormat.format(Date(backupInfo!!.timestamp))), - fontSize = MiuixTheme.textStyles.body2.fontSize - ) - Text( - text = stringResource(R.string.susfs_backup_info_device, backupInfo!!.deviceInfo), - fontSize = MiuixTheme.textStyles.body2.fontSize - ) - Text( - text = stringResource(R.string.susfs_backup_info_version, backupInfo!!.version), - fontSize = MiuixTheme.textStyles.body2.fontSize - ) - } - } - } - Spacer(modifier = Modifier.height(12.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(R.string.cancel), - onClick = { - showRestoreConfirmDialog = false - selectedBackupFile = null - backupInfo = null - }, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - TextButton( - text = stringResource(R.string.susfs_restore_confirm), - onClick = { - selectedBackupFile?.let { filePath -> - coroutineScope.launch { - isLoading = true - try { - val success = SuSFSManager.restoreFromBackup(context, filePath) - if (success) { - // 在后台线程读取配置,然后在主线程更新状态 - val configs = withContext(Dispatchers.IO) { - mapOf( - "unameValue" to SuSFSManager.getUnameValue(context), - "buildTimeValue" to SuSFSManager.getBuildTimeValue(context), - "autoStartEnabled" to SuSFSManager.isAutoStartEnabled(context), - "executeInPostFsData" to SuSFSManager.getExecuteInPostFsData(context), - "susPaths" to SuSFSManager.getSusPaths(context), - "susLoopPaths" to SuSFSManager.getSusLoopPaths(context), - "susMaps" to SuSFSManager.getSusMaps(context), - "susMounts" to SuSFSManager.getSusMounts(context), - "tryUmounts" to SuSFSManager.getTryUmounts(context), - "androidDataPath" to SuSFSManager.getAndroidDataPath(context), - "sdcardPath" to SuSFSManager.getSdcardPath(context), - "kstatConfigs" to SuSFSManager.getKstatConfigs(context), - "addKstatPaths" to SuSFSManager.getAddKstatPaths(context), - "hideSusMountsForAllProcs" to SuSFSManager.getHideSusMountsForAllProcs(context), - "enableHideBl" to SuSFSManager.getEnableHideBl(context), - "enableCleanupResidue" to SuSFSManager.getEnableCleanupResidue(context), - "umountForZygoteIsoService" to SuSFSManager.getUmountForZygoteIsoService(context), - "enableAvcLogSpoofing" to SuSFSManager.getEnableAvcLogSpoofing(context) - ) - } - - // 在主线程更新状态 - @Suppress("UNCHECKED_CAST") - unameValue = configs["unameValue"] as String - @Suppress("UNCHECKED_CAST") - buildTimeValue = configs["buildTimeValue"] as String - @Suppress("UNCHECKED_CAST") - autoStartEnabled = configs["autoStartEnabled"] as Boolean - @Suppress("UNCHECKED_CAST") - executeInPostFsData = configs["executeInPostFsData"] as Boolean - @Suppress("UNCHECKED_CAST") - susPaths = configs["susPaths"] as Set - @Suppress("UNCHECKED_CAST") - susLoopPaths = configs["susLoopPaths"] as Set - @Suppress("UNCHECKED_CAST") - susMaps = configs["susMaps"] as Set - @Suppress("UNCHECKED_CAST") - susMounts = configs["susMounts"] as Set - @Suppress("UNCHECKED_CAST") - tryUmounts = configs["tryUmounts"] as Set - @Suppress("UNCHECKED_CAST") - androidDataPath = configs["androidDataPath"] as String - @Suppress("UNCHECKED_CAST") - sdcardPath = configs["sdcardPath"] as String - @Suppress("UNCHECKED_CAST") - kstatConfigs = configs["kstatConfigs"] as Set - @Suppress("UNCHECKED_CAST") - addKstatPaths = configs["addKstatPaths"] as Set - @Suppress("UNCHECKED_CAST") - hideSusMountsForAllProcs = configs["hideSusMountsForAllProcs"] as Boolean - @Suppress("UNCHECKED_CAST") - enableHideBl = configs["enableHideBl"] as Boolean - @Suppress("UNCHECKED_CAST") - enableCleanupResidue = configs["enableCleanupResidue"] as Boolean - @Suppress("UNCHECKED_CAST") - umountForZygoteIsoService = configs["umountForZygoteIsoService"] as Boolean - @Suppress("UNCHECKED_CAST") - enableAvcLogSpoofing = configs["enableAvcLogSpoofing"] as Boolean - - // 延迟关闭对话框,给 UI 时间更新 - delay(300) - } - } catch (e: Exception) { - e.printStackTrace() - } finally { - // 先关闭对话框,确保在主线程上执行 - withContext(Dispatchers.Main) { - isLoading = false - showRestoreConfirmDialog = false - } - // 延迟清空状态,确保对话框完全关闭后再清空 - delay(100) - withContext(Dispatchers.Main) { - selectedBackupFile = null - backupInfo = null - } - } - } - } - }, - enabled = !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - colors = ButtonDefaults.textButtonColorsPrimary() - ) - } - } - ) - } // 槽位信息对话框 SlotInfoDialog( @@ -1072,8 +753,6 @@ fun SuSFSConfigScreen( }, onShowSlotInfo = { showSlotInfoDialog = true }, context = context, - onShowBackupDialog = { showBackupDialog = true }, - onShowRestoreDialog = { showRestoreDialog = true }, enableHideBl = enableHideBl, onEnableHideBlChange = { enabled: Boolean -> enableHideBl = enabled @@ -1098,14 +777,62 @@ fun SuSFSConfigScreen( onEnableAvcLogSpoofingChange = { enabled: Boolean -> coroutineScope.launch { isLoading = true - val success = SuSFSManager.setEnableAvcLogSpoofing(context, enabled) + val success = + SuSFSManager.setEnableAvcLogSpoofing(context, enabled) if (success) { enableAvcLogSpoofing = enabled } isLoading = false } }, - onReset = { showConfirmReset = true } + onReset = { showConfirmReset = true }, + onApply = { + coroutineScope.launch { + isLoading = true + val success = SuSFSManager.setUname( + context, + unameValue.trim(), + buildTimeValue.trim() + ) + if (success) { + SuSFSManager.saveExecuteInPostFsData( + context, + executeInPostFsData + ) + if (SuSFSManager.isAutoStartEnabled(context)) { + SuSFSManager.configureAutoStart(context, true) + } + } + isLoading = false + } + }, + onConfigReload = { + coroutineScope.launch { + unameValue = SuSFSManager.getUnameValue(context) + buildTimeValue = SuSFSManager.getBuildTimeValue(context) + autoStartEnabled = SuSFSManager.isAutoStartEnabled(context) + executeInPostFsData = + SuSFSManager.getExecuteInPostFsData(context) + susPaths = SuSFSManager.getSusPaths(context) + susLoopPaths = SuSFSManager.getSusLoopPaths(context) + susMaps = SuSFSManager.getSusMaps(context) + susMounts = SuSFSManager.getSusMounts(context) + tryUmounts = SuSFSManager.getTryUmounts(context) + androidDataPath = SuSFSManager.getAndroidDataPath(context) + sdcardPath = SuSFSManager.getSdcardPath(context) + kstatConfigs = SuSFSManager.getKstatConfigs(context) + addKstatPaths = SuSFSManager.getAddKstatPaths(context) + hideSusMountsForAllProcs = + SuSFSManager.getHideSusMountsForAllProcs(context) + enableHideBl = SuSFSManager.getEnableHideBl(context) + enableCleanupResidue = + SuSFSManager.getEnableCleanupResidue(context) + umountForZygoteIsoService = + SuSFSManager.getUmountForZygoteIsoService(context) + enableAvcLogSpoofing = + SuSFSManager.getEnableAvcLogSpoofing(context) + } + } ) } SuSFSTab.SUS_PATHS -> { @@ -1337,325 +1064,6 @@ fun SuSFSConfigScreen( } } -/** - * 基本设置内容组件 - */ -@Composable -fun BasicSettingsContent( - unameValue: String, - onUnameValueChange: (String) -> Unit, - buildTimeValue: String, - onBuildTimeValueChange: (String) -> Unit, - executeInPostFsData: Boolean, - onExecuteInPostFsDataChange: (Boolean) -> Unit, - autoStartEnabled: Boolean, - canEnableAutoStart: Boolean, - isLoading: Boolean, - onAutoStartToggle: (Boolean) -> Unit, - onShowSlotInfo: () -> Unit, - context: Context, - onShowBackupDialog: () -> Unit, - onShowRestoreDialog: () -> Unit, - enableHideBl: Boolean, - onEnableHideBlChange: (Boolean) -> Unit, - enableCleanupResidue: Boolean, - onEnableCleanupResidueChange: (Boolean) -> Unit, - enableAvcLogSpoofing: Boolean, - onEnableAvcLogSpoofingChange: (Boolean) -> Unit, - onReset: (() -> Unit)? = null -) { - val isAbDevice = produceState(initialValue = false) { - value = isAbDevice() - }.value - - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - // 说明卡片 - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier.padding(14.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - Text( - text = stringResource(R.string.susfs_config_description), - style = MiuixTheme.textStyles.title3, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - Text( - text = stringResource(R.string.susfs_config_description_text), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - } - } - - // Uname输入框 - TextField( - value = unameValue, - onValueChange = onUnameValueChange, - label = stringResource(R.string.susfs_uname_label), - useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) - - // 构建时间伪装输入框 - TextField( - value = buildTimeValue, - onValueChange = onBuildTimeValueChange, - label = stringResource(R.string.susfs_build_time_label), - useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) - - // 执行位置选择 - val locationItems = listOf( - stringResource(R.string.susfs_execution_location_service), - stringResource(R.string.susfs_execution_location_post_fs_data) - ) - SuperDropdown( - title = stringResource(R.string.susfs_execution_location_label), - summary = if (executeInPostFsData) { - stringResource(R.string.susfs_execution_location_post_fs_data) - } else { - stringResource(R.string.susfs_execution_location_service) - }, - items = locationItems, - selectedIndex = if (executeInPostFsData) 1 else 0, - onSelectedIndexChange = { index -> - onExecuteInPostFsDataChange(index == 1) - }, - enabled = !isLoading - ) - - // 当前值显示 - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - Text( - text = stringResource(R.string.susfs_current_value, SuSFSManager.getUnameValue(context)), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary - ) - Text( - text = stringResource(R.string.susfs_current_build_time, SuSFSManager.getBuildTimeValue(context)), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary - ) - Text( - text = stringResource(R.string.susfs_current_execution_location, if (SuSFSManager.getExecuteInPostFsData(context)) "Post-FS-Data" else "Service"), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary - ) - } - } - - // 开机自启动开关 - Card( - modifier = Modifier.fillMaxWidth(), - ) { - SuperSwitch( - title = stringResource(R.string.susfs_autostart_title), - summary = if (canEnableAutoStart) { - stringResource(R.string.susfs_autostart_description) - } else { - stringResource(R.string.susfs_autostart_requirement) - }, - leftAction = { - Icon( - Icons.Default.AutoMode, - modifier = Modifier.padding(end = 16.dp), - contentDescription = stringResource(R.string.susfs_autostart_title), - tint = if (canEnableAutoStart) colorScheme.onBackground else colorScheme.onSurfaceVariantSummary - ) - }, - checked = autoStartEnabled, - onCheckedChange = onAutoStartToggle, - enabled = !isLoading && canEnableAutoStart - ) - } - - // 隐藏BL脚本开关 - Card( - modifier = Modifier.fillMaxWidth(), - ) { - SuperSwitch( - title = stringResource(R.string.hide_bl_script), - summary = stringResource(R.string.hide_bl_script_description), - leftAction = { - Icon( - Icons.Default.Security, - modifier = Modifier.padding(end = 16.dp), - contentDescription = stringResource(R.string.hide_bl_script), - tint = colorScheme.onBackground - ) - }, - checked = enableHideBl, - onCheckedChange = onEnableHideBlChange, - enabled = !isLoading - ) - } - - // 清理残留脚本开关 - Card( - modifier = Modifier.fillMaxWidth(), - ) { - SuperSwitch( - title = stringResource(R.string.cleanup_residue), - summary = stringResource(R.string.cleanup_residue_description), - leftAction = { - Icon( - Icons.Default.CleaningServices, - modifier = Modifier.padding(end = 16.dp), - contentDescription = stringResource(R.string.cleanup_residue), - tint = colorScheme.onBackground - ) - }, - checked = enableCleanupResidue, - onCheckedChange = onEnableCleanupResidueChange, - enabled = !isLoading - ) - } - - // AVC日志欺骗开关 - Card( - modifier = Modifier.fillMaxWidth(), - ) { - SuperSwitch( - title = stringResource(R.string.avc_log_spoofing), - summary = stringResource(R.string.avc_log_spoofing_description), - leftAction = { - Icon( - Icons.Default.VisibilityOff, - modifier = Modifier.padding(end = 16.dp), - contentDescription = stringResource(R.string.avc_log_spoofing), - tint = colorScheme.onBackground - ) - }, - checked = enableAvcLogSpoofing, - onCheckedChange = onEnableAvcLogSpoofingChange, - enabled = !isLoading - ) - } - - // 槽位信息按钮 - if (isAbDevice) { - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier.padding(14.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - Icons.Default.Info, - contentDescription = null, - tint = colorScheme.primary, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = stringResource(R.string.susfs_slot_info_title), - style = MiuixTheme.textStyles.title3, - fontWeight = FontWeight.Medium, - color = colorScheme.onBackground - ) - } - Text( - text = stringResource(R.string.susfs_slot_info_description), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - TextButton( - text = stringResource(R.string.susfs_slot_info_title), - onClick = onShowSlotInfo, - enabled = !isLoading, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - } - } - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - // 备份按钮 - TextButton( - text = stringResource(R.string.susfs_backup_title), - onClick = onShowBackupDialog, - enabled = !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - // 还原按钮 - TextButton( - text = stringResource(R.string.susfs_restore_title), - onClick = onShowRestoreDialog, - enabled = !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - } - - // 重置按钮 - if (onReset != null) { - Spacer(modifier = Modifier.height(8.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_confirm_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * 槽位信息对话框 - */ @Composable fun SlotInfoDialog( showDialog: Boolean, @@ -1689,7 +1097,7 @@ fun SlotInfoDialog( ) { Text( text = stringResource(R.string.susfs_current_active_slot, currentActiveSlot), - style = MiuixTheme.textStyles.body1, + style = MiuixTheme.textStyles.body2, fontWeight = FontWeight.Medium, color = colorScheme.primary ) @@ -1727,7 +1135,7 @@ fun SlotInfoDialog( Spacer(modifier = Modifier.width(6.dp)) Text( text = slotInfo.slotName, - style = MiuixTheme.textStyles.title2, + style = MiuixTheme.textStyles.body1, fontWeight = FontWeight.Bold, color = if (slotInfo.slotName == currentActiveSlot) { colorScheme.primary @@ -1755,12 +1163,12 @@ fun SlotInfoDialog( } Text( text = stringResource(R.string.susfs_slot_uname, slotInfo.uname), - style = MiuixTheme.textStyles.body2, + style = MiuixTheme.textStyles.body2.copy(fontSize = 13.sp), color = colorScheme.onSurface ) Text( text = stringResource(R.string.susfs_slot_build_time, slotInfo.buildTime), - style = MiuixTheme.textStyles.body2, + style = MiuixTheme.textStyles.body2.copy(fontSize = 13.sp), color = colorScheme.onSurface ) @@ -1820,20 +1228,22 @@ fun SlotInfoDialog( cornerRadius = 8.dp ) { Text( - text = stringResource(R.string.refresh), - style = MiuixTheme.textStyles.body2, - maxLines = 2 + text = stringResource(R.string.refresh) ) } - - TextButton( - text = stringResource(android.R.string.cancel), + + Button( onClick = onDismiss, modifier = Modifier .fillMaxWidth() .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(android.R.string.cancel) + ) + } } } ) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/ActionButtons.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/ActionButtons.kt new file mode 100644 index 00000000..aad4c884 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/ActionButtons.kt @@ -0,0 +1,112 @@ +package com.sukisu.ultra.ui.susfs.component + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import top.yukonga.miuix.kmp.basic.* +import top.yukonga.miuix.kmp.theme.MiuixTheme + +@Composable +fun BottomActionButtons( + modifier: Modifier = Modifier, + primaryButtonText: String, + onPrimaryClick: () -> Unit, + secondaryButtonText: String? = null, + onSecondaryClick: (() -> Unit)? = null, + isLoading: Boolean = false, + enabled: Boolean = true +) { + Card( + modifier = modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (secondaryButtonText != null && onSecondaryClick != null) { + Button( + onClick = onSecondaryClick, + enabled = !isLoading && enabled, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Text( + text = secondaryButtonText, + style = MiuixTheme.textStyles.body2 + ) + } + Button( + onClick = onPrimaryClick, + enabled = !isLoading && enabled, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Text( + text = primaryButtonText, + style = MiuixTheme.textStyles.body2 + ) + } + } else { + Button( + onClick = onPrimaryClick, + enabled = !isLoading && enabled, + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = primaryButtonText, + style = MiuixTheme.textStyles.body2 + ) + } + } + } + } +} + +@Composable +fun ResetButton( + modifier: Modifier = Modifier, + title: String, + onClick: () -> Unit, + enabled: Boolean = true +) { + Card( + modifier = modifier + .padding(vertical = 12.dp) + .fillMaxWidth(), + ) { + Button( + onClick = onClick, + enabled = enabled, + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Text( + text = title + ) + } + } +} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/BackupRestoreComponent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/BackupRestoreComponent.kt new file mode 100644 index 00000000..2477f6e1 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/BackupRestoreComponent.kt @@ -0,0 +1,404 @@ +package com.sukisu.ultra.ui.susfs.component + +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +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.R +import com.sukisu.ultra.ui.susfs.util.SuSFSManager +import kotlinx.coroutines.launch +import top.yukonga.miuix.kmp.basic.* +import top.yukonga.miuix.kmp.extra.SuperDialog +import top.yukonga.miuix.kmp.theme.MiuixTheme +import java.io.File +import java.text.SimpleDateFormat +import java.util.* + +@Composable +fun BackupRestoreComponent( + isLoading: Boolean, + onLoadingChange: (Boolean) -> Unit, + onConfigReload: () -> Unit +) { + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + + var internalLoading by remember { mutableStateOf(false) } + val actualLoading = isLoading || internalLoading + + var showBackupDialog by remember { mutableStateOf(false) } + var showRestoreDialog by remember { mutableStateOf(false) } + var showRestoreConfirmDialog by remember { mutableStateOf(false) } + var selectedBackupFile by remember { mutableStateOf(null) } + var backupInfo by remember { mutableStateOf(null) } + + // 备份文件选择器 + val backupFileLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument("application/json") + ) { uri -> + uri?.let { fileUri -> + coroutineScope.launch { + try { + internalLoading = true + onLoadingChange(true) + val fileName = SuSFSManager.getDefaultBackupFileName() + val tempFile = File(context.cacheDir, fileName) + + val success = SuSFSManager.createBackup(context, tempFile.absolutePath) + if (success) { + try { + context.contentResolver.openOutputStream(fileUri)?.use { outputStream -> + tempFile.inputStream().use { inputStream -> + inputStream.copyTo(outputStream) + } + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + tempFile.delete() + } + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + internalLoading = false + onLoadingChange(false) + showBackupDialog = false + } + } + } + } + + // 还原文件选择器 + val restoreFileLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenDocument() + ) { uri -> + uri?.let { fileUri -> + coroutineScope.launch { + try { + val tempFile = File(context.cacheDir, "temp_restore.susfs_backup") + context.contentResolver.openInputStream(fileUri)?.use { inputStream -> + tempFile.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } + } + + // 验证备份文件 + val backup = SuSFSManager.validateBackupFile(tempFile.absolutePath) + if (backup != null) { + selectedBackupFile = tempFile.absolutePath + backupInfo = backup + showRestoreConfirmDialog = true + } else { + tempFile.delete() + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + showRestoreDialog = false + } + } + } + } + + // 备份对话框 + BackupDialog( + showDialog = showBackupDialog, + onDismiss = { showBackupDialog = false }, + isLoading = actualLoading, + onBackup = { + val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()) + val timestamp = dateFormat.format(Date()) + backupFileLauncher.launch("SuSFS_Config_$timestamp.susfs_backup") + } + ) + + // 还原对话框 + RestoreDialog( + showDialog = showRestoreDialog, + onDismiss = { showRestoreDialog = false }, + isLoading = actualLoading, + onSelectFile = { + restoreFileLauncher.launch(arrayOf("application/json", "*/*")) + } + ) + + // 还原确认对话框 + RestoreConfirmDialog( + showDialog = showRestoreConfirmDialog, + onDismiss = { + showRestoreConfirmDialog = false + selectedBackupFile = null + backupInfo = null + }, + backupInfo = backupInfo, + isLoading = actualLoading, + onConfirm = { + selectedBackupFile?.let { filePath -> + coroutineScope.launch { + try { + internalLoading = true + onLoadingChange(true) + val success = SuSFSManager.restoreFromBackup(context, filePath) + if (success) { + onConfigReload() + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + internalLoading = false + onLoadingChange(false) + showRestoreConfirmDialog = false + kotlinx.coroutines.delay(100) + selectedBackupFile = null + backupInfo = null + } + } + } + } + ) + + // 按钮行 + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = { showBackupDialog = true }, + enabled = !actualLoading, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(R.string.susfs_backup_title) + ) + } + Button( + onClick = { showRestoreDialog = true }, + enabled = !actualLoading, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(R.string.susfs_restore_title) + ) + } + } + } +} + +@Composable +private fun BackupDialog( + showDialog: Boolean, + onDismiss: () -> Unit, + isLoading: Boolean, + onBackup: () -> Unit +) { + val showDialogState = remember { mutableStateOf(showDialog) } + + LaunchedEffect(showDialog) { + showDialogState.value = showDialog + } + + if (showDialogState.value) { + SuperDialog( + show = showDialogState, + title = stringResource(R.string.susfs_backup_title), + onDismissRequest = onDismiss, + content = { + Column( + modifier = Modifier.padding(horizontal = 24.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text(stringResource(R.string.susfs_backup_description)) + Spacer(modifier = Modifier.height(12.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = onDismiss, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text(stringResource(R.string.cancel)) + } + Button( + onClick = onBackup, + enabled = !isLoading, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text(stringResource(R.string.susfs_backup_create)) + } + } + } + } + ) + } +} + +@Composable +private fun RestoreDialog( + showDialog: Boolean, + onDismiss: () -> Unit, + isLoading: Boolean, + onSelectFile: () -> Unit +) { + val showDialogState = remember { mutableStateOf(showDialog) } + + LaunchedEffect(showDialog) { + showDialogState.value = showDialog + } + + if (showDialogState.value) { + SuperDialog( + show = showDialogState, + title = stringResource(R.string.susfs_restore_title), + onDismissRequest = onDismiss, + content = { + Column( + modifier = Modifier.padding(horizontal = 24.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text(stringResource(R.string.susfs_restore_description)) + Spacer(modifier = Modifier.height(12.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = onDismiss, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text(stringResource(R.string.cancel)) + } + Button( + onClick = onSelectFile, + enabled = !isLoading, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text(stringResource(R.string.susfs_restore_select_file)) + } + } + } + } + ) + } +} + +@Composable +private fun RestoreConfirmDialog( + showDialog: Boolean, + onDismiss: () -> Unit, + backupInfo: SuSFSManager.BackupData?, + isLoading: Boolean, + onConfirm: () -> Unit +) { + val showDialogState = remember { mutableStateOf(showDialog && backupInfo != null) } + + LaunchedEffect(showDialog, backupInfo) { + showDialogState.value = showDialog && backupInfo != null + } + + if (showDialogState.value && backupInfo != null) { + SuperDialog( + show = showDialogState, + title = stringResource(R.string.susfs_restore_confirm_title), + onDismissRequest = onDismiss, + content = { + Column( + modifier = Modifier.padding(horizontal = 24.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text(stringResource(R.string.susfs_restore_confirm_description)) + + Card( + modifier = Modifier.fillMaxWidth(), + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + Text( + text = stringResource( + R.string.susfs_backup_info_date, + dateFormat.format(Date(backupInfo.timestamp)) + ), + fontSize = MiuixTheme.textStyles.body2.fontSize + ) + Text( + text = stringResource(R.string.susfs_backup_info_device, backupInfo.deviceInfo), + fontSize = MiuixTheme.textStyles.body2.fontSize + ) + Text( + text = stringResource(R.string.susfs_backup_info_version, backupInfo.version), + fontSize = MiuixTheme.textStyles.body2.fontSize + ) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = onDismiss, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text(stringResource(R.string.cancel)) + } + Button( + onClick = onConfirm, + enabled = !isLoading, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text(stringResource(R.string.susfs_restore_confirm)) + } + } + } + } + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt index 71d5a63f..b934a159 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigDialogs.kt @@ -42,18 +42,320 @@ import top.yukonga.miuix.kmp.basic.Card import top.yukonga.miuix.kmp.basic.Icon import top.yukonga.miuix.kmp.basic.IconButton 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.SuperDialog -import top.yukonga.miuix.kmp.extra.SuperDropdown import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme import top.yukonga.miuix.kmp.basic.CardDefaults -import top.yukonga.miuix.kmp.basic.Switch +import top.yukonga.miuix.kmp.extra.SuperSwitch + + +// 应用信息缓存 +object AppInfoCache { + private val appInfoMap = mutableMapOf() + + data class CachedAppInfo( + val appName: String, + val packageInfo: PackageInfo?, + val drawable: Drawable?, + val timestamp: Long = System.currentTimeMillis() + ) + + fun getAppInfo(packageName: String): CachedAppInfo? { + return appInfoMap[packageName] + } + + fun putAppInfo(packageName: String, appInfo: CachedAppInfo) { + appInfoMap[packageName] = appInfo + } + + fun clearCache() { + appInfoMap.clear() + } + + fun getAppInfoFromSuperUser(packageName: String): CachedAppInfo? { + val superUserApp = SuperUserViewModel.getAppsSafely().find { it.packageName == packageName } + return superUserApp?.let { app -> + CachedAppInfo( + appName = app.label, + packageInfo = app.packageInfo, + drawable = null + ) + } + } +} + +@Composable +fun AppIcon( + packageName: String, + packageInfo: PackageInfo? = null, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier +) { + val context = LocalContext.current + var iconBitmap by remember(packageName, packageInfo) { mutableStateOf(null) } + var isLoadingIcon by remember(packageName, packageInfo) { mutableStateOf(true) } + + LaunchedEffect(packageName, packageInfo) { + isLoadingIcon = true + iconBitmap = null + + withContext(Dispatchers.IO) { + try { + val drawable = when { + packageInfo != null -> { + packageInfo.applicationInfo?.loadIcon(context.packageManager) + } + else -> { + val cachedInfo = AppInfoCache.getAppInfo(packageName) + if (cachedInfo?.drawable != null) { + cachedInfo.drawable + } else if (cachedInfo?.packageInfo != null) { + cachedInfo.packageInfo.applicationInfo?.loadIcon(context.packageManager) + } else { + // 尝试从 PackageManager 获取 + try { + val packageManager = context.packageManager + val applicationInfo = packageManager.getApplicationInfo(packageName, 0) + val icon = packageManager.getApplicationIcon(applicationInfo) + // 更新缓存 + val newCachedInfo = AppInfoCache.CachedAppInfo( + appName = packageName, + packageInfo = null, + drawable = icon + ) + AppInfoCache.putAppInfo(packageName, newCachedInfo) + icon + } catch (e: Exception) { + Log.d("AppIcon", "获取应用图标失败: $packageName", e) + null + } + } + } + } + + iconBitmap = drawable?.toBitmap()?.asImageBitmap() + } catch (e: Exception) { + Log.d("AppIcon", "获取应用图标失败: $packageName", e) + } finally { + isLoadingIcon = false + } + } + } + + Box( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .background( + if (iconBitmap == null) colorScheme.surfaceVariant.copy(alpha = 0.3f) + else androidx.compose.ui.graphics.Color.Transparent + ), + contentAlignment = Alignment.Center + ) { + if (iconBitmap != null) { + Image( + bitmap = iconBitmap!!, + contentDescription = null, + modifier = Modifier.fillMaxSize() + ) + } else if (!isLoadingIcon) { + // 显示占位图标 + Icon( + imageVector = Icons.Default.Android, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = colorScheme.onSurfaceVariantSummary.copy(alpha = 0.6f) + ) + } + } +} + +@Composable +fun AppPathGroupCard( + packageName: String, + paths: List, + onDeleteGroup: () -> Unit, + onEditGroup: (() -> Unit)? = null, + isLoading: Boolean +) { + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + var superUserApps by remember { mutableStateOf(SuperUserViewModel.getAppsSafely()) } + + LaunchedEffect(Unit) { + snapshotFlow { SuperUserViewModel.apps } + .distinctUntilChanged() + .collect { _ -> + superUserApps = SuperUserViewModel.getAppsSafely() + } + } + + var cachedAppInfo by remember(packageName, superUserApps.size) { + mutableStateOf(AppInfoCache.getAppInfo(packageName)) + } + var isLoadingAppInfo by remember(packageName, superUserApps.size) { mutableStateOf(false) } + + LaunchedEffect(packageName, superUserApps.size) { + if (cachedAppInfo == null || superUserApps.isNotEmpty()) { + isLoadingAppInfo = true + coroutineScope.launch { + try { + val superUserAppInfo = AppInfoCache.getAppInfoFromSuperUser(packageName) + + if (superUserAppInfo != null) { + val packageManager = context.packageManager + val drawable = try { + superUserAppInfo.packageInfo?.applicationInfo?.let { + packageManager.getApplicationIcon(it) + } + } catch (_: Exception) { + null + } + + val newCachedInfo = AppInfoCache.CachedAppInfo( + appName = superUserAppInfo.appName, + packageInfo = superUserAppInfo.packageInfo, + drawable = drawable + ) + + AppInfoCache.putAppInfo(packageName, newCachedInfo) + cachedAppInfo = newCachedInfo + } else { + val packageManager = context.packageManager + val appInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA) + + val appName = try { + appInfo.applicationInfo?.let { + packageManager.getApplicationLabel(it).toString() + } ?: packageName + } catch (_: Exception) { + packageName + } + + val drawable = try { + appInfo.applicationInfo?.let { + packageManager.getApplicationIcon(it) + } + } catch (_: Exception) { + null + } + + val newCachedInfo = AppInfoCache.CachedAppInfo( + appName = appName, + packageInfo = appInfo, + drawable = drawable + ) + + AppInfoCache.putAppInfo(packageName, newCachedInfo) + cachedAppInfo = newCachedInfo + } + } catch (_: Exception) { + val newCachedInfo = AppInfoCache.CachedAppInfo( + appName = packageName, + packageInfo = null, + drawable = null + ) + AppInfoCache.putAppInfo(packageName, newCachedInfo) + cachedAppInfo = newCachedInfo + } finally { + isLoadingAppInfo = false + } + } + } + } + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.defaultColors( + color = colorScheme.surface + ) + ) { + Column( + modifier = Modifier.padding(12.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + // 应用图标 + AppIcon( + packageName = packageName, + packageInfo = cachedAppInfo?.packageInfo, + modifier = Modifier.size(32.dp) + ) + + Spacer(modifier = Modifier.width(12.dp)) + + Column(modifier = Modifier.weight(1f)) { + val displayName = cachedAppInfo?.appName?.ifEmpty { packageName } ?: packageName + Text( + text = displayName, + style = MiuixTheme.textStyles.title2, + fontWeight = FontWeight.Medium, + color = colorScheme.onSurface + ) + if (!isLoadingAppInfo && cachedAppInfo?.appName?.isNotEmpty() == true && + cachedAppInfo?.appName != packageName) { + Text( + text = packageName, + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary + ) + } + } + + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (onEditGroup != null) { + IconButton( + onClick = onEditGroup, + enabled = !isLoading + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(R.string.edit), + tint = colorScheme.primary + ) + } + } + IconButton( + onClick = onDeleteGroup, + enabled = !isLoading + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.delete), + tint = colorScheme.error + ) + } + } + } + + // 显示所有路径 + Spacer(modifier = Modifier.height(8.dp)) + + paths.forEach { path -> + Text( + text = path, + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary, + modifier = Modifier + .fillMaxWidth() + .background( + colorScheme.surfaceVariant.copy(alpha = 0.3f), + RoundedCornerShape(6.dp) + ) + .padding(8.dp) + ) + + if (path != paths.last()) { + Spacer(modifier = Modifier.height(4.dp)) + } + } + } + } +} -/** - * 添加路径对话框 - */ @Composable fun AddPathDialog( showDialog: Boolean, @@ -73,79 +375,33 @@ fun AddPathDialog( } } - val showDialogState = remember { mutableStateOf(showDialog) } - - LaunchedEffect(showDialog) { - showDialogState.value = showDialog - } - - if (showDialogState.value) { - SuperDialog( - show = showDialogState, - title = stringResource(titleRes), - onDismissRequest = { - onDismiss() - newPath = "" - }, - content = { - Column( - modifier = Modifier - .padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextField( - value = newPath, - onValueChange = { value -> newPath = value }, - label = stringResource(labelRes), - useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(android.R.string.cancel), - onClick = { - onDismiss() - newPath = "" - }, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - Button( - onClick = { - if (newPath.isNotBlank()) { - onConfirm(newPath.trim()) - newPath = "" - } - }, - enabled = newPath.isNotBlank() && !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Text( - text = stringResource(if (initialValue.isNotEmpty()) R.string.susfs_save else R.string.add), - style = MiuixTheme.textStyles.body2, - maxLines = 2 - ) - } - } - } + UniversalDialog( + showDialog = showDialog, + onDismiss = onDismiss, + onConfirm = { + if (newPath.isNotBlank()) { + onConfirm(newPath.trim()) + true + } else { + false } - ) - } + }, + titleRes = titleRes, + isLoading = isLoading, + fields = listOf( + DialogField.TextField( + value = newPath, + onValueChange = { newPath = it }, + labelRes = labelRes, + enabled = !isLoading + ) + ), + confirmTextRes = if (initialValue.isNotEmpty()) R.string.susfs_save else R.string.add, + isConfirmEnabled = newPath.isNotBlank() && !isLoading, + onReset = { newPath = "" } + ) } -/** - * 快捷添加应用路径对话框 - */ @Composable fun AddAppPathDialog( showDialog: Boolean, @@ -362,8 +618,7 @@ fun AddAppPathDialog( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - TextButton( - text = stringResource(R.string.cancel), + Button( onClick = { onDismiss() selectedApps = setOf() @@ -372,8 +627,13 @@ fun AddAppPathDialog( modifier = Modifier .weight(1f) .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(R.string.cancel) + ) + } Button( onClick = { if (selectedApps.isNotEmpty()) { @@ -390,9 +650,7 @@ fun AddAppPathDialog( cornerRadius = 8.dp ) { Text( - text = stringResource(R.string.add), - style = MiuixTheme.textStyles.body2, - maxLines = 2 + text = stringResource(R.string.add) ) } } @@ -402,98 +660,6 @@ fun AddAppPathDialog( } } - -/** - * 应用图标组件 - */ -@Composable -fun AppIcon( - packageName: String, - packageInfo: PackageInfo? = null, - @SuppressLint("ModifierParameter") modifier: Modifier = Modifier -) { - val context = LocalContext.current - var iconBitmap by remember(packageName, packageInfo) { mutableStateOf(null) } - var isLoadingIcon by remember(packageName, packageInfo) { mutableStateOf(true) } - - LaunchedEffect(packageName, packageInfo) { - isLoadingIcon = true - iconBitmap = null - - withContext(Dispatchers.IO) { - try { - val drawable = when { - packageInfo != null -> { - packageInfo.applicationInfo?.loadIcon(context.packageManager) - } - else -> { - val cachedInfo = AppInfoCache.getAppInfo(packageName) - if (cachedInfo?.drawable != null) { - cachedInfo.drawable - } else if (cachedInfo?.packageInfo != null) { - cachedInfo.packageInfo.applicationInfo?.loadIcon(context.packageManager) - } else { - // 尝试从 PackageManager 获取 - try { - val packageManager = context.packageManager - val applicationInfo = packageManager.getApplicationInfo(packageName, 0) - val icon = packageManager.getApplicationIcon(applicationInfo) - // 更新缓存 - val newCachedInfo = AppInfoCache.CachedAppInfo( - appName = packageName, - packageInfo = null, - drawable = icon - ) - AppInfoCache.putAppInfo(packageName, newCachedInfo) - icon - } catch (e: Exception) { - Log.d("AppIcon", "获取应用图标失败: $packageName", e) - null - } - } - } - } - - iconBitmap = drawable?.toBitmap()?.asImageBitmap() - } catch (e: Exception) { - Log.d("AppIcon", "获取应用图标失败: $packageName", e) - } finally { - isLoadingIcon = false - } - } - } - - Box( - modifier = modifier - .clip(RoundedCornerShape(8.dp)) - .background( - if (iconBitmap == null) colorScheme.surfaceVariant.copy(alpha = 0.3f) - else androidx.compose.ui.graphics.Color.Transparent - ), - contentAlignment = Alignment.Center - ) { - if (iconBitmap != null) { - Image( - bitmap = iconBitmap!!, - contentDescription = null, - modifier = Modifier.fillMaxSize() - ) - } else if (!isLoadingIcon) { - // 显示占位图标 - Icon( - imageVector = Icons.Default.Android, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = colorScheme.onSurfaceVariantSummary.copy(alpha = 0.6f) - ) - } - } -} - - -/** - * 添加尝试卸载对话框 - */ @Composable fun AddTryUmountDialog( showDialog: Boolean, @@ -514,92 +680,47 @@ fun AddTryUmountDialog( } } - val showDialogState = remember { mutableStateOf(showDialog) } - - LaunchedEffect(showDialog) { - showDialogState.value = showDialog - } - val umountModeItems = listOf( stringResource(R.string.susfs_umount_mode_normal), stringResource(R.string.susfs_umount_mode_detach) ) - if (showDialogState.value) { - SuperDialog( - show = showDialogState, - title = stringResource(if (initialPath.isNotEmpty()) R.string.susfs_edit_try_umount else R.string.susfs_add_try_umount), - onDismissRequest = { - onDismiss() - newUmountPath = "" - newUmountMode = 0 - }, - content = { - Column( - modifier = Modifier - .padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextField( - value = newUmountPath, - onValueChange = { newUmountPath = it }, - label = stringResource(R.string.susfs_path_label), - useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) - - SuperDropdown( - title = stringResource(R.string.susfs_umount_mode_label), - summary = umountModeItems[newUmountMode], - items = umountModeItems, - selectedIndex = newUmountMode, - onSelectedIndexChange = { newUmountMode = it }, - enabled = !isLoading - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(R.string.cancel), - onClick = { - onDismiss() - newUmountPath = "" - newUmountMode = 0 - }, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - Button( - onClick = { - if (newUmountPath.isNotBlank()) { - onConfirm(newUmountPath.trim(), newUmountMode) - newUmountPath = "" - newUmountMode = 0 - } - }, - enabled = newUmountPath.isNotBlank() && !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Text( - text = stringResource(if (initialPath.isNotEmpty()) R.string.susfs_save else R.string.add), - style = MiuixTheme.textStyles.body2, - maxLines = 2 - ) - } - } - } + UniversalDialog( + showDialog = showDialog, + onDismiss = onDismiss, + onConfirm = { + if (newUmountPath.isNotBlank()) { + onConfirm(newUmountPath.trim(), newUmountMode) + true + } else { + false } - ) - } + }, + titleRes = if (initialPath.isNotEmpty()) R.string.susfs_edit_try_umount else R.string.susfs_add_try_umount, + isLoading = isLoading, + fields = listOf( + DialogField.TextField( + value = newUmountPath, + onValueChange = { newUmountPath = it }, + labelRes = R.string.susfs_path_label, + enabled = !isLoading + ), + DialogField.Dropdown( + titleRes = R.string.susfs_umount_mode_label, + summary = umountModeItems[newUmountMode], + items = umountModeItems, + selectedIndex = newUmountMode, + onSelectedIndexChange = { newUmountMode = it }, + enabled = !isLoading + ) + ), + confirmTextRes = if (initialPath.isNotEmpty()) R.string.susfs_save else R.string.add, + isConfirmEnabled = newUmountPath.isNotBlank() && !isLoading, + onReset = { + newUmountPath = "" + newUmountMode = 0 + } + ) } /** @@ -664,405 +785,206 @@ fun AddKstatStaticallyDialog( } } - val showDialogState = remember { mutableStateOf(showDialog) } - - LaunchedEffect(showDialog) { - showDialogState.value = showDialog + val resetFields = { + newKstatPath = "" + newKstatIno = "" + newKstatDev = "" + newKstatNlink = "" + newKstatSize = "" + newKstatAtime = "" + newKstatAtimeNsec = "" + newKstatMtime = "" + newKstatMtimeNsec = "" + newKstatCtime = "" + newKstatCtimeNsec = "" + newKstatBlocks = "" + newKstatBlksize = "" } - if (showDialogState.value) { - SuperDialog( - show = showDialogState, - title = stringResource(if (initialConfig.isNotEmpty()) R.string.edit_kstat_statically_title else R.string.add_kstat_statically_title), - onDismissRequest = { - onDismiss() - // 清空所有字段 - newKstatPath = "" - newKstatIno = "" - newKstatDev = "" - newKstatNlink = "" - newKstatSize = "" - newKstatAtime = "" - newKstatAtimeNsec = "" - newKstatMtime = "" - newKstatMtimeNsec = "" - newKstatCtime = "" - newKstatCtimeNsec = "" - newKstatBlocks = "" - newKstatBlksize = "" - }, - content = { - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) + UniversalDialog( + showDialog = showDialog, + onDismiss = onDismiss, + onConfirm = { + if (newKstatPath.isNotBlank()) { + onConfirm( + newKstatPath.trim(), + newKstatIno.trim().ifBlank { "default" }, + newKstatDev.trim().ifBlank { "default" }, + newKstatNlink.trim().ifBlank { "default" }, + newKstatSize.trim().ifBlank { "default" }, + newKstatAtime.trim().ifBlank { "default" }, + newKstatAtimeNsec.trim().ifBlank { "default" }, + newKstatMtime.trim().ifBlank { "default" }, + newKstatMtimeNsec.trim().ifBlank { "default" }, + newKstatCtime.trim().ifBlank { "default" }, + newKstatCtimeNsec.trim().ifBlank { "default" }, + newKstatBlocks.trim().ifBlank { "default" }, + newKstatBlksize.trim().ifBlank { "default" } + ) + true + } else { + false + } + }, + titleRes = if (initialConfig.isNotEmpty()) R.string.edit_kstat_statically_title else R.string.add_kstat_statically_title, + isLoading = isLoading, + fields = listOf( + DialogField.CustomContent { + TextField( + value = newKstatPath, + onValueChange = { newKstatPath = it }, + label = stringResource(R.string.file_or_directory_path_label), + useLabelAsPlaceholder = true, + modifier = Modifier.fillMaxWidth(), + enabled = !isLoading + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { TextField( - value = newKstatPath, - onValueChange = { newKstatPath = it }, - label = stringResource(R.string.file_or_directory_path_label), + value = newKstatIno, + onValueChange = { newKstatIno = it }, + label = "ino", useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.weight(1f), enabled = !isLoading ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextField( - value = newKstatIno, - onValueChange = { newKstatIno = it }, - label = "ino", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - TextField( - value = newKstatDev, - onValueChange = { newKstatDev = it }, - label = "dev", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextField( - value = newKstatNlink, - onValueChange = { newKstatNlink = it }, - label = "nlink", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - TextField( - value = newKstatSize, - onValueChange = { newKstatSize = it }, - label = "size", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextField( - value = newKstatAtime, - onValueChange = { newKstatAtime = it }, - label = "atime", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - TextField( - value = newKstatAtimeNsec, - onValueChange = { newKstatAtimeNsec = it }, - label = "atime_nsec", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextField( - value = newKstatMtime, - onValueChange = { newKstatMtime = it }, - label = "mtime", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - TextField( - value = newKstatMtimeNsec, - onValueChange = { newKstatMtimeNsec = it }, - label = "mtime_nsec", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextField( - value = newKstatCtime, - onValueChange = { newKstatCtime = it }, - label = "ctime", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - TextField( - value = newKstatCtimeNsec, - onValueChange = { newKstatCtimeNsec = it }, - label = "ctime_nsec", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - TextField( - value = newKstatBlocks, - onValueChange = { newKstatBlocks = it }, - label = "blocks", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - TextField( - value = newKstatBlksize, - onValueChange = { newKstatBlksize = it }, - label = "blksize", - useLabelAsPlaceholder = true, - modifier = Modifier.weight(1f), - enabled = !isLoading - ) - } - - Text( - text = stringResource(R.string.hint_use_default_value), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary + TextField( + value = newKstatDev, + onValueChange = { newKstatDev = it }, + label = "dev", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(R.string.cancel), - onClick = { - onDismiss() - // 清空所有字段 - newKstatPath = "" - newKstatIno = "" - newKstatDev = "" - newKstatNlink = "" - newKstatSize = "" - newKstatAtime = "" - newKstatAtimeNsec = "" - newKstatMtime = "" - newKstatMtimeNsec = "" - newKstatCtime = "" - newKstatCtimeNsec = "" - newKstatBlocks = "" - newKstatBlksize = "" - }, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - Button( - onClick = { - if (newKstatPath.isNotBlank()) { - onConfirm( - newKstatPath.trim(), - newKstatIno.trim().ifBlank { "default" }, - newKstatDev.trim().ifBlank { "default" }, - newKstatNlink.trim().ifBlank { "default" }, - newKstatSize.trim().ifBlank { "default" }, - newKstatAtime.trim().ifBlank { "default" }, - newKstatAtimeNsec.trim().ifBlank { "default" }, - newKstatMtime.trim().ifBlank { "default" }, - newKstatMtimeNsec.trim().ifBlank { "default" }, - newKstatCtime.trim().ifBlank { "default" }, - newKstatCtimeNsec.trim().ifBlank { "default" }, - newKstatBlocks.trim().ifBlank { "default" }, - newKstatBlksize.trim().ifBlank { "default" } - ) - // 清空所有字段 - newKstatPath = "" - newKstatIno = "" - newKstatDev = "" - newKstatNlink = "" - newKstatSize = "" - newKstatAtime = "" - newKstatAtimeNsec = "" - newKstatMtime = "" - newKstatMtimeNsec = "" - newKstatCtime = "" - newKstatCtimeNsec = "" - newKstatBlocks = "" - newKstatBlksize = "" - } - }, - enabled = newKstatPath.isNotBlank() && !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Text( - text = stringResource(if (initialConfig.isNotEmpty()) R.string.susfs_save else R.string.add), - style = MiuixTheme.textStyles.body2, - maxLines = 2 - ) - } - } } - } - ) - } -} -/** - * 确认对话框 - */ -@Composable -fun ConfirmDialog( - showDialog: Boolean, - onDismiss: () -> Unit, - onConfirm: () -> Unit, - titleRes: Int, - messageRes: Int, - isLoading: Boolean = false -) { - val showDialogState = remember { mutableStateOf(showDialog) } - - LaunchedEffect(showDialog) { - showDialogState.value = showDialog - } - - if (showDialogState.value) { - SuperDialog( - show = showDialogState, - title = stringResource(titleRes), - onDismissRequest = onDismiss, - content = { - Column( - modifier = Modifier - .padding(horizontal = 24.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( - text = stringResource(messageRes), - style = MiuixTheme.textStyles.body2 + TextField( + value = newKstatNlink, + onValueChange = { newKstatNlink = it }, + label = "nlink", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + TextField( + value = newKstatSize, + onValueChange = { newKstatSize = it }, + label = "size", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextButton( - text = stringResource(R.string.cancel), - onClick = onDismiss, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) - Button( - onClick = onConfirm, - enabled = !isLoading, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Text( - text = stringResource(R.string.confirm), - style = MiuixTheme.textStyles.body2, - maxLines = 2 - ) - } - } } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TextField( + value = newKstatAtime, + onValueChange = { newKstatAtime = it }, + label = "atime", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + TextField( + value = newKstatAtimeNsec, + onValueChange = { newKstatAtimeNsec = it }, + label = "atime_nsec", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TextField( + value = newKstatMtime, + onValueChange = { newKstatMtime = it }, + label = "mtime", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + TextField( + value = newKstatMtimeNsec, + onValueChange = { newKstatMtimeNsec = it }, + label = "mtime_nsec", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TextField( + value = newKstatCtime, + onValueChange = { newKstatCtime = it }, + label = "ctime", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + TextField( + value = newKstatCtimeNsec, + onValueChange = { newKstatCtimeNsec = it }, + label = "ctime_nsec", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TextField( + value = newKstatBlocks, + onValueChange = { newKstatBlocks = it }, + label = "blocks", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + TextField( + value = newKstatBlksize, + onValueChange = { newKstatBlksize = it }, + label = "blksize", + useLabelAsPlaceholder = true, + modifier = Modifier.weight(1f), + enabled = !isLoading + ) + } + + Text( + text = stringResource(R.string.hint_use_default_value), + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary + ) } - ) - } -} - -// 应用信息缓存 -object AppInfoCache { - private val appInfoMap = mutableMapOf() - - data class CachedAppInfo( - val appName: String, - val packageInfo: PackageInfo?, - val drawable: Drawable?, - val timestamp: Long = System.currentTimeMillis() - ) - - fun getAppInfo(packageName: String): CachedAppInfo? { - return appInfoMap[packageName] - } - - fun putAppInfo(packageName: String, appInfo: CachedAppInfo) { - appInfoMap[packageName] = appInfo - } - - fun clearCache() { - appInfoMap.clear() - } - - fun getAppInfoFromSuperUser(packageName: String): CachedAppInfo? { - val superUserApp = SuperUserViewModel.getAppsSafely().find { it.packageName == packageName } - return superUserApp?.let { app -> - CachedAppInfo( - appName = app.label, - packageInfo = app.packageInfo, - drawable = null - ) - } - } -} - -/** - * 空状态显示组件 - */ -@Composable -fun EmptyStateCard( - message: String, - modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.15f) ), - cornerRadius = 8.dp - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(20.dp), - contentAlignment = Alignment.Center - ) { - Text( - text = message, - style = MiuixTheme.textStyles.body1, - color = colorScheme.onSurfaceVariantSummary, - textAlign = TextAlign.Center - ) - } - } + confirmTextRes = if (initialConfig.isNotEmpty()) R.string.susfs_save else R.string.add, + isConfirmEnabled = newKstatPath.isNotBlank() && !isLoading, + scrollable = true, + onReset = resetFields + ) } -/** - * 路径项目卡片组件 - */ @Composable fun PathItemCard( path: String, @@ -1149,9 +1071,6 @@ fun PathItemCard( } } -/** - * Kstat配置项目卡片组件 - */ @Composable fun KstatConfigItemCard( config: String, @@ -1348,9 +1267,6 @@ fun AddKstatPathItemCard( } } -/** - * 启用功能状态卡片组件 - */ @Composable fun FeatureStatusCard( feature: SuSFSManager.EnabledFeature, @@ -1392,28 +1308,18 @@ fun FeatureStatusCard( color = colorScheme.onSurfaceVariantSummary ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.susfs_enable_log_label), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium - ) - Switch( - checked = logEnabled, - onCheckedChange = { logEnabled = it } - ) - } + SuperSwitch( + title = stringResource(R.string.susfs_enable_log_label), + summary = "", + checked = logEnabled, + onCheckedChange = { checked -> logEnabled = checked } + ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { - TextButton( - text = stringResource(R.string.cancel), + Button( onClick = { // 恢复原始状态 logEnabled = SuSFSManager.getEnableLogState(context) @@ -1422,8 +1328,13 @@ fun FeatureStatusCard( modifier = Modifier .weight(1f) .heightIn(min = 48.dp) - .padding(vertical = 8.dp) - ) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(R.string.cancel) + ) + } Button( onClick = { coroutineScope.launch { @@ -1440,9 +1351,7 @@ fun FeatureStatusCard( cornerRadius = 8.dp ) { Text( - text = stringResource(R.string.susfs_apply), - style = MiuixTheme.textStyles.body2, - maxLines = 2 + text = stringResource(R.string.susfs_apply) ) } } @@ -1522,9 +1431,6 @@ fun FeatureStatusCard( } } -/** - * SUS挂载隐藏控制卡片组件 - */ @Composable fun SusMountHidingControlCard( hideSusMountsForAllProcs: Boolean, @@ -1538,7 +1444,7 @@ fun SusMountHidingControlCard( ) ) { Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { // 标题行 @@ -1568,39 +1474,26 @@ fun SusMountHidingControlCard( lineHeight = 16.sp ) - // 控制开关行 - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = stringResource(R.string.susfs_hide_mounts_for_all_procs_label), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface + // 控制开关 + SuperSwitch( + title = stringResource(R.string.susfs_hide_mounts_for_all_procs_label), + summary = if (hideSusMountsForAllProcs) { + stringResource(R.string.susfs_hide_mounts_for_all_procs_enabled_description) + } else { + stringResource(R.string.susfs_hide_mounts_for_all_procs_disabled_description) + }, + leftAction = { + Icon( + if (hideSusMountsForAllProcs) Icons.Default.VisibilityOff else Icons.Default.Visibility, + modifier = Modifier.padding(end = 16.dp), + contentDescription = stringResource(R.string.susfs_hide_mounts_for_all_procs_label), + tint = colorScheme.onBackground ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = if (hideSusMountsForAllProcs) { - stringResource(R.string.susfs_hide_mounts_for_all_procs_enabled_description) - } else { - stringResource(R.string.susfs_hide_mounts_for_all_procs_disabled_description) - }, - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 14.sp - ) - } - Switch( - checked = hideSusMountsForAllProcs, - onCheckedChange = onToggleHiding, - enabled = !isLoading - ) - } + }, + checked = hideSusMountsForAllProcs, + onCheckedChange = onToggleHiding, + enabled = !isLoading + ) // 当前设置显示 Text( @@ -1636,256 +1529,3 @@ fun SusMountHidingControlCard( } } } - -/** - * 应用路径分组卡片 - */ -@Composable -fun AppPathGroupCard( - packageName: String, - paths: List, - onDeleteGroup: () -> Unit, - onEditGroup: (() -> Unit)? = null, - isLoading: Boolean -) { - val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() - var superUserApps by remember { mutableStateOf(SuperUserViewModel.getAppsSafely()) } - - LaunchedEffect(Unit) { - snapshotFlow { SuperUserViewModel.apps } - .distinctUntilChanged() - .collect { _ -> - superUserApps = SuperUserViewModel.getAppsSafely() - } - } - - var cachedAppInfo by remember(packageName, superUserApps.size) { - mutableStateOf(AppInfoCache.getAppInfo(packageName)) - } - var isLoadingAppInfo by remember(packageName, superUserApps.size) { mutableStateOf(false) } - - LaunchedEffect(packageName, superUserApps.size) { - if (cachedAppInfo == null || superUserApps.isNotEmpty()) { - isLoadingAppInfo = true - coroutineScope.launch { - try { - val superUserAppInfo = AppInfoCache.getAppInfoFromSuperUser(packageName) - - if (superUserAppInfo != null) { - val packageManager = context.packageManager - val drawable = try { - superUserAppInfo.packageInfo?.applicationInfo?.let { - packageManager.getApplicationIcon(it) - } - } catch (_: Exception) { - null - } - - val newCachedInfo = AppInfoCache.CachedAppInfo( - appName = superUserAppInfo.appName, - packageInfo = superUserAppInfo.packageInfo, - drawable = drawable - ) - - AppInfoCache.putAppInfo(packageName, newCachedInfo) - cachedAppInfo = newCachedInfo - } else { - val packageManager = context.packageManager - val appInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA) - - val appName = try { - appInfo.applicationInfo?.let { - packageManager.getApplicationLabel(it).toString() - } ?: packageName - } catch (_: Exception) { - packageName - } - - val drawable = try { - appInfo.applicationInfo?.let { - packageManager.getApplicationIcon(it) - } - } catch (_: Exception) { - null - } - - val newCachedInfo = AppInfoCache.CachedAppInfo( - appName = appName, - packageInfo = appInfo, - drawable = drawable - ) - - AppInfoCache.putAppInfo(packageName, newCachedInfo) - cachedAppInfo = newCachedInfo - } - } catch (_: Exception) { - val newCachedInfo = AppInfoCache.CachedAppInfo( - appName = packageName, - packageInfo = null, - drawable = null - ) - AppInfoCache.putAppInfo(packageName, newCachedInfo) - cachedAppInfo = newCachedInfo - } finally { - isLoadingAppInfo = false - } - } - } - } - - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surface - ) - ) { - Column( - modifier = Modifier.padding(12.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - // 应用图标 - AppIcon( - packageName = packageName, - packageInfo = cachedAppInfo?.packageInfo, - modifier = Modifier.size(32.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Column(modifier = Modifier.weight(1f)) { - val displayName = cachedAppInfo?.appName?.ifEmpty { packageName } ?: packageName - Text( - text = displayName, - style = MiuixTheme.textStyles.title2, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface - ) - if (!isLoadingAppInfo && cachedAppInfo?.appName?.isNotEmpty() == true && - cachedAppInfo?.appName != packageName) { - Text( - text = packageName, - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary - ) - } - } - - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - if (onEditGroup != null) { - IconButton( - onClick = onEditGroup, - enabled = !isLoading - ) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = stringResource(R.string.edit), - tint = colorScheme.primary - ) - } - } - IconButton( - onClick = onDeleteGroup, - enabled = !isLoading - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = stringResource(R.string.delete), - tint = colorScheme.error - ) - } - } - } - - // 显示所有路径 - Spacer(modifier = Modifier.height(8.dp)) - - paths.forEach { path -> - Text( - text = path, - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - modifier = Modifier - .fillMaxWidth() - .background( - colorScheme.surfaceVariant.copy(alpha = 0.3f), - RoundedCornerShape(6.dp) - ) - .padding(8.dp) - ) - - if (path != paths.last()) { - Spacer(modifier = Modifier.height(4.dp)) - } - } - } - } -} - -/** - * 分组标题组件 - */ -@Composable -fun SectionHeader( - title: String, - subtitle: String?, - icon: ImageVector, - count: Int -) { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.25f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(14.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = icon, - contentDescription = null, - tint = colorScheme.primary, - modifier = Modifier.size(22.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - style = MiuixTheme.textStyles.title3, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface - ) - subtitle?.let { - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = it, - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary - ) - } - } - Box( - modifier = Modifier - .clip(RoundedCornerShape(12.dp)) - .background(colorScheme.primaryContainer) - .padding(horizontal = 10.dp, vertical = 5.dp) - ) { - Text( - text = count.toString(), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onPrimaryContainer, - fontWeight = FontWeight.Medium - ) - } - } - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt deleted file mode 100644 index e6e2b17b..00000000 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/SuSFSConfigTabs.kt +++ /dev/null @@ -1,1183 +0,0 @@ -package com.sukisu.ultra.ui.susfs.component - -import android.annotation.SuppressLint -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.layout.Column -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.runtime.Composable -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.compose.runtime.snapshotFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.sukisu.ultra.R -import com.sukisu.ultra.ui.susfs.util.SuSFSManager -import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel -import top.yukonga.miuix.kmp.basic.Button -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.TextField -import top.yukonga.miuix.kmp.theme.MiuixTheme -import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme -import top.yukonga.miuix.kmp.basic.CardDefaults -import top.yukonga.miuix.kmp.basic.Switch -/** - * SUS路径内容组件 - */ -@Composable -fun SusPathsContent( - susPaths: Set, - isLoading: Boolean, - onAddPath: () -> Unit, - onAddAppPath: () -> Unit, - onRemovePath: (String) -> Unit, - onEditPath: ((String) -> Unit)? = null, - forceRefreshApps: Boolean = false, - onReset: (() -> Unit)? = null -) { - var superUserApps by remember { mutableStateOf(SuperUserViewModel.getAppsSafely()) } - - LaunchedEffect(Unit) { - snapshotFlow { SuperUserViewModel.apps } - .distinctUntilChanged() - .collect { _ -> - superUserApps = SuperUserViewModel.getAppsSafely() - if (superUserApps.isNotEmpty()) { - try { - AppInfoCache.clearCache() - } catch (_: Exception) { - } - } - } - } - - LaunchedEffect(forceRefreshApps) { - if (forceRefreshApps) { - try { - AppInfoCache.clearCache() - } catch (_: Exception) { - // Ignore cache clear errors - } - } - } - - val (appPathGroups, otherPaths) = remember(susPaths, superUserApps) { - val appPathRegex = Regex(".*/Android/data/([^/]+)/?.*") - val uidPathRegex = Regex("/sys/fs/cgroup/uid_([0-9]+)") - val appPathMap = mutableMapOf>() - val uidToPackageMap = mutableMapOf() - val others = mutableListOf() - - // 构建UID到包名的映射 - try { - superUserApps.forEach { app: SuperUserViewModel.AppInfo -> - try { - val uid = app.packageInfo.applicationInfo?.uid - if (uid != null) { - uidToPackageMap[uid.toString()] = app.packageName - } - } catch (_: Exception) { - // Ignore individual app errors - } - } - } catch (_: Exception) { - // Ignore mapping errors - } - - susPaths.forEach { path -> - val appDataMatch = appPathRegex.find(path) - val uidMatch = uidPathRegex.find(path) - - when { - appDataMatch != null -> { - val packageName = appDataMatch.groupValues[1] - appPathMap.getOrPut(packageName) { mutableListOf() }.add(path) - } - uidMatch != null -> { - val uid = uidMatch.groupValues[1] - val packageName = uidToPackageMap[uid] - if (packageName != null) { - appPathMap.getOrPut(packageName) { mutableListOf() }.add(path) - } else { - others.add(path) - } - } - else -> { - others.add(path) - } - } - } - - val sortedAppGroups = appPathMap.toList() - .sortedBy { it.first } - .map { (packageName, paths) -> packageName to paths.sorted() } - - Pair(sortedAppGroups, others.sorted()) - } - - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - // 应用路径分组 - if (appPathGroups.isNotEmpty()) { - SectionHeader( - title = stringResource(R.string.app_paths_section), - subtitle = null, - icon = Icons.Default.Apps, - count = appPathGroups.size - ) - - appPathGroups.forEach { (packageName, paths) -> - AppPathGroupCard( - packageName = packageName, - paths = paths, - onDeleteGroup = { - paths.forEach { path -> onRemovePath(path) } - }, - onEditGroup = if (onEditPath != null) { - { - onEditPath(paths.first()) - } - } else null, - isLoading = isLoading - ) - } - } - - // 其他路径 - if (otherPaths.isNotEmpty()) { - SectionHeader( - title = stringResource(R.string.other_paths_section), - subtitle = null, - icon = Icons.Default.Folder, - count = otherPaths.size - ) - - otherPaths.forEach { path -> - PathItemCard( - path = path, - icon = Icons.Default.Folder, - onDelete = { onRemovePath(path) }, - onEdit = if (onEditPath != null) { { onEditPath(path) } } else null, - isLoading = isLoading - ) - } - } - - if (susPaths.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.susfs_no_paths_configured) - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onAddPath, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add_custom_path), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - - Button( - onClick = onAddAppPath, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Apps, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add_app_path), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - } - - // 重置按钮 - if (onReset != null && susPaths.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_paths_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * SUS循环路径内容组件 - */ -@Composable -fun SusLoopPathsContent( - susLoopPaths: Set, - isLoading: Boolean, - onAddLoopPath: () -> Unit, - onRemoveLoopPath: (String) -> Unit, - onEditLoopPath: ((String) -> Unit)? = null, - onReset: (() -> Unit)? = null -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - // 说明卡片 - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Column( - modifier = Modifier.padding(14.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(R.string.sus_loop_paths_description_title), - style = MiuixTheme.textStyles.title3, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - Text( - text = stringResource(R.string.sus_loop_paths_description_text), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - Text( - text = stringResource(R.string.susfs_loop_path_restriction_warning), - style = MiuixTheme.textStyles.body2, - color = colorScheme.secondary, - fontWeight = FontWeight.Medium - ) - } - } - - if (susLoopPaths.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.susfs_no_loop_paths_configured) - ) - } else { - SectionHeader( - title = stringResource(R.string.loop_paths_section), - subtitle = null, - icon = Icons.Default.Loop, - count = susLoopPaths.size - ) - - susLoopPaths.toList().forEach { path -> - PathItemCard( - path = path, - icon = Icons.Default.Loop, - onDelete = { onRemoveLoopPath(path) }, - onEdit = if (onEditLoopPath != null) { { onEditLoopPath(path) } } else null, - isLoading = isLoading - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onAddLoopPath, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add_loop_path), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - } - - // 重置按钮 - if (onReset != null && susLoopPaths.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_loop_paths_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * SUS Maps内容组件 - */ -@Composable -fun SusMapsContent( - susMaps: Set, - isLoading: Boolean, - onAddSusMap: () -> Unit, - onRemoveSusMap: (String) -> Unit, - onEditSusMap: ((String) -> Unit)? = null, - onReset: (() -> Unit)? = null -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - // 说明卡片 - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Column( - modifier = Modifier.padding(14.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - text = stringResource(R.string.sus_maps_description_title), - style = MiuixTheme.textStyles.title3, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - Text( - text = stringResource(R.string.sus_maps_description_text), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - Text( - text = stringResource(R.string.sus_maps_warning), - style = MiuixTheme.textStyles.body2, - color = colorScheme.secondary, - fontWeight = FontWeight.Medium - ) - Text( - text = stringResource(R.string.sus_maps_debug_info), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary.copy(alpha = 0.8f), - lineHeight = 16.sp - ) - } - } - - if (susMaps.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.susfs_no_sus_maps_configured) - ) - } else { - SectionHeader( - title = stringResource(R.string.sus_maps_section), - subtitle = null, - icon = Icons.Default.Security, - count = susMaps.size - ) - - susMaps.toList().forEach { map -> - PathItemCard( - path = map, - icon = Icons.Default.Security, - onDelete = { onRemoveSusMap(map) }, - onEdit = if (onEditSusMap != null) { { onEditSusMap(map) } } else null, - isLoading = isLoading - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onAddSusMap, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - } - - // 重置按钮 - if (onReset != null && susMaps.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_sus_maps_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * SUS挂载内容组件 - */ -@Composable -fun SusMountsContent( - susMounts: Set, - hideSusMountsForAllProcs: Boolean, - isLoading: Boolean, - onAddMount: () -> Unit, - onRemoveMount: (String) -> Unit, - onEditMount: ((String) -> Unit)? = null, - onToggleHideSusMountsForAllProcs: (Boolean) -> Unit, - onReset: (() -> Unit)? = null -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - SusMountHidingControlCard( - hideSusMountsForAllProcs = hideSusMountsForAllProcs, - isLoading = isLoading, - onToggleHiding = onToggleHideSusMountsForAllProcs - ) - - if (susMounts.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.susfs_no_mounts_configured) - ) - } else { - susMounts.toList().forEach { mount -> - PathItemCard( - path = mount, - icon = Icons.Default.Storage, - onDelete = { onRemoveMount(mount) }, - onEdit = if (onEditMount != null) { { onEditMount(mount) } } else null, - isLoading = isLoading - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onAddMount, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - } - - // 重置按钮 - if (onReset != null && susMounts.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_mounts_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * 尝试卸载内容组件 - */ -@Composable -fun TryUmountContent( - tryUmounts: Set, - umountForZygoteIsoService: Boolean, - isLoading: Boolean, - onAddUmount: () -> Unit, - onRemoveUmount: (String) -> Unit, - onEditUmount: ((String) -> Unit)? = null, - onToggleUmountForZygoteIsoService: (Boolean) -> Unit, - onReset: (() -> Unit)? = null -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surface - ) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier.weight(1f) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Security, - contentDescription = null, - tint = colorScheme.primary, - modifier = Modifier.size(18.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.umount_zygote_iso_service), - style = MiuixTheme.textStyles.title2, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface - ) - } - Spacer(modifier = Modifier.height(6.dp)) - Text( - text = stringResource(R.string.umount_zygote_iso_service_description), - style = MiuixTheme.textStyles.body1, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 14.sp - ) - } - Switch( - checked = umountForZygoteIsoService, - onCheckedChange = onToggleUmountForZygoteIsoService, - enabled = !isLoading - ) - } - } - - if (tryUmounts.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.susfs_no_umounts_configured) - ) - } else { - tryUmounts.toList().forEach { umountEntry -> - val parts = umountEntry.split("|") - val path = if (parts.isNotEmpty()) parts[0] else umountEntry - val mode = if (parts.size > 1) parts[1] else "0" - val modeText = if (mode == "0") - stringResource(R.string.susfs_umount_mode_normal_short) - else - stringResource(R.string.susfs_umount_mode_detach_short) - - PathItemCard( - path = path, - icon = Icons.Default.Storage, - additionalInfo = stringResource(R.string.susfs_umount_mode_display, modeText, mode), - onDelete = { onRemoveUmount(umountEntry) }, - onEdit = if (onEditUmount != null) { { onEditUmount(umountEntry) } } else null, - isLoading = isLoading - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onAddUmount, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - } - - // 重置按钮 - if (onReset != null && tryUmounts.isNotEmpty()) { - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_umounts_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * Kstat配置内容组件 - */ -@Composable -fun KstatConfigContent( - kstatConfigs: Set, - addKstatPaths: Set, - isLoading: Boolean, - onAddKstatStatically: () -> Unit, - onAddKstat: () -> Unit, - onRemoveKstatConfig: (String) -> Unit, - onEditKstatConfig: ((String) -> Unit)? = null, - onRemoveAddKstat: (String) -> Unit, - onEditAddKstat: ((String) -> Unit)? = null, - onUpdateKstat: (String) -> Unit, - onUpdateKstatFullClone: (String) -> Unit -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Column( - modifier = Modifier.padding(14.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - Text( - text = stringResource(R.string.kstat_config_description_title), - style = MiuixTheme.textStyles.title3, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - Text( - text = stringResource(R.string.kstat_config_description_add_statically), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - Text( - text = stringResource(R.string.kstat_config_description_add), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - Text( - text = stringResource(R.string.kstat_config_description_update), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - Text( - text = stringResource(R.string.kstat_config_description_update_full_clone), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - } - } - - if (kstatConfigs.isNotEmpty()) { - SectionHeader( - title = stringResource(R.string.static_kstat_config), - subtitle = null, - icon = Icons.Default.Settings, - count = kstatConfigs.size - ) - kstatConfigs.toList().forEach { config -> - KstatConfigItemCard( - config = config, - onDelete = { onRemoveKstatConfig(config) }, - onEdit = if (onEditKstatConfig != null) { { onEditKstatConfig(config) } } else null, - isLoading = isLoading - ) - } - } - - if (addKstatPaths.isNotEmpty()) { - SectionHeader( - title = stringResource(R.string.kstat_path_management), - subtitle = null, - icon = Icons.Default.Folder, - count = addKstatPaths.size - ) - addKstatPaths.toList().forEach { path -> - AddKstatPathItemCard( - path = path, - onDelete = { onRemoveAddKstat(path) }, - onEdit = if (onEditAddKstat != null) { { onEditAddKstat(path) } } else null, - onUpdate = { onUpdateKstat(path) }, - onUpdateFullClone = { onUpdateKstatFullClone(path) }, - isLoading = isLoading - ) - } - } - - if (kstatConfigs.isEmpty() && addKstatPaths.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.no_kstat_config_message) - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = onAddKstat, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - - Button( - onClick = onAddKstatStatically, - modifier = Modifier - .weight(1f) - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - cornerRadius = 8.dp - ) { - Icon( - imageVector = Icons.Default.Settings, - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(R.string.add), - style = MiuixTheme.textStyles.body1, - maxLines = 2 - ) - } - } - } -} - -/** - * 路径设置内容组件 - */ -@SuppressLint("SdCardPath") -@Composable -fun PathSettingsContent( - androidDataPath: String, - onAndroidDataPathChange: (String) -> Unit, - sdcardPath: String, - onSdcardPathChange: (String) -> Unit, - isLoading: Boolean, - onSetAndroidDataPath: () -> Unit, - onSetSdcardPath: () -> Unit, - onReset: (() -> Unit)? = null -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextField( - value = androidDataPath, - onValueChange = onAndroidDataPathChange, - label = stringResource(R.string.susfs_android_data_path_label), - useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) - - Button( - onClick = onSetAndroidDataPath, - enabled = !isLoading && androidDataPath.isNotBlank(), - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - ) { - Text( - stringResource(R.string.susfs_set_android_data_path), - maxLines = 2 - ) - } - } - } - - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Column( - modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - TextField( - value = sdcardPath, - onValueChange = onSdcardPathChange, - label = stringResource(R.string.susfs_sdcard_path_label), - useLabelAsPlaceholder = true, - modifier = Modifier.fillMaxWidth(), - enabled = !isLoading - ) - - Button( - onClick = onSetSdcardPath, - enabled = !isLoading && sdcardPath.isNotBlank(), - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 48.dp) - .padding(vertical = 8.dp), - ) { - Text( - stringResource(R.string.susfs_set_sdcard_path), - maxLines = 2 - ) - } - } - } - - // 重置按钮 - if (onReset != null) { - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onReset, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.RestoreFromTrash, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.susfs_reset_path_title), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } - } -} - -/** - * 启用功能状态内容组件 - */ -@Composable -fun EnabledFeaturesContent( - enabledFeatures: List, - onRefresh: () -> Unit -) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(12.dp) - ) { - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(14.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Settings, - contentDescription = null, - tint = colorScheme.primary, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(10.dp)) - Text( - text = stringResource(R.string.susfs_enabled_features_description), - style = MiuixTheme.textStyles.body2, - color = colorScheme.onSurfaceVariantSummary, - lineHeight = 18.sp - ) - } - } - - if (enabledFeatures.isEmpty()) { - EmptyStateCard( - message = stringResource(R.string.susfs_no_features_found) - ) - } else { - enabledFeatures.forEach { feature -> - FeatureStatusCard( - feature = feature, - onRefresh = onRefresh - ) - } - } - - // 刷新按钮 - Spacer(modifier = Modifier.height(16.dp)) - Card( - onClick = onRefresh, - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.defaultColors( - color = colorScheme.surfaceVariant.copy(alpha = 0.3f) - ), - cornerRadius = 8.dp - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Refresh, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.refresh), - style = MiuixTheme.textStyles.body1, - fontWeight = FontWeight.Medium, - color = colorScheme.primary - ) - } - } - } -} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/UniversalDialog.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/UniversalDialog.kt new file mode 100644 index 00000000..04bf844f --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/component/UniversalDialog.kt @@ -0,0 +1,351 @@ +package com.sukisu.ultra.ui.susfs.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sukisu.ultra.R +import top.yukonga.miuix.kmp.basic.Button +import top.yukonga.miuix.kmp.basic.Card +import top.yukonga.miuix.kmp.basic.CardDefaults +import top.yukonga.miuix.kmp.basic.Icon +import top.yukonga.miuix.kmp.basic.Text +import top.yukonga.miuix.kmp.basic.TextField +import top.yukonga.miuix.kmp.extra.SuperDialog +import top.yukonga.miuix.kmp.extra.SuperDropdown +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme + +sealed class DialogField { + data class TextField( + val value: String, + val onValueChange: (String) -> Unit, + val labelRes: Int, + val enabled: Boolean = true, + val modifier: Modifier = Modifier.fillMaxWidth() + ) : DialogField() + + data class Dropdown( + val titleRes: Int, + val summary: String, + val items: List, + val selectedIndex: Int, + val onSelectedIndexChange: (Int) -> Unit, + val enabled: Boolean = true + ) : DialogField() + + data class CustomContent( + val content: @Composable ColumnScope.() -> Unit + ) : DialogField() +} + +/** + * 通用多功能对话框组件 + * + * @param showDialog 是否显示对话框 + * @param onDismiss 关闭对话框回调 + * @param onConfirm 确认回调,返回是否应该关闭对话框 + * @param titleRes 标题资源ID + * @param isLoading 是否正在加载 + * @param fields 对话框字段列表 + * @param confirmTextRes 确认按钮文本资源ID,默认为"添加" + * @param cancelTextRes 取消按钮文本资源ID,默认为"取消" + * @param isConfirmEnabled 确认按钮是否启用,默认为true + * @param scrollable 内容是否可滚动,默认为false + * @param onReset 重置回调,用于清空字段 + */ +@Composable +fun UniversalDialog( + showDialog: Boolean, + onDismiss: () -> Unit, + onConfirm: () -> Boolean, + titleRes: Int, + isLoading: Boolean = false, + fields: List, + confirmTextRes: Int = R.string.add, + cancelTextRes: Int = R.string.cancel, + isConfirmEnabled: Boolean = true, + scrollable: Boolean = false, + onReset: (() -> Unit)? = null +) { + val showDialogState = remember { mutableStateOf(showDialog) } + + LaunchedEffect(showDialog) { + showDialogState.value = showDialog + } + + if (showDialogState.value) { + SuperDialog( + show = showDialogState, + title = stringResource(titleRes), + onDismissRequest = { + onDismiss() + onReset?.invoke() + }, + content = { + val contentModifier = if (scrollable) { + Modifier + .verticalScroll(rememberScrollState()) + .padding(horizontal = 24.dp) + } else { + Modifier.padding(horizontal = 24.dp) + } + + Column( + modifier = contentModifier, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + fields.forEach { field -> + when (field) { + is DialogField.TextField -> { + TextField( + value = field.value, + onValueChange = field.onValueChange, + label = stringResource(field.labelRes), + useLabelAsPlaceholder = true, + modifier = field.modifier, + enabled = field.enabled && !isLoading + ) + } + is DialogField.Dropdown -> { + SuperDropdown( + title = stringResource(field.titleRes), + summary = field.summary, + items = field.items, + selectedIndex = field.selectedIndex, + onSelectedIndexChange = field.onSelectedIndexChange, + enabled = field.enabled && !isLoading + ) + } + is DialogField.CustomContent -> { + field.content.invoke(this) + } + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = { + onDismiss() + onReset?.invoke() + }, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(cancelTextRes) + ) + } + Button( + onClick = { + if (onConfirm()) { + onDismiss() + onReset?.invoke() + } + }, + enabled = isConfirmEnabled && !isLoading, + modifier = Modifier + .weight(1f) + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(confirmTextRes) + ) + } + } + } + } + ) + } +} + +@Composable +fun DescriptionCard( + title: String, + description: String, + modifier: Modifier = Modifier, + warning: String? = null, + additionalInfo: String? = null +) { + Card( + modifier = modifier.fillMaxWidth(), + colors = CardDefaults.defaultColors( + color = colorScheme.surfaceVariant.copy(alpha = 0.3f) + ), + cornerRadius = 8.dp + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Text( + text = title, + style = MiuixTheme.textStyles.body1, + fontWeight = FontWeight.Medium, + color = colorScheme.primary + ) + Text( + text = description, + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary, + lineHeight = 16.sp + ) + warning?.let { + Text( + text = it, + style = MiuixTheme.textStyles.body2, + color = colorScheme.secondary, + fontWeight = FontWeight.Medium, + lineHeight = 16.sp + ) + } + additionalInfo?.let { + Text( + text = it, + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary.copy(alpha = 0.8f), + lineHeight = 14.sp + ) + } + } + } +} + +@Composable +fun ConfirmDialog( + showDialog: Boolean, + onDismiss: () -> Unit, + onConfirm: () -> Unit, + titleRes: Int, + messageRes: Int, + isLoading: Boolean = false +) { + UniversalDialog( + showDialog = showDialog, + onDismiss = onDismiss, + onConfirm = { + onConfirm() + true + }, + titleRes = titleRes, + isLoading = isLoading, + fields = listOf( + DialogField.CustomContent { + Text( + text = stringResource(messageRes), + style = MiuixTheme.textStyles.body2 + ) + } + ), + confirmTextRes = R.string.confirm, + cancelTextRes = R.string.cancel, + isConfirmEnabled = !isLoading + ) +} + +@Composable +fun EmptyStateCard( + message: String, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + colors = CardDefaults.defaultColors( + color = colorScheme.surfaceVariant.copy(alpha = 0.15f) + ), + cornerRadius = 8.dp + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = message, + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary, + textAlign = TextAlign.Center + ) + } + } +} + +@Composable +fun SectionHeader( + title: String, + subtitle: String?, + icon: ImageVector, + count: Int +) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.defaultColors( + color = colorScheme.surfaceVariant.copy(alpha = 0.25f) + ), + cornerRadius = 8.dp + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = colorScheme.primary, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MiuixTheme.textStyles.body1, + fontWeight = FontWeight.Medium, + color = colorScheme.onSurface + ) + subtitle?.let { + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = it, + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary + ) + } + } + Box( + modifier = Modifier + .clip(RoundedCornerShape(12.dp)) + .background(colorScheme.primaryContainer) + .padding(horizontal = 10.dp, vertical = 5.dp) + ) { + Text( + text = count.toString(), + style = MiuixTheme.textStyles.body2.copy(fontSize = 12.sp), + color = colorScheme.onPrimaryContainer, + fontWeight = FontWeight.Medium + ) + } + } + } +} diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/BasicSettingsContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/BasicSettingsContent.kt new file mode 100644 index 00000000..c12c52c3 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/BasicSettingsContent.kt @@ -0,0 +1,313 @@ +package com.sukisu.ultra.ui.susfs.content + +import android.content.Context +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.produceState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.BackupRestoreComponent +import com.sukisu.ultra.ui.susfs.component.DescriptionCard +import com.sukisu.ultra.ui.susfs.component.ResetButton +import com.sukisu.ultra.ui.susfs.util.SuSFSManager +import com.sukisu.ultra.ui.util.isAbDevice +import top.yukonga.miuix.kmp.basic.* +import top.yukonga.miuix.kmp.extra.SuperDropdown +import top.yukonga.miuix.kmp.extra.SuperSwitch +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme + +@Composable +fun BasicSettingsContent( + unameValue: String, + onUnameValueChange: (String) -> Unit, + buildTimeValue: String, + onBuildTimeValueChange: (String) -> Unit, + executeInPostFsData: Boolean, + onExecuteInPostFsDataChange: (Boolean) -> Unit, + autoStartEnabled: Boolean, + canEnableAutoStart: Boolean, + isLoading: Boolean, + onAutoStartToggle: (Boolean) -> Unit, + onShowSlotInfo: () -> Unit, + context: Context, + enableHideBl: Boolean, + onEnableHideBlChange: (Boolean) -> Unit, + enableCleanupResidue: Boolean, + onEnableCleanupResidueChange: (Boolean) -> Unit, + enableAvcLogSpoofing: Boolean, + onEnableAvcLogSpoofingChange: (Boolean) -> Unit, + onReset: (() -> Unit)? = null, + onApply: (() -> Unit)? = null, + onConfigReload: () -> Unit +) { + val isAbDevice = produceState(initialValue = false) { + value = isAbDevice() + }.value + + // 执行位置选择 + val locationItems = listOf( + stringResource(R.string.susfs_execution_location_service), + stringResource(R.string.susfs_execution_location_post_fs_data) + ) + + // 说明卡片 + DescriptionCard( + title = stringResource(R.string.susfs_config_description), + description = stringResource(R.string.susfs_config_description_text) + ) + + // Uname输入框 + TextField( + value = unameValue, + onValueChange = onUnameValueChange, + label = stringResource(R.string.susfs_uname_label), + useLabelAsPlaceholder = true, + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + enabled = !isLoading + ) + + // 构建时间伪装输入框 + TextField( + value = buildTimeValue, + onValueChange = onBuildTimeValueChange, + label = stringResource(R.string.susfs_build_time_label), + useLabelAsPlaceholder = true, + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + enabled = !isLoading + ) + + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + SuperDropdown( + title = stringResource(R.string.susfs_execution_location_label), + summary = if (executeInPostFsData) { + stringResource(R.string.susfs_execution_location_post_fs_data) + } else { + stringResource(R.string.susfs_execution_location_service) + }, + items = locationItems, + selectedIndex = if (executeInPostFsData) 1 else 0, + onSelectedIndexChange = { index -> + onExecuteInPostFsDataChange(index == 1) + }, + enabled = !isLoading, + leftAction = { + Icon( + Icons.Default.LocationOn, + contentDescription = null, + tint = colorScheme.primary, + modifier = Modifier.padding(end = 16.dp) + ) + } + ) + } + + // 当前值显示 + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Text( + text = stringResource(R.string.susfs_current_value, SuSFSManager.getUnameValue(context)), + style = MiuixTheme.textStyles.body2.copy(fontSize = 13.sp), + color = colorScheme.onSurfaceVariantSummary + ) + Text( + text = stringResource(R.string.susfs_current_build_time, SuSFSManager.getBuildTimeValue(context)), + style = MiuixTheme.textStyles.body2.copy(fontSize = 13.sp), + color = colorScheme.onSurfaceVariantSummary + ) + Text( + text = stringResource(R.string.susfs_current_execution_location, if (SuSFSManager.getExecuteInPostFsData(context)) "Post-FS-Data" else "Service"), + style = MiuixTheme.textStyles.body2.copy(fontSize = 13.sp), + color = colorScheme.onSurfaceVariantSummary + ) + } + } + + // 应用按钮 + if (onApply != null) { + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + Button( + onClick = { onApply() }, + enabled = !isLoading && (unameValue.isNotBlank() || buildTimeValue.isNotBlank()), + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 48.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(R.string.susfs_apply) + ) + } + } + } + + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + // 开机自启动开关 + SuperSwitch( + title = stringResource(R.string.susfs_autostart_title), + summary = if (canEnableAutoStart) { + stringResource(R.string.susfs_autostart_description) + } else { + stringResource(R.string.susfs_autostart_requirement) + }, + leftAction = { + Icon( + Icons.Default.AutoMode, + modifier = Modifier.padding(end = 16.dp), + contentDescription = stringResource(R.string.susfs_autostart_title), + tint = if (canEnableAutoStart) colorScheme.onBackground else colorScheme.onSurfaceVariantSummary + ) + }, + checked = autoStartEnabled, + onCheckedChange = onAutoStartToggle, + enabled = !isLoading && canEnableAutoStart + ) + + // 隐藏BL脚本开关 + SuperSwitch( + title = stringResource(R.string.hide_bl_script), + summary = stringResource(R.string.hide_bl_script_description), + leftAction = { + Icon( + Icons.Default.Security, + modifier = Modifier.padding(end = 16.dp), + contentDescription = stringResource(R.string.hide_bl_script), + tint = colorScheme.onBackground + ) + }, + checked = enableHideBl, + onCheckedChange = onEnableHideBlChange, + enabled = !isLoading + ) + + // 清理残留脚本开关 + SuperSwitch( + title = stringResource(R.string.cleanup_residue), + summary = stringResource(R.string.cleanup_residue_description), + leftAction = { + Icon( + Icons.Default.CleaningServices, + modifier = Modifier.padding(end = 16.dp), + contentDescription = stringResource(R.string.cleanup_residue), + tint = colorScheme.onBackground + ) + }, + checked = enableCleanupResidue, + onCheckedChange = onEnableCleanupResidueChange, + enabled = !isLoading + ) + + // AVC日志欺骗开关 + SuperSwitch( + title = stringResource(R.string.avc_log_spoofing), + summary = stringResource(R.string.avc_log_spoofing_description), + leftAction = { + Icon( + Icons.Default.VisibilityOff, + modifier = Modifier.padding(end = 16.dp), + contentDescription = stringResource(R.string.avc_log_spoofing), + tint = colorScheme.onBackground + ) + }, + checked = enableAvcLogSpoofing, + onCheckedChange = onEnableAvcLogSpoofingChange, + enabled = !isLoading + ) + } + + // 槽位信息按钮 + if (isAbDevice) { + Card( + modifier = Modifier + .padding(top = 12.dp) + .fillMaxWidth(), + ) { + Column( + modifier = Modifier.padding(14.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + Icons.Default.Info, + contentDescription = null, + tint = colorScheme.primary, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = stringResource(R.string.susfs_slot_info_title), + style = MiuixTheme.textStyles.title3, + fontWeight = FontWeight.Medium, + color = colorScheme.onBackground + ) + } + Text( + text = stringResource(R.string.susfs_slot_info_description), + style = MiuixTheme.textStyles.body2.copy(fontSize = 13.sp), + color = colorScheme.onSurfaceVariantSummary, + lineHeight = 16.sp + ) + Button( + onClick = onShowSlotInfo, + enabled = !isLoading, + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 48.dp) + .padding(vertical = 8.dp), + cornerRadius = 8.dp + ) { + Text( + text = stringResource(R.string.susfs_slot_info_title) + ) + } + } + } + } + + BackupRestoreComponent( + isLoading = isLoading, + onLoadingChange = { }, + onConfigReload = onConfigReload + ) + + // 重置按钮 + if (onReset != null) { + ResetButton( + title = stringResource(R.string.susfs_reset_confirm_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/EnabledFeaturesContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/EnabledFeaturesContent.kt new file mode 100644 index 00000000..bb37ada5 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/EnabledFeaturesContent.kt @@ -0,0 +1,79 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.FeatureStatusCard +import com.sukisu.ultra.ui.susfs.util.SuSFSManager +import top.yukonga.miuix.kmp.basic.* +import top.yukonga.miuix.kmp.basic.CardDefaults +import top.yukonga.miuix.kmp.theme.MiuixTheme +import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme + +@Composable +fun EnabledFeaturesContent( + enabledFeatures: List, + onRefresh: () -> Unit +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.defaultColors( + color = colorScheme.surfaceVariant.copy(alpha = 0.3f) + ), + cornerRadius = 8.dp + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = null, + tint = colorScheme.primary, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = stringResource(R.string.susfs_enabled_features_description), + style = MiuixTheme.textStyles.body2, + color = colorScheme.onSurfaceVariantSummary, + lineHeight = 16.sp + ) + } + } + + if (enabledFeatures.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_features_found) + ) + } else { + enabledFeatures.forEach { feature -> + FeatureStatusCard( + feature = feature, + onRefresh = onRefresh + ) + } + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.refresh), + onPrimaryClick = onRefresh + ) +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/KstatConfigContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/KstatConfigContent.kt new file mode 100644 index 00000000..4c51eecd --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/KstatConfigContent.kt @@ -0,0 +1,99 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.AddKstatPathItemCard +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.DescriptionCard +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.KstatConfigItemCard +import com.sukisu.ultra.ui.susfs.component.SectionHeader + +@Composable +fun KstatConfigContent( + kstatConfigs: Set, + addKstatPaths: Set, + isLoading: Boolean, + onAddKstatStatically: () -> Unit, + onAddKstat: () -> Unit, + onRemoveKstatConfig: (String) -> Unit, + onEditKstatConfig: ((String) -> Unit)? = null, + onRemoveAddKstat: (String) -> Unit, + onEditAddKstat: ((String) -> Unit)? = null, + onUpdateKstat: (String) -> Unit, + onUpdateKstatFullClone: (String) -> Unit +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + DescriptionCard( + title = stringResource(R.string.kstat_config_description_title), + description = stringResource(R.string.kstat_config_description_add_statically) + "\n" + + stringResource(R.string.kstat_config_description_add) + "\n" + + stringResource(R.string.kstat_config_description_update) + "\n" + + stringResource(R.string.kstat_config_description_update_full_clone) + ) + + if (kstatConfigs.isNotEmpty()) { + SectionHeader( + title = stringResource(R.string.static_kstat_config), + subtitle = null, + icon = Icons.Default.Settings, + count = kstatConfigs.size + ) + kstatConfigs.toList().forEach { config -> + KstatConfigItemCard( + config = config, + onDelete = { onRemoveKstatConfig(config) }, + onEdit = if (onEditKstatConfig != null) { + { onEditKstatConfig(config) } + } else null, + isLoading = isLoading + ) + } + } + + if (addKstatPaths.isNotEmpty()) { + SectionHeader( + title = stringResource(R.string.kstat_path_management), + subtitle = null, + icon = Icons.Default.Folder, + count = addKstatPaths.size + ) + addKstatPaths.toList().forEach { path -> + AddKstatPathItemCard( + path = path, + onDelete = { onRemoveAddKstat(path) }, + onEdit = if (onEditAddKstat != null) { + { onEditAddKstat(path) } + } else null, + onUpdate = { onUpdateKstat(path) }, + onUpdateFullClone = { onUpdateKstatFullClone(path) }, + isLoading = isLoading + ) + } + } + + if (kstatConfigs.isEmpty() && addKstatPaths.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.no_kstat_config_message) + ) + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.add), + onPrimaryClick = onAddKstat, + secondaryButtonText = stringResource(R.string.add), + onSecondaryClick = onAddKstatStatically, + isLoading = isLoading + ) +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/PathSettingsContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/PathSettingsContent.kt new file mode 100644 index 00000000..fefc0264 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/PathSettingsContent.kt @@ -0,0 +1,80 @@ +package com.sukisu.ultra.ui.susfs.content + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.ResetButton +import top.yukonga.miuix.kmp.basic.* + +@SuppressLint("SdCardPath") +@Composable +fun PathSettingsContent( + androidDataPath: String, + onAndroidDataPathChange: (String) -> Unit, + sdcardPath: String, + onSdcardPathChange: (String) -> Unit, + isLoading: Boolean, + onSetAndroidDataPath: () -> Unit, + onSetSdcardPath: () -> Unit, + onReset: (() -> Unit)? = null +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Card( + modifier = Modifier.fillMaxWidth(), + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + TextField( + value = androidDataPath, + onValueChange = onAndroidDataPathChange, + label = stringResource(R.string.susfs_android_data_path_label), + useLabelAsPlaceholder = true, + modifier = Modifier.fillMaxWidth(), + cornerRadius = 16.dp, + enabled = !isLoading + ) + + TextField( + value = sdcardPath, + onValueChange = onSdcardPathChange, + label = stringResource(R.string.susfs_sdcard_path_label), + useLabelAsPlaceholder = true, + modifier = Modifier.fillMaxWidth(), + cornerRadius = 16.dp, + enabled = !isLoading + ) + + Button( + onClick = { + onSetAndroidDataPath() + onSetSdcardPath() + }, + enabled = !isLoading && androidDataPath.isNotBlank() && sdcardPath.isNotBlank(), + modifier = Modifier.fillMaxWidth(), + cornerRadius = 16.dp + ) { + Text( + text = stringResource(R.string.susfs_apply) + ) + } + } + } + } + + if (onReset != null) { + ResetButton( + title = stringResource(R.string.susfs_reset_path_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusLoopPathsContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusLoopPathsContent.kt new file mode 100644 index 00000000..f382605f --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusLoopPathsContent.kt @@ -0,0 +1,77 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Loop +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.DescriptionCard +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.PathItemCard +import com.sukisu.ultra.ui.susfs.component.ResetButton +import com.sukisu.ultra.ui.susfs.component.SectionHeader + +@Composable +fun SusLoopPathsContent( + susLoopPaths: Set, + isLoading: Boolean, + onAddLoopPath: () -> Unit, + onRemoveLoopPath: (String) -> Unit, + onEditLoopPath: ((String) -> Unit)? = null, + onReset: (() -> Unit)? = null +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // 说明卡片 + DescriptionCard( + title = stringResource(R.string.sus_loop_paths_description_title), + description = stringResource(R.string.sus_loop_paths_description_text), + warning = stringResource(R.string.susfs_loop_path_restriction_warning) + ) + + if (susLoopPaths.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_loop_paths_configured) + ) + } else { + SectionHeader( + title = stringResource(R.string.loop_paths_section), + subtitle = null, + icon = Icons.Default.Loop, + count = susLoopPaths.size + ) + + susLoopPaths.toList().forEach { path -> + PathItemCard( + path = path, + icon = Icons.Default.Loop, + onDelete = { onRemoveLoopPath(path) }, + onEdit = if (onEditLoopPath != null) { + { onEditLoopPath(path) } + } else null, + isLoading = isLoading + ) + } + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.add_loop_path), + onPrimaryClick = onAddLoopPath, + isLoading = isLoading + ) + + if (onReset != null && susLoopPaths.isNotEmpty()) { + ResetButton( + title = stringResource(R.string.susfs_reset_loop_paths_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMapsContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMapsContent.kt new file mode 100644 index 00000000..086fa969 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMapsContent.kt @@ -0,0 +1,78 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Security +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.DescriptionCard +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.PathItemCard +import com.sukisu.ultra.ui.susfs.component.ResetButton +import com.sukisu.ultra.ui.susfs.component.SectionHeader + +@Composable +fun SusMapsContent( + susMaps: Set, + isLoading: Boolean, + onAddSusMap: () -> Unit, + onRemoveSusMap: (String) -> Unit, + onEditSusMap: ((String) -> Unit)? = null, + onReset: (() -> Unit)? = null +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // 说明卡片 + DescriptionCard( + title = stringResource(R.string.sus_maps_description_title), + description = stringResource(R.string.sus_maps_description_text), + warning = stringResource(R.string.sus_maps_warning), + additionalInfo = stringResource(R.string.sus_maps_debug_info) + ) + + if (susMaps.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_sus_maps_configured) + ) + } else { + SectionHeader( + title = stringResource(R.string.sus_maps_section), + subtitle = null, + icon = Icons.Default.Security, + count = susMaps.size + ) + + susMaps.toList().forEach { map -> + PathItemCard( + path = map, + icon = Icons.Default.Security, + onDelete = { onRemoveSusMap(map) }, + onEdit = if (onEditSusMap != null) { + { onEditSusMap(map) } + } else null, + isLoading = isLoading + ) + } + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.add), + onPrimaryClick = onAddSusMap, + isLoading = isLoading + ) + + if (onReset != null && susMaps.isNotEmpty()) { + ResetButton( + title = stringResource(R.string.susfs_reset_sus_maps_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMountsContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMountsContent.kt new file mode 100644 index 00000000..1d766180 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusMountsContent.kt @@ -0,0 +1,70 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Storage +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.PathItemCard +import com.sukisu.ultra.ui.susfs.component.ResetButton +import com.sukisu.ultra.ui.susfs.component.SusMountHidingControlCard + +@Composable +fun SusMountsContent( + susMounts: Set, + hideSusMountsForAllProcs: Boolean, + isLoading: Boolean, + onAddMount: () -> Unit, + onRemoveMount: (String) -> Unit, + onEditMount: ((String) -> Unit)? = null, + onToggleHideSusMountsForAllProcs: (Boolean) -> Unit, + onReset: (() -> Unit)? = null +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + SusMountHidingControlCard( + hideSusMountsForAllProcs = hideSusMountsForAllProcs, + isLoading = isLoading, + onToggleHiding = onToggleHideSusMountsForAllProcs + ) + + if (susMounts.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_mounts_configured) + ) + } else { + susMounts.toList().forEach { mount -> + PathItemCard( + path = mount, + icon = Icons.Default.Storage, + onDelete = { onRemoveMount(mount) }, + onEdit = if (onEditMount != null) { + { onEditMount(mount) } + } else null, + isLoading = isLoading + ) + } + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.add), + onPrimaryClick = onAddMount, + isLoading = isLoading + ) + + if (onReset != null && susMounts.isNotEmpty()) { + ResetButton( + title = stringResource(R.string.susfs_reset_mounts_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusPathsContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusPathsContent.kt new file mode 100644 index 00000000..c53873e9 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/SusPathsContent.kt @@ -0,0 +1,192 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.Composable +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.compose.runtime.snapshotFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.AppInfoCache +import com.sukisu.ultra.ui.susfs.component.AppPathGroupCard +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.PathItemCard +import com.sukisu.ultra.ui.susfs.component.ResetButton +import com.sukisu.ultra.ui.susfs.component.SectionHeader +import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel + +@Composable +fun SusPathsContent( + susPaths: Set, + isLoading: Boolean, + onAddPath: () -> Unit, + onAddAppPath: () -> Unit, + onRemovePath: (String) -> Unit, + onEditPath: ((String) -> Unit)? = null, + forceRefreshApps: Boolean = false, + onReset: (() -> Unit)? = null +) { + var superUserApps by remember { mutableStateOf(SuperUserViewModel.getAppsSafely()) } + + LaunchedEffect(Unit) { + snapshotFlow { SuperUserViewModel.apps } + .distinctUntilChanged() + .collect { _ -> + superUserApps = SuperUserViewModel.getAppsSafely() + if (superUserApps.isNotEmpty()) { + try { + AppInfoCache.clearCache() + } catch (_: Exception) { + } + } + } + } + + LaunchedEffect(forceRefreshApps) { + if (forceRefreshApps) { + try { + AppInfoCache.clearCache() + } catch (_: Exception) { + // Ignore cache clear errors + } + } + } + + val (appPathGroups, otherPaths) = remember(susPaths, superUserApps) { + val appPathRegex = Regex(".*/Android/data/([^/]+)/?.*") + val uidPathRegex = Regex("/sys/fs/cgroup/uid_([0-9]+)") + val appPathMap = mutableMapOf>() + val uidToPackageMap = mutableMapOf() + val others = mutableListOf() + + // 构建UID到包名的映射 + try { + superUserApps.forEach { app: SuperUserViewModel.AppInfo -> + try { + val uid = app.packageInfo.applicationInfo?.uid + if (uid != null) { + uidToPackageMap[uid.toString()] = app.packageName + } + } catch (_: Exception) { + // Ignore individual app errors + } + } + } catch (_: Exception) { + // Ignore mapping errors + } + + susPaths.forEach { path -> + val appDataMatch = appPathRegex.find(path) + val uidMatch = uidPathRegex.find(path) + + when { + appDataMatch != null -> { + val packageName = appDataMatch.groupValues[1] + appPathMap.getOrPut(packageName) { mutableListOf() }.add(path) + } + uidMatch != null -> { + val uid = uidMatch.groupValues[1] + val packageName = uidToPackageMap[uid] + if (packageName != null) { + appPathMap.getOrPut(packageName) { mutableListOf() }.add(path) + } else { + others.add(path) + } + } + else -> { + others.add(path) + } + } + } + + val sortedAppGroups = appPathMap.toList() + .sortedBy { it.first } + .map { (packageName, paths) -> packageName to paths.sorted() } + + Pair(sortedAppGroups, others.sorted()) + } + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + // 应用路径分组 + if (appPathGroups.isNotEmpty()) { + SectionHeader( + title = stringResource(R.string.app_paths_section), + subtitle = null, + icon = Icons.Default.Apps, + count = appPathGroups.size + ) + + appPathGroups.forEach { (packageName, paths) -> + AppPathGroupCard( + packageName = packageName, + paths = paths, + onDeleteGroup = { + paths.forEach { path -> onRemovePath(path) } + }, + onEditGroup = if (onEditPath != null) { + { + onEditPath(paths.first()) + } + } else null, + isLoading = isLoading + ) + } + } + + // 其他路径 + if (otherPaths.isNotEmpty()) { + SectionHeader( + title = stringResource(R.string.other_paths_section), + subtitle = null, + icon = Icons.Default.Folder, + count = otherPaths.size + ) + + otherPaths.forEach { path -> + PathItemCard( + path = path, + icon = Icons.Default.Folder, + onDelete = { onRemovePath(path) }, + onEdit = if (onEditPath != null) { + { onEditPath(path) } + } else null, + isLoading = isLoading + ) + } + } + + if (susPaths.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_paths_configured) + ) + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.add_custom_path), + onPrimaryClick = onAddPath, + secondaryButtonText = stringResource(R.string.susfs_apply), + onSecondaryClick = onAddAppPath, + isLoading = isLoading + ) + + if (onReset != null && susPaths.isNotEmpty()) { + ResetButton( + title = stringResource(R.string.susfs_reset_paths_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/TryUmountContent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/TryUmountContent.kt new file mode 100644 index 00000000..10d2c8c0 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/content/TryUmountContent.kt @@ -0,0 +1,99 @@ +package com.sukisu.ultra.ui.susfs.content + +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sukisu.ultra.R +import com.sukisu.ultra.ui.susfs.component.BottomActionButtons +import com.sukisu.ultra.ui.susfs.component.EmptyStateCard +import com.sukisu.ultra.ui.susfs.component.PathItemCard +import com.sukisu.ultra.ui.susfs.component.ResetButton +import top.yukonga.miuix.kmp.basic.* +import top.yukonga.miuix.kmp.extra.SuperSwitch +import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme + +@Composable +fun TryUmountContent( + tryUmounts: Set, + umountForZygoteIsoService: Boolean, + isLoading: Boolean, + onAddUmount: () -> Unit, + onRemoveUmount: (String) -> Unit, + onEditUmount: ((String) -> Unit)? = null, + onToggleUmountForZygoteIsoService: (Boolean) -> Unit, + onReset: (() -> Unit)? = null +) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Card( + modifier = Modifier.fillMaxWidth(), + ) { + SuperSwitch( + title = stringResource(R.string.umount_zygote_iso_service), + summary = stringResource(R.string.umount_zygote_iso_service_description), + leftAction = { + Icon( + Icons.Default.Security, + modifier = Modifier.padding(end = 16.dp), + contentDescription = stringResource(R.string.umount_zygote_iso_service), + tint = colorScheme.onBackground + ) + }, + checked = umountForZygoteIsoService, + onCheckedChange = onToggleUmountForZygoteIsoService, + enabled = !isLoading + ) + } + + if (tryUmounts.isEmpty()) { + EmptyStateCard( + message = stringResource(R.string.susfs_no_umounts_configured) + ) + } else { + tryUmounts.toList().forEach { umountEntry -> + val parts = umountEntry.split("|") + val path = if (parts.isNotEmpty()) parts[0] else umountEntry + val mode = if (parts.size > 1) parts[1] else "0" + val modeText = if (mode == "0") + stringResource(R.string.susfs_umount_mode_normal_short) + else + stringResource(R.string.susfs_umount_mode_detach_short) + + PathItemCard( + path = path, + icon = Icons.Default.Storage, + additionalInfo = stringResource( + R.string.susfs_umount_mode_display, + modeText, + mode + ), + onDelete = { onRemoveUmount(umountEntry) }, + onEdit = if (onEditUmount != null) { + { onEditUmount(umountEntry) } + } else null, + isLoading = isLoading + ) + } + } + } + + BottomActionButtons( + primaryButtonText = stringResource(R.string.add), + onPrimaryClick = onAddUmount, + isLoading = isLoading + ) + + if (onReset != null && tryUmounts.isNotEmpty()) { + ResetButton( + title = stringResource(R.string.susfs_reset_umounts_title), + onClick = onReset + ) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt index 7a0c085e..bf88d42d 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSManager.kt @@ -14,9 +14,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import java.io.File -import java.io.FileOutputStream -import java.io.IOException import androidx.core.content.edit import com.sukisu.ultra.ui.util.getRootShell import com.sukisu.ultra.ui.util.getSuSFSVersion @@ -27,13 +24,12 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import org.json.JSONArray import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.io.IOException import java.text.SimpleDateFormat import java.util.* -/** - * SuSFS 配置管理器 - * 用于管理SuSFS相关的配置和命令执行 - */ object SuSFSManager { private const val PREFS_NAME = "susfs_config" private const val KEY_UNAME_VALUE = "uname_value" @@ -57,20 +53,16 @@ object SuSFSManager { private const val KEY_UMOUNT_FOR_ZYGOTE_ISO_SERVICE = "umount_for_zygote_iso_service" private const val KEY_ENABLE_AVC_LOG_SPOOFING = "enable_avc_log_spoofing" - // 常量 - private const val SUSFS_BINARY_TARGET_NAME = "ksu_susfs" private const val DEFAULT_UNAME = "default" private const val DEFAULT_BUILD_TIME = "default" - private const val MODULE_ID = "susfs_manager" - private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID" const val MAX_SUSFS_VERSION = "2.0.0" private const val BACKUP_FILE_EXTENSION = ".susfs_backup" private const val MEDIA_DATA_PATH = "/data/media/0/Android/data" private const val CGROUP_UID_PATH_PREFIX = "/sys/fs/cgroup/uid_" + private const val SUSFS_BINARY_TARGET_NAME = "ksu_susfs" data class SlotInfo(val slotName: String, val uname: String, val buildTime: String) - data class CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "") data class EnabledFeature( val name: String, val isEnabled: Boolean, @@ -89,9 +81,6 @@ object SuSFSManager { } } - /** - * 应用信息数据类 - */ data class AppInfo( val packageName: String, val appName: String, @@ -99,9 +88,6 @@ object SuSFSManager { val isSystemApp: Boolean ) - /** - * 备份数据类 - */ data class BackupData( val version: String, val timestamp: Long, @@ -153,9 +139,6 @@ object SuSFSManager { } } - /** - * 模块配置数据类 - */ data class ModuleConfig( val targetPath: String, val unameValue: String, @@ -197,25 +180,49 @@ object SuSFSManager { private fun getPrefs(context: Context): SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - private fun getSuSFSVersionUse(context: Context): String = try { - val version = getSuSFSVersion() - val binaryName = "${SUSFS_BINARY_TARGET_NAME}_${version.removePrefix("v")}" - if (isBinaryAvailable(context, binaryName)) { - version - } else { + private fun getSuSFSBinaryName(): String { + val version = try { + getSuSFSVersion() + } catch (_: Exception) { MAX_SUSFS_VERSION } - } catch (_: Exception) { - MAX_SUSFS_VERSION + val versionSuffix = version.removePrefix("v") + return "${SUSFS_BINARY_TARGET_NAME}_$versionSuffix" } - fun isBinaryAvailable(context: Context, binaryName: String): Boolean = try { - context.assets.open(binaryName).use { true } - } catch (_: IOException) { false } + fun getSuSFSTargetPath(): String = "/data/adb/ksu/bin/$SUSFS_BINARY_TARGET_NAME" - private fun getSuSFSBinaryName(context: Context): String = "${SUSFS_BINARY_TARGET_NAME}_${getSuSFSVersionUse(context).removePrefix("v")}" + suspend fun copyBinaryFromAssets(context: Context): String? = withContext(Dispatchers.IO) { + try { + val binaryName = getSuSFSBinaryName() + val targetPath = getSuSFSTargetPath() + val tempFile = File(context.cacheDir, binaryName) + + context.assets.open(binaryName).use { input -> + FileOutputStream(tempFile).use { output -> + input.copyTo(output) + } + } + + val shell = Shell.getShell() + val success = shell.newJob() + .add("cp '${tempFile.absolutePath}' '$targetPath'") + .add("chmod 755 '$targetPath'") + .exec().isSuccess + + tempFile.delete() + + if (success && shell.newJob().add("test -f '$targetPath'").exec().isSuccess) { + targetPath + } else { + null + } + } catch (e: IOException) { + Log.e("SuSFSManager", "Failed to copy binary", e) + null + } + } - private fun getSuSFSTargetPath(): String = "/data/adb/ksu/bin/$SUSFS_BINARY_TARGET_NAME" private fun runCmd(shell: Shell, cmd: String): String { return shell.newJob() @@ -225,16 +232,31 @@ object SuSFSManager { .joinToString("\n") } - private fun runCmdWithResult(cmd: String): CommandResult { + private fun runCmdWithResult(cmd: String): SuSFSModuleManager.CommandResult { val result = Shell.getShell().newJob().add(cmd).exec() - return CommandResult(result.isSuccess, result.out.joinToString("\n"), result.err.joinToString("\n")) + return SuSFSModuleManager.CommandResult(result.isSuccess, result.out.joinToString("\n"), result.err.joinToString("\n")) + } + + private suspend fun executeSusfsCommandDirect(context: Context, command: String): SuSFSModuleManager.CommandResult = withContext(Dispatchers.IO) { + try { + val binaryPath = copyBinaryFromAssets(context) ?: return@withContext SuSFSModuleManager.CommandResult( + false, "", context.getString(R.string.susfs_binary_not_found) + ) + val shell = Shell.getShell() + val result = shell.newJob().add("$binaryPath $command").exec() + SuSFSModuleManager.CommandResult( + isSuccess = result.isSuccess, + output = result.out.joinToString("\n"), + errorOutput = result.err.joinToString("\n") + ) + } catch (e: Exception) { + Log.e("SuSFSManager", "Command execution failed", e) + SuSFSModuleManager.CommandResult(false, "", e.message ?: "Unknown error") + } } - /** - * 获取当前模块配置 - */ - private fun getCurrentModuleConfig(context: Context): ModuleConfig { + fun getCurrentModuleConfig(context: Context): ModuleConfig { return ModuleConfig( targetPath = getSuSFSTargetPath(), unameValue = getUnameValue(context), @@ -286,6 +308,9 @@ object SuSFSManager { fun getExecuteInPostFsData(context: Context): Boolean = getPrefs(context).getBoolean(KEY_EXECUTE_IN_POST_FS_DATA, false) + fun saveExecuteInPostFsData(context: Context, enabled: Boolean) = + getPrefs(context).edit { putBoolean(KEY_EXECUTE_IN_POST_FS_DATA, enabled) } + // SUS挂载隐藏控制 fun saveHideSusMountsForAllProcs(context: Context, hideForAll: Boolean) = getPrefs(context).edit { putBoolean(KEY_HIDE_SUS_MOUNTS_FOR_ALL_PROCS, hideForAll) } @@ -609,21 +634,26 @@ object SuSFSManager { // 还原配置到SharedPreferences private fun restoreConfigurations(context: Context, configurations: Map) { - val prefs = getPrefs(context) - prefs.edit { - configurations.forEach { (key, value) -> - when (value) { - is String -> putString(key, value) - is Boolean -> putBoolean(key, value) - is Set<*> -> { - @Suppress("UNCHECKED_CAST") - putStringSet(key, value as Set) + try { + val prefs = getPrefs(context) + prefs.edit { + configurations.forEach { (key, value) -> + when (value) { + is String -> putString(key, value) + is Boolean -> putBoolean(key, value) + is Set<*> -> { + @Suppress("UNCHECKED_CAST") + putStringSet(key, value as Set) + } + is Int -> putInt(key, value) + is Long -> putLong(key, value) + is Float -> putFloat(key, value) } - is Int -> putInt(key, value) - is Long -> putLong(key, value) - is Float -> putFloat(key, value) } } + } catch (e: Exception) { + e.printStackTrace() + throw e } } @@ -688,108 +718,25 @@ object SuSFSManager { } } - // 二进制文件管理 - private suspend fun copyBinaryFromAssets(context: Context): String? = withContext(Dispatchers.IO) { - try { - val binaryName = getSuSFSBinaryName(context) - val targetPath = getSuSFSTargetPath() - val tempFile = File(context.cacheDir, binaryName) - - context.assets.open(binaryName).use { input -> - FileOutputStream(tempFile).use { output -> - input.copyTo(output) - } - } - - val success = runCmdWithResult("cp '${tempFile.absolutePath}' '$targetPath' && chmod 755 '$targetPath'").isSuccess - tempFile.delete() - - if (success && runCmdWithResult("test -f '$targetPath'").isSuccess) targetPath else null - } catch (e: IOException) { - e.printStackTrace() - null - } - } - // 命令执行 - private suspend fun executeSusfsCommand(context: Context, command: String): Boolean = withContext(Dispatchers.IO) { - try { - val binaryPath = copyBinaryFromAssets(context) ?: run { - showToast(context, context.getString(R.string.susfs_binary_not_found)) - return@withContext false - } - - val result = runCmdWithResult("$binaryPath $command") - - if (!result.isSuccess) { - showToast(context, "${context.getString(R.string.susfs_command_failed)}\n${result.output}\n${result.errorOutput}") - } - - result.isSuccess - } catch (e: Exception) { - e.printStackTrace() - showToast(context, context.getString(R.string.susfs_command_error, e.message ?: "Unknown error")) - false + private suspend fun executeSusfsCommand(context: Context, command: String): Boolean { + val result = executeSusfsCommandDirect(context, command) + if (!result.isSuccess) { + showToast(context, "${context.getString(R.string.susfs_command_failed)}\n${result.output}\n${result.errorOutput}") } + return result.isSuccess } - private suspend fun executeSusfsCommandWithOutput(context: Context, command: String): CommandResult = withContext(Dispatchers.IO) { - try { - val binaryPath = copyBinaryFromAssets(context) ?: return@withContext CommandResult( - false, "", context.getString(R.string.susfs_binary_not_found) - ) - runCmdWithResult("$binaryPath $command") - } catch (e: Exception) { - e.printStackTrace() - CommandResult(false, "", e.message ?: "Unknown error") - } + private suspend fun executeSusfsCommandWithOutput(context: Context, command: String): SuSFSModuleManager.CommandResult { + return executeSusfsCommandDirect(context, command) } private suspend fun showToast(context: Context, message: String) = withContext(Dispatchers.Main) { Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } - /** - * 模块管理 - */ private suspend fun updateMagiskModule(context: Context): Boolean { - return removeMagiskModule() && createMagiskModule(context) - } - - /** - * 模块创建方法 - */ - private suspend fun createMagiskModule(context: Context): Boolean = withContext(Dispatchers.IO) { - try { - val config = getCurrentModuleConfig(context) - - // 创建模块目录 - if (!runCmdWithResult("mkdir -p $MODULE_PATH").isSuccess) return@withContext false - - // 创建module.prop - val moduleProp = ScriptGenerator.generateModuleProp(MODULE_ID) - if (!runCmdWithResult("cat > $MODULE_PATH/module.prop << 'EOF'\n$moduleProp\nEOF").isSuccess) return@withContext false - - // 生成并创建所有脚本文件 - val scripts = ScriptGenerator.generateAllScripts(config) - - scripts.all { (filename, content) -> - runCmdWithResult("cat > $MODULE_PATH/$filename << 'EOF'\n$content\nEOF").isSuccess && - runCmdWithResult("chmod 755 $MODULE_PATH/$filename").isSuccess - } - } catch (e: Exception) { - e.printStackTrace() - false - } - } - - private suspend fun removeMagiskModule(): Boolean = withContext(Dispatchers.IO) { - try { - runCmdWithResult("rm -rf $MODULE_PATH").isSuccess - } catch (e: Exception) { - e.printStackTrace() - false - } + return SuSFSModuleManager.updateMagiskModule(context) } // 功能状态获取 @@ -1398,9 +1345,6 @@ object SuSFSManager { return success } - /** - * 自启动配置检查 - */ fun hasConfigurationForAutoStart(context: Context): Boolean { val config = getCurrentModuleConfig(context) return config.hasAutoStartConfig() || runBlocking { @@ -1408,9 +1352,6 @@ object SuSFSManager { } } - /** - * 自启动配置方法 - */ suspend fun configureAutoStart(context: Context, enabled: Boolean): Boolean = withContext(Dispatchers.IO) { try { if (enabled) { @@ -1427,16 +1368,16 @@ object SuSFSManager { } } - val success = createMagiskModule(context) + val success = SuSFSModuleManager.createMagiskModule(context) if (success) { setAutoStartEnabled(context, true) - showToast(context, context.getString(R.string.susfs_autostart_enabled_success, MODULE_PATH)) + showToast(context, context.getString(R.string.susfs_autostart_enabled_success, SuSFSModuleManager.getModulePath())) } else { showToast(context, context.getString(R.string.susfs_autostart_enable_failed)) } success } else { - val success = removeMagiskModule() + val success = SuSFSModuleManager.removeMagiskModule() if (success) { setAutoStartEnabled(context, false) showToast(context, context.getString(R.string.susfs_autostart_disabled_success)) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleManager.kt new file mode 100644 index 00000000..c9d3390d --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleManager.kt @@ -0,0 +1,74 @@ +package com.sukisu.ultra.ui.susfs.util + +import android.content.Context +import android.util.Log +import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +object SuSFSModuleManager { + private const val TAG = "SuSFSModuleManager" + private const val MODULE_ID = "susfs_manager" + private const val MODULE_PATH = "/data/adb/modules/$MODULE_ID" + + data class CommandResult(val isSuccess: Boolean, val output: String, val errorOutput: String = "") + + private fun runCmdWithResult(cmd: String): CommandResult { + val result = Shell.getShell().newJob().add(cmd).exec() + return CommandResult( + isSuccess = result.isSuccess, + output = result.out.joinToString("\n"), + errorOutput = result.err.joinToString("\n") + ) + } + + fun getModulePath(): String = MODULE_PATH + + private fun getCurrentModuleConfig(context: Context): SuSFSManager.ModuleConfig { + return SuSFSManager.getCurrentModuleConfig(context) + } + + + suspend fun createMagiskModule(context: Context): Boolean = withContext(Dispatchers.IO) { + try { + val config = getCurrentModuleConfig(context) + + // 创建模块目录 + if (!runCmdWithResult("mkdir -p $MODULE_PATH").isSuccess) { + return@withContext false + } + + // 创建 module.prop + val moduleProp = ScriptGenerator.generateModuleProp(MODULE_ID) + val modulePropCmd = "cat > $MODULE_PATH/module.prop << 'EOF'\n$moduleProp\nEOF" + if (!runCmdWithResult(modulePropCmd).isSuccess) { + return@withContext false + } + + // 生成并创建所有脚本文件 + val scripts = ScriptGenerator.generateAllScripts(config) + scripts.all { (filename, content) -> + val writeCmd = "cat > $MODULE_PATH/$filename << 'EOF'\n$content\nEOF" + runCmdWithResult(writeCmd).isSuccess && + runCmdWithResult("chmod 755 $MODULE_PATH/$filename").isSuccess + } + } catch (e: Exception) { + Log.e(TAG, "Failed to create module", e) + false + } + } + + suspend fun removeMagiskModule(): Boolean = withContext(Dispatchers.IO) { + try { + runCmdWithResult("rm -rf $MODULE_PATH").isSuccess + } catch (e: Exception) { + Log.e(TAG, "Failed to remove module", e) + false + } + } + + suspend fun updateMagiskModule(context: Context): Boolean { + return removeMagiskModule() && createMagiskModule(context) + } +} + diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt index 73ec1f9a..92b7eb43 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/susfs/util/SuSFSModuleScripts.kt @@ -26,13 +26,13 @@ object ScriptGenerator { } // 日志相关的通用脚本片段 - private fun generateLogSetup(logFileName: String): String = """ + private fun generateLogSetup(logFileName: String): String = $$""" # 日志目录 - LOG_DIR="$LOG_DIR" - LOG_FILE="${'$'}LOG_DIR/$logFileName" + LOG_DIR="$$LOG_DIR" + LOG_FILE="$LOG_DIR/$$logFileName" # 创建日志目录 - mkdir -p "${'$'}LOG_DIR" + mkdir -p "$LOG_DIR" # 获取当前时间 get_current_time() { @@ -41,11 +41,11 @@ object ScriptGenerator { """.trimIndent() // 二进制文件检查的通用脚本片段 - private fun generateBinaryCheck(targetPath: String): String = """ + private fun generateBinaryCheck(targetPath: String): String = $$""" # 检查SuSFS二进制文件 - SUSFS_BIN="$targetPath" - if [ ! -f "${'$'}SUSFS_BIN" ]; then - echo "$(get_current_time): SuSFS二进制文件未找到: ${'$'}SUSFS_BIN" >> "${'$'}LOG_FILE" + SUSFS_BIN="$$targetPath" + if [ ! -f "$SUSFS_BIN" ]; then + echo "$(get_current_time): SuSFS二进制文件未找到: $SUSFS_BIN" >> "$LOG_FILE" exit 1 fi """.trimIndent() @@ -94,7 +94,7 @@ object ScriptGenerator { generateCleanupResidueSection() } - appendLine("echo \"$(get_current_time): Service脚本执行完成\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Service脚本执行完成\" >> \"$LOG_FILE\"") } } @@ -112,16 +112,16 @@ object ScriptGenerator { private fun StringBuilder.generateLogSettingSection(enableLog: Boolean) { appendLine("# 设置日志启用状态") val logValue = if (enableLog) 1 else 0 - appendLine("\"${'$'}SUSFS_BIN\" enable_log $logValue") - appendLine("echo \"$(get_current_time): 日志功能设置为: ${if (enableLog) "启用" else "禁用"}\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" enable_log $$logValue") + appendLine($$"echo \"$(get_current_time): 日志功能设置为: $${if (enableLog) "启用" else "禁用"}\" >> \"$LOG_FILE\"") appendLine() } private fun StringBuilder.generateAvcLogSpoofingSection(enableAvcLogSpoofing: Boolean) { appendLine("# 设置AVC日志欺骗状态") val avcLogValue = if (enableAvcLogSpoofing) 1 else 0 - appendLine("\"${'$'}SUSFS_BIN\" enable_avc_log_spoofing $avcLogValue") - appendLine("echo \"$(get_current_time): AVC日志欺骗功能设置为: ${if (enableAvcLogSpoofing) "启用" else "禁用"}\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" enable_avc_log_spoofing $$avcLogValue") + appendLine($$"echo \"$(get_current_time): AVC日志欺骗功能设置为: $${if (enableAvcLogSpoofing) "启用" else "禁用"}\" >> \"$LOG_FILE\"") appendLine() } @@ -129,8 +129,8 @@ object ScriptGenerator { if (susPaths.isNotEmpty()) { appendLine("# 添加SUS路径") susPaths.forEach { path -> - appendLine("\"${'$'}SUSFS_BIN\" add_sus_path '$path'") - appendLine("echo \"$(get_current_time): 添加SUS路径: $path\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_sus_path '$$path'") + appendLine($$"echo \"$(get_current_time): 添加SUS路径: $$path\" >> \"$LOG_FILE\"") } appendLine() } @@ -140,8 +140,8 @@ object ScriptGenerator { if (susLoopPaths.isNotEmpty()) { appendLine("# 添加SUS循环路径") susLoopPaths.forEach { path -> - appendLine("\"${'$'}SUSFS_BIN\" add_sus_path_loop '$path'") - appendLine("echo \"$(get_current_time): 添加SUS循环路径: $path\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_sus_path_loop '$$path'") + appendLine($$"echo \"$(get_current_time): 添加SUS循环路径: $$path\" >> \"$LOG_FILE\"") } appendLine() } @@ -156,8 +156,8 @@ object ScriptGenerator { if (addKstatPaths.isNotEmpty()) { appendLine("# 添加Kstat路径") addKstatPaths.forEach { path -> - appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat '$path'") - appendLine("echo \"$(get_current_time): 添加Kstat路径: $path\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_sus_kstat '$$path'") + appendLine($$"echo \"$(get_current_time): 添加Kstat路径: $$path\" >> \"$LOG_FILE\"") } appendLine() } @@ -171,11 +171,11 @@ object ScriptGenerator { val path = parts[0] val params = parts.drop(1).joinToString("' '", "'", "'") appendLine() - appendLine("\"${'$'}SUSFS_BIN\" add_sus_kstat_statically '$path' $params") - appendLine("echo \"$(get_current_time): 添加Kstat静态配置: $path\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_sus_kstat_statically '$$path' $$params") + appendLine($$"echo \"$(get_current_time): 添加Kstat静态配置: $$path\" >> \"$LOG_FILE\"") appendLine() - appendLine("\"${'$'}SUSFS_BIN\" update_sus_kstat '$path'") - appendLine("echo \"$(get_current_time): 更新Kstat配置: $path\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" update_sus_kstat '$$path'") + appendLine($$"echo \"$(get_current_time): 更新Kstat配置: $$path\" >> \"$LOG_FILE\"") } } appendLine() @@ -185,8 +185,8 @@ object ScriptGenerator { private fun StringBuilder.generateUnameSection(config: SuSFSManager.ModuleConfig) { if (!config.executeInPostFsData && (config.unameValue != DEFAULT_UNAME || config.buildTimeValue != DEFAULT_BUILD_TIME)) { appendLine("# 设置uname和构建时间") - appendLine("\"${'$'}SUSFS_BIN\" set_uname '${config.unameValue}' '${config.buildTimeValue}'") - appendLine("echo \"$(get_current_time): 设置uname为: ${config.unameValue}, 构建时间为: ${config.buildTimeValue}\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" set_uname '$${config.unameValue}' '$${config.buildTimeValue}'") + appendLine($$"echo \"$(get_current_time): 设置uname为: $${config.unameValue}, 构建时间为: $${config.buildTimeValue}\" >> \"$LOG_FILE\"") appendLine() } } @@ -194,44 +194,44 @@ object ScriptGenerator { private fun StringBuilder.generateHideBlSection() { appendLine("# 隐藏BL 来自 Shamiko 脚本") appendLine( - """ + $$""" RESETPROP_BIN="/data/adb/ksu/bin/resetprop" check_reset_prop() { local NAME=$1 local EXPECTED=$2 - local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME) - [ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED + local VALUE=$("$RESETPROP_BIN" $NAME) + [ -z $VALUE ] || [ $VALUE = $EXPECTED ] || "$RESETPROP_BIN" $NAME $EXPECTED } check_missing_prop() { local NAME=$1 local EXPECTED=$2 - local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME) - [ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED + local VALUE=$("$RESETPROP_BIN" $NAME) + [ -z $VALUE ] && "$RESETPROP_BIN" $NAME $EXPECTED } check_missing_match_prop() { local NAME=$1 local EXPECTED=$2 - local VALUE=$("${'$'}RESETPROP_BIN" ${'$'}NAME) - [ -z ${'$'}VALUE ] || [ ${'$'}VALUE = ${'$'}EXPECTED ] || "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED - [ -z ${'$'}VALUE ] && "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}EXPECTED + local VALUE=$("$RESETPROP_BIN" $NAME) + [ -z $VALUE ] || [ $VALUE = $EXPECTED ] || "$RESETPROP_BIN" $NAME $EXPECTED + [ -z $VALUE ] && "$RESETPROP_BIN" $NAME $EXPECTED } contains_reset_prop() { local NAME=$1 local CONTAINS=$2 local NEWVAL=$3 - case "$("${'$'}RESETPROP_BIN" ${'$'}NAME)" in - *"${'$'}CONTAINS"*) "${'$'}RESETPROP_BIN" ${'$'}NAME ${'$'}NEWVAL ;; + case "$("$RESETPROP_BIN" $NAME)" in + *"$CONTAINS"*) "$RESETPROP_BIN" $NAME $NEWVAL ;; esac } """.trimIndent()) appendLine() appendLine("sleep 30") appendLine() - appendLine("\"${'$'}RESETPROP_BIN\" -w sys.boot_completed 0") + appendLine($$"\"$RESETPROP_BIN\" -w sys.boot_completed 0") // 添加所有系统属性重置 val systemProps = listOf( @@ -292,27 +292,28 @@ object ScriptGenerator { // 清理残留脚本生成 private fun StringBuilder.generateCleanupResidueSection() { appendLine("# 清理工具残留文件") - appendLine("echo \"$(get_current_time): 开始清理工具残留\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): 开始清理工具残留\" >> \"$LOG_FILE\"") appendLine() // 定义清理函数 - appendLine(""" + appendLine( + $$""" cleanup_path() { local path="$1" local desc="$2" local current="$3" local total="$4" - if [ -n "${'$'}desc" ]; then - echo "$(get_current_time): [${'$'}current/${'$'}total] 清理: ${'$'}path (${'$'}desc)" >> "${'$'}LOG_FILE" + if [ -n "$desc" ]; then + echo "$(get_current_time): [$current/$total] 清理: $path ($desc)" >> "$LOG_FILE" else - echo "$(get_current_time): [${'$'}current/${'$'}total] 清理: ${'$'}path" >> "${'$'}LOG_FILE" + echo "$(get_current_time): [$current/$total] 清理: $path" >> "$LOG_FILE" fi - if rm -rf "${'$'}path" 2>/dev/null; then - echo "$(get_current_time): ✓ 成功清理: ${'$'}path" >> "${'$'}LOG_FILE" + if rm -rf "$path" 2>/dev/null; then + echo "$(get_current_time): ✓ 成功清理: $path" >> "$LOG_FILE" else - echo "$(get_current_time): ✗ 清理失败或不存在: ${'$'}path" >> "${'$'}LOG_FILE" + echo "$(get_current_time): ✗ 清理失败或不存在: $path" >> "$LOG_FILE" fi } """.trimIndent()) @@ -360,11 +361,11 @@ object ScriptGenerator { cleanupPaths.forEachIndexed { index, (path, desc) -> val current = index + 1 - appendLine("cleanup_path '$path' '$desc' $current \$TOTAL") + appendLine($$"cleanup_path '$$path' '$$desc' $$current $TOTAL") } appendLine() - appendLine("echo \"$(get_current_time): 工具残留清理完成\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): 工具残留清理完成\" >> \"$LOG_FILE\"") appendLine() } @@ -381,14 +382,14 @@ object ScriptGenerator { appendLine() appendLine(generateBinaryCheck(config.targetPath)) appendLine() - appendLine("echo \"$(get_current_time): Post-FS-Data脚本开始执行\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Post-FS-Data脚本开始执行\" >> \"$LOG_FILE\"") appendLine() // 设置uname和构建时间 - 只有在选择在post-fs-data中执行时才执行 if (config.executeInPostFsData && (config.unameValue != DEFAULT_UNAME || config.buildTimeValue != DEFAULT_BUILD_TIME)) { appendLine("# 设置uname和构建时间") - appendLine("\"${'$'}SUSFS_BIN\" set_uname '${config.unameValue}' '${config.buildTimeValue}'") - appendLine("echo \"$(get_current_time): 设置uname为: ${config.unameValue}, 构建时间为: ${config.buildTimeValue}\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" set_uname '$${config.unameValue}' '$${config.buildTimeValue}'") + appendLine($$"echo \"$(get_current_time): 设置uname为: $${config.unameValue}, 构建时间为: $${config.buildTimeValue}\" >> \"$LOG_FILE\"") appendLine() } @@ -397,7 +398,7 @@ object ScriptGenerator { // 添加AVC日志欺骗设置 generateAvcLogSpoofingSection(config.enableAvcLogSpoofing) - appendLine("echo \"$(get_current_time): Post-FS-Data脚本执行完成\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Post-FS-Data脚本执行完成\" >> \"$LOG_FILE\"") } } @@ -405,8 +406,8 @@ object ScriptGenerator { private fun StringBuilder.generateUmountZygoteIsoServiceSection(umountForZygoteIsoService: Boolean) { appendLine("# 设置Zygote隔离服务卸载状态") val umountValue = if (umountForZygoteIsoService) 1 else 0 - appendLine("\"${'$'}SUSFS_BIN\" umount_for_zygote_iso_service $umountValue") - appendLine("echo \"$(get_current_time): Zygote隔离服务卸载设置为: ${if (umountForZygoteIsoService) "启用" else "禁用"}\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" umount_for_zygote_iso_service $$umountValue") + appendLine($$"echo \"$(get_current_time): Zygote隔离服务卸载设置为: $${if (umountForZygoteIsoService) "启用" else "禁用"}\" >> \"$LOG_FILE\"") appendLine() } @@ -421,7 +422,7 @@ object ScriptGenerator { appendLine() appendLine(generateLogSetup("susfs_post_mount.log")) appendLine() - appendLine("echo \"$(get_current_time): Post-Mount脚本开始执行\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Post-Mount脚本开始执行\" >> \"$LOG_FILE\"") appendLine() appendLine(generateBinaryCheck(config.targetPath)) appendLine() @@ -430,8 +431,8 @@ object ScriptGenerator { if (config.susMounts.isNotEmpty()) { appendLine("# 添加SUS挂载") config.susMounts.forEach { mount -> - appendLine("\"${'$'}SUSFS_BIN\" add_sus_mount '$mount'") - appendLine("echo \"$(get_current_time): 添加SUS挂载: $mount\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_sus_mount '$$mount'") + appendLine($$"echo \"$(get_current_time): 添加SUS挂载: $$mount\" >> \"$LOG_FILE\"") } appendLine() } @@ -444,14 +445,14 @@ object ScriptGenerator { if (parts.size == 2) { val path = parts[0] val mode = parts[1] - appendLine("\"${'$'}SUSFS_BIN\" add_try_umount '$path' $mode") - appendLine("echo \"$(get_current_time): 添加尝试卸载: $path (模式: $mode)\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_try_umount '$$path' $$mode") + appendLine($$"echo \"$(get_current_time): 添加尝试卸载: $$path (模式: $$mode)\" >> \"$LOG_FILE\"") } } appendLine() } - appendLine("echo \"$(get_current_time): Post-Mount脚本执行完成\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Post-Mount脚本执行完成\" >> \"$LOG_FILE\"") } } @@ -467,7 +468,7 @@ object ScriptGenerator { appendLine() appendLine(generateLogSetup("susfs_boot_completed.log")) appendLine() - appendLine("echo \"$(get_current_time): Boot-Completed脚本开始执行\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Boot-Completed脚本开始执行\" >> \"$LOG_FILE\"") appendLine() appendLine(generateBinaryCheck(config.targetPath)) appendLine() @@ -475,8 +476,8 @@ object ScriptGenerator { // SUS挂载隐藏控制 val hideValue = if (config.hideSusMountsForAllProcs) 1 else 0 appendLine("# 设置SUS挂载隐藏控制") - appendLine("\"${'$'}SUSFS_BIN\" hide_sus_mnts_for_all_procs $hideValue") - appendLine("echo \"$(get_current_time): SUS挂载隐藏控制设置为: ${if (config.hideSusMountsForAllProcs) "对所有进程隐藏" else "仅对非KSU进程隐藏"}\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" hide_sus_mnts_for_all_procs $$hideValue") + appendLine($$"echo \"$(get_current_time): SUS挂载隐藏控制设置为: $${if (config.hideSusMountsForAllProcs) "对所有进程隐藏" else "仅对非KSU进程隐藏"}\" >> \"$LOG_FILE\"") appendLine() // 路径设置和SUS路径设置 @@ -499,7 +500,7 @@ object ScriptGenerator { } } - appendLine("echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"${'$'}LOG_FILE\"") + appendLine($$"echo \"$(get_current_time): Boot-Completed脚本执行完成\" >> \"$LOG_FILE\"") } } @@ -507,8 +508,8 @@ object ScriptGenerator { if (susMaps.isNotEmpty()) { appendLine("# 添加SUS映射") susMaps.forEach { map -> - appendLine("\"${'$'}SUSFS_BIN\" add_sus_map '$map'") - appendLine("echo \"$(get_current_time): 添加SUS映射: $map\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" add_sus_map '$$map'") + appendLine($$"echo \"$(get_current_time): 添加SUS映射: $$map\" >> \"$LOG_FILE\"") } appendLine() } @@ -521,12 +522,12 @@ object ScriptGenerator { appendLine("until [ -d \"/sdcard/Android\" ]; do sleep 1; done") appendLine("sleep 60") appendLine() - appendLine("\"${'$'}SUSFS_BIN\" set_android_data_root_path '$androidDataPath'") - appendLine("echo \"$(get_current_time): Android Data路径设置为: $androidDataPath\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" set_android_data_root_path '$$androidDataPath'") + appendLine($$"echo \"$(get_current_time): Android Data路径设置为: $$androidDataPath\" >> \"$LOG_FILE\"") appendLine() appendLine("# 设置SD卡路径") - appendLine("\"${'$'}SUSFS_BIN\" set_sdcard_root_path '$sdcardPath'") - appendLine("echo \"$(get_current_time): SD卡路径设置为: $sdcardPath\" >> \"${'$'}LOG_FILE\"") + appendLine($$"\"$SUSFS_BIN\" set_sdcard_root_path '$$sdcardPath'") + appendLine($$"echo \"$(get_current_time): SD卡路径设置为: $$sdcardPath\" >> \"$LOG_FILE\"") appendLine() } diff --git a/manager/app/src/main/res/values-ar/strings.xml b/manager/app/src/main/res/values-ar/strings.xml index 7cf05306..94be8106 100644 --- a/manager/app/src/main/res/values-ar/strings.xml +++ b/manager/app/src/main/res/values-ar/strings.xml @@ -5,8 +5,6 @@ إضغط للتثبيت يعمل الإصدار: %d - مستخدمين الجذر: %d - الإضافات: %d غير مدعوم KernelSU يدعم GKI kernels فقط إصدار النواة @@ -59,12 +57,8 @@ تشغيل الإفتراضي نموذج - موروث - عالمي - فردي مجموعات مُخصّص - تركيب مساحة الاسم الغاء تحميل الإضافات فشل تحديث ملف تعريف التطبيق لـ %s سياق SELinux @@ -89,7 +83,6 @@ معرف القالب غير صالح مزامنة القوالب عبر الإنترنت إنشاء قالب - للقراءة فقط استيراد / تصدير فشل في حفظ القالب تحرير القالب diff --git a/manager/app/src/main/res/values-az/strings.xml b/manager/app/src/main/res/values-az/strings.xml index c274a847..13e7f952 100644 --- a/manager/app/src/main/res/values-az/strings.xml +++ b/manager/app/src/main/res/values-az/strings.xml @@ -1,13 +1,11 @@ Ana səhifə - Super istifadəçilər: %d Nüvə Yüklənmədi Yükləmək üçün toxunun İşləyir Versiya: %d - Modullar: %d Hal-hazırda KernelSU yalnız GKI nüvələrini dəstəkləyir Dəstəklənmir Yüklə @@ -57,10 +55,6 @@ Profil adı Bacarıqlar Modulları umount et - Miras qalmış - Qlobal - Bölmənin ad sahəsi - Fərdi Qruplar Defolt olaraq modulları umount et SELinux konteksi @@ -96,6 +90,5 @@ Yadda saxla Sil Şablonu göstər - Yalnız oxu Şablon ID-si artıq mövcuddur! diff --git a/manager/app/src/main/res/values-bn-rBD/strings.xml b/manager/app/src/main/res/values-bn-rBD/strings.xml index 9d8ddff7..d03cee6f 100644 --- a/manager/app/src/main/res/values-bn-rBD/strings.xml +++ b/manager/app/src/main/res/values-bn-rBD/strings.xml @@ -6,7 +6,6 @@ মোডিউল ইনেবল করা যায়নি: %s ইন্সটল করটে চাপুন কাজ করছে - মোডিউল: %d অমূলক কর্নেল ম্যানেজার ভারসন @@ -21,7 +20,6 @@ রিবুট সেটিংস সফট রিবুট - গ্লোবাল গ্রুপস এসইলিনাক্স কন্টেক্সট %s এর জন্য অ্যাপ প্রফাইল আপডেট করা যায়নি @@ -32,10 +30,6 @@ মোডিউল ডিসেবল করা যায়নি: %s কোনো মোডিউল ইন্সটল করা নেই সংস্করণ: %d - সুপার ইউজার: %d - নেইম স্পেস মাউন্ট - ইনহেরিটেড - ইন্ডিভিজুয়াল ক্যাপাবিলিটিস আনমাউন্ট মোডিউলস রিকভারিতে বুট diff --git a/manager/app/src/main/res/values-bn/strings.xml b/manager/app/src/main/res/values-bn/strings.xml index 83983ef7..1a3121ef 100644 --- a/manager/app/src/main/res/values-bn/strings.xml +++ b/manager/app/src/main/res/values-bn/strings.xml @@ -5,8 +5,6 @@ ইনস্টল করার জন্য ক্লিক করুন ওয়ার্কিং ওয়ার্কিং সংস্করণ: %d - সুপার ইউজার: %d - মডিউল: %d অসমর্থিত KernelSU শুধুমাত্র GKI কার্নেল সমর্থন করে কার্নেল @@ -52,15 +50,12 @@ কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন. আমাদের %2$s চ্যানেল মার্জ করুন]]> প্রফাইলের নাম - নেমস্পেস মাউন্ট গ্রুপস যোগ্যতা এসই লিনাক্স কনটেক্সট ডিফল্ট টেমপ্লেট কাস্টম - গ্লোবাল - আলাদাভাবে আনমাউন্ট মোডিউল ম্যানেজার সঠিকভাবে কাজ করার জন্য বর্তমান KernelSU সংস্করণ %d খুবই কম। অনুগ্রহ করে %d বা উচ্চতর সংস্করণে আপগ্রেড করুন! লগ সংরক্ষণ করুন diff --git a/manager/app/src/main/res/values-bs/strings.xml b/manager/app/src/main/res/values-bs/strings.xml index f91a9e53..8e4584f6 100644 --- a/manager/app/src/main/res/values-bs/strings.xml +++ b/manager/app/src/main/res/values-bs/strings.xml @@ -1,9 +1,5 @@ - Imenski prostor nosača - Naslijeđen - Globalan - Pojedinačan Grupe Sposobnosti SELinux kontekst @@ -24,8 +20,6 @@ Početna Nije instalirano Kliknite da instalirate - Superkorisnici: %d - Module: %d Nepodržano KernelSU samo podržava GKI kernele sad Verzija Upravitelja diff --git a/manager/app/src/main/res/values-da/strings.xml b/manager/app/src/main/res/values-da/strings.xml index 6e2fc02f..418fd391 100644 --- a/manager/app/src/main/res/values-da/strings.xml +++ b/manager/app/src/main/res/values-da/strings.xml @@ -1,7 +1,6 @@ Arbejder - Moduler: %d Ikke understøttet Kernel-version KernelSU understøtter nu kun GKI-kerner. @@ -35,9 +34,6 @@ Join our %2$s channel]]> Standard Skabelon - Monter namespace - Arvet - Global Grupper Evner SELinux-kontext @@ -55,7 +51,6 @@ Version: %d Hjem Ikke installeret - Superbrugere: %d Fingeraftryk Ukendt Aktivering af modul fejlede: %s @@ -71,7 +66,6 @@ KernelSU er, og vil altid være, gratis og åben kildekode. Du kan dog vise os, at du holder af os, ved at give en donation. Brugerdefineret Profilnavn - Individuel Opdatering af App Profil for %s fejlede Den globale standardværdi for \"Umount moduler\" i App Profile. Hvis aktiveret, fjernes alle modulændringer til systemet for apps, der ikke har en profil angivet. Domæne @@ -96,7 +90,6 @@ Gem Slet Visningsskabelon - Skrivebeskyttet Skabelon-ID findes allerede! Import/Eksport Importér fra udklipsholder diff --git a/manager/app/src/main/res/values-de/strings.xml b/manager/app/src/main/res/values-de/strings.xml index 6b78edb3..7723e5a7 100644 --- a/manager/app/src/main/res/values-de/strings.xml +++ b/manager/app/src/main/res/values-de/strings.xml @@ -7,7 +7,6 @@ Version: %d Superuser Tippe zum Installieren - Superuser: %d Unbekannt Erzwingen In den Bootloader-Modus neustarten @@ -27,9 +26,6 @@ Vorlage Benutzerdefiniert App-Profilaktualisierung für %s fehlgeschlagen - Geerbt - Global - Individuell Domäne Aktualisieren Wenn du diese Option aktivierst, kann KernelSU alle von den Modulen für diese App geänderten Dateien wiederherstellen. @@ -40,7 +36,6 @@ Neue Version %s verfügbar, tippen zum Aktualisieren. Stopp erzwingen Neustarten - Module: %d Manager-Version SELinux Status Deaktiviert @@ -64,7 +59,6 @@ Neustarten, damit Änderungen wirksam werden Unserem %2$s-Kanal beitreten]]> Profilname - Namespace einhängen Gruppen Fähigkeiten Module aushängen @@ -89,7 +83,6 @@ Ungültige Vorlagen-ID Online-Vorlagen synchronisieren Vorlage erstellen - Schreibgeschützt Import/Export Schlug beim Speichern der Vorlage fehl Vorlage bearbeiten diff --git a/manager/app/src/main/res/values-es/strings.xml b/manager/app/src/main/res/values-es/strings.xml index dd3733c3..f25f1ed0 100644 --- a/manager/app/src/main/res/values-es/strings.xml +++ b/manager/app/src/main/res/values-es/strings.xml @@ -5,8 +5,6 @@ Haz clic para instalar Funcionando Versión: %d - Superusuarios: %d - Módulos: %d Sin soporte KernelSU solo admite kernels GKI por ahora Versión del kernel @@ -56,10 +54,6 @@ Plantilla Personalizado Nombre de perfil - Montaje del espacio de nombres - Heredado - Global - Individual Grupos Capacidades Contexto SELinux @@ -90,7 +84,6 @@ ID de plantilla no válida Sincronizar plantillas en línea Crear plantilla - Sólo lectura Importar/Exportar No se ha podido guardar la plantilla Editar plantilla diff --git a/manager/app/src/main/res/values-et/strings.xml b/manager/app/src/main/res/values-et/strings.xml index 01b3534e..1a830897 100644 --- a/manager/app/src/main/res/values-et/strings.xml +++ b/manager/app/src/main/res/values-et/strings.xml @@ -2,7 +2,6 @@ Töötamine Versioon: %d - Mooduleid: %d Tuum Manageri versioon Sõrmejälg @@ -19,7 +18,6 @@ Õpi KernelSUd https://kernelsu.org/guide/what-is-kernelsu.html Vaikimisi - Haagi nimeruum Lahtihaagitud moodulid Rakenduseprofiili uuendamine %s jaoks ebaõnnestus Haagi moodulid vaikimisi lahti @@ -28,7 +26,6 @@ Muuda malli Rakenduseprofiili mall ID - Vaid lugemiseks Malli ID juba eksisteerib! Ekspordi lõikelauale Sünkrooni võrgumallid @@ -37,7 +34,6 @@ Klõpsa paigaldamiseks Pole paigaldatud Mittetoetatud - Superkasutajaid: %d KernelSU toetab hetkel vaid GSI tuumasid SELinuxi olek Keelatud @@ -70,9 +66,6 @@ Vaata lähtekoodi %1$sis
Liitu meie %2$si kanaliga
Profiili nimi Kohandatud - Päritud - Globaalne - Individuaalne Võimekused Sobimatu malli ID SELinux kontekst diff --git a/manager/app/src/main/res/values-fa/strings.xml b/manager/app/src/main/res/values-fa/strings.xml index 37db04e4..8b6b6912 100644 --- a/manager/app/src/main/res/values-fa/strings.xml +++ b/manager/app/src/main/res/values-fa/strings.xml @@ -5,8 +5,6 @@ برای نصب ضربه بزنید به درستی کار می‌کند نسخه: %d - برنامه های با دسترسی روت: %d - ماژول‌ها: %d پشتیبانی نشده کرنل اس یو فقط هسته های gki را پشتیبانی میکند هسته @@ -58,10 +56,6 @@ قالب شخصی سازی شده اسم پروفایل - Mount namespace - اثر گرفته - گلوبال - تکی جداکردن ماژول ها ذخیره گزارش‌ها
diff --git a/manager/app/src/main/res/values-fil/strings.xml b/manager/app/src/main/res/values-fil/strings.xml index 06eea427..b233727d 100644 --- a/manager/app/src/main/res/values-fil/strings.xml +++ b/manager/app/src/main/res/values-fil/strings.xml @@ -10,7 +10,6 @@ Gumagana Bersyon: %d Hindi matukoy - Mga Modyul: %d Hindi Suportado Sinusuportahan lamang ng KernelSU ang mga GKI na kernel Nabigong paganahin ang module: %s @@ -38,8 +37,6 @@ Suportahan Kami Ang KernelSU ay, at palaging magiging, libre, at open source. Gayunpaman, maaari mong ipakita sa amin na nagmamalasakit ka sa pamamagitan ng pagbibigay ng donasyon. Sumali sa aming %2$s channel]]> - I-mount ang namespace - Indibidwal Mga Grupo Mga Kakayanan Konteksto ng SELinux @@ -63,10 +60,8 @@ I-uninstall Itago ang mga application ng system Pangalan ng profile - Minana Ang pangkalahatang default na halaga para sa \"Umount modules\" sa Mga Profile ng App. Kung pinagana, aalisin nito ang lahat ng mga pagbabago sa modyul sa system para sa mga aplikasyon na walang hanay ng Profile. I-save ang mga Log - Mga Superuser: %d Bersyon ng kernel Fingerprint Superuser @@ -79,7 +74,6 @@ Default Template Pasadya - Global I-unmount ang mga module bilang default Domain I-update @@ -96,7 +90,6 @@ I-save Burahin Tignan ang template - Read only Umiiral na ang Template ID! I-import/I-export Mag-import mula sa clipboard diff --git a/manager/app/src/main/res/values-fr/strings.xml b/manager/app/src/main/res/values-fr/strings.xml index eb56e969..9ba6ad96 100644 --- a/manager/app/src/main/res/values-fr/strings.xml +++ b/manager/app/src/main/res/values-fr/strings.xml @@ -3,8 +3,6 @@ Non installé Fonctionnel Version : %d - Super-utilisateurs : %d - Modules : %d KernelSU ne prend désormais en charge que les noyaux GKI Version du noyau Empreinte digitale @@ -55,11 +53,7 @@ Par défaut Personnalisé Nom du profil - Espace de noms de montage - Hérité - Individuel Contexte SELinux - Global Groupes Capacités Démonter les modules @@ -89,7 +83,6 @@ ID de modèle invalide Synchroniser les modèles en ligne Créer un modèle - Lecture seule Importer/exporter Échec de l\'enregistrement du modèle Modifier le modèle diff --git a/manager/app/src/main/res/values-hi/strings.xml b/manager/app/src/main/res/values-hi/strings.xml index da541809..020c13fb 100644 --- a/manager/app/src/main/res/values-hi/strings.xml +++ b/manager/app/src/main/res/values-hi/strings.xml @@ -9,24 +9,19 @@ लॉग भेजे डिसेबल्ड (बंद) हमें प्रोत्साहन दें - Inherited मॉड्यूल बंद कर दिए गए हैं क्योंकि यह मैजिक के साथ टकरा रहे है! क्या बदलाव हुए है पर्मिसिव डाउनलोड में रिबूट करें डिफ़ॉल्ट रूप से मॉड्यूल अनमाउन्ट करें इस विकल्प को चालू करने से KernelSU को इस एप्लिकेशन के लिए मॉड्यूल द्वारा किसी भी मोडिफाइड फ़ाइल को रिस्टोर करें। - Individual %s मॉड्यूल चालू करने में विफल जबर्दस्ती बंद करें EDL मोड में रिबूट करें फिर से चालू करें क्षमताएं - सुपरयूजर : %d %s की डाउनलोडिंग स्टार्ट करें - Global ऐप प्रोफाइल में \"अनमाउंट मॉड्यूल\" के लिए ग्लोबल डिफ़ॉल्ट वैल्यू। यदि चालू किया गया है, तो यह एप्लीकेशंस के लिऐ सिस्टम के सभी मॉड्यूल मोडिफिकेशन को हटा देगा जिनकी प्रोफ़ाइल सेट नहीं है। - मॉड्यूल्स : %d एनफोर्सिंग SELinux context फिंगरप्रिंट @@ -39,7 +34,6 @@ प्रोफाइल का नाम KernelSU मुफ़्त और ओपन सोर्स और हमेशा रहेगा। हालाँकि आप दान देकर हमें दिखा सकते हैं कि आप संरक्षण करते हैं। अनइंस्टॉल करें - Namspace माउंट करें इंस्टाल करें इंस्टाल करने के लिए क्लिक करें नियम diff --git a/manager/app/src/main/res/values-hr/strings.xml b/manager/app/src/main/res/values-hr/strings.xml index b6aae406..9e113a0b 100644 --- a/manager/app/src/main/res/values-hr/strings.xml +++ b/manager/app/src/main/res/values-hr/strings.xml @@ -11,8 +11,6 @@ Verzija: %d Kliknite da instalirate Radi - Superkorisnici: %d - Moduli: %d Nepodržano KernelSU sada samo podržava GKI kernele Verzija kernela @@ -56,11 +54,7 @@ Šablon Prilagođeno Naziv profila - Naslijeđen - Imenski prostor nosača Ažuriranje Profila Aplikacije za %s nije uspjelo - Globalan - Pojedinačan Umount module Grupe Sposobnosti @@ -94,7 +88,6 @@ Spremi Izbriši Prikaži predložak - Samo za čitanje ID predloška već postoji! Uvoz/Izvoz Uvezi iz međuspremnika diff --git a/manager/app/src/main/res/values-hu/strings.xml b/manager/app/src/main/res/values-hu/strings.xml index a31b5f1f..f5167b56 100644 --- a/manager/app/src/main/res/values-hu/strings.xml +++ b/manager/app/src/main/res/values-hu/strings.xml @@ -2,7 +2,6 @@ Működik Verzió: %d - Modulok: %d A KernelSU jelenleg csak GKI kerneleket támogat Kernel Alkalmazás verziója @@ -27,10 +26,7 @@ Sablon Egyedi Profil neve - Névtér csatlakoztatása - Örökölt https://kernelsu.org/guide/what-is-kernelsu.html - Különálló Csoportok Jogosultságok SELinux kontextus @@ -47,7 +43,6 @@ Kezdőlap Nincs telepítve Kattintson a telepítéshez - Engedélyezett alkalmazások: %d Nem támogatott SELinux állapot Kényszerített @@ -71,7 +66,6 @@ Naplók küldése Indítsa újra a készüléket a változások érvényesítéséhez A KernelSU ingyenes, nyílt forráskódú és mindig is az lesz. Ön azonban adományozással megmutathatja, hogy törődik a projekttel. - Globális Modulok leválasztása Nem sikerült frissíteni az App Profilt ehhez: %s A \"Modulok leválasztása\" globális alapértelmezett értéke az App Profile-ban. Ha engedélyezve van, eltávolít minden modulmódosítást a rendszerből azon alkalmazások esetében, amelyeknek nincs profilja beállítva. @@ -89,7 +83,6 @@ Hibás sablon ID Online sablonok szinkronizálása Sablon készítése - Csak olvasható Import/Export A sablon mentése sikertelen Sablon szerkesztése diff --git a/manager/app/src/main/res/values-in/strings.xml b/manager/app/src/main/res/values-in/strings.xml index 759eb50e..efba066b 100644 --- a/manager/app/src/main/res/values-in/strings.xml +++ b/manager/app/src/main/res/values-in/strings.xml @@ -5,8 +5,6 @@ Klik untuk menginstal Berfungsi Versi: %d - SuperUser: %d - Modul: %d Tidak didukung KernelSU saat ini hanya mendukung kernel GKI. Versi kernel @@ -56,10 +54,6 @@ Templat Khusus Nama profil - Gunakan Namespace - Diwariskan - Universal - Individual Kelompok Kemampuan Konteks SELinux @@ -101,7 +95,6 @@ Hapus Papan klip kosong! Lihat templat - readonly Debugging WebView Dapat digunakan untuk mendebug antarmuka web (WebUI). Harap aktifkan hanya saat diperlukan. %1$s image partisi terekomendasi diff --git a/manager/app/src/main/res/values-it/strings.xml b/manager/app/src/main/res/values-it/strings.xml index 6ed4692a..b6d87848 100644 --- a/manager/app/src/main/res/values-it/strings.xml +++ b/manager/app/src/main/res/values-it/strings.xml @@ -5,8 +5,6 @@ Clicca per installare In esecuzione Versione: %d - Applicazioni con accesso root: %d - Moduli installati: %d Non supportato KernelSU ora supporta solo i kernel GKI Kernel @@ -52,11 +50,7 @@ KernelSU è, e sempre sarà, gratuito e open source. Puoi comunque mostrarci il tuo apprezzamento facendo una donazione. Unisciti al nostro canale %2$s]]> Nome profilo - Spazio dei nomi del mount - Globale Gruppi - Ereditato - Individuale Predefinito Personalizzato Modello @@ -85,7 +79,6 @@ Identificativo modello non valido Nome Visualizza modello - Sola lettura L\'identificatore del modello è già in uso! Importa/Esporta Importa dagli appunti diff --git a/manager/app/src/main/res/values-iw/strings.xml b/manager/app/src/main/res/values-iw/strings.xml index f0ff9fe8..c06a497f 100644 --- a/manager/app/src/main/res/values-iw/strings.xml +++ b/manager/app/src/main/res/values-iw/strings.xml @@ -9,24 +9,19 @@ שלח לוג מושבת תמכו בנו - ירושה מודולים מושבתים מכיוון שהם מתנגשים עם זה של Magisk! יומן שינויים התרים הפעלה מחדש למצב הורדה טעינת מודולים כברירת מחדל הפעלת אפשרות זו תאפשר ל-KernelSU לשחזר קבצים שהשתנו על ידי המודולים עבור יישום זה. - אישי הפעלת המודל נכשלה: %s עצירה בכח הפעלה מחדש למצב EDL איתחול יכולת - משתמשי על: %d מפעיל מודל: %s - גלובלי ערך ברירת המחדל הגלובלי עבור \"טעינת מודולים\" בפרופילי אפליקציה. אם מופעל, זה יסיר את כל שינויי המודול למערכת עבור יישומים שאין להם ערכת פרופיל. - מודלים:%d אכיפה הקשר SELinux טביעת אצבע @@ -39,7 +34,6 @@ שם פרופיל KernelSU הוא, ותמיד יהיה, חינמי וקוד פתוח. עם זאת, תוכל להראות לנו שאכפת לך על ידי תרומה. הסרה - טעינת מרחב שמות התקנה לחץ להתקנה כללים diff --git a/manager/app/src/main/res/values-ja/strings.xml b/manager/app/src/main/res/values-ja/strings.xml index 7bbef90a..d7369a2f 100644 --- a/manager/app/src/main/res/values-ja/strings.xml +++ b/manager/app/src/main/res/values-ja/strings.xml @@ -5,8 +5,6 @@ タップでインストール 動作中 バージョン: %d - スーパーユーザー: %d - モジュール: %d 非対応 現在、 KernelSU は GKI カーネルにのみ対応しています カーネル @@ -56,10 +54,6 @@ テンプレート カスタム プロファイル名 - 名前空間のマウント - 継承 - 共通 - 分離 モジュールのアンマウント グループ SELinux コンテキスト @@ -90,7 +84,6 @@ 無効なテンプレート ID オンラインテンプレートの同期 テンプレートの作成 - 読み取り専用 インポート/エクスポート テンプレートの保存に失敗しました テンプレートの編集 diff --git a/manager/app/src/main/res/values-kn/strings.xml b/manager/app/src/main/res/values-kn/strings.xml index 3964f203..18af6da0 100644 --- a/manager/app/src/main/res/values-kn/strings.xml +++ b/manager/app/src/main/res/values-kn/strings.xml @@ -8,22 +8,17 @@ Umount ಮಾಡ್ಯೂಲ್‌ಗಳು ಲಾಗ್ ಕಳುಹಿಸಿ ನಮ್ಮನ್ನು ಬೆಂಬಲಿಸಿ - ಪಿತ್ರಾರ್ಜಿತ ಮಾಡ್ಯೂಲ್‌ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ ಏಕೆಂದರೆ ಇದು ಮ್ಯಾಜಿಸ್ಕ್‌ನೊಂದಿಗೆ ಸಂಘರ್ಷವಾಗಿದೆ! ಚೇಂಜ್ಲಾಗ್ Permissive ಡೀಫಾಲ್ಟ್ ಆಗಿ Umount ಮಾಡ್ಯೂಲ್ ಈ ಆಯ್ಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ ಈ ಅಪ್ಲಿಕೇಶನ್‌ಗಾಗಿ ಮಾಡ್ಯೂಲ್‌ಗಳ ಮೂಲಕ ಯಾವುದೇ ಮಾರ್ಪಡಿಸಿದ ಫೈಲ್‌ಗಳನ್ನು ಮರುಸ್ಥಾಪಿಸಲು KernelSU ಗೆ ಅನುಮತಿಸುತ್ತದೆ. - ವೈಯಕ್ತಿಕ ಮಾಡ್ಯೂಲ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ವಿಫಲವಾಗಿದೆ: %s ಫೋರ್ಸ್ ಸ್ಟಾಪ್ EDL ಗೆ ರೀಬೂಟ್ ಸಾಮರ್ಥ್ಯಗಳು - ಸೂಪರ್‌ಯೂಸರ್‌ಗಳು: %d ಡೌನ್‌ಲೋಡ್ ಮಾಡುವುದನ್ನು ಪ್ರಾರಂಭಿಸಿ: %s - ಜಾಗತಿಕ ಅಪ್ಲಿಕೇಶನ್ ಪ್ರೊಫೈಲ್‌ಗಳಲ್ಲಿ \"Umount ಮಾಡ್ಯೂಲ್\" ಗಾಗಿ ಜಾಗತಿಕ ಡೀಫಾಲ್ಟ್ ಮೌಲ್ಯ. ಸಕ್ರಿಯಗೊಳಿಸಿದರೆ, ಪ್ರೊಫೈಲ್ ಸೆಟ್ ಅನ್ನು ಹೊಂದಿರದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗಾಗಿ ಸಿಸ್ಟಮ್‌ಗೆ ಎಲ್ಲಾ ಮಾಡ್ಯೂಲ್ ಮಾರ್ಪಾಡುಗಳನ್ನು ಇದು ತೆಗೆದುಹಾಕುತ್ತದೆ. - ಮಾಡ್ಯೂಲ್‌ಗಳು: %d SELinux ಸಂದರ್ಭ ಡೀಫಾಲ್ಟ್ ಲಾಂಚ್ @@ -33,7 +28,6 @@ ಪ್ರೊಫೈಲ್ ಹೆಸರು KernelSU ಉಚಿತ ಮತ್ತು ಮುಕ್ತ ಮೂಲವಾಗಿದೆ ಮತ್ತು ಯಾವಾಗಲೂ ಇರುತ್ತದೆ. ಆದಾಗ್ಯೂ ನೀವು ದೇಣಿಗೆ ನೀಡುವ ಮೂಲಕ ನೀವು ಕಾಳಜಿ ವಹಿಸುತ್ತೀರಿ ಎಂದು ನಮಗೆ ತೋರಿಸಬಹುದು. ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ - ಮೌಂಟ್ ನೇಮ್‌ಸ್ಪೇಸ್ ನಿಯಮಗಳು ಗುಂಪುಗಳು ಮಾಡ್ಯೂಲ್ diff --git a/manager/app/src/main/res/values-ko/strings.xml b/manager/app/src/main/res/values-ko/strings.xml index 9419ad38..75788060 100644 --- a/manager/app/src/main/res/values-ko/strings.xml +++ b/manager/app/src/main/res/values-ko/strings.xml @@ -5,8 +5,6 @@ 이 곳을 눌러 설치하기 작동 중 버전: %d - 슈퍼유저: %d개 - 모듈: %d개 지원되지 않음 KernelSU는 현재 GKI 커널만 지원합니다. 커널 버전 @@ -61,10 +59,6 @@ 사용자 지정 템플릿 프로필 이름 - 마운트할 네임스페이스 - 상속 - 전역 - 개별 사용자 그룹 모듈 마운트 해제 SELinux 컨텍스트 @@ -106,7 +100,6 @@ 설명 저장 삭제 - 읽기 전용 템플릿 ID가 이미 존재합니다! 불러오기/내보내기 클립보드에서 불러오기 diff --git a/manager/app/src/main/res/values-lt/strings.xml b/manager/app/src/main/res/values-lt/strings.xml index 5b879cdb..59e25272 100644 --- a/manager/app/src/main/res/values-lt/strings.xml +++ b/manager/app/src/main/res/values-lt/strings.xml @@ -35,10 +35,6 @@ Šablonas Pasirinktinis Profilio pavadinimas - Prijungti vardų erdvę - Paveldėtas - Globalus - Individualus Grupės Galimybės SELinux kontekstas @@ -60,10 +56,8 @@ KernelSU dabar palaiko tik GKI branduolius Spustelėkite norėdami įdiegti Veikia - Supernaudotojai: %d Versija: %d Nepalaikoma - Moduliai: %d Tvarkyklės versija Branduolys SELinux statusas diff --git a/manager/app/src/main/res/values-lv/strings.xml b/manager/app/src/main/res/values-lv/strings.xml index 27d6a36a..c4ae7296 100644 --- a/manager/app/src/main/res/values-lv/strings.xml +++ b/manager/app/src/main/res/values-lv/strings.xml @@ -14,8 +14,6 @@ Noklikšķiniet, lai uzstādītu Darbojas Versija: %d - Superlietotāji: %d - Moduļi: %d Neatbalstīts KernelSU pagaidām atbalsta tikai GKI kodolus Kodols @@ -56,8 +54,6 @@ Veidne Pielāgots Profila vārds - Mount nosaukumvieta - Individuāls Iespējas SELinux konteksts Atvienot moduļus @@ -83,7 +79,6 @@ Saglabāt Dzēst Skatīt veidni - tikai lasīt Importēt/Eksportēt Nevar atrast vietējo eksportējamo veidni! Neizdevās saglabāt veidni @@ -98,12 +93,10 @@ Moduļi nav pieejami dēļ konflikta ar Magisk! KernelSU ir un vienmēr būs bezmaksas un atvērtā koda. Tomēr jūs varat parādīt mums, ka jums rūp, veicot ziedojumu. Grupas - Globāli Pašreizējā KernelSU versija %d ir pārāk zema, lai pārvaldnieks darbotos pareizi. Lūdzu, atjauniniet uz versiju %d vai jaunāku! Iespējot WebView atkļūdošanu Ieteicams %1$s nodalījuma attēls Nākamais - Mantots Izvēlieties failu Instalēt neaktīvajā slotā (pēc OTA) Pēc restartēšanas jūsu ierīce tiks **PIESPIESTI** palaista pašreizējā neaktīvajā slotā! diff --git a/manager/app/src/main/res/values-mr/strings.xml b/manager/app/src/main/res/values-mr/strings.xml index 43bd5878..243ae427 100644 --- a/manager/app/src/main/res/values-mr/strings.xml +++ b/manager/app/src/main/res/values-mr/strings.xml @@ -5,8 +5,6 @@ इंस्टॉल साठी क्लिक करा कार्यरत आवृत्ती: %d - मॉड्यूल्स: %d - सुपरयूझर: %d असमर्थित KernelSU आता फक्त GKI कर्नलचे समर्थन करते कर्नल @@ -51,15 +49,11 @@ KernelSU विनामूल्य आणि मुक्त स्रोत आहे, आणि नेहमीच असेल. तथापि, देणगी देऊन तुम्ही आम्हाला दाखवू शकता की तुमची काळजी आहे. आम्हाला पाठिंबा द्या कस्टम - माउंट नेमस्पेस डीफॉल्ट साचा - वैयक्तिक क्षमता %1$s वर स्रोत कोड पहा
आमच्या %2$s चॅनेलमध्ये सामील व्हा
प्रोफाइल नाव - इनहेरीटेड - जागतिक गट SELinux संदर्भ उमाउंट मॉड्यूल्स diff --git a/manager/app/src/main/res/values-ms/strings.xml b/manager/app/src/main/res/values-ms/strings.xml index 67215ee7..ed2601c1 100644 --- a/manager/app/src/main/res/values-ms/strings.xml +++ b/manager/app/src/main/res/values-ms/strings.xml @@ -6,8 +6,6 @@ Reboot ke Download Modul tidak berjaya diaktifkan: %s Reboot ke EDL - Superusers: %d - Modul: %d Enforcing Cap Jari Reboot ke Recovery diff --git a/manager/app/src/main/res/values-nl/strings.xml b/manager/app/src/main/res/values-nl/strings.xml index 02a35922..af6ca2dc 100644 --- a/manager/app/src/main/res/values-nl/strings.xml +++ b/manager/app/src/main/res/values-nl/strings.xml @@ -5,8 +5,6 @@ Klik om te installeren Werkend Versie: %d - Supergebruikers: %d - Modules: %d Niet ondersteund KernelSU ondersteunt alleen GKI kernels Kernel version @@ -56,10 +54,6 @@ Sjabloon Aangepast Profiel naam - Koppel naamruimte - Overgenomen - Globaal - Individuëel Groepen Mogelijkheden SELinux context @@ -91,7 +85,6 @@ Beschrijving Beheer lokale en online sjabloon van app-profiel. Verwijderen - Alleen lezen Sjabloon ID bestaat al! Synchroniseer online-sjablonen Mislukt naar opslaan sjabloon diff --git a/manager/app/src/main/res/values-pl/strings.xml b/manager/app/src/main/res/values-pl/strings.xml index 5cf08e0b..842eba10 100644 --- a/manager/app/src/main/res/values-pl/strings.xml +++ b/manager/app/src/main/res/values-pl/strings.xml @@ -6,8 +6,6 @@ Kliknij, aby zainstalować Działa Wersja: %d - Superużytkownicy: %d - Moduły: %d Nieobsługiwany KernelSU obsługuje obecnie tylko jądra GKI. Wersja jądra @@ -57,10 +55,6 @@ Szablon Własny Nazwa profilu - Przestrzeń nazw montowania - Odziedziczona - Globalna - Indywidualna Grupy Uprawnienia Kontekst SELinux @@ -98,7 +92,6 @@ Opis Zapisz Usuń - Tylko do odczytu Importuj/Eksportuj Importuj ze schowka Eksportuj do schowka diff --git a/manager/app/src/main/res/values-pt-rBR/strings.xml b/manager/app/src/main/res/values-pt-rBR/strings.xml index ec408eed..7fce8c4b 100644 --- a/manager/app/src/main/res/values-pt-rBR/strings.xml +++ b/manager/app/src/main/res/values-pt-rBR/strings.xml @@ -5,8 +5,6 @@ Clique para instalar Em execução Versão: %d - SuperUsuários: %d - Módulos: %d Sem suporte KernelSU suporta apenas kernels GKI agora Versão do kernel @@ -56,10 +54,6 @@ Modelo Personalizado Nome do perfil - Montar namespace - Herdado - Global - Individual Grupos Capacidades Contexto do SELinux @@ -90,7 +84,6 @@ ID do modelo inválido Sincronizar modelos online Criar modelo - Somente leitura Importar/Exportar Falha ao salvar o modelo Editar modelo diff --git a/manager/app/src/main/res/values-pt/strings.xml b/manager/app/src/main/res/values-pt/strings.xml index 0a3c19ef..6ea50705 100644 --- a/manager/app/src/main/res/values-pt/strings.xml +++ b/manager/app/src/main/res/values-pt/strings.xml @@ -4,8 +4,6 @@ Início Clique para instalar Funcionando - Super Usuário: %d - Módulos: %d Versão: %d Kernel Instalar @@ -45,14 +43,10 @@ Aprenda a instalar o KernelSU e usar os módulos O KernelSU é, e sempre será, gratuito e de código aberto. No entanto, você pode nos mostrar que se importa fazendo uma doação. Veja o código-fonte em %1$s
Junte-se ao nosso canal %2$s
- Individual - Global - Herdado Padrão Modelo Personalizado Nome do perfil - Montar namespace Modo de segurança Reinicie para entrar em vigor Aprender KernelSU diff --git a/manager/app/src/main/res/values-ro/strings.xml b/manager/app/src/main/res/values-ro/strings.xml index 42e0b439..7187661e 100644 --- a/manager/app/src/main/res/values-ro/strings.xml +++ b/manager/app/src/main/res/values-ro/strings.xml @@ -5,8 +5,6 @@ Click pentru a instala Funcționează Versiune: %d - Super-utilizatori: %d - Module: %d Necompatibil KernelSU suportă doar nuclee GKI acum Nucleu @@ -55,10 +53,6 @@ Șablon Personalizat Nume profil - Montare spațiu de nume - Moștenit - Global - Individual Grupuri Capabilități Context SELinux @@ -89,7 +83,6 @@ ID șablon nevalid Sincronizează șabloanele online Creează un șablon - doar citire Import/Export Nu s-a salvat șablonul Editează șablonul diff --git a/manager/app/src/main/res/values-ru/strings.xml b/manager/app/src/main/res/values-ru/strings.xml index e2fb20e7..5024a6f7 100644 --- a/manager/app/src/main/res/values-ru/strings.xml +++ b/manager/app/src/main/res/values-ru/strings.xml @@ -5,9 +5,7 @@ Нажмите, чтобы установить Работает Версия: %d - Суперпользователи: %d - Модули: %d Не поддерживается KernelSU поддерживает только GKI ядра. Версия ядра @@ -59,10 +57,6 @@ Шаблон Пользовательский Название профиля - Монтировать пространство имен - Унаследованный - Глобальный - Индивидуальный Группы Возможности Контекст SELinux @@ -93,7 +87,6 @@ Неверный ID шаблона Синхронизировать онлайн-шаблоны Создать шаблон - Только чтение Импорт/Экспорт Не удалось сохранить шаблон Редактирование шаблона diff --git a/manager/app/src/main/res/values-sl/strings.xml b/manager/app/src/main/res/values-sl/strings.xml index 2486a425..0f2e16f0 100644 --- a/manager/app/src/main/res/values-sl/strings.xml +++ b/manager/app/src/main/res/values-sl/strings.xml @@ -3,7 +3,6 @@ Klikni za namestitev V obdelavi Verzija: %d - Superuporabniki: %d KernelSU podpira samo GKI kernele Kernel Verzija upravitelja @@ -32,10 +31,6 @@ Glej odprto kodo na %1$s
Pridružite se našem %2$s kanalu
Privzeto Predloga - Imenski prostor vmestitve - Podedovano - Globalno - Pozameznik Zmožnosti Izvrzi module Po privzetem izvrzi module @@ -47,7 +42,6 @@ Dnevnik sprememb Predloga za aplikacijski profil Domov - Moduli: %d Ne podpira SuperUporabnik Napaka pri omogočanju modula: %s @@ -86,7 +80,6 @@ Opis Shrani Odstrani - le za branje id predloge že obstaja! Uvoz iz odložišča Izvoz v odložišče diff --git a/manager/app/src/main/res/values-sr/strings.xml b/manager/app/src/main/res/values-sr/strings.xml index 73920b6f..5ede2b29 100644 --- a/manager/app/src/main/res/values-sr/strings.xml +++ b/manager/app/src/main/res/values-sr/strings.xml @@ -1,7 +1,5 @@ - Superkorisnici - Moduli: %d Додирните да бисте инсталирали Почетна Није инсталирано diff --git a/manager/app/src/main/res/values-te/strings.xml b/manager/app/src/main/res/values-te/strings.xml index 9887318f..1263f9c5 100644 --- a/manager/app/src/main/res/values-te/strings.xml +++ b/manager/app/src/main/res/values-te/strings.xml @@ -15,7 +15,5 @@ ఇన్‌స్టాల్ చేయడానికి క్లిక్ చేయండి పని చేస్తోంది వెర్షన్: %d - సూపర్‌యూజర్‌లు: %d - మాడ్యూల్స్: %d లాగ్‌లు సేవ్ చేయండి diff --git a/manager/app/src/main/res/values-th/strings.xml b/manager/app/src/main/res/values-th/strings.xml index 21a5120e..f513e883 100644 --- a/manager/app/src/main/res/values-th/strings.xml +++ b/manager/app/src/main/res/values-th/strings.xml @@ -6,8 +6,6 @@ กำลังทำงาน เวอร์ชัน: %d เวอร์ชันตัวจัดการ - สิทธิ์ผู้ใช้ขั้นสูง: %d - โมดูล: %d ไม่รองรับ Enforcing รีบูตเข้าสู่โหมดกู้คืน @@ -55,10 +53,6 @@ ค่าเริ่มต้น เทมเพลต ชื่อโปรไฟล์ - Mount เนมสเปซ - ทั่วไป - สืบทอด - ส่วนบุคคล หมวดหมู่ ความสามารถของแอป การเปิดใช้งานตัวเลือกนี้จะทำให้ KernelSU สามารถกู้คืนไฟล์ที่แก้ไขโดยโมดูลสำหรับแอปนี้ได้ @@ -88,7 +82,6 @@ ไอดีเทมเพลตไม่ถูกต้อง ซิงค์เทมเพลตออนไลน์ สร้างเทมเพลต - อ่านเท่านั้น นำเข้า/ส่งออก ไม่สามารถบันทึกเทมเพลต แก้ไขเทมเพลต diff --git a/manager/app/src/main/res/values-tr/strings.xml b/manager/app/src/main/res/values-tr/strings.xml index e050a20c..43551f2f 100644 --- a/manager/app/src/main/res/values-tr/strings.xml +++ b/manager/app/src/main/res/values-tr/strings.xml @@ -6,8 +6,6 @@ Kurmak için tıklayın Çalışıyor Sürüm: %d - Süper kullanıcılar: %d - Modüller: %d Desteklenmiyor KernelSU şimdilik sadece GKI çekirdeklerini destekliyor Çekirdek Versiyonu @@ -57,10 +55,6 @@ Şablon Özel Profil adı - Ad alanını bağla - Kalıtsal - Küresel - Bireysel Gruplar Yetkinlikler SELinux içeriği @@ -92,7 +86,6 @@ Kaydet Sil Şablonu görüntüle - Salt okunur Şablon kimliği zaten var! İçe aktar/Dışa aktar Panodan içe aktar diff --git a/manager/app/src/main/res/values-uk/strings.xml b/manager/app/src/main/res/values-uk/strings.xml index fda89aba..7ca3ac0f 100644 --- a/manager/app/src/main/res/values-uk/strings.xml +++ b/manager/app/src/main/res/values-uk/strings.xml @@ -5,8 +5,6 @@ Натисніть щоб встановити Працює Версія: %d - Суперкористувачі: %d - Модулі: %d Не підтримується KernelSU зараз підтримує лише ядра GKI. Версія ядра @@ -56,10 +54,6 @@ Шаблон Власний Назва профілю - Змонтувати простір імен - Наслідуваний - Глобальний - Індивідуальний Групи Можливості Контекст SELinux @@ -90,7 +84,6 @@ Недійсний ідентифікатор шаблону Синхронізувати мережеві шаблони Створити шаблон - Тільки для читання Імпорт/Експорт Помилка при збереженні шаблону Редагувати шаблон diff --git a/manager/app/src/main/res/values-vi/strings.xml b/manager/app/src/main/res/values-vi/strings.xml index a9b261d2..d7e1a363 100644 --- a/manager/app/src/main/res/values-vi/strings.xml +++ b/manager/app/src/main/res/values-vi/strings.xml @@ -63,19 +63,13 @@ Chế độ an toàn Khởi động lại để có hiệu lực https://kernelsu.org/guide/what-is-kernelsu.html - Superusers: %d - Modules: %d Tên miền Quy tắc Khởi chạy Khởi động lại - Không gian tên Tính tương thích Cập nhật quy tắc SELinux cho %s thất bại Buộc dừng - Thừa hưởng - Toàn cục - Riêng biệt Bối cảnh SELinux Umount modules Phiên bản KernelSU hiện tại %d quá thấp để trình quản lý hoạt động bình thường. Vui lòng cập nhật lên phiên bản %d hoặc cao hơn! @@ -100,7 +94,6 @@ Xóa Bộ nhớ tạm đang trống! Xem mẫu - Chỉ đọc ID Gỡ lỗi WebView Có thể sử dụng để gỡ lỗi WebUI. Vui lòng chỉ bật khi cần thiết. 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 de0a7db5..ecdb737d 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -5,7 +5,6 @@ 点击安装 工作中 版本:%d - 超级用户数:%d 不支持 内核上未检测到 KernelSU 驱动程序,内核错误? 内核版本 @@ -47,7 +46,6 @@ 安全模式 重启生效 因与 Magisk 有冲突,所有模块不可用! - 模块数:%d 了解 KernelSU https://kernelsu.org/zh_CN/guide/what-is-kernelsu.html 了解如何安装 KernelSU 以及如何开发模块 @@ -58,10 +56,6 @@ 模版 自定义 名称 - 命名空间 - 继承 - 全局 - 私有 权能 SELinux @@ -94,7 +88,6 @@ 保存 删除 查看模版 - 只读 模版 ID 已存在! 导入/导出 从剪切板导入 @@ -193,7 +186,6 @@ 搜索 清空日志 确定要清空选中的日志文件吗?此操作无法撤销。 - 日志清空成功 按类型筛选 所有类型 显示 %1$d / %2$d 条记录 @@ -202,7 +194,6 @@ 刷新 原始日志 按 UID、命令或详情搜索… - 清除搜索 查看 KernelSU 超级用户访问日志 排除子类型 当前应用 @@ -220,7 +211,6 @@ 启用动态管理器 动态管理器签名大小 动态管理器签名哈希 - 哈希必须是 64 个 16 进制字符 动态管理器配置设置成功 设置动态管理器配置失败 无效的管理器配置 @@ -238,14 +228,12 @@ 运行环境清理失败 Umount 路径管理 - 管理内核卸载路径 添加或删除路径后需要重启设备才能生效。系统会在下次启动时应用新的配置。 添加 Umount 路径 挂载路径 卸载标志 0=正常卸载, 8=MNT_DETACH, -1=自动 标志 - 默认条目 确认删除 确定要删除路径 %s 吗? 路径已添加,重启后生效 @@ -260,9 +248,6 @@ 个性化 自定义应用外观和主题 - 主题设置 - 主题模式 - 选择应用的显示主题 刷入完成 准备中… @@ -284,20 +269,14 @@ 复制失败 未知错误 刷入失败 - LKM 修复/安装 - 刷入 AnyKernel3 内核版本:%1$s 使用的修补工具:%1$s - 配置 - 应用程序设置 工具 需要root权限 KPM 补丁 用于添加额外的KPM功能 KPM 补丁 - 在刷入前对内核镜像应用KPM补丁 KPM 撤销补丁 - 撤销之前应用的KPM补丁 KPM 补丁已启用 KPM 撤销补丁已启用 KPM 补丁模式 @@ -315,7 +294,6 @@ KPM 撤销补丁失败 KPM 补丁操作失败: %s 跟随内核 - 原样使用内核,不进行任何KPM修改 内核刷入 AnyKernel3 内核 刷入AnyKernel3格式的内核zip包 @@ -331,18 +309,11 @@ kpm 模块加载失败 参数 执行 - KPM 版本 关闭 以下内核模块功能由 KernelPatch 开发,并经过修改后加入了 SukiSU Ultra 的内核模块功能 - SukiSU Ultra 期待 成功 失败 - SukiSU Ultra 在未来将是 KSU 的相对独立分支,但我们仍然感谢官方 KernelSU 和 MKSU 等的贡献! 不支持 - 支持 - 内核未修补 - 内核未配置 - 自定义设置 KPM 安装 加载 嵌入 @@ -381,22 +352,17 @@ 配置说明 此功能允许您自定义 SuSFS 的 uname 值和构建时间伪装。输入您想要设置的值,点击应用即可生效 Uname 值 - 请输入自定义 uname 值 构建时间伪装 - 请输入构建时间伪装值 当前值: %s 当前构建时间: %s - 重置为默认值 应用 确认重置 无法找到 ksu_susfs 文件 SuSFS 命令执行失败 - 执行 SuSFS 命令时出错: %s SuSFS 内核名称和构建时间设置成功: %s, %s - SuSFS 配置 开机自启动 重启时自动应用所有非默认配置 @@ -420,7 +386,6 @@ 路径未找到错误 路径 挂载路径 - 例如: /system/addon.d 暂无 SuS 路径配置 暂无 SuS 挂载配置 暂无尝试卸载配置 @@ -445,8 +410,6 @@ Android Data 路径 SDCard 路径 - 设置 Android Data 路径 - 设置 SDCard 路径 显示当前 SuSFS 启用的功能状态 未找到功能状态信息 @@ -460,12 +423,9 @@ 欺骗 Cmdline/Bootconfig 开放重定向支持 日志记录支持 - 自动默认挂载 - 自动绑定挂载 自动尝试卸载绑定挂载 隐藏 KSU SuSFS 符号 SuS Kstat 支持 - SuS SU 模式切换功能 可配置的 SuSFS 功能 SuSFS 启用日志 @@ -477,8 +437,6 @@ 当前执行位置:%s Service Post-FS-Data - 在系统服务启动后执行 - 在文件系统挂载后但系统完全启动前执行,可能会导致循环重启 槽位信息 查看当前启动槽位信息并复制数值 当前活动槽位:%s @@ -572,10 +530,7 @@ 应用路径 其他路径 其他 - 应用 添加应用路径 - SuSFS 库版本不匹配,内核:%1$s vs 管理器:%2$s,建议更新内核或管理器 - 警告 搜索应用 %1$d 个已选应用 %1$d 个已添加应用 @@ -592,7 +547,6 @@ 重置循环路径 确定要清空所有 SuS 循环路径吗?此操作无法撤销。 循环路径 - /data/example/path 注意:只有不在 /storage/ 和 /sdcard/ 内的路径才能通过循环路径添加。 错误:循环路径不能位于 /storage/ 或 /sdcard/ 目录内 循环路径 @@ -604,11 +558,9 @@ AVC 日志欺骗已启用 AVC 日志欺骗已禁用 禁用: 禁用在内核 AVC 日志中欺骗 \'su\' 的 sus tcontext。\n启用: 启用在内核 AVC 日志中将 \'su\' 的 sus tcontext 欺骗为 \'kernel\' - 重要提示:\n- 内核中默认设置为 \'0\'\n- 启用此功能有时会使开发人员在调试权限或 SELinux 问题时难以识别原因,因此建议用户在调试时禁用此功能。 SUS映射 库文件路径 - /data/adb/modules/my_module/zygisk/arm64-v8a.so 添加SUS映射 编辑SUS映射 SUS映射添加成功: %1$s diff --git a/manager/app/src/main/res/values-zh-rHK/strings.xml b/manager/app/src/main/res/values-zh-rHK/strings.xml index 5c292744..a9d95063 100644 --- a/manager/app/src/main/res/values-zh-rHK/strings.xml +++ b/manager/app/src/main/res/values-zh-rHK/strings.xml @@ -5,8 +5,6 @@ 按一下以安裝 運作中 KernelSU 版本:%d - 超級使用者:%d 個 - 已安裝模組:%d 個 不支援 KernelSU 現在僅支援 GKI 核心 核心 @@ -54,8 +52,6 @@ 預設 設定檔名稱 範本 - 繼承 - 全域 功能 卸載模組 無法更新 %s 應用程式設定檔 @@ -66,8 +62,6 @@ 網域 更新 自訂 - 掛載命名空間 - 個人 群組 SELinux 環境 預設解除安裝模組 @@ -89,7 +83,6 @@ 模板 ID 無效 同步在線規則 創建模板 - 只讀 匯出 / 匯入 模板儲存失敗 編輯模板 diff --git a/manager/app/src/main/res/values-zh-rTW/strings.xml b/manager/app/src/main/res/values-zh-rTW/strings.xml index edf3f90e..b07bffa5 100644 --- a/manager/app/src/main/res/values-zh-rTW/strings.xml +++ b/manager/app/src/main/res/values-zh-rTW/strings.xml @@ -5,7 +5,6 @@ 點選開始安裝 已開始運作 版本:%d - 授權:%d 個應用程式 未受支援 KernelSU 目前僅支援 GKI 核心 核心版本 @@ -44,7 +43,6 @@ 安全模式 將在重新啟動時生效 與 Magisk 發生衝突,無法使用模組功能! - 掛載:%d 個模組 深入瞭解 KernelSU https://kernelsu.org/zh_TW/guide/what-is-kernelsu.html 知曉安裝、使用 KernelSU 本體與其模組功能的方法 @@ -65,10 +63,6 @@ 重新執行 範本 Profile 名稱 - 命名空間掛載 - 繼承 - 全域 - 個體 群組 SELinux 上下文 定域 @@ -93,7 +87,6 @@ 無法取得更新說明:%s 名稱 同步線上範本 - 唯讀 匯入/匯出 無法儲存範本 說明 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index d9887453..9063c9e6 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -6,8 +6,6 @@ Click to install Working Version: %d - Superusers: %d - Modules: %d Unsupported No KernelSU driver detected on your kernel, wrong kernel? Kernel version @@ -62,10 +60,6 @@ Template Custom Profile name - Mount namespace - Inherited - Global - Individual Groups Capabilities SELinux context @@ -98,7 +92,6 @@ Save Delete View template - Read only Template ID already exists! Import/Export Import from clipboard @@ -198,7 +191,6 @@ Search Clear Logs Are you sure you want to clear the selected log file? This action cannot be undone. - Logs cleared successfully Filter by Type All Types Showing %1$d of %2$d entries @@ -207,7 +199,6 @@ Refresh Raw Log Search by UID, command, or details… - Clear search View KernelSU superuser access logs Exclude sub-types Current App @@ -225,7 +216,6 @@ 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 @@ -244,14 +234,12 @@ Failed to clean runtime environment Umount Path Management - Manage kernel unmount paths A reboot is required for changes to take effect. The system will apply the new configuration on the next boot. Add Umount Path Mount Path Unmount Flags 0=Normal unmount, 8=MNT_DETACH, -1=Auto Flags - Default Entry Confirm Delete Are you sure you want to delete the path %s? Path added, will take effect after reboot @@ -267,9 +255,6 @@ Personalization Customize the app\'s appearance and theme - Theme Settings - Theme Mode - Select the app\'s display theme Flash Complete Preparing… @@ -291,20 +276,14 @@ Copy failed Unknown error Flash failed - LKM repair/installation - Flashing AnyKernel3 Kernel version:%1$s Using the patching tool:%1$s - Configure - Application Settings Tools Requires root privileges KPM Patch For adding additional KPM features KPM Patch - Apply KPM patch to kernel image before flashing KPM Undo Patch - Undo previously applied KPM patch KPM patch enabled KPM undo patch enabled KPM Patch Mode @@ -322,7 +301,6 @@ KPM undo patch failed KPM patch operation failed: %s Follow Kernel - Use kernel as-is without any KPM modifications Kernel Flashing AnyKernel3 Kernel Flash AnyKernel3 format kernel zip @@ -339,18 +317,11 @@ Load of kpm module failed Parameters Execute - KPM Version Close The following kernel module functions were developed by KernelPatch and modified to include the kernel module functions of SukiSU Ultra - SukiSU Ultra Look forward to Success Failed - SukiSU Ultra will be a relatively independent branch of KSU in the future, but we still appreciate the official KernelSU and MKSU etc. for their contributions! Unsupported - Supported - Kernel not patched - Kernel not configured - Custom settings KPM Install Load Embed @@ -389,22 +360,17 @@ Configuration Description This feature allows you to customize the SuSFS uname value and build time spoofing. Enter the values you want to set and click Apply to take effect Uname Value - Please enter custom uname value Build Time Spoofing - Please enter build time spoofing value Current value: %s Current build time: %s - Reset to Default Apply Confirm Reset Cannot find ksu_susfs file SuSFS command execution failed - Error executing SuSFS command: %s SuSFS uname and build time set successfully: %s, %s - SuSFS Configuration Auto Start Automatically apply all non-default configurations on reboot @@ -428,7 +394,6 @@ Path not found error Path Mount Path - e.g.: /system/addon.d No SUS paths configured No SUS mounts configured No try umount configured @@ -452,8 +417,6 @@ Android Data Path SD Card Path - Set Android Data Path - Set SD Card Path Display current SuSFS enabled features status No feature status information found @@ -467,12 +430,9 @@ Spoof Cmdline/Bootconfig Open Redirect Support Logging Support - Auto Default Mount - Auto Bind Mount Auto Try Umount Bind Mount Hide KSU SUSFS Symbols SUS Kstat Support - SUS SU mode switching function Configurable SuSFS Features SuSFS Enable Log @@ -484,8 +444,6 @@ Current execution location: %s Service Post-FS-Data - Execute after system services start - Execute after file system is mounted but before system is fully booted,May cause a boot loop Slot Information View current boot slot information and copy values Current Active Slot: %s @@ -579,10 +537,7 @@ Application Path Other paths Other - App Add App Path - SuSFS library version mismatch, kernel: %1$s vs manager: %2$s, It is recommended to update the kernel or manager - Warning Search Apps %1$d apps selected %1$d apps already added @@ -599,7 +554,6 @@ Reset Loop Paths Are you sure you want to clear all SUS loop paths? This action cannot be undone Loop Path - /data/example/path Note: Only paths NOT inside /storage/ and /sdcard/ can be added via loop paths Error: Loop paths cannot be inside /storage/ or /sdcard/ directories Loop Paths @@ -611,11 +565,9 @@ AVC log spoofing has been enabled AVC log spoofing has been disabled Disabled: Disable spoofing the sus tcontext of \'su\' shown in avc log in kernel\nEnabled: Enable spoofing the sus tcontext of \'su\' with \'kernel\' shown in avc log in kernel - Important Note:\n- It is set to \'0\' by default in kernel\n- Enabling this will sometimes make developers hard to identify the cause when they are debugging with some permission or SELinux issue, so users are advised to disable this when doing SUS Maps Library Path - /data/adb/modules/my_module/zygisk/arm64-v8a.so Add SUS Map Edit SUS Map SUS map added successfully: %1$s