Step 3: Added theme mode switching, introduced uninstall path manager and user-mode scanning toggle
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,14 +15,4 @@ add_library(kernelsu
|
||||
|
||||
find_library(log-lib log)
|
||||
|
||||
if(ANDROID_ABI STREQUAL "arm64-v8a")
|
||||
set(zakosign-lib ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libzakosign.so)
|
||||
elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
|
||||
set(zakosign-lib ${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a/libzakosign.so)
|
||||
endif()
|
||||
|
||||
if(ANDROID_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "armeabi-v7a")
|
||||
target_link_libraries(kernelsu ${log-lib} ${zakosign-lib})
|
||||
else()
|
||||
target_link_libraries(kernelsu ${log-lib})
|
||||
endif()
|
||||
target_link_libraries(kernelsu ${log-lib})
|
||||
|
||||
@@ -419,7 +419,7 @@ NativeBridgeNP(getManagersList, jobject) {
|
||||
LogDebug("getManagersList: count=%d", managerListInfo.count);
|
||||
return obj;
|
||||
}
|
||||
|
||||
#if 0
|
||||
NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
if (!modulePath) {
|
||||
@@ -438,6 +438,7 @@ NativeBridge(verifyModuleSignature, jboolean, jstring modulePath) {
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
NativeBridgeNP(isUidScannerEnabled, jboolean) {
|
||||
return is_uid_scanner_enabled();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "prelude.h"
|
||||
#include "ksu.h"
|
||||
|
||||
#if 0
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
|
||||
// Zako extern declarations
|
||||
@@ -23,6 +24,7 @@ extern uint32_t zako_file_verify_esig(int fd, uint32_t flags);
|
||||
extern const char* zako_file_verrcidx2str(uint8_t index);
|
||||
|
||||
#endif // __aarch64__ || _M_ARM64 || __arm__ || _M_ARM
|
||||
#endif
|
||||
|
||||
static int fd = -1;
|
||||
|
||||
@@ -348,6 +350,7 @@ bool clear_uid_scanner_environment(void)
|
||||
return ksuctl(KSU_IOCTL_ENABLE_UID_SCANNER, &cmd);
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool verify_module_signature(const char* input) {
|
||||
#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) || defined(_M_ARM)
|
||||
if (input == NULL) {
|
||||
@@ -404,3 +407,4 @@ bool verify_module_signature(const char* input) {
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -119,7 +119,9 @@ bool clear_dynamic_manager();
|
||||
|
||||
bool get_managers_list(struct manager_list_info* info);
|
||||
|
||||
#if 0
|
||||
bool verify_module_signature(const char* input);
|
||||
#endif
|
||||
|
||||
bool is_uid_scanner_enabled();
|
||||
|
||||
|
||||
@@ -371,6 +371,7 @@ private fun StatusCard(
|
||||
text = workingText,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = if (isSystemInDarkTheme()) Color(0xFFB8E6C5) else Color(0xFF1A5A2E)
|
||||
)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
Text(
|
||||
@@ -378,6 +379,7 @@ private fun StatusCard(
|
||||
text = stringResource(R.string.home_working_version, ksuVersion),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (isSystemInDarkTheme()) Color(0xFF9DD4AC) else Color(0xFF2D7A4A)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.add
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Palette
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.HazeStyle
|
||||
import dev.chrisbanes.haze.HazeTint
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.SuperDropdown
|
||||
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.MiuixScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Back
|
||||
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
|
||||
|
||||
@Composable
|
||||
@Destination<RootGraph>
|
||||
fun Personalization(
|
||||
navigator: DestinationsNavigator
|
||||
) {
|
||||
val scrollBehavior = MiuixScrollBehavior()
|
||||
val hazeState = remember { HazeState() }
|
||||
val hazeStyle = HazeStyle(
|
||||
backgroundColor = colorScheme.background,
|
||||
tint = HazeTint(colorScheme.background.copy(0.8f))
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
modifier = Modifier.hazeEffect(hazeState) {
|
||||
style = hazeStyle
|
||||
blurRadius = 30.dp
|
||||
noiseFactor = 0f
|
||||
},
|
||||
color = Color.Transparent,
|
||||
title = stringResource(R.string.personalization),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
navigator.popBackStack()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Useful.Back,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
popupHost = { },
|
||||
contentWindowInsets = WindowInsets.systemBars.add(WindowInsets.displayCutout).only(WindowInsetsSides.Horizontal)
|
||||
) { innerPadding ->
|
||||
val context = LocalContext.current
|
||||
val activity = context as? Activity
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.height(getWindowSize().height.dp)
|
||||
.scrollEndHaptic()
|
||||
.overScrollVertical()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.hazeSource(state = hazeState)
|
||||
.padding(horizontal = 12.dp),
|
||||
contentPadding = innerPadding,
|
||||
overscrollEffect = null,
|
||||
) {
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val themeItems = listOf(
|
||||
stringResource(id = R.string.theme_follow_system),
|
||||
stringResource(id = R.string.theme_light),
|
||||
stringResource(id = R.string.theme_dark),
|
||||
)
|
||||
|
||||
var themeMode by rememberSaveable {
|
||||
mutableIntStateOf(prefs.getInt("theme_mode", 0))
|
||||
}
|
||||
|
||||
SuperDropdown(
|
||||
title = stringResource(id = R.string.theme_mode),
|
||||
summary = stringResource(id = R.string.theme_mode_summary),
|
||||
items = themeItems,
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.Palette,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(id = R.string.theme_mode),
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
selectedIndex = themeMode,
|
||||
onSelectedIndexChange = { index ->
|
||||
prefs.edit {
|
||||
putInt("theme_mode", index)
|
||||
}
|
||||
themeMode = index
|
||||
activity?.recreate()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
@@ -13,9 +18,11 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CleaningServices
|
||||
import androidx.compose.material.icons.filled.Groups
|
||||
import androidx.compose.material.icons.filled.Scanner
|
||||
import androidx.compose.material.icons.rounded.Adb
|
||||
import androidx.compose.material.icons.rounded.BugReport
|
||||
import androidx.compose.material.icons.rounded.ContactPage
|
||||
@@ -25,14 +32,14 @@ import androidx.compose.material.icons.rounded.DeveloperMode
|
||||
import androidx.compose.material.icons.rounded.EnhancedEncryption
|
||||
import androidx.compose.material.icons.rounded.Fence
|
||||
import androidx.compose.material.icons.rounded.FolderDelete
|
||||
import androidx.compose.material.icons.rounded.Palette
|
||||
import androidx.compose.material.icons.rounded.RemoveCircle
|
||||
import androidx.compose.material.icons.rounded.RemoveModerator
|
||||
import androidx.compose.material.icons.rounded.RestartAlt
|
||||
import androidx.compose.material.icons.rounded.Security
|
||||
import androidx.compose.material.icons.rounded.Update
|
||||
import androidx.compose.material.icons.rounded.UploadFile
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -54,6 +61,8 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AboutScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.LogViewerDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.PersonalizationDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.UmountManagerDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.HazeStyle
|
||||
@@ -62,27 +71,30 @@ import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||
import com.sukisu.ultra.ui.component.DynamicManagerCard
|
||||
import com.sukisu.ultra.ui.component.KsuIsValid
|
||||
import com.sukisu.ultra.ui.component.SendLogDialog
|
||||
import com.sukisu.ultra.ui.component.SuperDropdown
|
||||
import com.sukisu.ultra.ui.component.UninstallDialog
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.rememberLoadingDialog
|
||||
import com.sukisu.ultra.ui.util.cleanRuntimeEnvironment
|
||||
import com.sukisu.ultra.ui.util.execKsud
|
||||
import com.sukisu.ultra.ui.util.getUidMultiUserScan
|
||||
import com.sukisu.ultra.ui.util.readUidScannerFile
|
||||
import com.sukisu.ultra.ui.util.setUidAutoScan
|
||||
import com.sukisu.ultra.ui.util.setUidMultiUserScan
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.ButtonDefaults
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextButton
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import top.yukonga.miuix.kmp.extra.SuperArrow
|
||||
import top.yukonga.miuix.kmp.extra.SuperDialog
|
||||
import top.yukonga.miuix.kmp.extra.SuperSwitch
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
|
||||
import top.yukonga.miuix.kmp.utils.getWindowSize
|
||||
@@ -148,6 +160,31 @@ fun SettingPager(
|
||||
mutableStateOf(prefs.getBoolean("check_update", true))
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val personalization = stringResource(id = R.string.personalization)
|
||||
SuperArrow(
|
||||
title = personalization,
|
||||
summary = stringResource(id = R.string.personalization_summary),
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.Palette,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = personalization,
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
navigator.navigate(PersonalizationDestination) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
@@ -493,6 +530,20 @@ fun SettingPager(
|
||||
DynamicManagerCard()
|
||||
}
|
||||
|
||||
KsuIsValid {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
UidScannerSection(prefs = prefs, scope = scope, context = context)
|
||||
}
|
||||
}
|
||||
|
||||
KsuIsValid {
|
||||
val lkmMode = Natives.isLkmMode
|
||||
if (lkmMode) {
|
||||
@@ -544,6 +595,28 @@ fun SettingPager(
|
||||
}
|
||||
)
|
||||
}
|
||||
KsuIsValid {
|
||||
val lkmMode = Natives.isLkmMode
|
||||
if (lkmMode) {
|
||||
val umontManager = stringResource(id = R.string.umount_path_manager)
|
||||
SuperArrow(
|
||||
title = umontManager,
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Rounded.FolderDelete,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = umontManager,
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
navigator.navigate(UmountManagerDestination) {
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SuperArrow(
|
||||
title = stringResource(id = R.string.send_log),
|
||||
leftAction = {
|
||||
@@ -602,3 +675,149 @@ enum class UninstallType(val icon: ImageVector, val title: Int, val message: Int
|
||||
NONE(Icons.Rounded.Adb, 0, 0)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UidScannerSection(
|
||||
prefs: SharedPreferences,
|
||||
scope: CoroutineScope,
|
||||
context: Context
|
||||
) {
|
||||
val realAuto = Natives.isUidScannerEnabled()
|
||||
val realMulti = getUidMultiUserScan()
|
||||
|
||||
var autoOn by remember { mutableStateOf(realAuto) }
|
||||
var multiOn by remember { mutableStateOf(realMulti) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
autoOn = realAuto
|
||||
multiOn = realMulti
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", autoOn)
|
||||
putBoolean("uid_multi_user_scan", multiOn)
|
||||
}
|
||||
}
|
||||
|
||||
SuperSwitch(
|
||||
title = stringResource(R.string.uid_auto_scan_title),
|
||||
summary = stringResource(R.string.uid_auto_scan_summary),
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Filled.Scanner,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(R.string.uid_auto_scan_title),
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
checked = autoOn,
|
||||
onCheckedChange = { target ->
|
||||
autoOn = target
|
||||
if (!target) multiOn = false
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
setUidAutoScan(target)
|
||||
val actual = Natives.isUidScannerEnabled() || readUidScannerFile()
|
||||
withContext(Dispatchers.Main) {
|
||||
autoOn = actual
|
||||
if (!actual) multiOn = false
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", actual)
|
||||
putBoolean("uid_multi_user_scan", multiOn)
|
||||
}
|
||||
if (actual != target) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.uid_scanner_setting_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = autoOn,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
SuperSwitch(
|
||||
title = stringResource(R.string.uid_multi_user_scan_title),
|
||||
summary = stringResource(R.string.uid_multi_user_scan_summary),
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Filled.Groups,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(R.string.uid_multi_user_scan_title),
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
checked = multiOn,
|
||||
onCheckedChange = { target ->
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val ok = setUidMultiUserScan(target)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (ok) {
|
||||
multiOn = target
|
||||
prefs.edit { putBoolean("uid_multi_user_scan", target) }
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.uid_scanner_setting_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = autoOn,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
SuperArrow(
|
||||
title = stringResource(R.string.clean_runtime_environment),
|
||||
summary = stringResource(R.string.clean_runtime_environment_summary),
|
||||
leftAction = {
|
||||
Icon(
|
||||
Icons.Filled.CleaningServices,
|
||||
modifier = Modifier.padding(end = 16.dp),
|
||||
contentDescription = stringResource(R.string.clean_runtime_environment),
|
||||
tint = colorScheme.onBackground
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.clean_runtime_environment),
|
||||
content = context.getString(R.string.clean_runtime_environment_confirm)
|
||||
) == ConfirmResult.Confirmed
|
||||
) {
|
||||
if (cleanRuntimeEnvironment()) {
|
||||
autoOn = false
|
||||
multiOn = false
|
||||
prefs.edit {
|
||||
putBoolean("uid_auto_scan", false)
|
||||
putBoolean("uid_multi_user_scan", false)
|
||||
}
|
||||
Natives.setUidScannerEnabled(false)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.clean_runtime_environment_success),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.clean_runtime_environment_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Folder
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.rounded.Add
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.Button
|
||||
import top.yukonga.miuix.kmp.basic.Card
|
||||
import top.yukonga.miuix.kmp.basic.FloatingActionButton
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
|
||||
import top.yukonga.miuix.kmp.basic.Scaffold
|
||||
import top.yukonga.miuix.kmp.basic.Text
|
||||
import top.yukonga.miuix.kmp.basic.TextButton
|
||||
import top.yukonga.miuix.kmp.basic.TextField
|
||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||
import top.yukonga.miuix.kmp.extra.SuperDialog
|
||||
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Back
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Delete
|
||||
import top.yukonga.miuix.kmp.icon.icons.useful.Refresh
|
||||
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
|
||||
|
||||
private val SPACING_SMALL = 3.dp
|
||||
private val SPACING_MEDIUM = 8.dp
|
||||
private val SPACING_LARGE = 16.dp
|
||||
|
||||
data class UmountPathEntry(
|
||||
val path: String,
|
||||
val flags: Int,
|
||||
val isDefault: Boolean
|
||||
)
|
||||
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
fun UmountManager(navigator: DestinationsNavigator) {
|
||||
val scrollBehavior = MiuixScrollBehavior()
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
|
||||
var pathList by remember { mutableStateOf<List<UmountPathEntry>>(emptyList()) }
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
var showAddDialog by remember { mutableStateOf(false) }
|
||||
|
||||
fun loadPaths() {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
isLoading = true
|
||||
val result = listUmountPaths()
|
||||
val entries = parseUmountPaths(result)
|
||||
withContext(Dispatchers.Main) {
|
||||
pathList = entries
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
loadPaths()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = stringResource(R.string.umount_path_manager),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navigator.navigateUp() }) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Useful.Back,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { loadPaths() }) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Useful.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
color = Color.Transparent,
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = { showAddDialog = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Add,
|
||||
contentDescription = null,
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.height(getWindowSize().height.dp)
|
||||
.scrollEndHaptic()
|
||||
.overScrollVertical()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(SPACING_LARGE)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(SPACING_LARGE),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.width(SPACING_MEDIUM))
|
||||
Text(
|
||||
text = stringResource(R.string.umount_path_restart_notice)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
top.yukonga.miuix.kmp.basic.CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = SPACING_LARGE, vertical = SPACING_MEDIUM),
|
||||
verticalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)
|
||||
) {
|
||||
items(pathList, key = { it.path }) { entry ->
|
||||
UmountPathCard(
|
||||
entry = entry,
|
||||
onDelete = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val success = removeUmountPath(entry.path)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.umount_path_removed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
loadPaths()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.operation_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(SPACING_LARGE))
|
||||
}
|
||||
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = SPACING_LARGE),
|
||||
horizontalArrangement = Arrangement.spacedBy(SPACING_MEDIUM)
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.confirm_action),
|
||||
content = context.getString(R.string.confirm_clear_custom_paths)
|
||||
) == ConfirmResult.Confirmed) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val success = clearCustomUmountPaths()
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.custom_paths_cleared),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
loadPaths()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.operation_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(R.string.clear_custom_paths))
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val success = applyUmountConfigToKernel()
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.config_applied),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.operation_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(R.string.apply_config))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showAddDialog) {
|
||||
AddUmountPathDialog(
|
||||
onDismiss = { showAddDialog = false },
|
||||
onConfirm = { path, flags ->
|
||||
showAddDialog = false
|
||||
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val success = addUmountPath(path, flags)
|
||||
withContext(Dispatchers.Main) {
|
||||
if (success) {
|
||||
saveUmountConfig()
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.umount_path_added),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
loadPaths()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.operation_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UmountPathCard(
|
||||
entry: UmountPathEntry,
|
||||
onDelete: () -> Unit
|
||||
) {
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(SPACING_LARGE),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Folder,
|
||||
contentDescription = null,
|
||||
tint = if (entry.isDefault)
|
||||
colorScheme.primary
|
||||
else
|
||||
colorScheme.secondary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(SPACING_LARGE))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = entry.path
|
||||
)
|
||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||
Text(
|
||||
text = buildString {
|
||||
append(context.getString(R.string.flags))
|
||||
append(": ")
|
||||
append(entry.flags.toUmountFlagName(context))
|
||||
if (entry.isDefault) {
|
||||
append(" | ")
|
||||
append(context.getString(R.string.default_entry))
|
||||
}
|
||||
},
|
||||
color = colorScheme.onSurfaceVariantSummary
|
||||
)
|
||||
}
|
||||
|
||||
if (!entry.isDefault) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (confirmDialog.awaitConfirm(
|
||||
title = context.getString(R.string.confirm_delete),
|
||||
content = context.getString(R.string.confirm_delete_umount_path, entry.path)
|
||||
) == ConfirmResult.Confirmed) {
|
||||
onDelete()
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = MiuixIcons.Useful.Delete,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddUmountPathDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (String, Int) -> Unit
|
||||
) {
|
||||
var path by rememberSaveable { mutableStateOf("") }
|
||||
var flags by rememberSaveable { mutableStateOf("-1") }
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
|
||||
SuperDialog(
|
||||
show = showDialog,
|
||||
title = stringResource(R.string.add_umount_path),
|
||||
onDismissRequest = {
|
||||
showDialog.value = false
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
TextField(
|
||||
value = path,
|
||||
onValueChange = { path = it },
|
||||
label = stringResource(R.string.mount_path),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
||||
|
||||
TextField(
|
||||
value = flags,
|
||||
onValueChange = { flags = it },
|
||||
label = stringResource(R.string.umount_flags),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(SPACING_SMALL))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.umount_flags_hint),
|
||||
color = colorScheme.onSurfaceVariantSummary,
|
||||
modifier = Modifier.padding(start = SPACING_MEDIUM)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(SPACING_MEDIUM))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
TextButton(
|
||||
text = stringResource(android.R.string.cancel),
|
||||
onClick = {
|
||||
showDialog.value = false
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Spacer(Modifier.width(20.dp))
|
||||
TextButton(
|
||||
text = stringResource(android.R.string.ok),
|
||||
onClick = {
|
||||
val flagsInt = flags.toIntOrNull() ?: -1
|
||||
showDialog.value = false
|
||||
onConfirm(path, flagsInt)
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = path.isNotBlank()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseUmountPaths(output: String): List<UmountPathEntry> {
|
||||
val lines = output.lines().filter { it.isNotBlank() }
|
||||
if (lines.size < 2) return emptyList()
|
||||
|
||||
return lines.drop(2).mapNotNull { line ->
|
||||
val parts = line.trim().split(Regex("\\s+"))
|
||||
if (parts.size >= 3) {
|
||||
UmountPathEntry(
|
||||
path = parts[0],
|
||||
flags = parts[1].toIntOrNull() ?: -1,
|
||||
isDefault = parts[2].equals("Yes", ignoreCase = true)
|
||||
)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.toUmountFlagName(context: Context): String {
|
||||
return when (this) {
|
||||
-1 -> context.getString(R.string.mnt_detach)
|
||||
else -> this.toString()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||
import top.yukonga.miuix.kmp.theme.darkColorScheme
|
||||
import top.yukonga.miuix.kmp.theme.lightColorScheme
|
||||
@@ -11,8 +13,18 @@ fun KernelSUTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val themeMode = prefs.getInt("theme_mode", 0)
|
||||
|
||||
val useDarkTheme = when (themeMode) {
|
||||
1 -> false // 浅色
|
||||
2 -> true // 深色
|
||||
else -> darkTheme // 跟随系统
|
||||
}
|
||||
|
||||
val colorScheme = when {
|
||||
darkTheme -> darkColorScheme()
|
||||
useDarkTheme -> darkColorScheme()
|
||||
else -> lightColorScheme()
|
||||
}
|
||||
MiuixTheme(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -206,4 +206,44 @@
|
||||
<string name="invalid_sign_config">无效的管理器配置</string>
|
||||
<string name="dynamic_manager_disabled_success">动态管理器已禁用</string>
|
||||
<string name="dynamic_manager_clear_failed">清空动态管理器配置失败</string>
|
||||
<string name="uid_auto_scan_title">用户态扫描应用列表</string>
|
||||
<string name="uid_auto_scan_summary">开启后将使用用户态扫描应用列表,提高稳定性 (因内核扫描应用列表出现卡死等问题可以尝试打开此选项) </string>
|
||||
<string name="uid_multi_user_scan_title">多用户应用扫描</string>
|
||||
<string name="uid_multi_user_scan_summary">开启后将扫描所有用户的应用,包括工作资料等</string>
|
||||
<string name="uid_scanner_setting_failed">设置失败,请检查权限</string>
|
||||
<string name="clean_runtime_environment">清理运行环境</string>
|
||||
<string name="clean_runtime_environment_summary">清理运行时文件并停止扫描服务</string>
|
||||
<string name="clean_runtime_environment_confirm">您确定要清理运行环境吗?这将停止扫描服务并删除相关文件</string>
|
||||
<string name="clean_runtime_environment_success">运行环境清理成功</string>
|
||||
<string name="clean_runtime_environment_failed">运行环境清理失败</string>
|
||||
<!-- umount Manager -->
|
||||
<string name="umount_path_manager">Umount 路径管理</string>
|
||||
<string name="umount_path_manager_summary">管理内核卸载路径</string>
|
||||
<string name="umount_path_restart_notice">添加或删除路径后需要重启设备才能生效。系统会在下次启动时应用新的配置。</string>
|
||||
<string name="add_umount_path">添加 Umount 路径</string>
|
||||
<string name="mount_path">挂载路径</string>
|
||||
<string name="umount_flags">卸载标志</string>
|
||||
<string name="umount_flags_hint">0=正常卸载, 8=MNT_DETACH, -1=自动</string>
|
||||
<string name="flags">标志</string>
|
||||
<string name="default_entry">默认条目</string>
|
||||
<string name="confirm_delete">确认删除</string>
|
||||
<string name="confirm_delete_umount_path">确定要删除路径 %s 吗?</string>
|
||||
<string name="umount_path_added">路径已添加,重启后生效</string>
|
||||
<string name="umount_path_removed">路径已删除,重启后生效</string>
|
||||
<string name="operation_failed">操作失败</string>
|
||||
<string name="confirm_action">确认操作</string>
|
||||
<string name="confirm_clear_custom_paths">确定要清除所有自定义路径吗?(默认路径将保留)</string>
|
||||
<string name="custom_paths_cleared">自定义路径已清除</string>
|
||||
<string name="clear_custom_paths">清除自定义</string>
|
||||
<string name="apply_config">应用配置</string>
|
||||
<string name="config_applied">配置已应用到内核</string>
|
||||
<!-- Personalization Settings -->
|
||||
<string name="personalization">个性化</string>
|
||||
<string name="personalization_summary">自定义应用外观和主题</string>
|
||||
<string name="theme_settings">主题设置</string>
|
||||
<string name="theme_mode">主题模式</string>
|
||||
<string name="theme_mode_summary">选择应用的显示主题</string>
|
||||
<string name="theme_light">浅色</string>
|
||||
<string name="theme_dark">深色</string>
|
||||
<string name="theme_follow_system">跟随系统</string>
|
||||
</resources>
|
||||
|
||||
@@ -208,4 +208,46 @@
|
||||
<string name="invalid_sign_config">Invalid Manager configuration</string>
|
||||
<string name="dynamic_manager_disabled_success">Dynamic Manager disabled</string>
|
||||
<string name="dynamic_manager_clear_failed">Failed to clear dynamic Manager</string>
|
||||
<!-- UID Scanner Settings -->
|
||||
<string name="uid_auto_scan_title">User-mode scanning application list</string>
|
||||
<string name="uid_auto_scan_summary">Enabling this option will use user-mode scanning for the application list, improving stability. (If you encounter issues such as freezing during kernel scanning of the application list, you may try enabling this option.)</string>
|
||||
<string name="uid_multi_user_scan_title">Multi-User Application Scanning</string>
|
||||
<string name="uid_multi_user_scan_summary">When enabled, scans applications for all users, including work profiles</string>
|
||||
<string name="uid_scanner_setting_failed">Setting failed, please check permissions</string>
|
||||
<string name="clean_runtime_environment">Clean Runtime Environment</string>
|
||||
<string name="clean_runtime_environment_summary">Clean up runtime files and stop the scanner service</string>
|
||||
<string name="clean_runtime_environment_confirm">Are you sure you want to clean the runtime environment? This will stop the scanner service and remove related files.</string>
|
||||
<string name="clean_runtime_environment_success">Runtime environment cleaned successfully</string>
|
||||
<string name="clean_runtime_environment_failed">Failed to clean runtime environment</string>
|
||||
<!-- umount Manager -->
|
||||
<string name="umount_path_manager">Umount Path Management</string>
|
||||
<string name="umount_path_manager_summary">Manage kernel unmount paths</string>
|
||||
<string name="umount_path_restart_notice">A reboot is required for changes to take effect. The system will apply the new configuration on the next boot.</string>
|
||||
<string name="add_umount_path">Add Umount Path</string>
|
||||
<string name="mount_path">Mount Path</string>
|
||||
<string name="umount_flags">Unmount Flags</string>
|
||||
<string name="umount_flags_hint">0=Normal unmount, 8=MNT_DETACH, -1=Auto</string>
|
||||
<string name="flags">Flags</string>
|
||||
<string name="default_entry">Default Entry</string>
|
||||
<string name="confirm_delete">Confirm Delete</string>
|
||||
<string name="confirm_delete_umount_path">Are you sure you want to delete the path %s?</string>
|
||||
<string name="umount_path_added">Path added, will take effect after reboot</string>
|
||||
<string name="umount_path_removed">Path removed, will take effect after reboot</string>
|
||||
<string name="operation_failed">Operation failed</string>
|
||||
<string name="confirm_action">Confirm Action</string>
|
||||
<string name="confirm_clear_custom_paths">Are you sure you want to clear all custom paths? (Default paths will be preserved)</string>
|
||||
<string name="custom_paths_cleared">Custom paths cleared</string>
|
||||
<string name="clear_custom_paths">Clear Custom Paths</string>
|
||||
<string name="apply_config">Apply Configuration</string>
|
||||
<string name="config_applied">Configuration applied to kernel</string>
|
||||
<string name="mnt_detach">MNT_DETACH</string>
|
||||
<!-- Personalization Settings -->
|
||||
<string name="personalization">Personalization</string>
|
||||
<string name="personalization_summary">Customize the app\'s appearance and theme</string>
|
||||
<string name="theme_settings">Theme Settings</string>
|
||||
<string name="theme_mode">Theme Mode</string>
|
||||
<string name="theme_mode_summary">Select the app\'s display theme</string>
|
||||
<string name="theme_light">Light</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
<string name="theme_follow_system">Follow System</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user