Merge some files and rewrite the update history

This commit is contained in:
ShirkNeko
2025-03-22 14:09:21 +08:00
parent b28789ac7a
commit ba26677cfc
166 changed files with 6003 additions and 4896 deletions

View File

@@ -10,6 +10,8 @@ plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.lsplugin.apksign)
id("kotlin-parcelize")
}
val managerVersionCode: Int by rootProject.extra
@@ -23,7 +25,7 @@ apksign {
}
android {
namespace = "me.weishu.kernelsu"
namespace = "shirkneko.zako.sukisu"
buildTypes {
release {
@@ -68,7 +70,7 @@ android {
applicationVariants.all {
outputs.forEach {
val output = it as BaseVariantOutputImpl
output.outputFileName = "KernelSU_${managerVersionName}_${managerVersionCode}-$name.apk"
output.outputFileName = "SukiSU_${managerVersionName}_${managerVersionCode}-$name.apk"
}
kotlin.sourceSets {
getByName(name) {
@@ -102,6 +104,8 @@ dependencies {
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.foundation)
implementation(libs.androidx.documentfile)
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
@@ -133,4 +137,7 @@ dependencies {
implementation(libs.androidx.webkit)
implementation(libs.lsposed.cxx)
implementation(libs.com.github.topjohnwu.libsu.core)
}

View File

@@ -3,6 +3,9 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".KernelSUApplication"
@@ -15,6 +18,7 @@
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/Theme.KernelSU"
android:requestLegacyExternalStorage="true"
tools:targetApi="34">
<activity
android:name=".ui.MainActivity"

View File

@@ -1,5 +1,5 @@
// IKsuInterface.aidl
package me.weishu.kernelsu;
package shirkneko.zako.sukisu;
import android.content.pm.PackageInfo;
import rikka.parcelablelist.ParcelableListSlice;

Binary file not shown.

View File

@@ -10,7 +10,7 @@ project("kernelsu")
find_package(cxx REQUIRED CONFIG)
link_libraries(cxx::cxx)
add_library(kernelsu
add_library(zako
SHARED
jni.cc
ksu.cc
@@ -18,4 +18,4 @@ add_library(kernelsu
find_library(log-lib log)
target_link_libraries(kernelsu ${log-lib})
target_link_libraries(zako ${log-lib})

View File

@@ -12,7 +12,7 @@
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
Java_shirkneko_zako_sukisu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
auto cpkg = env->GetStringUTFChars(pkg, nullptr);
auto result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg);
@@ -21,13 +21,13 @@ Java_me_weishu_kernelsu_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg)
extern "C"
JNIEXPORT jint JNICALL
Java_me_weishu_kernelsu_Natives_getVersion(JNIEnv *env, jobject) {
Java_shirkneko_zako_sukisu_Natives_getVersion(JNIEnv *env, jobject) {
return get_version();
}
extern "C"
JNIEXPORT jintArray JNICALL
Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
Java_shirkneko_zako_sukisu_Natives_getAllowList(JNIEnv *env, jobject) {
int uids[1024];
int size = 0;
bool result = get_allow_list(uids, &size);
@@ -42,13 +42,13 @@ Java_me_weishu_kernelsu_Natives_getAllowList(JNIEnv *env, jobject) {
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
Java_shirkneko_zako_sukisu_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
return is_safe_mode();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
Java_shirkneko_zako_sukisu_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
return is_lkm_mode();
}
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
extern "C"
JNIEXPORT jobject JNICALL
Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
Java_shirkneko_zako_sukisu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
return nullptr;
}
@@ -129,7 +129,7 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg,
bool useDefaultProfile = !get_app_profile(key, &profile);
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
auto constructor = env->GetMethodID(cls, "<init>", "()V");
auto obj = env->NewObject(cls, constructor);
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
@@ -207,8 +207,8 @@ Java_me_weishu_kernelsu_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg,
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("me/weishu/kernelsu/Natives$Profile");
Java_shirkneko_zako_sukisu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("shirkneko/zako/sukisu/Natives$Profile");
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
@@ -293,16 +293,16 @@ Java_me_weishu_kernelsu_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobjec
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
Java_shirkneko_zako_sukisu_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
return uid_should_umount(uid);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
Java_shirkneko_zako_sukisu_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
return is_su_enabled();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_me_weishu_kernelsu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
Java_shirkneko_zako_sukisu_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
return set_su_enabled(enabled);
}

View File

@@ -1,362 +0,0 @@
package me.weishu.kernelsu.ui.screen
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FileUpload
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.getCurrentKmi
import me.weishu.kernelsu.ui.util.getSupportedKmis
import me.weishu.kernelsu.ui.util.isAbDevice
import me.weishu.kernelsu.ui.util.isInitBoot
import me.weishu.kernelsu.ui.util.rootAvailable
/**
* @author weishu
* @date 2024/3/12.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember {
mutableStateOf<InstallMethod?>(null)
}
var lkmSelection by remember {
mutableStateOf<LkmSelection>(LkmSelection.KmiNone)
}
val onInstall = {
installMethod?.let { method ->
val flashIt = FlashIt.FlashBoot(
boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot
)
navigator.navigate(FlashScreenDestination(flashIt))
}
}
val currentKmi by produceState(initialValue = "") { value = getCurrentKmi() }
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
kmi?.let {
lkmSelection = LkmSelection.KmiString(it)
onInstall()
}
}
val onClickNext = {
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
// no lkm file selected and cannot get current kmi
selectKmiDialog.show()
} else {
onInstall()
}
}
val selectLkmLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
lkmSelection = LkmSelection.LkmUri(uri)
}
}
}
val onLkmUpload = {
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopBar(
onBack = dropUnlessResumed { navigator.popBackStack() },
onLkmUpload = onLkmUpload,
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
SelectInstallMethod { method ->
installMethod = method
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text(
stringResource(
id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)"
)
)
}
Button(modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
onClick = {
onClickNext()
}) {
Text(
stringResource(id = R.string.install_next),
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
}
}
}
}
}
sealed class InstallMethod {
data class SelectFile(
val uri: Uri? = null,
@StringRes override val label: Int = R.string.select_file,
override val summary: String?
) : InstallMethod()
data object DirectInstall : InstallMethod() {
override val label: Int
get() = R.string.direct_install
}
data object DirectInstallToInactiveSlot : InstallMethod() {
override val label: Int
get() = R.string.install_inactive_slot
}
abstract val label: Int
open val summary: String? = null
}
@Composable
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val selectFileTip = stringResource(
id = R.string.select_file_tip, if (isInitBoot()) "init_boot" else "boot"
)
val radioOptions =
mutableListOf<InstallMethod>(InstallMethod.SelectFile(summary = selectFileTip))
if (rootAvailable) {
radioOptions.add(InstallMethod.DirectInstall)
if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
}
}
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
val selectImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
val option = InstallMethod.SelectFile(uri, summary = selectFileTip)
selectedOption = option
onSelected(option)
}
}
}
val confirmDialog = rememberConfirmDialog(onConfirm = {
selectedOption = InstallMethod.DirectInstallToInactiveSlot
onSelected(InstallMethod.DirectInstallToInactiveSlot)
}, onDismiss = null)
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
val onClick = { option: InstallMethod ->
when (option) {
is InstallMethod.SelectFile -> {
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
is InstallMethod.DirectInstall -> {
selectedOption = option
onSelected(option)
}
is InstallMethod.DirectInstallToInactiveSlot -> {
confirmDialog.showConfirm(dialogTitle, dialogContent)
}
}
}
Column {
radioOptions.forEach { option ->
val interactionSource = remember { MutableInteractionSource() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.toggleable(
value = option.javaClass == selectedOption?.javaClass,
onValueChange = {
onClick(option)
},
role = Role.RadioButton,
indication = LocalIndication.current,
interactionSource = interactionSource
)
) {
RadioButton(
selected = option.javaClass == selectedOption?.javaClass,
onClick = {
onClick(option)
},
interactionSource = interactionSource
)
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
Text(
text = stringResource(id = option.label),
fontSize = MaterialTheme.typography.titleMedium.fontSize,
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
fontStyle = MaterialTheme.typography.titleMedium.fontStyle
)
option.summary?.let {
Text(
text = it,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
fontStyle = MaterialTheme.typography.bodySmall.fontStyle
)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
return rememberCustomDialog { dismiss ->
val supportedKmi by produceState(initialValue = emptyList<String>()) {
value = getSupportedKmis()
}
val options = supportedKmi.map { value ->
ListOption(
titleText = value
)
}
var selection by remember { mutableStateOf<String?>(null) }
ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
onSelected(selection)
}, onCloseRequest = {
dismiss()
}), header = Header.Default(
title = stringResource(R.string.select_kmi),
), selection = ListSelection.Single(
showRadioButtons = true,
options = options,
) { _, option ->
selection = option.titleText
})
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
onBack: () -> Unit = {},
onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar(
title = { Text(stringResource(R.string.install)) }, navigationIcon = {
IconButton(
onClick = onBack
) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) }
}, actions = {
IconButton(onClick = onLkmUpload) {
Icon(Icons.Filled.FileUpload, contentDescription = null)
}
},
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
)
}
@Composable
@Preview
fun SelectInstallPreview() {
InstallScreen(EmptyDestinationsNavigator)
}

View File

@@ -1,192 +0,0 @@
package me.weishu.kernelsu.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
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.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun SuperUserScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<SuperUserViewModel>()
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val listState = rememberLazyListState()
LaunchedEffect(key1 = navigator) {
viewModel.search = ""
if (viewModel.appList.isEmpty()) {
viewModel.fetchAppList()
}
}
LaunchedEffect(viewModel.search) {
if (viewModel.search.isEmpty()) {
listState.scrollToItem(0)
}
}
Scaffold(
topBar = {
SearchAppBar(
title = { Text(stringResource(R.string.superuser)) },
searchText = viewModel.search,
onSearchTextChange = { viewModel.search = it },
onClearClick = { viewModel.search = "" },
dropdownContent = {
var showDropdown by remember { mutableStateOf(false) }
IconButton(
onClick = { showDropdown = true },
) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(id = R.string.settings)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
DropdownMenuItem(text = {
Text(stringResource(R.string.refresh))
}, onClick = {
scope.launch {
viewModel.fetchAppList()
}
showDropdown = false
})
DropdownMenuItem(text = {
Text(
if (viewModel.showSystemApps) {
stringResource(R.string.hide_system_apps)
} else {
stringResource(R.string.show_system_apps)
}
)
}, onClick = {
viewModel.showSystemApps = !viewModel.showSystemApps
showDropdown = false
})
}
}
},
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
) { innerPadding ->
PullToRefreshBox(
modifier = Modifier.padding(innerPadding),
onRefresh = {
scope.launch { viewModel.fetchAppList() }
},
isRefreshing = viewModel.isRefreshing
) {
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
items(viewModel.appList, key = { it.packageName + it.uid }) { app ->
AppItem(app) {
navigator.navigate(AppProfileScreenDestination(app))
}
}
}
}
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AppItem(
app: SuperUserViewModel.AppInfo,
onClickListener: () -> Unit,
) {
ListItem(
modifier = Modifier.clickable(onClick = onClickListener),
headlineContent = { Text(app.label) },
supportingContent = {
Column {
Text(app.packageName)
FlowRow {
if (app.allowSu) {
LabelText(label = "ROOT")
} else {
if (Natives.uidShouldUmount(app.uid)) {
LabelText(label = "UMOUNT")
}
}
if (app.hasCustomProfile) {
LabelText(label = "CUSTOM")
}
}
}
},
leadingContent = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo)
.crossfade(true)
.build(),
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.width(48.dp)
.height(48.dp)
)
},
)
}
@Composable
fun LabelText(label: String) {
Box(
modifier = Modifier
.padding(top = 4.dp, end = 4.dp)
.background(
Color.Black,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = label,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
style = TextStyle(
fontSize = 8.sp,
color = Color.White,
)
)
}
}

View File

@@ -1,10 +0,0 @@
package me.weishu.kernelsu.ui.theme
import androidx.compose.ui.graphics.Color
val YELLOW = Color(0xFFeed502)
val YELLOW_LIGHT = Color(0xFFffff52)
val SECONDARY_LIGHT = Color(0xffa9817f)
val YELLOW_DARK = Color(0xFFb7a400)
val SECONDARY_DARK = Color(0xFF4c2b2b)

View File

@@ -1,46 +0,0 @@
package me.weishu.kernelsu.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = YELLOW,
secondary = YELLOW_DARK,
tertiary = SECONDARY_DARK
)
private val LightColorScheme = lightColorScheme(
primary = YELLOW,
secondary = YELLOW_LIGHT,
tertiary = SECONDARY_LIGHT
)
@Composable
fun KernelSUTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -1,7 +0,0 @@
package me.weishu.kernelsu.ui.util.module
data class LatestVersionInfo(
val versionCode : Int = 0,
val downloadUrl : String = "",
val changelog : String = ""
)

View File

@@ -1,22 +1,16 @@
package me.weishu.kernelsu
package shirkneko.zako.sukisu
import android.app.Application
import android.system.Os
import coil.Coil
import coil.ImageLoader
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import okhttp3.Cache
import okhttp3.OkHttpClient
import java.io.File
import java.util.Locale
lateinit var ksuApp: KernelSUApplication
class KernelSUApplication : Application() {
lateinit var okhttpClient: OkHttpClient
override fun onCreate() {
super.onCreate()
ksuApp = this
@@ -36,20 +30,7 @@ class KernelSUApplication : Application() {
if (!webroot.exists()) {
webroot.mkdir()
}
// Provide working env for rust's temp_dir()
Os.setenv("TMPDIR", cacheDir.absolutePath, true)
okhttpClient =
OkHttpClient.Builder().cache(Cache(File(cacheDir, "okhttp"), 10 * 1024 * 1024))
.addInterceptor { block ->
block.proceed(
block.request().newBuilder()
.header("User-Agent", "KernelSU/${BuildConfig.VERSION_CODE}")
.header("Accept-Language", Locale.getDefault().toLanguageTag()).build()
)
}.build()
}
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu
package shirkneko.zako.sukisu
import android.system.Os

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu
package shirkneko.zako.sukisu
import android.os.Parcelable
import androidx.annotation.Keep
@@ -30,7 +30,7 @@ object Natives {
const val ROOT_GID = 0
init {
System.loadLibrary("kernelsu")
System.loadLibrary("zako")
}
// become root manager, return true if success.

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.profile
package shirkneko.zako.sukisu.profile
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.profile
package shirkneko.zako.sukisu.profile
/**
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui;
package shirkneko.zako.sukisu.ui;
import android.content.Context;
import android.content.Intent;
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import me.weishu.kernelsu.IKsuInterface;
import shirkneko.zako.sukisu.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice;
/**

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui
package shirkneko.zako.sukisu.ui
import android.os.Build
import android.os.Bundle
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
@@ -29,8 +30,9 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
@@ -39,13 +41,16 @@ import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationSty
import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.screen.BottomBarDestination
import me.weishu.kernelsu.ui.theme.KernelSUTheme
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.rootAvailable
import me.weishu.kernelsu.ui.util.install
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import shirkneko.zako.sukisu.ui.screen.BottomBarDestination
import shirkneko.zako.sukisu.ui.theme.CardConfig
import shirkneko.zako.sukisu.ui.theme.KernelSUTheme
import shirkneko.zako.sukisu.ui.theme.loadCustomBackground
import shirkneko.zako.sukisu.ui.theme.loadThemeMode
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.rootAvailable
import shirkneko.zako.sukisu.ui.util.install
class MainActivity : ComponentActivity() {
@@ -59,8 +64,14 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) install()
// 加载保存的背景设置
loadCustomBackground()
loadThemeMode()
CardConfig.load(applicationContext)
val isManager = Natives.becomeManager(ksuApp.packageName)
if (isManager) install()
setContent {
KernelSUTheme {
@@ -96,8 +107,16 @@ private fun BottomBar(navController: NavHostController) {
val navigator = navController.rememberDestinationsNavigator()
val isManager = Natives.becomeManager(ksuApp.packageName)
val fullFeatured = isManager && !Natives.requireNewKernel() && rootAvailable()
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
NavigationBar(
tonalElevation = 8.dp,
tonalElevation = cardElevation, // 动态设置阴影
containerColor = cardColor.copy(alpha = cardAlpha), // 动态设置颜色和透明度
contentColor = if (cardColor.luminance() > 0.5) Color.Black else Color.White, // 根据背景亮度设置文字颜色
windowInsets = WindowInsets.systemBars.union(WindowInsets.displayCutout).only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
)
@@ -127,8 +146,12 @@ private fun BottomBar(navController: NavHostController) {
}
},
label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false
alwaysShowLabel = false,
colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
selectedTextColor = Color.Black,
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
}
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component
package shirkneko.zako.sukisu.ui.component
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
@@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.R
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.R
@Preview
@Composable
@@ -72,7 +72,7 @@ private fun AboutCardContent() {
shape = CircleShape
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
painter = painterResource(id = R.drawable.ic_launcher_monochrome),
contentDescription = "icon",
modifier = Modifier.scale(1.4f)
)
@@ -98,8 +98,8 @@ private fun AboutCardContent() {
val annotatedString = AnnotatedString.Companion.fromHtml(
htmlString = stringResource(
id = R.string.about_source_code,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
"<b><a href=\"https://github.com/ShirkNeko/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/SukiKSU\">Telegram</a></b>"
),
linkStyles = TextLinkStyles(
style = SpanStyle(

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component
package shirkneko.zako.sukisu.ui.component
import android.graphics.text.LineBreaker
import android.os.Build
@@ -88,6 +88,7 @@ interface ConfirmDialogHandle : DialogHandle {
)
suspend fun awaitConfirm(
title: String,
content: String,
markdown: Boolean = false,

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component
package shirkneko.zako.sukisu.ui.component
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component
package shirkneko.zako.sukisu.ui.component
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
@@ -20,9 +20,11 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -40,6 +42,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import shirkneko.zako.sukisu.ui.theme.CardConfig
private const val TAG = "SearchBar"
@@ -59,6 +62,11 @@ fun SearchAppBar(
val focusRequester = remember { FocusRequester() }
var onSearch by remember { mutableStateOf(false) }
// 获取卡片颜色和透明度
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
val cardElevation = CardConfig.cardElevation
if (onSearch) {
LaunchedEffect(Unit) { focusRequester.requestFocus() }
}
@@ -140,7 +148,11 @@ fun SearchAppBar(
},
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
)
)
}
@@ -155,4 +167,4 @@ private fun SearchAppBarPreview() {
onSearchTextChange = { searchText = it },
onClearClick = { searchText = "" }
)
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component
package shirkneko.zako.sukisu.ui.component
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component.profile
package shirkneko.zako.sukisu.ui.component.profile
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedTextField
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SwitchItem
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
@Composable
fun AppProfileConfig(

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component.profile
package shirkneko.zako.sukisu.ui.component.profile
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@@ -9,17 +9,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.AssistChip
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
@@ -49,12 +42,12 @@ import com.maxkeppeler.sheets.input.models.ValidationResult
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.util.isSepolicyValid
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.component.profile
package shirkneko.zako.sukisu.ui.component.profile
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.util.listAppProfileTemplates
import me.weishu.kernelsu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import androidx.annotation.StringRes
import androidx.compose.animation.Crossfade
@@ -64,20 +64,20 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.profile.AppProfileConfig
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.component.profile.TemplateConfig
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.forceStopApp
import me.weishu.kernelsu.ui.util.getSepolicy
import me.weishu.kernelsu.ui.util.launchApp
import me.weishu.kernelsu.ui.util.restartApp
import me.weishu.kernelsu.ui.util.setSepolicy
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
import me.weishu.kernelsu.ui.viewmodel.getTemplateInfoById
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.profile.AppProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.component.profile.TemplateConfig
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.forceStopApp
import shirkneko.zako.sukisu.ui.util.getSepolicy
import shirkneko.zako.sukisu.ui.util.launchApp
import shirkneko.zako.sukisu.ui.util.restartApp
import shirkneko.zako.sukisu.ui.util.setSepolicy
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
import shirkneko.zako.sukisu.ui.viewmodel.getTemplateInfoById
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
@@ -8,8 +8,9 @@ import androidx.compose.ui.graphics.vector.ImageVector
import com.ramcosta.composedestinations.generated.destinations.HomeScreenDestination
import com.ramcosta.composedestinations.generated.destinations.ModuleScreenDestination
import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDestination
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import me.weishu.kernelsu.R
import shirkneko.zako.sukisu.R
enum class BottomBarDestination(
val direction: DirectionDestinationSpec,
@@ -20,5 +21,6 @@ enum class BottomBarDestination(
) {
Home(HomeScreenDestination, R.string.home, Icons.Filled.Home, Icons.Outlined.Home, false),
SuperUser(SuperUserScreenDestination, R.string.superuser, Icons.Filled.Security, Icons.Outlined.Security, true),
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true)
Module(ModuleScreenDestination, R.string.module, Icons.Filled.Apps, Icons.Outlined.Apps, true),
Settings(SettingScreenDestination, R.string.settings, Icons.Filled.Settings, Icons.Outlined.Settings, false),
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.os.Environment
import androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.runModuleAction
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.runModuleAction
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date

View File

@@ -1,40 +1,18 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.net.Uri
import android.os.Environment
import android.os.Parcelable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
@@ -52,25 +30,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.KeyEventBlocker
import me.weishu.kernelsu.ui.util.FlashResult
import me.weishu.kernelsu.ui.util.LkmSelection
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.flashModule
import me.weishu.kernelsu.ui.util.installBoot
import me.weishu.kernelsu.ui.util.reboot
import me.weishu.kernelsu.ui.util.restoreBoot
import me.weishu.kernelsu.ui.util.uninstallPermanently
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.KeyEventBlocker
import shirkneko.zako.sukisu.ui.util.*
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* @author weishu
* @date 2023/1/1.
*/
import java.util.*
enum class FlashingStatus {
FLASHING,
@@ -78,27 +43,20 @@ enum class FlashingStatus {
FAILED
}
// Lets you flash modules sequentially when mutiple zipUris are selected
fun flashModulesSequentially(
uris: List<Uri>,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
for (uri in uris) {
flashModule(uri, onStdout, onStderr).apply {
if (code != 0) {
return FlashResult(code, err, showReboot)
}
}
}
return FlashResult(0, "", true)
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
fun getFlashingStatus(): FlashingStatus {
return currentFlashingStatus.value
}
fun setFlashingStatus(status: FlashingStatus) {
currentFlashingStatus.value = status
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@Destination<RootGraph>
fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
var text by rememberSaveable { mutableStateOf("") }
var tempText: String
val logContent = rememberSaveable { StringBuilder() }
@@ -108,18 +66,27 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
val scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
var flashing by rememberSaveable {
mutableStateOf(FlashingStatus.FLASHING)
}
LaunchedEffect(Unit) {
if (text.isNotEmpty()) {
return@LaunchedEffect
}
withContext(Dispatchers.IO) {
flashIt(flashIt, onStdout = {
setFlashingStatus(FlashingStatus.FLASHING)
flashIt(flashIt, onFinish = { showReboot, code ->
if (code != 0) {
text += "Error: exit code = $code.\nPlease save and check the log.\n"
setFlashingStatus(FlashingStatus.FAILED)
} else {
setFlashingStatus(FlashingStatus.SUCCESS)
}
if (showReboot) {
text += "\n\n\n"
showFloatAction = true
}
}, onStdout = {
tempText = "$it\n"
if (tempText.startsWith("")) { // clear command
if (tempText.startsWith("[H[J")) { // clear command
text = tempText.substring(6)
} else {
text += tempText
@@ -127,24 +94,15 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
logContent.append(it).append("\n")
}, onStderr = {
logContent.append(it).append("\n")
}).apply {
if (code != 0) {
text += "Error code: $code.\n $err Please save and check the log.\n"
}
if (showReboot) {
text += "\n\n\n"
showFloatAction = true
}
flashing = if (code == 0) FlashingStatus.SUCCESS else FlashingStatus.FAILED
}
})
}
}
Scaffold(
topBar = {
TopBar(
flashing,
onBack = dropUnlessResumed {
currentFlashingStatus.value,
onBack = dropUnlessResumed {
navigator.popBackStack()
},
onSave = {
@@ -207,37 +165,30 @@ fun FlashScreen(navigator: DestinationsNavigator, flashIt: FlashIt) {
@Parcelize
sealed class FlashIt : Parcelable {
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) :
FlashIt()
data class FlashModules(val uris: List<Uri>) : FlashIt()
data class FlashBoot(val boot: Uri? = null, val lkm: LkmSelection, val ota: Boolean) : FlashIt()
data class FlashModule(val uri: Uri) : FlashIt()
data object FlashRestore : FlashIt()
data object FlashUninstall : FlashIt()
}
fun flashIt(
flashIt: FlashIt,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
return when (flashIt) {
) {
when (flashIt) {
is FlashIt.FlashBoot -> installBoot(
flashIt.boot,
flashIt.lkm,
flashIt.ota,
onFinish,
onStdout,
onStderr
)
is FlashIt.FlashModules -> {
flashModulesSequentially(flashIt.uris, onStdout, onStderr)
}
FlashIt.FlashRestore -> restoreBoot(onStdout, onStderr)
FlashIt.FlashUninstall -> uninstallPermanently(onStdout, onStderr)
is FlashIt.FlashModule -> flashModule(flashIt.uri, onFinish, onStdout, onStderr)
FlashIt.FlashRestore -> restoreBoot(onFinish, onStdout, onStderr)
FlashIt.FlashUninstall -> uninstallPermanently(onFinish, onStdout, onStderr)
}
}
@@ -281,6 +232,6 @@ private fun TopBar(
@Preview
@Composable
fun InstallPreview() {
InstallScreen(EmptyDestinationsNavigator)
fun FlashScreenPreview() {
FlashScreen(EmptyDestinationsNavigator, FlashIt.FlashUninstall)
}

View File

@@ -1,9 +1,10 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.content.Context
import android.os.Build
import android.os.PowerManager
import android.system.Os
import android.util.Log
import androidx.annotation.StringRes
import androidx.compose.animation.*
import androidx.compose.foundation.clickable
@@ -11,20 +12,15 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.platform.LocalUriHandler
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -36,39 +32,59 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.*
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.util.*
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo
import shirkneko.zako.sukisu.*
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.saveable.rememberSaveable
import shirkneko.zako.sukisu.ui.theme.CardConfig
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>(start = true)
@Composable
fun HomeScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current
var isSimpleMode by rememberSaveable { mutableStateOf(false) }
// 从 SharedPreferences 加载简洁模式状态
LaunchedEffect(Unit) {
isSimpleMode = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false)
}
val kernelVersion = getKernelVersion()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopBar(
kernelVersion,
onSettingsClick = {
navigator.navigate(SettingScreenDestination)
},
onInstallClick = {
navigator.navigate(InstallScreenDestination)
},
onInstallClick = { navigator.navigate(InstallScreenDestination) },
onSettingsClick = { navigator.navigate(SettingScreenDestination) },
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
.padding(top = 12.dp)
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
@@ -99,9 +115,42 @@ fun HomeScreen(navigator: DestinationsNavigator) {
if (checkUpdate) {
UpdateCard()
}
val prefs = remember { context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) }
var clickCount by rememberSaveable { mutableStateOf(prefs.getInt("click_count", 0)) }
if (!isSimpleMode && clickCount < 3) {
AnimatedVisibility(
visible = clickCount < 3,
exit = shrinkVertically() + fadeOut()
) {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
clickCount++
prefs.edit().putInt("click_count", clickCount).apply()
}
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.using_mksu_manager),
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
InfoCard()
DonateCard()
LearnMoreCard()
if (!isSimpleMode) {
DonateCard()
LearnMoreCard()
}
Spacer(Modifier)
}
}
@@ -122,6 +171,11 @@ fun UpdateCard() {
val newVersionUrl = newVersion.downloadUrl
val changelog = newVersion.changelog
Log.d("UpdateCard", "Current version code: $currentVersionCode")
Log.d("UpdateCard", "New version code: $newVersionCode")
val uriHandler = LocalUriHandler.current
val title = stringResource(id = R.string.module_changelog)
val updateText = stringResource(id = R.string.module_update)
@@ -167,30 +221,27 @@ private fun TopBar(
onSettingsClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior? = null
) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
actions = {
if (kernelVersion.isGKI()) {
IconButton(onClick = onInstallClick) {
Icon(
imageVector = Icons.Filled.Archive,
contentDescription = stringResource(id = R.string.install)
)
Icon(Icons.Filled.Archive, stringResource(R.string.install))
}
}
var showDropdown by remember { mutableStateOf(false) }
IconButton(onClick = {
showDropdown = true
}) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(id = R.string.reboot)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
IconButton(onClick = { showDropdown = true }) {
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
) {
RebootDropdownItem(id = R.string.reboot)
@@ -205,13 +256,6 @@ private fun TopBar(
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
}
}
IconButton(onClick = onSettingsClick) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = stringResource(id = R.string.settings)
)
}
},
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
@@ -226,10 +270,8 @@ private fun StatusCard(
onClickInstall: () -> Unit = {}
) {
ElevatedCard(
colors = CardDefaults.elevatedCardColors(containerColor = run {
if (ksuVersion != null) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.errorContainer
})
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier
.fillMaxWidth()
@@ -247,7 +289,7 @@ private fun StatusCard(
}
val workingMode = when (lkmMode) {
null -> ""
null -> " <Non-GKI>"
true -> " <LKM>"
else -> " <GKI>"
}
@@ -277,6 +319,19 @@ private fun StatusCard(
text = stringResource(R.string.home_module_count, getModuleCount()),
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(4.dp))
val suSFS = getSuSFS()
val translatedStatus = when (suSFS) {
"Supported" -> stringResource(R.string.status_supported)
"Not Supported" -> stringResource(R.string.status_not_supported)
else -> stringResource(R.string.status_unknown)
}
Text(
text = stringResource(R.string.home_susfs, translatedStatus),
style = MaterialTheme.typography.bodyMedium
)
}
}
@@ -319,9 +374,8 @@ fun WarningCard(
message: String, color: Color = MaterialTheme.colorScheme.error, onClick: (() -> Unit)? = null
) {
ElevatedCard(
colors = CardDefaults.elevatedCardColors(
containerColor = color
)
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(
modifier = Modifier
@@ -341,7 +395,10 @@ fun LearnMoreCard() {
val uriHandler = LocalUriHandler.current
val url = stringResource(R.string.home_learn_kernelsu_url)
ElevatedCard {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier
.fillMaxWidth()
@@ -368,7 +425,10 @@ fun LearnMoreCard() {
fun DonateCard() {
val uriHandler = LocalUriHandler.current
ElevatedCard {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Row(modifier = Modifier
.fillMaxWidth()
@@ -394,8 +454,13 @@ fun DonateCard() {
@Composable
private fun InfoCard() {
val context = LocalContext.current
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false)
ElevatedCard {
ElevatedCard(
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Column(
modifier = Modifier
.fillMaxWidth()
@@ -405,26 +470,77 @@ private fun InfoCard() {
val uname = Os.uname()
@Composable
fun InfoCardItem(label: String, content: String) {
fun InfoCardItem(
label: String,
content: String,
) {
contents.appendLine(label).appendLine(content).appendLine()
Text(text = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium)
}
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
InfoCardItem(stringResource(R.string.home_kernel), uname.release)
Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context)
InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
if (!isSimpleMode) {
Spacer(Modifier.height(16.dp))
val androidVersion = Build.VERSION.RELEASE
InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
}
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_fingerprint), Build.FINGERPRINT)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
Spacer(Modifier.height(16.dp))
val deviceModel = Build.MODEL
InfoCardItem(stringResource(R.string.home_device_model), deviceModel)
Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context)
InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
if (!isSimpleMode) {
Spacer(modifier = Modifier.height(16.dp))
val suSFS = getSuSFS()
if (suSFS == "Supported") {
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
} else {
val susSUMode = try {
susfsSUS_SU_Mode()
} catch (e: Exception) {
0
}
if (susSUMode == 2 || susSUMode == 0) {
val isSUS_SU = getSuSFSFeatures() == "CONFIG_KSU_SUSFS_SUS_SU"
val susSUModeLabel = stringResource(R.string.sus_su_mode)
val susSUModeValue = susSUMode.toString()
val susSUModeText = if (isSUS_SU) " $susSUModeLabel $susSUModeValue" else ""
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${getSuSFSVariant()})$susSUModeText"
)
} else {
InfoCardItem(
stringResource(R.string.home_susfs_version),
"${getSuSFSVersion()} (${stringResource(R.string.manual_hook)})"
)
}
}
}
}
}
}

View File

@@ -0,0 +1,571 @@
package shirkneko.zako.sukisu.ui.screen
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FileUpload
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.utils.AssetsUtil
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
/**
* @author weishu
* @date 2024/3/12.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
val context = LocalContext.current
var showRebootDialog by remember { mutableStateOf(false) }
val onFlashComplete = {
showRebootDialog = true
}
if (showRebootDialog) {
RebootDialog(
show = true,
onDismiss = { showRebootDialog = false },
onConfirm = {
showRebootDialog = false
try {
val process = Runtime.getRuntime().exec("su")
process.outputStream.bufferedWriter().use { writer ->
writer.write("svc power reboot\n")
writer.write("exit\n")
}
} catch (e: Exception) {
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
}
}
)
}
val onInstall = {
installMethod?.let { method ->
when (method) {
is InstallMethod.HorizonKernel -> {
method.uri?.let { uri ->
val worker = HorizonKernelWorker(context)
worker.uri = uri
worker.setOnFlashCompleteListener(onFlashComplete)
worker.start()
}
}
else -> {
val flashIt = FlashIt.FlashBoot(
boot = if (method is InstallMethod.SelectFile) method.uri else null,
lkm = lkmSelection,
ota = method is InstallMethod.DirectInstallToInactiveSlot
)
navigator.navigate(FlashScreenDestination(flashIt))
}
}
}
Unit
}
val currentKmi by produceState(initialValue = "") {
value = getCurrentKmi()
}
val selectKmiDialog = rememberSelectKmiDialog { kmi ->
kmi?.let {
lkmSelection = LkmSelection.KmiString(it)
onInstall()
}
}
val onClickNext = {
if (lkmSelection == LkmSelection.KmiNone && currentKmi.isBlank()) {
selectKmiDialog.show()
} else {
onInstall()
}
Unit
}
val selectLkmLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
lkmSelection = LkmSelection.LkmUri(uri)
}
}
}
val onLkmUpload = {
selectLkmLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream"
})
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopBar(
onBack = { navigator.popBackStack() },
onLkmUpload = onLkmUpload,
scrollBehavior = scrollBehavior
)
},
contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
SelectInstallMethod { method ->
installMethod = method
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
(lkmSelection as? LkmSelection.LkmUri)?.let {
Text(
stringResource(
id = R.string.selected_lkm,
it.uri.lastPathSegment ?: "(file)"
)
)
}
Button(
modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null,
onClick = onClickNext
) {
Text(
stringResource(id = R.string.install_next),
fontSize = MaterialTheme.typography.bodyMedium.fontSize
)
}
}
}
}
}
private fun launchHorizonKernelFlash(context: Context, uri: Uri) {
val worker = HorizonKernelWorker(context)
worker.uri = uri
worker.setOnFlashCompleteListener {
}
worker.start()
}
@Composable
private fun RebootDialog(
show: Boolean,
onDismiss: () -> Unit,
onConfirm: () -> Unit
) {
if (show) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(id = R.string.reboot_complete_title)) },
text = { Text(stringResource(id = R.string.reboot_complete_msg)) },
confirmButton = {
TextButton(onClick = onConfirm) {
Text(stringResource(id = R.string.yes))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(id = R.string.no))
}
}
)
}
}
private class HorizonKernelWorker(private val context: Context) : Thread() {
var uri: Uri? = null
private lateinit var filePath: String
private lateinit var binaryPath: String
private var onFlashComplete: (() -> Unit)? = null
fun setOnFlashCompleteListener(listener: () -> Unit) {
onFlashComplete = listener
}
override fun run() {
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
try {
cleanup()
if (!rootAvailable()) {
showError(context.getString(R.string.root_required))
return
}
copy()
if (!File(filePath).exists()) {
showError(context.getString(R.string.copy_failed))
return
}
getBinary()
patch()
flash()
(context as? Activity)?.runOnUiThread {
onFlashComplete?.invoke()
}
} catch (e: Exception) {
showError(e.message ?: context.getString(R.string.unknown_error))
}
}
private fun cleanup() {
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
}
private fun copy() {
uri?.let { safeUri ->
context.contentResolver.openInputStream(safeUri)?.use { input ->
FileOutputStream(File(filePath)).use { output ->
input.copyTo(output)
}
}
}
}
private fun getBinary() {
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
if (!File(binaryPath).exists()) {
throw IOException("Failed to extract update-binary")
}
}
private fun patch() {
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
}
private fun flash() {
val process = ProcessBuilder("su")
.redirectErrorStream(true)
.start()
try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
if (line.startsWith("ui_print")) {
showLog(line.removePrefix("ui_print"))
}
}
}
} finally {
process.destroy()
}
if (!File("${context.filesDir.absolutePath}/done").exists()) {
throw IOException("Flash failed")
}
}
private fun runCommand(su: Boolean, cmd: String): Int {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.waitFor()
} finally {
process.destroy()
}
}
private fun showError(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
private fun showLog(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
}
sealed class InstallMethod {
data class SelectFile(
val uri: Uri? = null,
@StringRes override val label: Int = R.string.select_file,
override val summary: String?
) : InstallMethod()
data object DirectInstall : InstallMethod() {
override val label: Int
get() = R.string.direct_install
}
data object DirectInstallToInactiveSlot : InstallMethod() {
override val label: Int
get() = R.string.install_inactive_slot
}
data class HorizonKernel(
val uri: Uri? = null,
@StringRes override val label: Int = R.string.horizon_kernel,
override val summary: String? = null
) : InstallMethod()
abstract val label: Int
open val summary: String? = null
}
@Composable
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice()
val selectFileTip = stringResource(
id = R.string.select_file_tip,
if (isInitBoot()) "init_boot" else "boot"
)
val radioOptions = mutableListOf<InstallMethod>(
InstallMethod.SelectFile(summary = selectFileTip)
)
if (rootAvailable) {
radioOptions.add(InstallMethod.DirectInstall)
if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
}
radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
}
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
var currentSelectingMethod by remember { mutableStateOf<InstallMethod?>(null) }
val selectImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri ->
val option = when (currentSelectingMethod) {
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel")
else -> null
}
option?.let {
selectedOption = it
onSelected(it)
}
}
}
}
val confirmDialog = rememberConfirmDialog(
onConfirm = {
selectedOption = InstallMethod.DirectInstallToInactiveSlot
onSelected(InstallMethod.DirectInstallToInactiveSlot)
},
onDismiss = null
)
val dialogTitle = stringResource(id = android.R.string.dialog_alert_title)
val dialogContent = stringResource(id = R.string.install_inactive_slot_warning)
val onClick = { option: InstallMethod ->
currentSelectingMethod = option
when (option) {
is InstallMethod.SelectFile, is InstallMethod.HorizonKernel -> {
selectImageLauncher.launch(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/octet-stream", "application/zip"))
})
}
is InstallMethod.DirectInstall -> {
selectedOption = option
onSelected(option)
}
is InstallMethod.DirectInstallToInactiveSlot -> {
confirmDialog.showConfirm(dialogTitle, dialogContent)
}
}
}
Column {
radioOptions.forEach { option ->
val interactionSource = remember { MutableInteractionSource() }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.toggleable(
value = option.javaClass == selectedOption?.javaClass,
onValueChange = { onClick(option) },
role = Role.RadioButton,
indication = LocalIndication.current,
interactionSource = interactionSource
)
) {
RadioButton(
selected = option.javaClass == selectedOption?.javaClass,
onClick = { onClick(option) },
interactionSource = interactionSource
)
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
Text(
text = stringResource(id = option.label),
fontSize = MaterialTheme.typography.titleMedium.fontSize,
fontFamily = MaterialTheme.typography.titleMedium.fontFamily,
fontStyle = MaterialTheme.typography.titleMedium.fontStyle
)
option.summary?.let {
Text(
text = it,
fontSize = MaterialTheme.typography.bodySmall.fontSize,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
fontStyle = MaterialTheme.typography.bodySmall.fontStyle
)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberSelectKmiDialog(onSelected: (String?) -> Unit): DialogHandle {
return rememberCustomDialog { dismiss ->
val supportedKmi by produceState(initialValue = emptyList<String>()) {
value = getSupportedKmis()
}
val options = supportedKmi.map { value ->
ListOption(titleText = value)
}
var selection by remember { mutableStateOf<String?>(null) }
ListDialog(
state = rememberUseCaseState(
visible = true,
onFinishedRequest = {
onSelected(selection)
},
onCloseRequest = {
dismiss()
}
),
header = Header.Default(
title = stringResource(R.string.select_kmi),
),
selection = ListSelection.Single(
showRadioButtons = true,
options = options,
) { _, option ->
selection = option.titleText
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
onBack: () -> Unit = {},
onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar(
title = { Text(stringResource(R.string.install)) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
actions = {
IconButton(onClick = onLkmUpload) {
Icon(Icons.Filled.FileUpload, contentDescription = null)
}
},
windowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
),
scrollBehavior = scrollBehavior
)
}
@Preview
@Composable
fun SelectInstallPreview() {
InstallScreen(EmptyDestinationsNavigator)
}

View File

@@ -1,14 +1,13 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.app.Activity.RESULT_OK
import android.app.Activity.*
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -24,22 +23,18 @@ 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.safeDrawing
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Wysiwyg
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.PlayArrow
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.automirrored.outlined.*
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -58,7 +53,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberTopAppBarState
@@ -74,7 +68,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
@@ -94,24 +88,30 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.SearchAppBar
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.util.DownloadListener
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.download
import me.weishu.kernelsu.ui.util.hasMagisk
import me.weishu.kernelsu.ui.util.reboot
import me.weishu.kernelsu.ui.util.restoreModule
import me.weishu.kernelsu.ui.util.toggleModule
import me.weishu.kernelsu.ui.util.uninstallModule
import me.weishu.kernelsu.ui.util.getFileName
import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel
import me.weishu.kernelsu.ui.webui.WebUIActivity
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.util.DownloadListener
import shirkneko.zako.sukisu.ui.util.*
import shirkneko.zako.sukisu.ui.util.download
import shirkneko.zako.sukisu.ui.util.hasMagisk
import shirkneko.zako.sukisu.ui.util.reboot
import shirkneko.zako.sukisu.ui.util.restoreModule
import shirkneko.zako.sukisu.ui.util.toggleModule
import shirkneko.zako.sukisu.ui.util.uninstallModule
import shirkneko.zako.sukisu.ui.webui.WebUIActivity
import okhttp3.OkHttpClient
import shirkneko.zako.sukisu.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.theme.getCardColors
import shirkneko.zako.sukisu.ui.theme.getCardElevation
import shirkneko.zako.sukisu.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.zip.ZipInputStream
import androidx.compose.ui.graphics.Color
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@@ -121,6 +121,122 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
val context = LocalContext.current
val snackBarHost = LocalSnackbarHost.current
val scope = rememberCoroutineScope()
val confirmDialog = rememberConfirmDialog()
val buttonTextColor = androidx.compose.ui.graphics.Color.Black
val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
}
val data = it.data ?: return@rememberLauncherForActivityResult
scope.launch {
val clipData = data.clipData
if (clipData != null) {
// 处理多选结果
val selectedModules = mutableSetOf<Uri>()
val selectedModuleNames = mutableMapOf<Uri, String>()
suspend fun processUri(uri: Uri) {
val moduleName = withContext(Dispatchers.IO) {
try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
var entry = zipInputStream.nextEntry
var name = context.getString(R.string.unknown_module)
while (entry != null) {
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line?.substringAfter("=") ?: name
break
}
}
break
}
entry = zipInputStream.nextEntry
}
name
} catch (e: Exception) {
context.getString(R.string.unknown_module)
}
}
selectedModules.add(uri)
selectedModuleNames[uri] = moduleName
}
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
processUri(uri)
}
// 显示确认对话框
val modulesList = selectedModuleNames.values.joinToString("\n", "")
val confirmResult = confirmDialog.awaitConfirm(
title = context.getString(R.string.module_install),
content = context.getString(R.string.module_install_multiple_confirm_with_names, selectedModules.size, modulesList),
confirm = context.getString(R.string.install),
dismiss = context.getString(R.string.cancel)
)
if (confirmResult == ConfirmResult.Confirmed) {
// 批量安装模块
selectedModules.forEach { uri ->
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
}
viewModel.markNeedRefresh()
}
} else {
// 单个文件安装逻辑
val uri = data.data ?: return@launch
val moduleName = withContext(Dispatchers.IO) {
try {
val zipInputStream = ZipInputStream(context.contentResolver.openInputStream(uri))
var entry = zipInputStream.nextEntry
var name = context.getString(R.string.unknown_module)
while (entry != null) {
if (entry.name == "module.prop") {
val reader = BufferedReader(InputStreamReader(zipInputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line?.startsWith("name=") == true) {
name = line?.substringAfter("=") ?: name
break
}
}
break
}
entry = zipInputStream.nextEntry
}
name
} catch (e: Exception) {
context.getString(R.string.unknown_module)
}
}
val confirmResult = confirmDialog.awaitConfirm(
title = context.getString(R.string.module_install),
content = context.getString(R.string.module_install_confirm, moduleName),
confirm = context.getString(R.string.install),
dismiss = context.getString(R.string.cancel)
)
if (confirmResult == ConfirmResult.Confirmed) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(uri)))
viewModel.markNeedRefresh()
}
}
}
}
val backupLauncher = ModuleModify.rememberModuleBackupLauncher(context, snackBarHost)
val restoreLauncher = ModuleModify.rememberModuleRestoreLauncher(context, snackBarHost)
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
LaunchedEffect(Unit) {
@@ -160,43 +276,62 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contentDescription = stringResource(id = R.string.settings)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
DropdownMenuItem(text = {
Text(stringResource(R.string.module_sort_action_first))
}, trailingIcon = {
Checkbox(viewModel.sortActionFirst, null)
}, onClick = {
viewModel.sortActionFirst =
!viewModel.sortActionFirst
prefs.edit()
.putBoolean(
"module_sort_action_first",
viewModel.sortActionFirst
)
.apply()
scope.launch {
viewModel.fetchModuleList()
DropdownMenu(
expanded = showDropdown,
onDismissRequest = { showDropdown = false }
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.module_sort_action_first)) },
trailingIcon = { Checkbox(viewModel.sortActionFirst, null) },
onClick = {
viewModel.sortActionFirst = !viewModel.sortActionFirst
prefs.edit()
.putBoolean("module_sort_action_first", viewModel.sortActionFirst)
.apply()
scope.launch {
viewModel.fetchModuleList()
}
}
})
DropdownMenuItem(text = {
Text(stringResource(R.string.module_sort_enabled_first))
}, trailingIcon = {
Checkbox(viewModel.sortEnabledFirst, null)
}, onClick = {
viewModel.sortEnabledFirst =
!viewModel.sortEnabledFirst
prefs.edit()
.putBoolean(
"module_sort_enabled_first",
viewModel.sortEnabledFirst
)
.apply()
scope.launch {
viewModel.fetchModuleList()
)
DropdownMenuItem(
text = { Text(stringResource(R.string.module_sort_enabled_first)) },
trailingIcon = { Checkbox(viewModel.sortEnabledFirst, null) },
onClick = {
viewModel.sortEnabledFirst = !viewModel.sortEnabledFirst
prefs.edit()
.putBoolean("module_sort_enabled_first", viewModel.sortEnabledFirst)
.apply()
scope.launch {
viewModel.fetchModuleList()
}
}
})
)
DropdownMenuItem(
text = { Text(stringResource(R.string.backup_modules)) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.Download,
contentDescription = "备份"
)
},
onClick = {
showDropdown = false
backupLauncher.launch(ModuleModify.createBackupIntent())
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.restore_modules)) },
leadingIcon = {
Icon(
imageVector = Icons.Outlined.Refresh,
contentDescription = "还原"
)
},
onClick = {
showDropdown = false
restoreLauncher.launch(ModuleModify.createRestoreIntent())
}
)
}
}
},
@@ -206,63 +341,36 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
floatingActionButton = {
if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install)
val confirmTitle = stringResource(R.string.module)
var zipUris by remember { mutableStateOf<List<Uri>>(emptyList()) }
val confirmDialog = rememberConfirmDialog(onConfirm = {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(zipUris)))
viewModel.markNeedRefresh()
})
val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
}
val data = it.data ?: return@rememberLauncherForActivityResult
val clipData = data.clipData
val uris = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
clipData.getItemAt(i)?.uri?.let { uris.add(it) }
}
} else {
data.data?.let { uris.add(it) }
}
if (uris.size == 1) {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(uris.first()))))
} else if (uris.size > 1) {
// multiple files selected
val moduleNames = uris.mapIndexed { index, uri -> "\n${index + 1}. ${uri.getFileName(context)}" }.joinToString("")
val confirmContent = context.getString(R.string.module_install_prompt_with_name, moduleNames)
zipUris = uris
confirmDialog.showConfirm(
title = confirmTitle,
content = confirmContent,
markdown = true
)
}
}
ExtendedFloatingActionButton(
onClick = {
// Select the zip files to install
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
selectZipLauncher.launch(intent)
selectZipLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
)
},
icon = { Icon(Icons.Filled.Add, moduleInstall) },
text = { Text(text = moduleInstall) },
icon = {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = moduleInstall,
tint = buttonTextColor
)
},
text = {
Text(
text = moduleInstall,
color = buttonTextColor
)
}
)
}
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
contentWindowInsets = WindowInsets.safeDrawing.only(
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
),
snackbarHost = { SnackbarHost(hostState = snackBarHost) }
) { innerPadding ->
when {
hasMagisk -> {
Box(
@@ -277,15 +385,14 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
)
}
}
else -> {
ModuleList(
navigator,
navigator = navigator,
viewModel = viewModel,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
boxModifier = Modifier.padding(innerPadding),
onInstallModule = {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(listOf(it))))
navigator.navigate(FlashScreenDestination(FlashIt.FlashModule(it)))
},
onClickModule = { id, name, hasWebUi ->
if (hasWebUi) {
@@ -345,7 +452,7 @@ private fun ModuleList(
val changelogResult = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
runCatching {
ksuApp.okhttpClient.newCall(
OkHttpClient().newCall(
okhttp3.Request.Builder().url(changelogUrl).build()
).execute().body!!.string()
}
@@ -560,7 +667,8 @@ fun ModuleItem(
onClick: (ModuleViewModel.ModuleInfo) -> Unit
) {
ElevatedCard(
modifier = Modifier.fillMaxWidth()
colors = getCardColors(MaterialTheme.colorScheme.secondaryContainer),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
val textDecoration = if (!module.remove) null else TextDecoration.LineThrough
val interactionSource = remember { MutableInteractionSource() }
@@ -617,7 +725,7 @@ fun ModuleItem(
fontSize = MaterialTheme.typography.bodySmall.fontSize,
lineHeight = MaterialTheme.typography.bodySmall.lineHeight,
fontFamily = MaterialTheme.typography.bodySmall.fontFamily,
textDecoration = textDecoration
textDecoration = textDecoration
)
}
@@ -668,7 +776,11 @@ fun ModuleItem(
navigator.navigate(ExecuteModuleActionScreenDestination(module.dirId))
viewModel.markNeedRefresh()
},
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
Icon(
modifier = Modifier.size(20.dp),
@@ -694,7 +806,11 @@ fun ModuleItem(
enabled = !module.remove && module.enabled,
onClick = { onClick(module) },
interactionSource = interactionSource,
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
Icon(
modifier = Modifier.size(20.dp),
@@ -720,7 +836,11 @@ fun ModuleItem(
enabled = !module.remove,
onClick = { onUpdate(module) },
shape = ButtonDefaults.textShape,
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
Icon(
modifier = Modifier.size(20.dp),
@@ -743,7 +863,11 @@ fun ModuleItem(
FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
onClick = { onUninstallClicked(module) },
contentPadding = ButtonDefaults.TextButtonContentPadding
contentPadding = ButtonDefaults.TextButtonContentPadding,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = Color.White,
contentColor = Color.Black
)
) {
if (!module.remove) {
Icon(
@@ -756,6 +880,7 @@ fun ModuleItem(
modifier = Modifier.size(20.dp).rotate(180f),
imageVector = Icons.Outlined.Refresh,
contentDescription = null,
)
}
if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
@@ -792,3 +917,4 @@ fun ModuleItemPreview() {
)
ModuleItem(EmptyDestinationsNavigator, module, "", {}, {}, {}, {})
}

View File

@@ -0,0 +1,470 @@
package shirkneko.zako.sukisu.ui.screen
import android.content.Context
import android.net.Uri
import android.os.Build
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
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.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.theme.CardConfig
import shirkneko.zako.sukisu.ui.theme.ThemeColors
import shirkneko.zako.sukisu.ui.theme.ThemeConfig
import shirkneko.zako.sukisu.ui.theme.saveCustomBackground
import shirkneko.zako.sukisu.ui.theme.saveThemeColors
import shirkneko.zako.sukisu.ui.theme.saveThemeMode
import shirkneko.zako.sukisu.ui.theme.saveDynamicColorState
import shirkneko.zako.sukisu.ui.util.getSuSFS
import shirkneko.zako.sukisu.ui.util.getSuSFSFeatures
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_0
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_2
import shirkneko.zako.sukisu.ui.util.susfsSUS_SU_Mode
fun saveCardConfig(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
with(prefs.edit()) {
putFloat("card_alpha", CardConfig.cardAlpha)
putBoolean("custom_background_enabled", CardConfig.cardElevation == 0.dp)
apply()
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun MoreSettingsScreen(navigator: DestinationsNavigator) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val context = LocalContext.current
val prefs = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) }
// 主题模式选择
var themeMode by remember {
mutableStateOf(
when(ThemeConfig.forceDarkMode) {
true -> 2 // 深色
false -> 1 // 浅色
null -> 0 // 跟随系统
}
)
}
// 动态颜色开关状态
var useDynamicColor by remember {
mutableStateOf(ThemeConfig.useDynamicColor)
}
var showThemeModeDialog by remember { mutableStateOf(false) }
// 主题模式选项
val themeOptions = listOf(
stringResource(R.string.theme_follow_system),
stringResource(R.string.theme_light),
stringResource(R.string.theme_dark)
)
// 简洁模块开关状态
var isSimpleMode by remember {
mutableStateOf(prefs.getBoolean("is_simple_mode", false))
}
// 更新简洁模块开关状态
val onSimpleModeChange = { newValue: Boolean ->
prefs.edit().putBoolean("is_simple_mode", newValue).apply()
isSimpleMode = newValue
}
// SELinux 状态
var selinuxEnabled by remember {
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
}
// 卡片配置状态
var cardAlpha by rememberSaveable { mutableStateOf(CardConfig.cardAlpha) }
var showCardSettings by remember { mutableStateOf(false) }
var isCustomBackgroundEnabled by rememberSaveable {
mutableStateOf(ThemeConfig.customBackgroundUri != null)
}
// 初始化卡片配置
LaunchedEffect(Unit) {
CardConfig.apply {
cardAlpha = prefs.getFloat("card_alpha", 0.85f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else CardConfig.defaultElevation
}
}
// 主题色选项
val themeColorOptions = listOf(
stringResource(R.string.color_default) to ThemeColors.Default,
stringResource(R.string.color_blue) to ThemeColors.Blue,
stringResource(R.string.color_green) to ThemeColors.Green,
stringResource(R.string.color_purple) to ThemeColors.Purple,
stringResource(R.string.color_orange) to ThemeColors.Orange,
stringResource(R.string.color_pink) to ThemeColors.Pink,
stringResource(R.string.color_gray) to ThemeColors.Gray,
stringResource(R.string.color_ivory) to ThemeColors.Ivory
)
var showThemeColorDialog by remember { mutableStateOf(false) }
// 图片选择器
val pickImageLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
uri?.let {
context.saveCustomBackground(it)
isCustomBackgroundEnabled = true
CardConfig.cardElevation = 0.dp
saveCardConfig(context)
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.more_settings)) },
navigationIcon = {
IconButton(onClick = { navigator.popBackStack() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, null)
}
},
scrollBehavior = scrollBehavior
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(top = 12.dp)
) {
// SELinux 开关
SwitchItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.selinux),
summary = if (selinuxEnabled)
stringResource(R.string.selinux_enabled) else
stringResource(R.string.selinux_disabled),
checked = selinuxEnabled
) { enabled ->
val command = if (enabled) "setenforce 1" else "setenforce 0"
Shell.getShell().newJob().add(command).exec().let { result ->
if (result.isSuccess) selinuxEnabled = enabled
}
}
// 添加简洁模块开关
SwitchItem(
icon = Icons.Filled.FormatPaint,
title = stringResource(R.string.simple_mode),
summary = stringResource(R.string.simple_mode_summary),
checked = isSimpleMode
) {
onSimpleModeChange(it)
}
// region SUSFS 配置(仅在支持时显示)
val suSFS = getSuSFS()
val isSUS_SU = getSuSFSFeatures()
if (suSFS == "Supported") {
if (isSUS_SU == "CONFIG_KSU_SUSFS_SUS_SU") {
// 初始化时,默认启用
var isEnabled by rememberSaveable {
mutableStateOf(true) // 默认启用
}
// 在启动时检查状态
LaunchedEffect(Unit) {
// 如果当前模式不是2就强制启用
val currentMode = susfsSUS_SU_Mode()
val wasManuallyDisabled = prefs.getBoolean("enable_sus_su", true)
if (currentMode != "2" && wasManuallyDisabled) {
susfsSUS_SU_2() // 强制切换到模式2
prefs.edit().putBoolean("enable_sus_su", true).apply()
}
isEnabled = currentMode == "2"
}
SwitchItem(
icon = Icons.Filled.VisibilityOff,
title = stringResource(id = R.string.settings_susfs_toggle),
summary = stringResource(id = R.string.settings_susfs_toggle_summary),
checked = isEnabled
) {
if (it) {
// 手动启用
susfsSUS_SU_2()
prefs.edit().putBoolean("enable_sus_su", true).apply()
} else {
// 手动关闭
susfsSUS_SU_0()
prefs.edit().putBoolean("enable_sus_su", false).apply()
}
isEnabled = it
}
}
}
// endregion
// 动态颜色开关
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
SwitchItem(
icon = Icons.Filled.ColorLens,
title = stringResource(R.string.dynamic_color_title),
summary = stringResource(R.string.dynamic_color_summary),
checked = useDynamicColor
) { enabled ->
useDynamicColor = enabled
context.saveDynamicColorState(enabled)
}
}
// 只在未启用动态颜色时显示主题色选择
if (!useDynamicColor) {
ListItem(
leadingContent = { Icon(Icons.Default.Palette, null) },
headlineContent = { Text("主题颜色") },
supportingContent = {
val currentThemeName = when (ThemeConfig.currentTheme) {
is ThemeColors.Default -> stringResource(R.string.color_default)
is ThemeColors.Blue -> stringResource(R.string.color_blue)
is ThemeColors.Green -> stringResource(R.string.color_green)
is ThemeColors.Purple -> stringResource(R.string.color_purple)
is ThemeColors.Orange -> stringResource(R.string.color_orange)
is ThemeColors.Pink -> stringResource(R.string.color_pink)
is ThemeColors.Gray -> stringResource(R.string.color_gray)
is ThemeColors.Ivory -> stringResource(R.string.color_ivory)
else -> stringResource(R.string.color_default)
}
Text(currentThemeName)
},
modifier = Modifier.clickable { showThemeColorDialog = true }
)
if (showThemeColorDialog) {
AlertDialog(
onDismissRequest = { showThemeColorDialog = false },
title = { Text(stringResource(R.string.choose_theme_color)) },
text = {
Column {
themeColorOptions.forEach { (name, theme) ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
context.saveThemeColors(when (theme) {
ThemeColors.Default -> "default"
ThemeColors.Blue -> "blue"
ThemeColors.Green -> "green"
ThemeColors.Purple -> "purple"
ThemeColors.Orange -> "orange"
ThemeColors.Pink -> "pink"
ThemeColors.Gray -> "gray"
ThemeColors.Ivory -> "ivory"
else -> "default"
})
showThemeColorDialog = false
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = ThemeConfig.currentTheme::class == theme::class,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.size(24.dp)
.background(theme.Primary, shape = CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Text(name)
}
}
}
},
confirmButton = {}
)
}
}
// 自定义背景开关
SwitchItem(
icon = Icons.Filled.Wallpaper,
title = stringResource(id = R.string.settings_custom_background),
summary = stringResource(id = R.string.settings_custom_background_summary),
checked = isCustomBackgroundEnabled
) { isChecked ->
if (isChecked) {
pickImageLauncher.launch("image/*")
} else {
context.saveCustomBackground(null)
isCustomBackgroundEnabled = false
CardConfig.cardElevation = CardConfig.defaultElevation
CardConfig.cardAlpha = 1f
saveCardConfig(context)
}
}
// 卡片管理展开控制
if (ThemeConfig.customBackgroundUri != null) {
ListItem(
leadingContent = { Icon(Icons.Default.ExpandMore, null) },
headlineContent = { Text(stringResource(R.string.settings_card_manage)) },
modifier = Modifier.clickable { showCardSettings = !showCardSettings }
)
if (showCardSettings) {
// 透明度 Slider
ListItem(
leadingContent = { Icon(Icons.Filled.Opacity, null) },
headlineContent = { Text(stringResource(R.string.settings_card_alpha)) },
supportingContent = {
Slider(
value = cardAlpha,
onValueChange = { newValue ->
cardAlpha = newValue
CardConfig.cardAlpha = newValue
prefs.edit().putFloat("card_alpha", newValue).apply()
},
onValueChangeFinished = {
CoroutineScope(Dispatchers.IO).launch {
saveCardConfig(context)
}
},
valueRange = 0f..1f,
// 确保使用自定义颜色
colors = getSliderColors(cardAlpha, useCustomColors = true),
thumb = {
SliderDefaults.Thumb(
interactionSource = remember { MutableInteractionSource() },
thumbSize = DpSize(0.dp, 0.dp)
)
}
)
}
)
ListItem(
leadingContent = { Icon(Icons.Filled.DarkMode, null) },
headlineContent = { Text(stringResource(R.string.theme_mode)) },
supportingContent = { Text(themeOptions[themeMode]) },
modifier = Modifier.clickable {
showThemeModeDialog = true
}
)
// 主题模式选择对话框
if (showThemeModeDialog) {
AlertDialog(
onDismissRequest = { showThemeModeDialog = false },
title = { Text(stringResource(R.string.theme_mode)) },
text = {
Column {
themeOptions.forEachIndexed { index, option ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
themeMode = index
val newThemeMode = when(index) {
0 -> null // 跟随系统
1 -> false // 浅色
2 -> true // 深色
else -> null
}
context.saveThemeMode(newThemeMode)
showThemeModeDialog = false
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = themeMode == index,
onClick = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(option)
}
}
}
},
confirmButton = {}
)
}
}
}
}
}
}
@Composable
private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false): SliderColors {
val theme = ThemeConfig.currentTheme
val isDarkTheme = ThemeConfig.forceDarkMode ?: isSystemInDarkTheme()
val useDynamicColor = ThemeConfig.useDynamicColor
return when {
// 使用动态颜色时
useDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
SliderDefaults.colors(
activeTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f),
inactiveTrackColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
thumbColor = MaterialTheme.colorScheme.primary
)
}
// 使用自定义主题色时
useCustomColors -> {
SliderDefaults.colors(
activeTrackColor = theme.getCustomSliderActiveColor(),
inactiveTrackColor = theme.getCustomSliderInactiveColor(),
thumbColor = theme.Primary
)
}
else -> {
val activeColor = if (isDarkTheme) {
theme.Primary.copy(alpha = cardAlpha)
} else {
theme.Primary.copy(alpha = cardAlpha)
}
val inactiveColor = if (isDarkTheme) {
Color.DarkGray.copy(alpha = 0.3f)
} else {
Color.LightGray.copy(alpha = 0.3f)
}
SliderDefaults.colors(
activeTrackColor = activeColor,
inactiveTrackColor = inactiveColor,
thumbColor = activeColor
)
}
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.content.Context
import android.content.Intent
@@ -18,23 +18,10 @@ import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Compress
import androidx.compose.material.icons.filled.ContactPage
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.DeveloperMode
import androidx.compose.material.icons.filled.Fence
import androidx.compose.material.icons.filled.FolderDelete
import androidx.compose.material.icons.filled.RemoveModerator
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Update
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
@@ -62,7 +49,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import androidx.lifecycle.compose.dropUnlessResumed
import com.maxkeppeker.sheets.core.models.base.Header
import com.maxkeppeker.sheets.core.models.base.IconSource
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
@@ -73,25 +59,30 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplateScreenDestination
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.generated.destinations.MoreSettingsScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.AboutDialog
import me.weishu.kernelsu.ui.component.ConfirmResult
import me.weishu.kernelsu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.getBugreportFile
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.AboutDialog
import shirkneko.zako.sukisu.ui.component.ConfirmResult
import shirkneko.zako.sukisu.ui.component.DialogHandle
import shirkneko.zako.sukisu.ui.component.SwitchItem
import shirkneko.zako.sukisu.ui.component.rememberConfirmDialog
import shirkneko.zako.sukisu.ui.component.rememberCustomDialog
import shirkneko.zako.sukisu.ui.component.rememberLoadingDialog
import shirkneko.zako.sukisu.ui.util.LocalSnackbarHost
import shirkneko.zako.sukisu.ui.util.getBugreportFile
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.MaterialTheme
import shirkneko.zako.sukisu.ui.theme.CardConfig
/**
* @author weishu
@@ -101,15 +92,14 @@ import java.time.format.DateTimeFormatter
@Destination<RootGraph>
@Composable
fun SettingScreen(navigator: DestinationsNavigator) {
// region 界面基础设置
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current
// endregion
Scaffold(
topBar = {
TopBar(
onBack = dropUnlessResumed {
navigator.popBackStack()
},
scrollBehavior = scrollBehavior
)
},
@@ -121,6 +111,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
val loadingDialog = rememberLoadingDialog()
val shrinkDialog = rememberConfirmDialog()
// endregion
Column(
modifier = Modifier
@@ -128,10 +119,12 @@ fun SettingScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState())
) {
// region 上下文与协程
val context = LocalContext.current
val scope = rememberCoroutineScope()
// endregion
// region 日志导出功能
val exportBugreportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/gzip")
) { uri: Uri? ->
@@ -146,8 +139,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
loadingDialog.hide()
snackBarHost.showSnackbar(context.getString(R.string.log_saved))
}
// endregion
}
// region 配置项列表
// 配置文件模板入口
val profileTemplate = stringResource(id = R.string.settings_profile_template)
ListItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
@@ -157,7 +152,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
navigator.navigate(AppProfileTemplateScreenDestination)
}
)
// 卸载模块开关
var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules())
}
@@ -171,7 +166,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
umountChecked = it
}
}
// SU 禁用开关(仅在兼容版本显示)
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled())
@@ -190,6 +185,8 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
// 更新检查开关
var checkUpdate by rememberSaveable {
mutableStateOf(
prefs.getBoolean("check_update", true)
@@ -205,6 +202,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
checkUpdate = it
}
// Web调试开关
var enableWebDebugging by rememberSaveable {
mutableStateOf(
prefs.getBoolean("enable_web_debugging", false)
@@ -219,6 +217,21 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.edit().putBoolean("enable_web_debugging", it).apply()
enableWebDebugging = it
}
// endregion
val newButtonTitle = stringResource(id = R.string.more_settings)
ListItem(
leadingContent = {
Icon(
Icons.Filled.ExpandMore,
contentDescription = newButtonTitle
)
},
headlineContent = { Text(newButtonTitle) },
supportingContent = { Text(stringResource(id = R.string.more_settings)) },
modifier = Modifier.clickable {
navigator.navigate(MoreSettingsScreenDestination)
}
)
var showBottomsheet by remember { mutableStateOf(false) }
@@ -458,18 +471,17 @@ fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
onBack: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null
) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar(
title = { Text(stringResource(R.string.settings)) },
navigationIcon = {
IconButton(
onClick = onBack
) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior
)
@@ -480,3 +492,5 @@ private fun TopBar(
private fun SettingsPreview() {
SettingScreen(EmptyDestinationsNavigator)
}

View File

@@ -0,0 +1,403 @@
package shirkneko.zako.sukisu.ui.screen
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.SearchAppBar
import shirkneko.zako.sukisu.ui.util.ModuleModify
import shirkneko.zako.sukisu.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph>
@Composable
fun SuperUserScreen(navigator: DestinationsNavigator) {
val viewModel = viewModel<SuperUserViewModel>()
val scope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val listState = rememberLazyListState()
val context = LocalContext.current
val snackBarHostState = remember { SnackbarHostState() }
// 添加备份和还原启动器
val backupLauncher = ModuleModify.rememberAllowlistBackupLauncher(context, snackBarHostState)
val restoreLauncher = ModuleModify.rememberAllowlistRestoreLauncher(context, snackBarHostState)
LaunchedEffect(key1 = navigator) {
viewModel.search = ""
if (viewModel.appList.isEmpty()) {
viewModel.fetchAppList()
}
}
LaunchedEffect(viewModel.search) {
if (viewModel.search.isEmpty()) {
listState.scrollToItem(0)
}
}
Scaffold(
topBar = {
SearchAppBar(
title = { Text(stringResource(R.string.superuser)) },
searchText = viewModel.search,
onSearchTextChange = { viewModel.search = it },
onClearClick = { viewModel.search = "" },
dropdownContent = {
var showDropdown by remember { mutableStateOf(false) }
IconButton(
onClick = { showDropdown = true },
) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(id = R.string.settings)
)
DropdownMenu(expanded = showDropdown, onDismissRequest = {
showDropdown = false
}) {
DropdownMenuItem(text = {
Text(stringResource(R.string.refresh))
}, onClick = {
scope.launch {
viewModel.fetchAppList()
}
showDropdown = false
})
DropdownMenuItem(text = {
Text(
if (viewModel.showSystemApps) {
stringResource(R.string.hide_system_apps)
} else {
stringResource(R.string.show_system_apps)
}
)
}, onClick = {
viewModel.showSystemApps = !viewModel.showSystemApps
showDropdown = false
})
// 批量操作菜单项已移除
DropdownMenuItem(text = {
Text(stringResource(R.string.backup_allowlist))
}, onClick = {
backupLauncher.launch(ModuleModify.createAllowlistBackupIntent())
showDropdown = false
})
DropdownMenuItem(text = {
Text(stringResource(R.string.restore_allowlist))
}, onClick = {
restoreLauncher.launch(ModuleModify.createAllowlistRestoreIntent())
showDropdown = false
})
}
}
},
scrollBehavior = scrollBehavior
)
},
snackbarHost = { SnackbarHost(snackBarHostState) },
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
bottomBar = {
// 批量操作按钮,直接放在底部栏
if (viewModel.showBatchActions && viewModel.selectedApps.isNotEmpty()) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(true)
}
}
) {
Text("批量授权")
}
Button(
onClick = {
scope.launch {
viewModel.updateBatchPermissions(false)
}
}
) {
Text("批量取消授权")
}
}
}
}
) { innerPadding ->
PullToRefreshBox(
modifier = Modifier.padding(innerPadding),
onRefresh = {
scope.launch { viewModel.fetchAppList() }
},
isRefreshing = viewModel.isRefreshing
) {
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
// 获取分组后的应用列表 - 修改分组逻辑,避免应用重复出现在多个分组中
val rootApps = viewModel.appList.filter { it.allowSu }
val customApps = viewModel.appList.filter { !it.allowSu && it.hasCustomProfile }
val otherApps = viewModel.appList.filter { !it.allowSu && !it.hasCustomProfile }
// 显示ROOT权限应用组
if (rootApps.isNotEmpty()) {
item {
GroupHeader(title = "ROOT 权限应用")
}
items(rootApps, key = { "root_" + it.packageName + it.uid }) { app ->
AppItem(
app = app,
isSelected = viewModel.selectedApps.contains(app.packageName),
onToggleSelection = { viewModel.toggleAppSelection(app.packageName) },
onSwitchChange = { allowSu ->
scope.launch {
val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList()
}
}
},
onClick = {
if (viewModel.showBatchActions) {
viewModel.toggleAppSelection(app.packageName)
} else {
navigator.navigate(AppProfileScreenDestination(app))
}
},
onLongClick = {
// 长按进入多选模式
if (!viewModel.showBatchActions) {
viewModel.toggleBatchMode()
viewModel.toggleAppSelection(app.packageName)
}
},
viewModel = viewModel
)
}
}
// 显示自定义配置应用组
if (customApps.isNotEmpty()) {
item {
GroupHeader(title = "自定义配置应用")
}
items(customApps, key = { "custom_" + it.packageName + it.uid }) { app ->
AppItem(
app = app,
isSelected = viewModel.selectedApps.contains(app.packageName),
onToggleSelection = { viewModel.toggleAppSelection(app.packageName) },
onSwitchChange = { allowSu ->
scope.launch {
val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList()
}
}
},
onClick = {
if (viewModel.showBatchActions) {
viewModel.toggleAppSelection(app.packageName)
} else {
navigator.navigate(AppProfileScreenDestination(app))
}
},
onLongClick = {
// 长按进入多选模式
if (!viewModel.showBatchActions) {
viewModel.toggleBatchMode()
viewModel.toggleAppSelection(app.packageName)
}
},
viewModel = viewModel
)
}
}
// 显示其他应用组
if (otherApps.isNotEmpty()) {
item {
GroupHeader(title = "其他应用")
}
items(otherApps, key = { "other_" + it.packageName + it.uid }) { app ->
AppItem(
app = app,
isSelected = viewModel.selectedApps.contains(app.packageName),
onToggleSelection = { viewModel.toggleAppSelection(app.packageName) },
onSwitchChange = { allowSu ->
scope.launch {
val profile = Natives.getAppProfile(app.packageName, app.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
viewModel.fetchAppList()
}
}
},
onClick = {
if (viewModel.showBatchActions) {
viewModel.toggleAppSelection(app.packageName)
} else {
navigator.navigate(AppProfileScreenDestination(app))
}
},
onLongClick = {
// 长按进入多选模式
if (!viewModel.showBatchActions) {
viewModel.toggleBatchMode()
viewModel.toggleAppSelection(app.packageName)
}
},
viewModel = viewModel
)
}
}
}
}
}
}
@Composable
fun GroupHeader(title: String) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(
text = title,
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun AppItem(
app: SuperUserViewModel.AppInfo,
isSelected: Boolean,
onToggleSelection: () -> Unit,
onSwitchChange: (Boolean) -> Unit,
onClick: () -> Unit,
onLongClick: () -> Unit,
viewModel: SuperUserViewModel
) {
ListItem(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures(
onLongPress = { onLongClick() },
onTap = { onClick() }
)
},
headlineContent = { Text(app.label) },
supportingContent = {
Column {
Text(app.packageName)
FlowRow {
if (app.allowSu) {
LabelText(label = "ROOT")
} else {
if (Natives.uidShouldUmount(app.uid)) {
LabelText(label = "UMOUNT")
}
}
if (app.hasCustomProfile) {
LabelText(label = "CUSTOM")
}
}
}
},
leadingContent = {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(app.packageInfo)
.crossfade(true)
.build(),
contentDescription = app.label,
modifier = Modifier
.padding(4.dp)
.width(48.dp)
.height(48.dp)
)
},
trailingContent = {
if (!viewModel.showBatchActions) {
Switch(
checked = app.allowSu,
onCheckedChange = onSwitchChange
)
} else {
Checkbox(
checked = isSelected,
onCheckedChange = { onToggleSelection() }
)
}
}
)
}
@Composable
fun LabelText(label: String) {
Box(
modifier = Modifier
.padding(top = 4.dp, end = 4.dp)
.background(
Color.Black,
shape = RoundedCornerShape(4.dp)
)
) {
Text(
text = label,
modifier = Modifier.padding(vertical = 2.dp, horizontal = 5.dp),
style = TextStyle(
fontSize = 8.sp,
color = Color.White,
)
)
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.widget.Toast
import androidx.compose.foundation.clickable
@@ -49,7 +49,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
@@ -59,8 +58,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.ramcosta.composedestinations.result.getOr
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import androidx.lifecycle.compose.dropUnlessResumed
/**
* @author weishu

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.screen
package shirkneko.zako.sukisu.ui.screen
import android.widget.Toast
import androidx.activity.compose.BackHandler
@@ -44,18 +44,18 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.lifecycle.compose.dropUnlessResumed
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.result.ResultBackNavigator
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.profile.RootProfileConfig
import me.weishu.kernelsu.ui.util.deleteAppProfileTemplate
import me.weishu.kernelsu.ui.util.getAppProfileTemplate
import me.weishu.kernelsu.ui.util.setAppProfileTemplate
import me.weishu.kernelsu.ui.viewmodel.TemplateViewModel
import me.weishu.kernelsu.ui.viewmodel.toJSON
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.R
import shirkneko.zako.sukisu.ui.component.profile.RootProfileConfig
import shirkneko.zako.sukisu.ui.util.deleteAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import shirkneko.zako.sukisu.ui.viewmodel.TemplateViewModel
import shirkneko.zako.sukisu.ui.viewmodel.toJSON
import androidx.lifecycle.compose.dropUnlessResumed
/**
* @author weishu

View File

@@ -0,0 +1,42 @@
package shirkneko.zako.sukisu.ui.theme
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.material3.CardDefaults
object CardConfig {
val defaultElevation: Dp = 2.dp
var cardAlpha by mutableStateOf(1f)
var cardElevation by mutableStateOf(defaultElevation)
fun save(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
prefs.edit().apply {
putFloat("card_alpha", cardAlpha)
putBoolean("custom_background_enabled", cardElevation == 0.dp)
apply()
}
}
fun load(context: Context) {
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
cardAlpha = prefs.getFloat("card_alpha", 1f)
cardElevation = if (prefs.getBoolean("custom_background_enabled", false)) 0.dp else defaultElevation
}
}
@Composable
fun getCardColors(originalColor: Color) = CardDefaults.elevatedCardColors(
containerColor = originalColor.copy(alpha = CardConfig.cardAlpha),
contentColor = if (originalColor.luminance() > 0.5) Color.Black else Color.White
)
fun getCardElevation() = CardConfig.cardElevation

View File

@@ -0,0 +1,162 @@
package shirkneko.zako.sukisu.ui.theme
import androidx.compose.ui.graphics.Color
sealed class ThemeColors {
abstract val Primary: Color
abstract val Secondary: Color
abstract val Tertiary: Color
abstract val OnPrimary: Color
abstract val OnSecondary: Color
abstract val OnTertiary: Color
abstract val PrimaryContainer: Color
abstract val SecondaryContainer: Color
abstract val TertiaryContainer: Color
abstract val OnPrimaryContainer: Color
abstract val OnSecondaryContainer: Color
abstract val OnTertiaryContainer: Color
open fun getCustomSliderActiveColor(): Color = Primary
open fun getCustomSliderInactiveColor(): Color = PrimaryContainer
// Default Theme (Yellow)
object Default : ThemeColors() {
override val Primary = Color(0xFFFFD700)
override val Secondary = Color(0xFFFFBC52)
override val Tertiary = Color(0xFF795548)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFFBE9)
override val SecondaryContainer = Color(0xFFFFE6B3)
override val TertiaryContainer = Color(0xFFD7CCC8)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Blue Theme
object Blue : ThemeColors() {
override val Primary = Color(0xFF2196F3)
override val Secondary = Color(0xFF1E88E5)
override val Tertiary = Color(0xFF0D47A1)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFE3F2FD)
override val SecondaryContainer = Color(0xFFBBDEFB)
override val TertiaryContainer = Color(0xFF90CAF9)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Green Theme
object Green : ThemeColors() {
override val Primary = Color(0xFF4CAF50)
override val Secondary = Color(0xFF43A047)
override val Tertiary = Color(0xFF1B5E20)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFE8F5E9)
override val SecondaryContainer = Color(0xFFC8E6C9)
override val TertiaryContainer = Color(0xFFA5D6A7)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Purple Theme
object Purple : ThemeColors() {
override val Primary = Color(0xFF9C27B0)
override val Secondary = Color(0xFF8E24AA)
override val Tertiary = Color(0xFF4A148C)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFF3E5F5)
override val SecondaryContainer = Color(0xFFE1BEE7)
override val TertiaryContainer = Color(0xFFCE93D8)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Orange Theme
object Orange : ThemeColors() {
override val Primary = Color(0xFFFF9800)
override val Secondary = Color(0xFFFB8C00)
override val Tertiary = Color(0xFFE65100)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFF3E0)
override val SecondaryContainer = Color(0xFFFFE0B2)
override val TertiaryContainer = Color(0xFFFFCC80)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Pink Theme
object Pink : ThemeColors() {
override val Primary = Color(0xFFE91E63)
override val Secondary = Color(0xFFD81B60)
override val Tertiary = Color(0xFF880E4F)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFCE4EC)
override val SecondaryContainer = Color(0xFFF8BBD0)
override val TertiaryContainer = Color(0xFFF48FB1)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Gray Theme
object Gray : ThemeColors() {
override val Primary = Color(0xFF9E9E9E)
override val Secondary = Color(0xFF757575)
override val Tertiary = Color(0xFF616161)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFEEEEEE)
override val SecondaryContainer = Color(0xFFE0E0E0)
override val TertiaryContainer = Color(0xFFBDBDBD)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
// Ivory Theme
object Ivory : ThemeColors() {
override val Primary = Color(0xFFFAF0E6)
override val Secondary = Color(0xFFFFF0E6)
override val Tertiary = Color(0xFFD7CCC8)
override val OnPrimary = Color(0xFFFFFFFF)
override val OnSecondary = Color(0xFFFFFFFF)
override val OnTertiary = Color(0xFFFFFFFF)
override val PrimaryContainer = Color(0xFFFFFAE3)
override val SecondaryContainer = Color(0xFFFFF0E6)
override val TertiaryContainer = Color(0xFFFFF0E6)
override val OnPrimaryContainer = Color(0xFF000000)
override val OnSecondaryContainer = Color(0xFF000000)
override val OnTertiaryContainer = Color(0xFF000000)
}
companion object {
fun fromName(name: String): ThemeColors = when (name.lowercase()) {
"blue" -> Blue
"green" -> Green
"purple" -> Purple
"orange" -> Orange
"pink" -> Pink
"gray" -> Gray
"ivory" -> Ivory
else -> Default
}
}
}

View File

@@ -0,0 +1,289 @@
package shirkneko.zako.sukisu.ui.theme
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.zIndex
import coil.compose.rememberAsyncImagePainter
import androidx.compose.foundation.background
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
object ThemeConfig {
var customBackgroundUri by mutableStateOf<Uri?>(null)
var forceDarkMode by mutableStateOf<Boolean?>(null)
var currentTheme by mutableStateOf<ThemeColors>(ThemeColors.Default)
var useDynamicColor by mutableStateOf(false)
}
@Composable
private fun getDarkColorScheme() = darkColorScheme(
primary = ThemeConfig.currentTheme.Primary,
onPrimary = ThemeConfig.currentTheme.OnPrimary,
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
onPrimaryContainer = Color.White,
secondary = ThemeConfig.currentTheme.Secondary,
onSecondary = ThemeConfig.currentTheme.OnSecondary,
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer,
onSecondaryContainer = Color.White,
tertiary = ThemeConfig.currentTheme.Tertiary,
onTertiary = ThemeConfig.currentTheme.OnTertiary,
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer,
onTertiaryContainer = Color.White,
background = Color.Transparent,
surface = Color.Transparent,
onBackground = Color.White,
onSurface = Color.White
)
@Composable
private fun getLightColorScheme() = lightColorScheme(
primary = ThemeConfig.currentTheme.Primary,
onPrimary = ThemeConfig.currentTheme.OnPrimary,
primaryContainer = ThemeConfig.currentTheme.PrimaryContainer,
onPrimaryContainer = ThemeConfig.currentTheme.OnPrimaryContainer,
secondary = ThemeConfig.currentTheme.Secondary,
onSecondary = ThemeConfig.currentTheme.OnSecondary,
secondaryContainer = ThemeConfig.currentTheme.SecondaryContainer,
onSecondaryContainer = ThemeConfig.currentTheme.OnSecondaryContainer,
tertiary = ThemeConfig.currentTheme.Tertiary,
onTertiary = ThemeConfig.currentTheme.OnTertiary,
tertiaryContainer = ThemeConfig.currentTheme.TertiaryContainer,
onTertiaryContainer = ThemeConfig.currentTheme.OnTertiaryContainer,
background = Color.Transparent,
surface = Color.Transparent
)
// 复制图片到应用内部存储
fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
val fileName = "custom_background.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
outputStream.close()
inputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
return null
}
}
@Composable
fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
true -> true
false -> false
null -> isSystemInDarkTheme()
},
dynamicColor: Boolean = ThemeConfig.useDynamicColor,
content: @Composable () -> Unit
) {
val context = LocalContext.current
context.loadCustomBackground()
context.loadThemeColors()
context.loadDynamicColorState()
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(context).copy(
background = Color.Transparent,
surface = Color.Transparent,
onBackground = Color.White,
onSurface = Color.White
) else dynamicLightColorScheme(context).copy(
background = Color.Transparent,
surface = Color.Transparent
)
}
darkTheme -> getDarkColorScheme()
else -> getLightColorScheme()
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography
) {
Box(modifier = Modifier.fillMaxSize()) {
// 背景图层
ThemeConfig.customBackgroundUri?.let { uri ->
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(-1f)
) {
// 背景图片
Box(
modifier = Modifier
.fillMaxSize()
.paint(
painter = rememberAsyncImagePainter(
model = uri,
onError = {
ThemeConfig.customBackgroundUri = null
context.saveCustomBackground(null)
}
),
contentScale = ContentScale.Crop
)
)
// 亮度调节层
Box(
modifier = Modifier
.fillMaxSize()
.background(
if (darkTheme) {
Color.Black.copy(alpha = 0.4f)
} else {
Color.White.copy(alpha = 0.1f)
}
)
)
// 边缘渐变遮罩层
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.radialGradient(
colors = listOf(
Color.Transparent,
if (darkTheme) {
Color.Black.copy(alpha = 0.5f)
} else {
Color.Black.copy(alpha = 0.2f)
}
),
radius = 1200f
)
)
)
}
}
// 内容图层
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(1f)
) {
content()
}
}
}
}
fun Context.saveCustomBackground(uri: Uri?) {
val newUri = uri?.let { copyImageToInternalStorage(it) }
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putString("custom_background", newUri?.toString())
.apply()
ThemeConfig.customBackgroundUri = newUri
}
fun Context.loadCustomBackground() {
val uriString = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("custom_background", null)
ThemeConfig.customBackgroundUri = uriString?.let { Uri.parse(it) }
}
fun Context.saveThemeMode(forceDark: Boolean?) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putString("theme_mode", when(forceDark) {
true -> "dark"
false -> "light"
null -> "system"
})
.apply()
ThemeConfig.forceDarkMode = forceDark
}
fun Context.loadThemeMode() {
val mode = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_mode", "system")
ThemeConfig.forceDarkMode = when(mode) {
"dark" -> true
"light" -> false
else -> null
}
}
fun Context.saveThemeColors(themeName: String) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putString("theme_colors", themeName)
.apply()
ThemeConfig.currentTheme = when(themeName) {
"blue" -> ThemeColors.Blue
"green" -> ThemeColors.Green
"purple" -> ThemeColors.Purple
"orange" -> ThemeColors.Orange
"pink" -> ThemeColors.Pink
"gray" -> ThemeColors.Gray
"ivory" -> ThemeColors.Ivory
else -> ThemeColors.Default
}
}
fun Context.loadThemeColors() {
val themeName = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getString("theme_colors", "default")
ThemeConfig.currentTheme = when(themeName) {
"blue" -> ThemeColors.Blue
"green" -> ThemeColors.Green
"purple" -> ThemeColors.Purple
"orange" -> ThemeColors.Orange
"pink" -> ThemeColors.Pink
"gray" -> ThemeColors.Gray
"ivory" -> ThemeColors.Ivory
else -> ThemeColors.Default
}
}
fun Context.saveDynamicColorState(enabled: Boolean) {
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit()
.putBoolean("use_dynamic_color", enabled)
.apply()
ThemeConfig.useDynamicColor = enabled
}
fun Context.loadDynamicColorState() {
val enabled = getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.getBoolean("use_dynamic_color", true)
ThemeConfig.useDynamicColor = enabled
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.theme
package shirkneko.zako.sukisu.ui.theme
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util
package shirkneko.zako.sukisu.ui.util
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util
package shirkneko.zako.sukisu.ui.util
import android.annotation.SuppressLint
import android.app.DownloadManager
@@ -8,11 +8,11 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.core.content.ContextCompat
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.util.module.LatestVersionInfo
import shirkneko.zako.sukisu.ui.util.module.LatestVersionInfo
/**
* @author weishu
@@ -63,45 +63,62 @@ fun download(
}
fun checkNewVersion(): LatestVersionInfo {
val url = "https://api.github.com/repos/tiann/KernelSU/releases/latest"
// default null value if failed
// 改为新的 release 接口
val url = "https://api.github.com/repos/ShirkNeko/KernelSU/releases/latest"
val defaultValue = LatestVersionInfo()
runCatching {
ksuApp.okhttpClient.newCall(okhttp3.Request.Builder().url(url).build()).execute()
return runCatching {
okhttp3.OkHttpClient().newCall(okhttp3.Request.Builder().url(url).build()).execute()
.use { response ->
if (!response.isSuccessful) {
Log.d("CheckUpdate", "Network request failed: ${response.message}")
return defaultValue
}
val body = response.body?.string() ?: return defaultValue
val body = response.body?.string()
if (body == null) {
Log.d("CheckUpdate", "Response body is null")
return defaultValue
}
Log.d("CheckUpdate", "Response body: $body")
val json = org.json.JSONObject(body)
// 直接从 tag_name 提取版本号(如 v1.1
val tagName = json.optString("tag_name", "")
val versionName = tagName.removePrefix("v") // 移除前缀 "v"
// 从 body 字段获取更新日志(保留换行符)
val changelog = json.optString("body")
.replace("\\r\\n", "\n") // 转换换行符
val assets = json.getJSONArray("assets")
for (i in 0 until assets.length()) {
val asset = assets.getJSONObject(i)
val name = asset.getString("name")
if (!name.endsWith(".apk")) {
if (!name.endsWith(".apk")) continue
// 修改正则表达式,只匹配 SukiSU 和版本号
val regex = Regex("SukiSU.*_(\\d+)-release")
val matchResult = regex.find(name)
if (matchResult == null) {
Log.d("CheckUpdate", "No match found in $name, skipping")
continue
}
val versionCode = matchResult.groupValues[1].toInt()
val regex = Regex("v(.+?)_(\\d+)-")
val matchResult = regex.find(name) ?: continue
val versionName = matchResult.groupValues[1]
val versionCode = matchResult.groupValues[2].toInt()
val downloadUrl = asset.getString("browser_download_url")
return LatestVersionInfo(
versionCode,
downloadUrl,
changelog
changelog,
versionName
)
}
Log.d("CheckUpdate", "No valid apk asset found, returning default value")
defaultValue
}
}
return defaultValue
}.getOrDefault(defaultValue)
}
@Composable
fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
DisposableEffect(context) {
@@ -141,3 +158,4 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
}
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util;
package shirkneko.zako.sukisu.ui.util;
/*
* Copyright (C) 2009 The Android Open Source Project
*

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util
package shirkneko.zako.sukisu.ui.util
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.util
package shirkneko.zako.sukisu.ui.util
import android.content.ContentResolver
import android.content.Context
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.BuildConfig
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import shirkneko.zako.sukisu.BuildConfig
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import org.json.JSONArray
import java.io.File
@@ -30,12 +30,7 @@ import java.io.File
private const val TAG = "KsuCli"
private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libksud.so"
}
data class FlashResult(val code: Int, val err: String, val showReboot: Boolean) {
constructor(result: Shell.Result, showReboot: Boolean) : this(result.code, result.err.joinToString("\n"), showReboot)
constructor(result: Shell.Result) : this(result, result.isSuccess)
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomk.so"
}
object KsuCli {
@@ -104,7 +99,7 @@ fun execKsud(args: String, newShell: Boolean = false): Boolean {
fun install() {
val start = SystemClock.elapsedRealtime()
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so").absolutePath
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so").absolutePath
val result = execKsud("install --magiskboot $magiskboot", true)
Log.w(TAG, "install result: $result, cost: ${SystemClock.elapsedRealtime() - start}ms")
}
@@ -179,9 +174,10 @@ private fun flashWithIO(
fun flashModule(
uri: Uri,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit
): FlashResult {
): Boolean {
val resolver = ksuApp.contentResolver
with(resolver.openInputStream(uri)) {
val file = File(ksuApp.cacheDir, "module.zip")
@@ -194,7 +190,8 @@ fun flashModule(
file.delete()
return FlashResult(result)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
}
@@ -223,19 +220,21 @@ fun runModuleAction(
}
fun restoreBoot(
onStdout: (String) -> Unit, onStderr: (String) -> Unit
): FlashResult {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO("${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot", onStdout, onStderr)
return FlashResult(result)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
fun uninstallPermanently(
onStdout: (String) -> Unit, onStderr: (String) -> Unit
): FlashResult {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
onFinish: (Boolean, Int) -> Unit, onStdout: (String) -> Unit, onStderr: (String) -> Unit
): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
return FlashResult(result)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
@Parcelize
@@ -249,9 +248,10 @@ fun installBoot(
bootUri: Uri?,
lkm: LkmSelection,
ota: Boolean,
onFinish: (Boolean, Int) -> Unit,
onStdout: (String) -> Unit,
onStderr: (String) -> Unit,
): FlashResult {
): Boolean {
val resolver = ksuApp.contentResolver
val bootFile = bootUri?.let { uri ->
@@ -265,7 +265,7 @@ fun installBoot(
}
}
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libmagiskboot.so")
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
var cmd = "boot-patch --magiskboot ${magiskboot.absolutePath}"
cmd += if (bootFile == null) {
@@ -314,7 +314,8 @@ fun installBoot(
lkmFile?.delete()
// if boot uri is empty, it is direct install, when success, we should show reboot button
return FlashResult(result, bootUri == null && result.isSuccess)
onFinish(bootUri == null && result.isSuccess, result.code)
return result.isSuccess
}
fun reboot(reason: String = "") {
@@ -434,3 +435,48 @@ fun restartApp(packageName: String) {
forceStopApp(packageName)
launchApp(packageName)
}
private fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakomksd.so"
}
fun getSuSFS(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} support")
return result
}
fun getSuSFSVersion(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} version")
return result
}
fun getSuSFSVariant(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} variant")
return result
}
fun getSuSFSFeatures(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} features")
return result
}
fun susfsSUS_SU_0(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 0")
return result
}
fun susfsSUS_SU_2(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 2")
return result
}
fun susfsSUS_SU_Mode(): String {
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su mode")
return result
}

View File

@@ -1,11 +1,11 @@
package me.weishu.kernelsu.ui.util
package shirkneko.zako.sukisu.ui.util
import android.content.Context
import android.os.Build
import android.system.Os
import com.topjohnwu.superuser.ShellUtils
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ui.screen.getManagerVersion
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ui.screen.getManagerVersion
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter

View File

@@ -0,0 +1,330 @@
package shirkneko.zako.sukisu.ui.util
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import shirkneko.zako.sukisu.R
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object ModuleModify {
suspend fun showRestoreConfirmation(context: Context): Boolean {
val result = CompletableDeferred<Boolean>()
withContext(Dispatchers.Main) {
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.restore_confirm_title))
.setMessage(context.getString(R.string.restore_confirm_message))
.setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) }
.setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) }
.setOnCancelListener { result.complete(false) }
.show()
}
return result.await()
}
suspend fun backupModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
withContext(Dispatchers.IO) {
try {
val busyboxPath = "/data/adb/ksu/bin/busybox"
val moduleDir = "/data/adb/modules"
// 直接将tar输出重定向到用户选择的文件
val command = """
cd "$moduleDir" &&
$busyboxPath tar -cz ./* > /proc/self/fd/1
""".trimIndent()
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command))
// 直接将tar输出写入到用户选择的文件
context.contentResolver.openOutputStream(uri)?.use { output ->
process.inputStream.copyTo(output)
}
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.backup_success),
duration = SnackbarDuration.Long
)
}
} catch (e: Exception) {
Log.e("Backup", context.getString(R.string.backup_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.backup_failed, e.message),
duration = SnackbarDuration.Long
)
}
}
}
}
suspend fun restoreModules(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
val userConfirmed = showRestoreConfirmation(context)
if (!userConfirmed) return
withContext(Dispatchers.IO) {
try {
val busyboxPath = "/data/adb/ksu/bin/busybox"
val moduleDir = "/data/adb/modules"
// 直接从用户选择的文件读取并解压
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "$busyboxPath tar -xz -C $moduleDir"))
context.contentResolver.openInputStream(uri)?.use { input ->
input.copyTo(process.outputStream)
}
process.outputStream.close()
process.waitFor()
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
val snackbarResult = snackBarHost.showSnackbar(
message = context.getString(R.string.restore_success),
actionLabel = context.getString(R.string.restart_now),
duration = SnackbarDuration.Long
)
if (snackbarResult == SnackbarResult.ActionPerformed) {
reboot()
}
}
} catch (e: Exception) {
Log.e("Restore", context.getString(R.string.restore_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
message = context.getString(
R.string.restore_failed,
e.message ?: context.getString(R.string.unknown_error)
),
duration = SnackbarDuration.Long
)
}
}
}
}
suspend fun showAllowlistRestoreConfirmation(context: Context): Boolean {
val result = CompletableDeferred<Boolean>()
withContext(Dispatchers.Main) {
AlertDialog.Builder(context)
.setTitle(context.getString(R.string.allowlist_restore_confirm_title))
.setMessage(context.getString(R.string.allowlist_restore_confirm_message))
.setPositiveButton(context.getString(R.string.confirm)) { _, _ -> result.complete(true) }
.setNegativeButton(context.getString(R.string.cancel)) { _, _ -> result.complete(false) }
.setOnCancelListener { result.complete(false) }
.show()
}
return result.await()
}
suspend fun backupAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
withContext(Dispatchers.IO) {
try {
val allowlistPath = "/data/adb/ksu/.allowlist"
// 直接复制文件到用户选择的位置
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat $allowlistPath"))
context.contentResolver.openOutputStream(uri)?.use { output ->
process.inputStream.copyTo(output)
}
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_backup_success),
duration = SnackbarDuration.Long
)
}
} catch (e: Exception) {
Log.e("AllowlistBackup", context.getString(R.string.allowlist_backup_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_backup_failed, e.message),
duration = SnackbarDuration.Long
)
}
}
}
}
suspend fun restoreAllowlist(context: Context, snackBarHost: SnackbarHostState, uri: Uri) {
val userConfirmed = showAllowlistRestoreConfirmation(context)
if (!userConfirmed) return
withContext(Dispatchers.IO) {
try {
val allowlistPath = "/data/adb/ksu/.allowlist"
// 直接从用户选择的文件读取并写入到目标位置
val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "cat > $allowlistPath"))
context.contentResolver.openInputStream(uri)?.use { input ->
input.copyTo(process.outputStream)
}
process.outputStream.close()
process.waitFor()
val error = BufferedReader(InputStreamReader(process.errorStream)).readText()
if (process.exitValue() != 0) {
throw IOException(context.getString(R.string.command_execution_failed, error))
}
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_restore_success),
duration = SnackbarDuration.Long
)
}
} catch (e: Exception) {
Log.e("AllowlistRestore", context.getString(R.string.allowlist_restore_failed, ""), e)
withContext(Dispatchers.Main) {
snackBarHost.showSnackbar(
context.getString(R.string.allowlist_restore_failed, e.message),
duration = SnackbarDuration.Long
)
}
}
}
}
@Composable
fun rememberModuleBackupLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
backupModules(context, snackBarHost, uri)
}
}
}
}
@Composable
fun rememberModuleRestoreLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
restoreModules(context, snackBarHost, uri)
}
}
}
}
@Composable
fun rememberAllowlistBackupLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
backupAllowlist(context, snackBarHost, uri)
}
}
}
}
@Composable
fun rememberAllowlistRestoreLauncher(
context: Context,
snackBarHost: SnackbarHostState,
scope: kotlinx.coroutines.CoroutineScope = rememberCoroutineScope()
) = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == android.app.Activity.RESULT_OK) {
result.data?.data?.let { uri ->
scope.launch {
restoreAllowlist(context, snackBarHost, uri)
}
}
}
}
fun createBackupIntent(): Intent {
return Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/zip"
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
putExtra(Intent.EXTRA_TITLE, "modules_backup_$timestamp.zip")
}
}
fun createRestoreIntent(): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/zip"
}
}
fun createAllowlistBackupIntent(): Intent {
return Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
putExtra(Intent.EXTRA_TITLE, "ksu_allowlist_backup_$timestamp.dat")
}
}
fun createAllowlistRestoreIntent(): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
}
}
private fun reboot() {
Runtime.getRuntime().exec(arrayOf("su", "-c", "reboot"))
}
}

View File

@@ -1,34 +1,33 @@
package me.weishu.kernelsu.ui.util
package shirkneko.zako.sukisu.ui.util
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell
import me.weishu.kernelsu.R
import shirkneko.zako.sukisu.R
@Composable
fun getSELinuxStatus(): String {
val shell = Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR)
.build("sh")
val shell = Shell.Builder.create().build("sh")
val list = ArrayList<String>()
val result = shell.use {
it.newJob().add("getenforce").to(list, list).exec()
}
val output = result.out.joinToString("\n").trim()
if (result.isSuccess) {
return when (output) {
val output = list.joinToString("\n").trim()
return if (result.isSuccess) {
when (output) {
"Enforcing" -> stringResource(R.string.selinux_status_enforcing)
"Permissive" -> stringResource(R.string.selinux_status_permissive)
"Disabled" -> stringResource(R.string.selinux_status_disabled)
else -> stringResource(R.string.selinux_status_unknown)
}
}
return if (output.endsWith("Permission denied")) {
stringResource(R.string.selinux_status_enforcing)
} else {
stringResource(R.string.selinux_status_unknown)
if (output.contains("Permission denied")) {
stringResource(R.string.selinux_status_enforcing)
} else {
stringResource(R.string.selinux_status_unknown)
}
}
}
}

View File

@@ -0,0 +1,8 @@
package shirkneko.zako.sukisu.ui.util.module
data class LatestVersionInfo(
val versionCode : Int = 0,
val downloadUrl : String = "",
val changelog : String = "",
val versionName: String = ""
)

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.viewmodel
package shirkneko.zako.sukisu.ui.viewmodel
import android.os.SystemClock
import android.util.Log
@@ -10,9 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.util.HanziToPinyin
import me.weishu.kernelsu.ui.util.listModules
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.listModules
import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator
@@ -134,8 +133,11 @@ class ModuleViewModel : ViewModel() {
val result = kotlin.runCatching {
val url = m.updateJson
Log.i(TAG, "checkUpdate url: $url")
val response = ksuApp.okhttpClient.newCall(
okhttp3.Request.Builder().url(url).build()
val response = okhttp3.OkHttpClient()
.newCall(
okhttp3.Request.Builder()
.url(url)
.build()
).execute()
Log.d(TAG, "checkUpdate code: ${response.code}")
if (response.isSuccessful) {

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.viewmodel
package shirkneko.zako.sukisu.ui.viewmodel
import android.content.ComponentName
import android.content.Intent
@@ -18,19 +18,18 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.IKsuInterface
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.ui.KsuService
import me.weishu.kernelsu.ui.util.HanziToPinyin
import me.weishu.kernelsu.ui.util.KsuCli
import shirkneko.zako.sukisu.IKsuInterface
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.ksuApp
import shirkneko.zako.sukisu.ui.KsuService
import shirkneko.zako.sukisu.ui.util.HanziToPinyin
import shirkneko.zako.sukisu.ui.util.KsuCli
import java.text.Collator
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class SuperUserViewModel : ViewModel() {
companion object {
private const val TAG = "SuperUserViewModel"
private var apps by mutableStateOf<List<AppInfo>>(emptyList())
@@ -54,7 +53,6 @@ class SuperUserViewModel : ViewModel() {
if (profile == null) {
return false
}
return if (profile.allowSu) {
!profile.rootUseDefault
} else {
@@ -68,6 +66,12 @@ class SuperUserViewModel : ViewModel() {
var isRefreshing by mutableStateOf(false)
private set
// 批量操作相关状态
var showBatchActions by mutableStateOf(false)
private set
var selectedApps by mutableStateOf<Set<String>>(emptySet())
private set
private val sortedList by derivedStateOf {
val comparator = compareBy<AppInfo> {
when {
@@ -89,21 +93,65 @@ class SuperUserViewModel : ViewModel() {
) || HanziToPinyin.getInstance()
.toPinyinString(it.label).contains(search, true)
}.filter {
it.uid == 2000 // Always show shell
|| showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
it.uid == 2000 || showSystemApps || it.packageInfo.applicationInfo!!.flags.and(ApplicationInfo.FLAG_SYSTEM) == 0
}
}
private suspend inline fun connectKsuService(
crossinline onDisconnect: () -> Unit = {}
): Pair<IBinder, ServiceConnection> = suspendCoroutine {
// 切换批量操作模式
fun toggleBatchMode() {
showBatchActions = !showBatchActions
if (!showBatchActions) {
clearSelection()
}
}
// 切换应用选择状态
fun toggleAppSelection(packageName: String) {
selectedApps = if (selectedApps.contains(packageName)) {
selectedApps - packageName
} else {
selectedApps + packageName
}
}
// 清除所有选择
fun clearSelection() {
selectedApps = emptySet()
}
// 批量更新权限
suspend fun updateBatchPermissions(allowSu: Boolean) {
selectedApps.forEach { packageName ->
val app = apps.find { it.packageName == packageName }
app?.let {
val profile = Natives.getAppProfile(packageName, it.uid)
val updatedProfile = profile.copy(allowSu = allowSu)
if (Natives.setAppProfile(updatedProfile)) {
apps = apps.map { app ->
if (app.packageName == packageName) {
app.copy(profile = updatedProfile)
} else {
app
}
}
}
}
}
clearSelection()
showBatchActions = false // 批量操作完成后退出批量模式
fetchAppList() // 刷新列表以显示最新状态
}
private suspend fun connectKsuService(
onDisconnect: () -> Unit = {}
): Pair<IBinder, ServiceConnection> = suspendCoroutine { continuation ->
val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
onDisconnect()
}
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
it.resume(binder as IBinder to this)
continuation.resume(binder as IBinder to this)
}
}
@@ -124,7 +172,6 @@ class SuperUserViewModel : ViewModel() {
}
suspend fun fetchAppList() {
isRefreshing = true
val result = connectKsuService {
@@ -157,4 +204,4 @@ class SuperUserViewModel : ViewModel() {
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}")
}
}
}
}

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.viewmodel
package shirkneko.zako.sukisu.ui.viewmodel
import android.os.Parcelable
import android.util.Log
@@ -10,18 +10,19 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.ksuApp
import me.weishu.kernelsu.profile.Capabilities
import me.weishu.kernelsu.profile.Groups
import me.weishu.kernelsu.ui.util.getAppProfileTemplate
import me.weishu.kernelsu.ui.util.listAppProfileTemplates
import me.weishu.kernelsu.ui.util.setAppProfileTemplate
import shirkneko.zako.sukisu.Natives
import shirkneko.zako.sukisu.profile.Capabilities
import shirkneko.zako.sukisu.profile.Groups
import shirkneko.zako.sukisu.ui.util.getAppProfileTemplate
import shirkneko.zako.sukisu.ui.util.listAppProfileTemplates
import shirkneko.zako.sukisu.ui.util.setAppProfileTemplate
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator
import java.util.Locale
import java.util.concurrent.TimeUnit
/**
@@ -137,7 +138,13 @@ class TemplateViewModel : ViewModel() {
private fun fetchRemoteTemplates() {
runCatching {
ksuApp.okhttpClient.newCall(
val client: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build()
client.newCall(
Request.Builder().url(TEMPLATE_INDEX_URL).build()
).execute().use { response ->
if (!response.isSuccessful) {
@@ -148,7 +155,7 @@ private fun fetchRemoteTemplates() {
0.until(remoteTemplateIds.length()).forEach { i ->
val id = remoteTemplateIds.getString(i)
Log.i(TAG, "fetch template: $id")
val templateJson = ksuApp.okhttpClient.newCall(
val templateJson = client.newCall(
Request.Builder().url(TEMPLATE_URL.format(id)).build()
).runCatching {
execute().use { response ->

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package me.weishu.kernelsu.ui.webui;
package shirkneko.zako.sukisu.ui.webui;
import java.net.URLConnection;

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.webui;
package shirkneko.zako.sukisu.ui.webui;
import android.content.Context;
import android.util.Log;

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.webui
package shirkneko.zako.sukisu.ui.webui
import android.annotation.SuppressLint
import android.app.ActivityManager
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell
import me.weishu.kernelsu.ui.util.createRootShell
import shirkneko.zako.sukisu.ui.util.createRootShell
import java.io.File
@SuppressLint("SetJavaScriptEnabled")

View File

@@ -1,4 +1,4 @@
package me.weishu.kernelsu.ui.webui
package shirkneko.zako.sukisu.ui.webui
import android.app.Activity
import android.content.Context
@@ -14,9 +14,9 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler
import me.weishu.kernelsu.ui.util.createRootShell
import me.weishu.kernelsu.ui.util.listModules
import me.weishu.kernelsu.ui.util.withNewRootShell
import shirkneko.zako.sukisu.ui.util.createRootShell
import shirkneko.zako.sukisu.ui.util.listModules
import shirkneko.zako.sukisu.ui.util.withNewRootShell
import org.json.JSONArray
import org.json.JSONObject
import java.io.File

View File

@@ -0,0 +1,26 @@
package shirkneko.zako.sukisu.utils
import android.content.Context
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
object AssetsUtil {
@Throws(IOException::class)
fun exportFiles(context: Context, src: String, out: String) {
val fileNames = context.assets.list(src)
if (fileNames?.isNotEmpty() == true) {
val file = File(out)
file.mkdirs()
fileNames.forEach { fileName ->
exportFiles(context, "$src/$fileName", "$out/$fileName")
}
} else {
context.assets.open(src).use { inputStream ->
FileOutputStream(File(out)).use { outputStream ->
inputStream.copyTo(outputStream)
}
}
}
}
}

View File

@@ -1 +1,2 @@
libksud.so
libzakomk.so
libzakomksd.so

View File

@@ -18,7 +18,7 @@
<string name="selinux_status_permissive">متساهل</string>
<string name="selinux_status_unknown">مجهول</string>
<string name="superuser">مستخدم خارق</string>
<string name="module_failed_to_enable">فشل في تمكين الوحدة %s</string>
<string name="module_failed_to_enable">لا يمكن تشغيل %s الوحدة</string>
<string name="module_failed_to_disable">فشل تعطيل الإضافة : %s</string>
<string name="module_empty">لا توجد إضافات مثبتة</string>
<string name="module">الإضافات</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">تعرف على كيفية تثبيت KernelSU واستخدام الإضافات</string>
<string name="home_support_title">إدعمنا</string>
<string name="home_support_content">KernelSU سيظل دائماً مجانياً ومفتوح المصدر. مع ذلك، يمكنك أن تظهر لنا أنك تهتم بالتبرع.</string>
<string name="about_source_code"><![CDATA[أنظر إلى مصدر البرمجة في %1$s<br/>إنضم إلى قناتنا في %2$s ]]></string>
<string name="profile_capabilities">القدرات</string>
<string name="module_update">تحديث</string>
<string name="module_downloading">تحميل الإضافة: %s</string>
@@ -73,11 +72,10 @@
<string name="settings_umount_modules_default_summary">القيمة الافتراضية العامة لـ\"إلغاء تحميل الإضافات\" في ملفات تعريف التطبيقات. إذا تم تمكينه، إزالة جميع تعديلات الإضافات على النظام للتطبيقات التي لا تحتوي على مجموعة ملف تعريف.</string>
<string name="profile_umount_modules_summary">سيسمح تمكين هذا الخيار لـKernelSU باستعادة أي ملفات معدلة بواسطة الإضافات لهذا التطبيق.</string>
<string name="profile_selinux_domain">المجال</string>
<string name="profile_selinux_rules">القواعد</string>
<string name="profile_selinux_rules" formatted="false">القواعد</string>
<string name="restart_app">إعادة تشغيل التطبيق</string>
<string name="failed_to_update_sepolicy">فشل تحديث قواعد SELinux لـ %s</string>
<string name="profile_name">اسم الملف الشخصي</string>
<string name="require_kernel_version">إصدار KernelSU الحالي %d منخفض جدًا بحيث لا يعمل المدير بشكل صحيح. الرجاء الترقية إلى الإصدار %d أو أعلى!</string>
<string name="module_changelog">سجل التغييرات</string>
<string name="app_profile_template_import_success">تم الاستيراد بنجاح</string>
<string name="app_profile_export_to_clipboard">تصدير إلى الحافظة</string>
@@ -132,6 +130,4 @@
<string name="log_saved">السجلات محفوظة</string>
<string name="module_sort_enabled_first">فرز (الممكن أولاً)</string>
<string name="module_sort_action_first">فرز (الإجراء أولاً)</string>
<string name="settings_disable_su">تعطيل توافق su</string>
<string name="settings_disable_su_summary">قم بتعطيل أي تطبيقات مؤقتا من الحصول على امتيازات الجذر عبر الأمر su (لن تتأثر عمليات الجذر الحالية).</string>
</resources>

View File

@@ -53,7 +53,6 @@
<string name="profile_default">Defolt</string>
<string name="profile_custom">Özəl</string>
<string name="home_support_content">KernelSU pulsuz və açıq mənbəlidir,həmişə belə olacaqdır. Bununla belə, ianə etməklə bizə qayğı göstərdiyinizi göstərə bilərsiniz.</string>
<string name="about_source_code">Mənbə kodlarımıza baxın %1$s<br/>Kanalımıza %2$s qoşulun</string>
<string name="profile_name">Profil adı</string>
<string name="profile_capabilities">Bacarıqlar</string>
<string name="profile_umount_modules">Modulları umount et</string>

View File

@@ -41,7 +41,7 @@
<string name="refresh">রিফ্রেশ</string>
<string name="show_system_apps">শো সিস্টেম অ্যাপস</string>
<string name="hide_system_apps">হাইড সিস্টেম অ্যাপস</string>
<string name="send_log">সেন্ড লগ</string>
<string name="send_log" formatted="false">সেন্ড লগ</string>
<string name="safe_mode">সেইফ মোড</string>
<string name="reboot_to_apply">রিবুট এপ্লাই</string>
<string name="module_magisk_conflict">মডিউলগুলি অক্ষম কারণ তারা ম্যাজিস্কের সাথে বিরোধিতা করে!</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">কিভাবে কার্নেলএসইউ ইনস্টল করতে হয় এবং মডিউল ব্যবহার করতে হয় তা শিখুন</string>
<string name="home_support_title">সাপোর্ট টাইটেল</string>
<string name="home_support_content">কার্নেলএসইউ বিনামূল্যে এবং ওপেন সোর্স, এবং সবসময় থাকবে। আপনি সবসময় একটি অনুদান দিয়ে আপনার কৃতজ্ঞতা প্রদর্শন করতে পারেন.</string>
<string name="about_source_code"><![CDATA[Bekijk source code op %1$s<br/>আমাদের %2$s চ্যানেল মার্জ করুন]]></string>
<string name="profile_name">প্রফাইলের নাম</string>
<string name="profile_namespace">নেমস্পেস মাউন্ট</string>
<string name="profile_groups">গ্রুপস</string>
@@ -62,6 +61,5 @@
<string name="profile_namespace_global">গ্লোবাল</string>
<string name="profile_namespace_individual">আলাদাভাবে</string>
<string name="profile_umount_modules">আনমাউন্ট মোডিউল</string>
<string name="require_kernel_version">ম্যানেজার সঠিকভাবে কাজ করার জন্য বর্তমান KernelSU সংস্করণ %d খুবই কম। অনুগ্রহ করে %d বা উচ্চতর সংস্করণে আপগ্রেড করুন!</string>
<string name="save_log">লগ সংরক্ষণ করুন</string>
</resources>

View File

@@ -9,7 +9,6 @@
<string name="profile_selinux_context">SELinux kontekst</string>
<string name="profile_umount_modules">Umount module</string>
<string name="failed_to_update_app_profile">Ažuriranje Profila Aplikacije za %s nije uspjelo</string>
<string name="require_kernel_version">Trenutna KernelSU verzija %d je preniska da bi upravitelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju!</string>
<string name="settings_umount_modules_default">Umount module po zadanom</string>
<string name="settings_umount_modules_default_summary">Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil.</string>
<string name="profile_umount_modules_summary">Uključivanjem ove opcije omogućit će KernelSU-u da vrati sve izmjenute datoteke od strane modula za ovu aplikaciju.</string>
@@ -18,7 +17,7 @@
<string name="module_start_downloading">Započnite sa skidanjem: %s</string>
<string name="new_version_available">Nova verzija: %s je dostupna, kliknite da skinete</string>
<string name="launch_app">Pokrenite</string>
<string name="force_stop_app">Prisilno Zaustavite</string>
<string name="force_stop_app" formatted="false">Prisilno Zaustavite</string>
<string name="restart_app">Resetujte</string>
<string name="selinux_status_enforcing">U Provođenju</string>
<string name="home">Početna</string>
@@ -48,7 +47,6 @@
<string name="home_support_title">Podržite Nas</string>
<string name="send_log">Pošaljite Izvještaj</string>
<string name="home_learn_kernelsu">Naučite KernelSU</string>
<string name="about_source_code">Pogledajte izvornu kodu na %1$s<br/>Pridružite nam se na %2$s kanalu</string>
<string name="profile_selinux_domain">Domena</string>
<string name="profile_selinux_rules">Pravila</string>
<string name="failed_to_update_sepolicy">Neuspješno ažuriranje SELinux pravila za: %s</string>

View File

@@ -32,7 +32,6 @@
<string name="home_learn_kernelsu">Lær KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Lær hvordan man installerer KernelSU og moduler</string>
<string name="about_source_code">Se source koden ved %1$s<br/>Deltage i vores %2$s kanal</string>
<string name="profile_default">Standard</string>
<string name="profile_template">Skabelon</string>
<string name="profile_namespace">Monter navnerum</string>
@@ -75,8 +74,7 @@
<string name="failed_to_update_app_profile">Opdatering af App Profil for %s fejlede</string>
<string name="settings_umount_modules_default_summary">Den globale standard værdi for \"Afmonter moduler\" i App Profiler. Hvis aktiveret vil den fjerne alle modulers modifikationer til system applikationerne der ikke har en sat Profil.</string>
<string name="profile_selinux_domain">Domæne</string>
<string name="profile_selinux_rules">Regler</string>
<string name="profile_selinux_rules" formatted="false">Regler</string>
<string name="restart_app">Genstart</string>
<string name="require_kernel_version">Den nuværende KernelSU version %d er for lav til manageren for at fungere ordentligt. Opgrader til version %d eller højere!</string>
<string name="save_log">Gem Logfiler</string>
</resources>

View File

@@ -38,7 +38,7 @@
<string name="failed_to_update_sepolicy">Aktualisieren der SELinux-Regeln schlug fehl für: %s</string>
<string name="launch_app">Starten</string>
<string name="new_version_available">Neue Version %s verfügbar, tippen zum Aktualisieren.</string>
<string name="force_stop_app">Stopp erzwingen</string>
<string name="force_stop_app" formatted="false">Stopp erzwingen</string>
<string name="restart_app">Neustarten</string>
<string name="home_module_count">Module: %d</string>
<string name="home_manager_version">Manager-Version</string>
@@ -62,7 +62,6 @@
<string name="home_learn_kernelsu">KernelSU verstehen</string>
<string name="safe_mode">Sicherer Modus</string>
<string name="reboot_to_apply">Neustarten, damit Änderungen wirksam werden</string>
<string name="about_source_code"><![CDATA[Quellcode einsehen unter %1$s<br/>Unserem %2$s-Kanal beitreten]]></string>
<string name="profile_name">Profilname</string>
<string name="profile_namespace">Namespace einhängen</string>
<string name="profile_groups">Gruppen</string>
@@ -77,7 +76,6 @@
<string name="reboot_userspace">Soft-Reboot</string>
<string name="module_uninstall_confirm">Möchtest du wirklich Modul %s deinstallieren?</string>
<string name="module_uninstall_failed">Deinstallation fehlgeschlagen: %s</string>
<string name="require_kernel_version">Die aktuelle KernelSU-Version %d ist zu alt für diese Manager-Version. Bitte auf Version %d oder höher aktualisieren!</string>
<string name="module_changelog">Änderungsprotokoll</string>
<string name="app_profile_template_import_success">Erfolgreich importiert</string>
<string name="app_profile_export_to_clipboard">In Zwischenablage exportieren</string>
@@ -128,7 +126,6 @@
<string name="flash_success">Schreiben erfolgreich</string>
<string name="flash_failed">Schreiben fehlgeschlagen</string>
<string name="selected_lkm">Wähle LKM: %s</string>
<string name="shrink_sparse_image">Spärliches Bild minimieren</string>
<string name="action">Aktion</string>
<string name="log_saved">Protokolle gespeichert</string>
</resources>

View File

@@ -51,7 +51,6 @@
<string name="home_click_to_learn_kernelsu">Aprende a instalar KernelSU y a utilizar módulos</string>
<string name="home_support_title">Apóyanos</string>
<string name="home_support_content">KernelSU es, y siempre será, gratuito y de código abierto. Sin embargo, puedes demostrarnos que te importamos haciendo una donación.</string>
<string name="about_source_code">Ver código fuente en %1$s<br/>Únete a nuestro canal %2$s</string>
<string name="profile_default">Predeterminado</string>
<string name="profile_template">Plantilla</string>
<string name="profile_custom">Personalizado</string>
@@ -75,10 +74,9 @@
<string name="module_start_downloading">Iniciar descarga: %s</string>
<string name="new_version_available">La nueva versión %s está disponible, haga clic para actualizar.</string>
<string name="launch_app">Iniciar</string>
<string name="force_stop_app">Forzar detención</string>
<string name="force_stop_app" formatted="false">Forzar detención</string>
<string name="restart_app">Reiniciar</string>
<string name="failed_to_update_sepolicy">Error al actualizar las reglas SELinux para: %s</string>
<string name="require_kernel_version">La versión %d actual de KernelSU es demasiado baja para que el gestor funcione correctamente. Por favor, ¡actualice a la versión %d o superior!</string>
<string name="module_changelog">Registro de cambios</string>
<string name="app_profile_template_import_success">Importado con éxito</string>
<string name="app_profile_export_to_clipboard">Exportar al portapapeles</string>

View File

@@ -67,7 +67,6 @@
<string name="profile_groups">Grupid</string>
<string name="home_support_content">KernelSU on, ja alati jääb, tasuta ning avatud lähtekoodiga kättesaadavaks. Sellegipoolest võid sa näidata, et hoolid, ning teha annetuse.</string>
<string name="profile_template">Mall</string>
<string name="about_source_code">Vaata lähtekoodi %1$sis<br/>Liitu meie %2$si kanaliga</string>
<string name="profile_name">Profiili nimi</string>
<string name="profile_custom">Kohandatud</string>
<string name="profile_namespace_inherited">Päritud</string>
@@ -76,14 +75,13 @@
<string name="profile_capabilities">Võimekused</string>
<string name="app_profile_template_id_invalid">Sobimatu malli ID</string>
<string name="profile_selinux_context">SELinux kontekst</string>
<string name="require_kernel_version">Praegune KernelSU versioon %d on liiga madal, haldur ei saa korrektselt töötada. Palun täienda versioonile %d või kõrgem!</string>
<string name="profile_selinux_domain">Domeen</string>
<string name="launch_app">Käivita</string>
<string name="force_stop_app">Sundpeata</string>
<string name="profile_selinux_rules">Reeglid</string>
<string name="module_update">Uuenda</string>
<string name="module_downloading">Mooduli allalaadimine: %s</string>
<string name="new_version_available">Uus versioon %s on saadaval, klõpsa täiendamiseks.</string>
<string name="new_version_available" formatted="false">Uus versioon %s on saadaval, klõpsa täiendamiseks.</string>
<string name="restart_app">Taaskäivita</string>
<string name="module_changelog">Muudatuste logi</string>
<string name="app_profile_template_name">Nimi</string>

View File

@@ -50,9 +50,6 @@
<string name="home_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
<string name="home_support_title">از ما حمایت کنید</string>
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
<string name="about_source_code">
<![CDATA[ View source code at %1$s<br/>Join our %2$s channel ]]>
</string>
<string name="profile">پروفایل برنامه</string>
<string name="profile_default">پیش‌فرض</string>
<string name="profile_template">قالب</string>

View File

@@ -37,7 +37,6 @@
<string name="home_click_to_learn_kernelsu">Matutunan kung paano mag-install ng KernelSU at gumamit ng mga modyul</string>
<string name="home_support_title">Suportahan Kami</string>
<string name="home_support_content">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.</string>
<string name="about_source_code">Tingnan ang source code sa %1$s<br/>Sumali sa aming %2$s channel</string>
<string name="profile_namespace">I-mount ang namespace</string>
<string name="profile_namespace_individual">Indibidwal</string>
<string name="profile_groups">Mga Grupo</string>
@@ -45,14 +44,13 @@
<string name="profile_selinux_context">Konteksto ng SELinux</string>
<string name="profile_umount_modules">I-unmount ang mga modyul</string>
<string name="failed_to_update_app_profile">Nabigong i-update ang App Profile para sa %s</string>
<string name="require_kernel_version">Ang kasalukuyang bersyon ng KernelSU %d ay masyadong mababa para gumana nang maayos ang manager. Mangyaring mag-upgrade sa bersyon %d o mas mataas!</string>
<string name="profile_umount_modules_summary">Ang pagpapagana sa opsyong ito ay magbibigay-daan sa KernelSU na ibalik ang anumang binagong file ng mga modyul para sa aplikasyon na ito.</string>
<string name="profile_selinux_rules">Mga Tuntunin</string>
<string name="module_downloading">Nagda-download ng modyul: %s</string>
<string name="module_start_downloading">Simulan ang pag-download: %s</string>
<string name="new_version_available">Bagong bersyon: Available ang %s, i-click upang i-download</string>
<string name="launch_app">Ilunsad</string>
<string name="force_stop_app">Pilit na I-hinto</string>
<string name="force_stop_app" formatted="false">Pilit na I-hinto</string>
<string name="restart_app">I-restart</string>
<string name="failed_to_update_sepolicy">Nabigong i-update ang mga panuntunan ng SELinux para sa: %s</string>
<string name="home_manager_version">Bersyon ng Manager</string>

View File

@@ -50,7 +50,6 @@
<string name="home_support_title">Soutenez-nous</string>
<string name="home_click_to_learn_kernelsu">Découvrez comment installer KernelSU et utiliser les modules</string>
<string name="home_support_content">KernelSU est, et restera toujours, gratuit et open source. Vous pouvez cependant nous témoigner de votre soutien en nous faisant un don.</string>
<string name="about_source_code"><![CDATA[Voir le code source à %1$s<br/>Rejoignez notre canal %2$s]]></string>
<string name="profile_template">Modèle</string>
<string name="profile_default">Par défaut</string>
<string name="profile_custom">Personnalisé</string>
@@ -74,10 +73,9 @@
<string name="launch_app">Lancer</string>
<string name="new_version_available">La nouvelle version %s est disponible, appuyez ici pour mettre à jour.</string>
<string name="module_start_downloading">Début du téléchargement de : %s</string>
<string name="force_stop_app">Forcer l\'arrêt</string>
<string name="force_stop_app" formatted="false">Forcer l\'arrêt</string>
<string name="restart_app">Relancer l\'application</string>
<string name="failed_to_update_sepolicy">Échec de la mise à jour des règles SELinux pour %s</string>
<string name="require_kernel_version">La version actuelle de KernelSU (%d) est trop ancienne pour que le gestionnaire fonctionne correctement. Veuillez passer à la version %d ou à une version supérieure!</string>
<string name="failed_to_update_sepolicy">Échec de la mise à jour des règles SELinux pour : %s</string>
<string name="app_profile_template_import_success">Importation réussie</string>
<string name="app_profile_export_to_clipboard">Exporter vers le presse-papiers</string>
<string name="app_profile_template_export_empty">Impossible de trouver un modèle local à exporter!</string>
@@ -132,6 +130,4 @@
<string name="module_sort_enabled_first">Trier par activé</string>
<string name="action">Action</string>
<string name="log_saved">Journaux enregistrés</string>
<string name="settings_disable_su">Désactiver la compatibilité avec su</string>
<string name="settings_disable_su_summary">Désactivez temporairement l\'accès des applications aux privilèges root par le biais de la commande su (les processus root existants ne seront pas affectés).</string>
</resources>

View File

@@ -19,7 +19,7 @@
<string name="profile_namespace_individual">Individual</string>
<string name="module_failed_to_enable">%s मॉड्यूल चालू करने में विफल</string>
<string name="force_stop_app">जबर्दस्ती बंद करें</string>
<string name="reboot_edl">EDL मोड में रिबूट करें</string>
<string name="reboot_edl" formatted="false">EDL मोड में रिबूट करें</string>
<string name="restart_app">फिर से चालू करें</string>
<string name="profile_capabilities">क्षमताएं</string>
<string name="home_superuser_count">सुपरयूजर : %d</string>
@@ -33,7 +33,6 @@
<string name="profile_default">डिफॉल्ट</string>
<string name="launch_app">लॉन्च करें</string>
<string name="safe_mode">सेफ मोड</string>
<string name="require_kernel_version">मैनेजर के ठीक से काम करने के लिए वर्तमान KernelSU वर्जन %d बहुत कम है। कृपया वर्जन %d या उच्चतर में अपग्रेड करें!</string>
<string name="reboot_recovery">रिकवरी में रिबूट करें</string>
<string name="reboot_userspace">सॉफ्ट रिबूट</string>
<string name="profile_name">प्रोफाइल का नाम</string>
@@ -76,7 +75,6 @@
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="failed_to_update_sepolicy">%s के लिए SELinux नियमों को अपटेड करने में विफल</string>
<string name="reboot_bootloader">बुटलोडर में रिबूट करें</string>
<string name="about_source_code">%1$s पर स्रोत कोड देखें<br/>हमारे %2$s चैनल से जुड़ें</string>
<string name="home_manager_version">मैनेजर वर्जन</string>
<string name="new_version_available">नया वर्जन: %s उपलब्ध है,अपग्रेड के लिए क्लिक करें</string>
<string name="save_log">लॉग सहेजें</string>

View File

@@ -51,7 +51,6 @@
<string name="home_click_to_learn_kernelsu">Naučite kako da instalirate KernelSU i da koristite module</string>
<string name="home_support_title">Podržite Nas</string>
<string name="home_support_content">KernelSU je, i uvijek če biti, besplatan, i otvorenog izvora. Možete nam međutim pokazati da vas je briga s time da napravite donaciju.</string>
<string name="about_source_code">Pogledajte izvornu kodu na %1$s<br/>Pridružite nam se na %2$s kanalu</string>
<string name="profile_default">Zadano</string>
<string name="profile_template">Šablon</string>
<string name="profile_custom">Prilagođeno</string>
@@ -65,7 +64,6 @@
<string name="profile_groups">Grupe</string>
<string name="profile_capabilities">Sposobnosti</string>
<string name="profile_selinux_context">SELinux kontekst</string>
<string name="require_kernel_version">Trenutna KernelSU verzija %d je preniska da bi voditelj ispravno radio. Molimo vas da nadogradite na verziju %d ili noviju!</string>
<string name="settings_umount_modules_default">Umount module po zadanom</string>
<string name="settings_umount_modules_default_summary">Globalna zadana vrijednost za \"Umount module\" u Profilima Aplikacije. Ako je omogućeno, uklonit će sve izmjene modula na sistemu za aplikacije koje nemaju postavljen Profil.</string>
<string name="profile_selinux_domain">Domena</string>
@@ -76,7 +74,7 @@
<string name="module_start_downloading">Započnite sa preuzimanjem: %s</string>
<string name="new_version_available">Nova verzija: %s je dostupna, kliknite da preuzmete</string>
<string name="launch_app">Pokrenite</string>
<string name="force_stop_app">Prisilno Zaustavite</string>
<string name="force_stop_app" formatted="false">Prisilno Zaustavite</string>
<string name="restart_app">Resetujte</string>
<string name="save_log">Spremi Zapise</string>
</resources>

View File

@@ -22,7 +22,6 @@
<string name="home_learn_kernelsu">Tudjon meg többet a KernelSU-ról</string>
<string name="home_click_to_learn_kernelsu">Ismerje meg a KernelSU telepítését és a modulok használatát</string>
<string name="home_support_title">Támogasson minket</string>
<string name="about_source_code">Tekintse meg a forráskódot a %1$s-on<br/>Csatlakozzon a %2$s csatornánkhoz</string>
<string name="profile_default">Alapértelmezett</string>
<string name="profile_template">Sablon</string>
<string name="profile_custom">Egyedi</string>
@@ -42,7 +41,7 @@
<string name="module_downloading">Modul letöltése: %s</string>
<string name="module_start_downloading">Letöltés indítása: %s</string>
<string name="launch_app">Indítás</string>
<string name="force_stop_app">Kényszerített leállítás</string>
<string name="force_stop_app" formatted="false">Kényszerített leállítás</string>
<string name="restart_app">újraindítás</string>
<string name="home">Kezdőlap</string>
<string name="home_not_installed">Nincs telepítve</string>
@@ -77,7 +76,6 @@
<string name="settings_umount_modules_default_summary">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.</string>
<string name="new_version_available">Elérhető az új, %s verzió, kattintson a frissítéshez.</string>
<string name="failed_to_update_sepolicy">Nem sikerült frissíteni az SELinux szabályokat a következőhöz: %s</string>
<string name="require_kernel_version">A jelenlegi KernelSU verzió %d túlságosan elavult a megfelelő működéshez. Kérjük frissítsen a %d verzióra vagy újabbra!</string>
<string name="app_profile_template_import_success">Sikeresen importálva</string>
<string name="app_profile_export_to_clipboard">Exportálás a vágólapról</string>
<string name="app_profile_template_export_empty">Nem található helyi sablon az exportáláshoz!</string>
@@ -124,10 +122,8 @@
<string name="action">Művelet</string>
<string name="direct_install">Közvetlen telepítés (Ajánlott)</string>
<string name="install_inactive_slot_warning">Az eszköze **KÉNYSZERÍTETTEN** a jelenleg inaktív helyről fog indulni újraindítás után!\nCsak az OTA befejezése után használja.\nFolytatja?</string>
<string name="shrink_sparse_image_message">Átméretezi a sparse képfájlt, ahol a modul található, a tényleges méretére. Vegye figyelembe, hogy ez a modul rendellenes működését okozhatja, ezért kérjük, hogy csak akkor használja, ha szükséges (például biztonsági mentéshez).</string>
<string name="settings_restore_stock_image_message">Állítsa vissza a gyári képfájlt (ha létezik biztonsági mentés). Általában OTA előtt használják. Ha a KernelSU-t szeretné eltávolítani, használja a végleges eltávolítás opciót.</string>
<string name="settings_check_update">Frissítés ellenőrzése</string>
<string name="settings_check_update_summary">Automatikusan keressen frissítéseket az alkalmazás megnyitásakor</string>
<string name="log_saved">Mentett naplók</string>
<string name="shrink_sparse_image">Sparse képfájl minimalizálása</string>
</resources>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string>
<string name="home_support_title">Dukung Kami</string>
<string name="home_support_content">KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan.</string>
<string name="about_source_code"><![CDATA[Lihat kode sumber di %1$s<br/>Gabung kanal %2$s kami]]></string>
<string name="profile">Profil Apl</string>
<string name="profile_default">Bawaan</string>
<string name="profile_template">Templat</string>
@@ -75,10 +74,9 @@
<string name="module_start_downloading">Mulai mengunduh: %s</string>
<string name="new_version_available">Tersedia versi terbaru %s, Klik untuk membarui.</string>
<string name="launch_app">Jalankan</string>
<string name="force_stop_app">Paksa berhenti</string>
<string name="force_stop_app" formatted="false">Paksa berhenti</string>
<string name="restart_app">Mulai ulang</string>
<string name="failed_to_update_sepolicy">Gagal membarui aturan SELinux pada: %s</string>
<string name="require_kernel_version">Versi KernelSU %d terlalu rendah agar manajer berfungsi normal. Harap membarui ke versi %d atau di atasnya!</string>
<string name="module_changelog">Catatan Perubahan</string>
<string name="app_profile_template_import_success">Berhasil diimpor</string>
<string name="app_profile_export_to_clipboard">Ekspor ke papan klip</string>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Scopri come installare KernelSU e utilizzare i moduli</string>
<string name="home_support_title">Supportaci</string>
<string name="home_support_content">KernelSU è, e sempre sarà, gratuito e open source. Puoi comunque mostrarci il tuo apprezzamento facendo una donazione.</string>
<string name="about_source_code"><![CDATA[Visualizza il codice sorgente su %1$s<br/>Unisciti al nostro canale %2$s]]></string>
<string name="profile_name">Nome profilo</string>
<string name="profile_namespace">Spazio dei nomi del mount</string>
<string name="profile_namespace_global">Globale</string>
@@ -71,13 +70,12 @@
<string name="module_downloading">Sto scaricando il modulo: %s</string>
<string name="module_start_downloading">Inizia a scaricare:%s</string>
<string name="new_version_available">Nuova versione: %s disponibile, tocca per aggiornare</string>
<string name="force_stop_app">Arresto forzato</string>
<string name="force_stop_app" formatted="false">Arresto forzato</string>
<string name="restart_app">Riavvia</string>
<string name="failed_to_update_sepolicy">Aggiornamento regole SELinux per %s fallito</string>
<string name="profile_umount_modules_summary">Attivando questa opzione permetterai a KernelSU di ripristinare ogni file modificato dai moduli per questa app.</string>
<string name="profile_selinux_domain">Dominio</string>
<string name="settings_umount_modules_default_summary">Il valore predefinito per \"Scollega moduli\" in App Profile. Se attivato, rimuoverà tutte le modifiche al sistema da parte dei moduli per le applicazioni che non hanno un profilo impostato.</string>
<string name="require_kernel_version">La versione attualmente installata di KernelSU (%d) è troppo vecchia ed il gestore non può funzionare correttamente. Si prega di aggiornare alla versione %d o successiva!</string>
<string name="module_changelog">Registro aggiornamenti</string>
<string name="app_profile_template_create">Crea modello</string>
<string name="app_profile_template_edit">Modifica modello</string>

View File

@@ -19,7 +19,7 @@
<string name="profile_namespace_individual">אישי</string>
<string name="module_failed_to_enable">הפעלת המודל נכשלה: %s</string>
<string name="force_stop_app">עצירה בכח</string>
<string name="reboot_edl">הפעלה מחדש למצב EDL</string>
<string name="reboot_edl" formatted="false">הפעלה מחדש למצב EDL</string>
<string name="restart_app">איתחול</string>
<string name="profile_capabilities">יכולת</string>
<string name="home_superuser_count">משתמשי על: %d</string>
@@ -33,7 +33,6 @@
<string name="profile_default">ברירת מחדל</string>
<string name="launch_app">להשיק</string>
<string name="safe_mode">מצב בטוח</string>
<string name="require_kernel_version">גרסת KernelSU הנוכחית %d נמוכה מדי כדי שהמנהל יפעל כראוי. אנא שדרג לגרסה %d ומעלה!</string>
<string name="reboot_recovery">הפעלה מחדש לריקברי</string>
<string name="reboot_userspace">רך Reboot</string>
<string name="profile_name">שם פרופיל</string>
@@ -76,7 +75,6 @@
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="failed_to_update_sepolicy">נכשל עדכון כללי SELinux עבור: %s</string>
<string name="reboot_bootloader">הפעלה מחדש לבוטלאודר</string>
<string name="about_source_code">ראה את קוד המקור ב%1$s<br/>הצטרף אלינו %2$s בערוץ</string>
<string name="home_manager_version">גרסת מנהל</string>
<string name="new_version_available">גרסה חדשה עבור: %s זמינה, לחץ כדי לשדרג</string>
<string name="save_log">שמור יומנים</string>

View File

@@ -18,8 +18,8 @@
<string name="selinux_status_permissive">Permissive</string>
<string name="selinux_status_unknown">不明</string>
<string name="superuser">スーパーユーザー</string>
<string name="module_failed_to_enable">モジュールの有効化に失敗しました: %s</string>
<string name="module_failed_to_disable">モジュールの無効化に失敗しました: %s</string>
<string name="module_failed_to_enable">%s モジュールをオンにできませんでした</string>
<string name="module_failed_to_disable">%s モジュールをオフにできませんでした</string>
<string name="module_empty">モジュールがインストールされていません</string>
<string name="module">モジュール</string>
<string name="uninstall">アンインストール</string>
@@ -35,7 +35,7 @@
<string name="about">アプリについて</string>
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
<string name="module_uninstall_success">%s はアンインストールされました</string>
<string name="module_uninstall_failed">アンインストールに失敗しました: %s</string>
<string name="module_uninstall_failed">%s をアンインストールできませんでした</string>
<string name="module_version">バージョン</string>
<string name="module_author">制作者</string>
<string name="refresh">更新</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">KernelSU のインストール方法やモジュールの使い方はこちら</string>
<string name="home_support_title">支援する</string>
<string name="home_support_content">KernelSU はこれからもずっと無料でオープンソースです。寄付をして頂くことで、開発を支援していただけます。</string>
<string name="about_source_code"><![CDATA[%1$s でソースコードを表示<br/>%2$s チャンネルに参加]]></string>
<string name="profile">アプリのプロファイル</string>
<string name="profile_default">既定</string>
<string name="profile_template">テンプレート</string>
@@ -70,7 +69,7 @@
<string name="module_update">アップデート</string>
<string name="module_start_downloading">ダウンロードを開始: %s</string>
<string name="launch_app">起動</string>
<string name="force_stop_app">強制停止</string>
<string name="force_stop_app" formatted="false">強制停止</string>
<string name="restart_app">再起動</string>
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
<string name="profile_capabilities">ケーパビリティ</string>
@@ -78,7 +77,6 @@
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
<string name="settings_umount_modules_default">既定でモジュールのマウントを解除</string>
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通のデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
<string name="require_kernel_version">現在の KernelSU バージョン %d はマネージャーが適切に機能するには低すぎます。 バージョン %d 以降にアップグレードしてください!</string>
<string name="module_changelog">変更履歴</string>
<string name="app_profile_template_import_success">インポート成功</string>
<string name="app_profile_export_to_clipboard">クリップボードからエクスポート</string>

View File

@@ -26,9 +26,8 @@
<string name="home_module_count">ಮಾಡ್ಯೂಲ್‌ಗಳು: %d</string>
<string name="profile_selinux_context">SELinux ಸಂದರ್ಭ</string>
<string name="profile_default">ಡೀಫಾಲ್ಟ್</string>
<string name="launch_app">ಲಾಂಚ್</string>
<string name="launch_app" formatted="false">ಲಾಂಚ್</string>
<string name="safe_mode">ಸುರಕ್ಷಿತ ಮೋಡ್</string>
<string name="require_kernel_version">ಪ್ರಸ್ತುತ KernelSU ಆವೃತ್ತಿ %d ಮ್ಯಾನೇಜರ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸಲು ತುಂಬಾ ಕಡಿಮೆಯಾಗಿದೆ. ದಯವಿಟ್ಟು ಆವೃತ್ತಿ %d ಅಥವಾ ಹೆಚ್ಚಿನದಕ್ಕೆ ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಿ!</string>
<string name="reboot_userspace">ಸಾಫ್ಟ್ ರೀಬೂಟ್</string>
<string name="profile_name">ಪ್ರೊಫೈಲ್ ಹೆಸರು</string>
<string name="home_support_content">KernelSU ಉಚಿತ ಮತ್ತು ಮುಕ್ತ ಮೂಲವಾಗಿದೆ ಮತ್ತು ಯಾವಾಗಲೂ ಇರುತ್ತದೆ. ಆದಾಗ್ಯೂ ನೀವು ದೇಣಿಗೆ ನೀಡುವ ಮೂಲಕ ನೀವು ಕಾಳಜಿ ವಹಿಸುತ್ತೀರಿ ಎಂದು ನಮಗೆ ತೋರಿಸಬಹುದು.</string>
@@ -62,7 +61,6 @@
<string name="home_kernel">ಕರ್ನಲ್</string>
<string name="failed_to_update_app_profile">%s ಗಾಗಿ ಅಪ್ಲಿಕೇಶನ್ ಪ್ರೊಫೈಲ್ ಅನ್ನು ನವೀಕರಿಸಲು ವಿಫಲವಾಗಿದೆ</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="about_source_code">%1$s ನಲ್ಲಿ ಮೂಲ ಕೋಡ್ ಅನ್ನು ವೀಕ್ಷಿಸಿ<br/>ನಮ್ಮ %2$s ಚಾನಲ್‌ಗೆ ಸೇರಿ</string>
<string name="home_manager_version">ಮ್ಯಾನೇಜರ್ ವರ್ಷನ್</string>
<string name="new_version_available">ಹೊಸ ಆವೃತ್ತಿ: %s ಲಭ್ಯವಿದೆ, ಅಪ್‌ಗ್ರೇಡ್ ಮಾಡಲು ಕ್ಲಿಕ್ ಮಾಡಿ</string>
<string name="save_log">ಲಾಗ್ಗಳನ್ನು ಉಳಿಸಿ</string>

View File

@@ -49,9 +49,8 @@
<string name="home_click_to_learn_kernelsu">KernelSU 설치 방법과 모듈 사용 방법을 확인합니다</string>
<string name="home_support_title">지원이 필요합니다</string>
<string name="home_support_content">KernelSU는 지금도, 앞으로도 항상 무료이며 오픈 소스로 유지됩니다. 기부를 통해 여러분의 관심을 보여주세요.</string>
<string name="about_source_code"><![CDATA[%1$s에서 소스 코드 보기<br/>%2$s 채널 참가하기]]></string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="settings_umount_modules_default_summary">앱 프로필 메뉴의 \"모듈 마운트 해제\" 설정에 대한 전역 기본값을 설정합니다. 활성화 시, 개별 프로필이 설정되지 않은 앱은 시스템에 대한 모듈의 모든 수정사항이 적용되지 않습니다.</string>
<string name="settings_umount_modules_default_summary" formatted="false">앱 프로필 메뉴의 \"모듈 마운트 해제\" 설정에 대한 전역 기본값을 설정합니다. 활성화 시, 개별 프로필이 설정되지 않은 앱은 시스템에 대한 모듈의 모든 수정사항이 적용되지 않습니다.</string>
<string name="restart_app">다시 시작</string>
<string name="profile_selinux_rules">규칙</string>
<string name="new_version_available">새 버전: %s이 사용 가능합니다, 여기를 눌러 업그레이드하세요.</string>
@@ -80,7 +79,6 @@
<string name="save_log">로그 저장</string>
<string name="module_changelog">업데이트 내역</string>
<string name="enable_web_debugging_summary">WebUI 디버깅에 사용 가능, 필요할 때만 활성화해주세요.</string>
<string name="shrink_sparse_image">스파스 이미지 최소화</string>
<string name="flashing">플래시 중</string>
<string name="selected_lkm">선택된 LKM: %s</string>
<string name="select_file_tip">%1$s 파티션 이미지 권장됨</string>
@@ -88,8 +86,6 @@
<string name="install_next">다음</string>
<string name="settings_uninstall_permanent_message">완전히, 그리고 영구히 KernelSU (루트 및 모든 모듈)를 삭제합니다.</string>
<string name="enable_web_debugging">WebView 디버깅 활성화</string>
<string name="require_kernel_version">현재 KernelSU 버전 %d는 매니저가 올바르게 작동하기에 너무 낮습니다. 버전 %d 이상으로 업그레이드해 주세요!</string>
<string name="shrink_sparse_image_message">모듈이 위치한 스파스 이미지의 크기를 실제 크기로 조정합니다. 모듈이 비정상적으로 작동할 수 있으니, 필요할 때만 (예: 백업) 사용해 주세요.</string>
<string name="action">동작</string>
<string name="settings_uninstall_temporary">임시적 삭제</string>
<string name="module_changelog_failed">업데이트 내역 가져오기 실패: %s</string>

View File

@@ -30,7 +30,6 @@
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_learn_kernelsu">Sužinokite apie KernelSU</string>
<string name="home_click_to_learn_kernelsu">Sužinokite, kaip įdiegti KernelSU ir naudoti modulius</string>
<string name="about_source_code">Peržiūrėkite šaltinio kodą %1$s<br/>Prisijunkite prie mūsų %2$s kanalo</string>
<string name="profile_default">Numatytas</string>
<string name="profile_template">Šablonas</string>
<string name="profile_custom">Pasirinktinis</string>
@@ -52,7 +51,7 @@
<string name="module_start_downloading">Pradedamas atsisiuntimas: %s</string>
<string name="new_version_available">Nauja versija: %s pasiekiama, spustelėkite norėdami atsinaujinti</string>
<string name="launch_app">Paleisti</string>
<string name="force_stop_app">Priversti sustoti</string>
<string name="force_stop_app" formatted="false">Priversti sustoti</string>
<string name="restart_app">Perkrauti</string>
<string name="failed_to_update_sepolicy">Nepavyko atnaujinti SELinux taisyklių: %s</string>
<string name="home">Namai</string>
@@ -78,6 +77,5 @@
<string name="failed_to_update_app_profile">Nepavyko atnaujinti programos profilio %s</string>
<string name="settings_umount_modules_default_summary">Visuotinė numatytoji „Modulių atjungimo“ reikšmė programų profiliuose. Jei įjungta, ji pašalins visus sistemos modulio pakeitimus programoms, kurios neturi profilio.</string>
<string name="module_changelog">Keitimų žurnalas</string>
<string name="require_kernel_version">Ši KernelSU versija %d yra per žema, kad šis vadybininkas galėtų tinkamai funkcionuoti. Prašome atsinaujinti į versiją %d ar aukščiau!</string>
<string name="save_log">Saglabāt Žurnālus</string>
</resources>

View File

@@ -51,7 +51,6 @@
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Uzzināt, kā instalēt KernelSU un izmantot moduļus</string>
<string name="home_support_title">Atbalsti mūs</string>
<string name="about_source_code">Skatiet avota kodu vietnē %1$s<br/> Pievienojies mūsu %2$s kanālam</string>
<string name="profile_default">Noklusējums</string>
<string name="profile_template">Veidne</string>
<string name="profile_custom">Pielāgots</string>
@@ -71,7 +70,7 @@
<string name="module_start_downloading">Sākt lejupielādi: %s</string>
<string name="new_version_available">Jaunā versija: %s ir pieejama, noklikšķiniet, lai atjauninātu</string>
<string name="launch_app">Palaist</string>
<string name="force_stop_app">Piespiedu apstāšana</string>
<string name="force_stop_app" formatted="false">Piespiedu apstāšana</string>
<string name="restart_app">Restartēt aplikāciju</string>
<string name="module_changelog">Izmaiņu žurnāls</string>
<string name="settings_profile_template">Lietotnes profila veidne</string>
@@ -99,7 +98,6 @@
<string name="home_support_content">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.</string>
<string name="profile_groups">Grupas</string>
<string name="profile_namespace_global">Globāli</string>
<string name="require_kernel_version">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!</string>
<string name="enable_web_debugging">Iespējot WebView atkļūdošanu</string>
<string name="select_file_tip">Ieteicams %1$s nodalījuma attēls</string>
<string name="install_next">Nākamais</string>

View File

@@ -56,7 +56,6 @@
<string name="profile_template">साचा</string>
<string name="profile_namespace_individual">वैयक्तिक</string>
<string name="profile_capabilities">क्षमता</string>
<string name="about_source_code">%1$s वर स्रोत कोड पहा<br/>आमच्या %2$s चॅनेलमध्ये सामील व्हा</string>
<string name="profile_name">प्रोफाइल नाव</string>
<string name="profile_namespace_inherited">इनहेरीटेड</string>
<string name="profile_namespace_global">जागतिक</string>

View File

@@ -50,8 +50,6 @@
<string name="home_click_to_learn_kernelsu">Leer hoe KernelSU te installeren en modules te gebruiken</string>
<string name="home_support_title">Ondersteun ons</string>
<string name="home_support_content">KernelSU is, en zal altijd, vrij en open source zijn. Je kan altijd je appreciatie tonen met een donatie.</string>
<string name="about_source_code"><![CDATA[Bekijk source code op %1$s<br/>Vervoeg ons %2$s kanaal]]></string>
<string name="profile" translatable="false">App profiel</string>
<string name="profile_default">Standaard</string>
<string name="profile_template">Sjabloon</string>
<string name="profile_custom">Aangepast</string>
@@ -74,11 +72,10 @@
<string name="module_downloading">Downloaden van module: %s</string>
<string name="new_version_available">Nieuwe versie %s is beschikbaar,klik om te upgraden.</string>
<string name="launch_app">Start</string>
<string name="force_stop_app">Forceer stop</string>
<string name="force_stop_app" formatted="false">Forceer stop</string>
<string name="restart_app">Herstart</string>
<string name="module_start_downloading">Begin met downloaden: %s</string>
<string name="failed_to_update_sepolicy">Kan SELinux-regels niet bijwerken voor: %s</string>
<string name="require_kernel_version">De huidige KernelSU-versie %d is te laag voor de manager om goed te werken. Upgrade naar versie %d of hoger!</string>
<string name="module_changelog">wijzigings logboek</string>
<string name="settings_profile_template">App-profiel Sjabloon</string>
<string name="app_profile_template_create">Maken sjabloon</string>

View File

@@ -51,7 +51,6 @@
<string name="home_click_to_learn_kernelsu">Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów</string>
<string name="home_support_title">Wesprzyj nas</string>
<string name="home_support_content">KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę.</string>
<string name="about_source_code"><![CDATA[Przejrzyj kod źródłowy na %1$s<br/>Dołącz do kanału %2$s]]></string>
<string name="profile" translatable="false">Profil aplikacji</string>
<string name="profile_default">Domyślny</string>
<string name="profile_template">Szablon</string>
@@ -76,10 +75,9 @@
<string name="module_start_downloading">Rozpocznij pobieranie: %s</string>
<string name="new_version_available">Nowa wersja %s jest dostępna. Kliknij, aby zaktualizować.</string>
<string name="launch_app">Uruchom</string>
<string name="force_stop_app">Wymuś zatrzymanie</string>
<string name="force_stop_app" formatted="false">Wymuś zatrzymanie</string>
<string name="restart_app">Restartuj</string>
<string name="failed_to_update_sepolicy">Nie udało się zaktualizować reguł SELinux dla %s</string>
<string name="require_kernel_version">Obecna wersja KernelSU %d jest zbyt stara, aby menedżer działał poprawnie. Prosimy o aktualizację do wersji %d lub nowszej!</string>
<string name="module_changelog">Dziennik zmian</string>
<string name="enable_web_debugging">Włącz debugowanie WebView</string>
<string name="enable_web_debugging_summary">Może być użyte do debugowania WebUI. Włącz tylko w razie potrzeby.</string>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string>
<string name="home_support_title">Apoie-nos</string>
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
<string name="about_source_code"><![CDATA[Veja o código-fonte no %1$s<br/>Junte-se ao nosso canal do %2$s]]></string>
<string name="profile" translatable="false">Perfil do Aplicativo</string>
<string name="profile_default">Padrão</string>
<string name="profile_template">Modelo</string>
@@ -75,10 +74,9 @@
<string name="module_start_downloading">Começando a baixar %s</string>
<string name="new_version_available">Nova versão %s está disponível, clique para atualizar.</string>
<string name="launch_app">Iniciar</string>
<string name="force_stop_app">Forçar parada</string>
<string name="force_stop_app" formatted="false">Forçar parada</string>
<string name="restart_app">Reiniciar</string>
<string name="failed_to_update_sepolicy">Falha ao atualizar as regras do SELinux para %s</string>
<string name="require_kernel_version">A versão atual do KernelSU %d é muito baixa para o gerenciador funcionar corretamente. Por favor, atualize para a versão %d ou superior!</string>
<string name="module_changelog">Registro de alterações</string>
<string name="app_profile_template_import_success">Importado com sucesso</string>
<string name="app_profile_export_to_clipboard">Exportar para a área de transferência</string>
@@ -133,6 +131,4 @@
<string name="log_saved">Registros salvos</string>
<string name="module_sort_action_first">Ordenar (Ação primeiro)</string>
<string name="module_sort_enabled_first">Ordenar (Ativado primeiro)</string>
<string name="settings_disable_su_summary">Desative temporariamente a capacidade de qualquer app obter privilégios root por meio do comando su (processos root existentes não serão afetados).</string>
<string name="settings_disable_su">Desativar compatibilidade su</string>
</resources>

View File

@@ -44,7 +44,6 @@
<string name="show_system_apps">Mostrar aplicativos do sistema</string>
<string name="home_click_to_learn_kernelsu">Aprenda a instalar o KernelSU e usar os módulos</string>
<string name="home_support_content">O KernelSU é, e sempre será, gratuito e de código aberto. No entanto, você pode nos mostrar que se importa fazendo uma doação.</string>
<string name="about_source_code">Veja o código-fonte em %1$s<br/>Junte-se ao nosso canal %2$s</string>
<string name="profile_namespace_individual">Individual</string>
<string name="profile_namespace_global">Global</string>
<string name="profile_namespace_inherited">Herdado</string>
@@ -72,11 +71,10 @@
<string name="module_start_downloading">Iniciar o download: %s</string>
<string name="module_downloading">Baixando módulo: %s</string>
<string name="failed_to_update_sepolicy">Falha ao atualizar as regras do SELinux para: %s</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_learn_kernelsu_url" formatted="false">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="restart_app">Reiniciar</string>
<string name="launch_app">Lançar</string>
<string name="force_stop_app">Forçar parada</string>
<string name="new_version_available">Nova versão: %s está disponível, clique para baixar</string>
<string name="require_kernel_version">A versão atual do KernelSU %d é muito baixa para o gerenciador funcionar corretamente. Atualize para a versão %d ou superior!</string>
<string name="save_log">Salvar Registros</string>
</resources>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Află cum să instalezi KernelSU și să utilizezi module</string>
<string name="home_support_title">Suport</string>
<string name="home_support_content">KernelSU este, și va fi întotdeauna, gratuit și cu codul sursă deschis. Cu toate acestea, ne poți arăta că îți pasă făcând o donație.</string>
<string name="about_source_code"><![CDATA[Vezi codul sursă la %1$s<br/>Alătură-te canalului nostru %2$s]]></string>
<string name="profile_default">Implicit</string>
<string name="profile_template">Șablon</string>
<string name="profile_custom">Personalizat</string>
@@ -75,9 +74,8 @@
<string name="new_version_available">Versiune nouă: %s este disponibilă, clic pentru a actualiza</string>
<string name="failed_to_update_sepolicy">Nu s-au reușit actualizările regulilor SELinux pentru: %s</string>
<string name="launch_app">Lansare</string>
<string name="force_stop_app">Oprire forțată</string>
<string name="force_stop_app" formatted="false">Oprire forțată</string>
<string name="restart_app">Repornește</string>
<string name="require_kernel_version">Versiunea actuală a KernelSU %d este prea mică pentru ca managerul să funcționeze corect. Actualizează la versiunea %d sau o versiune superioară!</string>
<string name="module_changelog">Jurnalul modificărilor</string>
<string name="app_profile_template_import_success">Importat cu succes</string>
<string name="app_profile_export_to_clipboard">Export în clipboard</string>

View File

@@ -52,7 +52,6 @@
<string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
<string name="home_support_title">Поддержите нас</string>
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
<string name="about_source_code"><![CDATA[Посмотреть исходный код на %1$s<br/>Присоединяйтесь к нашему %2$s каналу]]></string>
<string name="profile" translatable="false">App Profile</string>
<!--Don't translate this string!-->
<string name="profile_default">По умолчанию</string>
@@ -79,9 +78,8 @@
<string name="new_version_available">Новая версия: %s доступна, нажмите чтобы скачать.</string>
<string name="force_stop_app">Остановить принудительно</string>
<string name="failed_to_update_sepolicy">Не удалось обновить правила SELinux для %s</string>
<string name="launch_app">Запустить</string>
<string name="launch_app" formatted="false">Запустить</string>
<string name="restart_app">Перезапустить</string>
<string name="require_kernel_version">Текущая версия KernelSU %d слишком низкая для правильной работы менеджера. Пожалуйста, обновите до версии %d или выше!</string>
<string name="module_changelog">Список изменений</string>
<string name="app_profile_template_import_success">Успешный импорт</string>
<string name="app_profile_export_to_clipboard">Экспортировать в буфер обмена</string>
@@ -136,6 +134,4 @@
<string name="log_saved">Логи сохранены</string>
<string name="module_sort_action_first">Сортировать (Сначала с действием)</string>
<string name="module_sort_enabled_first">Сортировать (Сначала включённые)</string>
<string name="settings_disable_su">Отключить su совместимость</string>
<string name="settings_disable_su_summary">Временно отключить возможность получения root привилегий любым приложениям с помощью команды su (существующие root процессы не будут затронуты).</string>
</resources>

View File

@@ -29,7 +29,6 @@
<string name="home_learn_kernelsu">Naučite se KernelSU</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/guide/what-is-kernelsu.html</string>
<string name="home_click_to_learn_kernelsu">Naučite se, kako namestiti KernelSU in uporabiti module</string>
<string name="about_source_code">Glej odprto kodo na %1$s<br/>Pridružite se našem %2$s kanalu</string>
<string name="profile_default">Privzeto</string>
<string name="profile_template">Predloga</string>
<string name="profile_namespace">Imenski prostor vmestitve</string>
@@ -42,7 +41,7 @@
<string name="profile_selinux_domain">Domena</string>
<string name="module_update">Posodobitev</string>
<string name="module_downloading">Nalaganje modula: %s</string>
<string name="launch_app">Zaženi</string>
<string name="launch_app" formatted="false">Zaženi</string>
<string name="restart_app">Ponovni zagon</string>
<string name="module_changelog">Dnevnik sprememb</string>
<string name="settings_profile_template">Predloga za aplikacijski profil</string>
@@ -69,7 +68,6 @@
<string name="profile_selinux_context">SELinux kontekst</string>
<string name="home_support_content">KernelSU je, in bo vedno brezplačen in odprtokoden. Kljub temu, nam lahko z donacijo pokažete, da vam je mar.</string>
<string name="failed_to_update_app_profile">Napaka pri posodobitvi aplikacijskega profila za %s</string>
<string name="require_kernel_version">Za pravilno funkionalnost upravitelja je trenutna KernelSU verzija %d prenizka. Potrebna je nadgradnja na verzijo %d ali več!</string>
<string name="settings_umount_modules_default_summary">Globalno privzeta vrednost za \"Izvrzi module\" v aplikacijskih profilih. Če je omogočena, bo to odstranilo vse sistemske modifikacije modulov za aplikacije, ki nimajo nastavljenega profila.</string>
<string name="profile_umount_modules_summary">Omogočanje te opcije bo dovolilo KernelSU, da obnovi vse zaradi modulov spremenjene datoteke za to aplikacijo.</string>
<string name="force_stop_app">Prisilna ustavitev</string>

View File

@@ -20,8 +20,8 @@
<string name="selinux_status_permissive">Permissive</string>
<string name="selinux_status_unknown">ไม่ทราบ</string>
<string name="superuser">สิทธิ์ผู้ใช้ขั้นสูง</string>
<string name="module_failed_to_enable">ล้มเหลวในการเปิดใช้งานโมดูล %s</string>
<string name="module_failed_to_disable">ล้มเหลวในการปิดใช้งานโมดูล: %s</string>
<string name="module_failed_to_enable">ไม่สามารเปิดใช้งานโมดูล %s</string>
<string name="module_failed_to_disable">ไม่สามารปิดใช้งานโมดูล: %s</string>
<string name="module_empty">ไม่มีโมดูลที่ติดตั้ง</string>
<string name="module">โมดูล</string>
<string name="uninstall">ถอนการติดตั้ง</string>
@@ -34,7 +34,7 @@
<string name="reboot_download">รีบูตเข้าสู่โหมด Download</string>
<string name="reboot_edl">รีบูตเข้าสู่โหมด EDL</string>
<string name="module_uninstall_success">%s ถอนการติดตั้งสำเร็จ</string>
<string name="module_uninstall_failed">ถอนการติดตั้ง %s ล้มเหลว</string>
<string name="module_uninstall_failed">ไม่สามารถถอนการติดตั้ง %s</string>
<string name="module_uninstall_confirm">คุณแน่ใจว่าจะถอนการติดตั้งโมดูล %s หรือไม่\?</string>
<string name="module_author">ผู้สร้าง</string>
<string name="module_version">เวอร์ชัน</string>
@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">เรียนรู้วิธีการติดตั้ง KernelSU และวิธีใช้งานโมดูลต่าง ๆ</string>
<string name="home_support_title">สนับสนุนพวกเรา</string>
<string name="home_support_content">KernelSU เป็นโอเพ่นซอร์สฟรีทั้งจากนี้และตลอดไป อย่างไรก็ตาม คุณสามารถแสดงความห่วงใยได้ด้วยการบริจาค</string>
<string name="about_source_code"><![CDATA[ดูซอร์สโค้ดได้ที่ %1$s<br/>และเข้าร่วมช่อง %2$s channel]]></string>
<string name="profile_custom">กำหนดเอง</string>
<string name="profile_default">ค่าเริ่มต้น</string>
<string name="profile_template">เทมเพลต</string>
@@ -72,12 +71,11 @@
<string name="module_downloading">กำลังดาวน์โหลดโมดูล: %s</string>
<string name="module_start_downloading">กำลังเริ่มดาวน์โหลด: %s</string>
<string name="new_version_available">เวอร์ชันใหม่: %s พร้อมใช้งาน คลิกเพื่ออัปเกรด</string>
<string name="force_stop_app">บังคับหยุด</string>
<string name="force_stop_app" formatted="false">บังคับหยุด</string>
<string name="restart_app">รีสตาร์ท</string>
<string name="settings_umount_modules_default_summary">หากเปิดใช้งานค่าเริ่มต้นโดยทั่วไปสำหรับ \"Umount โมดูล\" ในโปรไฟล์แอป จะเป็นการลบการแก้ไขโมดูลทั้งหมดในระบบสำหรับแอปพลิเคชันที่ไม่มีการตั้งค่าโปรไฟล์</string>
<string name="launch_app">เปิด</string>
<string name="failed_to_update_sepolicy">ไม่สามารถอัปเดตกฎ SElinux สำหรับ %s</string>
<string name="require_kernel_version">KernelSU เวอร์ชัน %d ต่ำเกินไป ทำให้ตัวจัดการไม่สามารถทำงานได้อย่างถูกต้อง โปรดอัปเกรดเป็นเวอร์ชัน %d หรือสูงกว่า!</string>
<string name="module_changelog">บันทึกการเปลี่ยนแปลง</string>
<string name="app_profile_template_import_success">นำเข้าเสร็จสิ้น</string>
<string name="app_profile_export_to_clipboard">ส่งออกไปยังคลิปบอร์ด</string>

View File

@@ -51,7 +51,6 @@
<string name="home_click_to_learn_kernelsu">KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin</string>
<string name="home_support_title">Bizi destekleyin</string>
<string name="home_support_content">KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
<string name="about_source_code"><![CDATA[%1$s adresinde kaynak kodunu görüntüleyin.<br/>%2$s kanalımıza katılın.]]></string>
<string name="profile" translatable="false">Uygulama profili</string>
<string name="profile_default">Varsayılan</string>
<string name="profile_template">Şablon</string>
@@ -66,7 +65,6 @@
<string name="profile_selinux_context">SELinux içeriği</string>
<string name="profile_umount_modules">Modüllerin bağlantısını kes</string>
<string name="failed_to_update_app_profile">%s için uygulama profili güncellenemedi.</string>
<string name="require_kernel_version">Mevcut KernelSU sürümü %d, yöneticinin düzgün çalışabilmesi için çok düşük. Lütfen %d sürümüne veya daha yüksek bir sürüme güncelleyin!</string>
<string name="settings_umount_modules_default">Varsayılan olarak modüllerin bağlantısını kes</string>
<string name="settings_umount_modules_default_summary">Uygulama profilindeki \"Modüllerin bağlantısını kes\" seçeneği için varsayılan değer. Etkinleştirilirse, profil ayarı yapılmamış uygulamalar için modüllerin sistemde yaptığı tüm değişiklikler kaldırılacaktır.</string>
<string name="profile_umount_modules_summary">Bu seçeneği etkinleştirmek, KernelSU\'nun bu uygulama için modüller tarafından değiştirilen dosyaları geri yüklemesine izin verir.</string>
@@ -77,7 +75,7 @@
<string name="module_start_downloading">İndirme başladı: %s</string>
<string name="new_version_available">Yeni sürüm: %s var, güncellemek için tıklayın.</string>
<string name="launch_app">Uygulamayı başlat</string>
<string name="force_stop_app">Uygulamayı durmaya zorla</string>
<string name="force_stop_app" formatted="false">Uygulamayı durmaya zorla</string>
<string name="restart_app">Uygulamayı yeniden başlat</string>
<string name="failed_to_update_sepolicy">%s için SELinux kuralları güncellenemedi.</string>
<string name="module_changelog">Değişiklik geçmişi</string>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
<string name="home_support_title">Підтримати нас</string>
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
<string name="about_source_code"><![CDATA[Переглянути вихідний код на %1$s<br/>Приєднуйтесь до нашого каналу %2$s]]></string>
<string name="profile">Профіль додатка</string>
<string name="profile_default">Типовий</string>
<string name="profile_template">Шаблон</string>
@@ -74,12 +73,11 @@
<string name="module_downloading">Завантаження модуля: %s</string>
<string name="module_start_downloading">Початок завантаження: %s</string>
<string name="launch_app">Запустити</string>
<string name="force_stop_app">Примусово зупинити</string>
<string name="force_stop_app" formatted="false">Примусово зупинити</string>
<string name="restart_app">Перезапустити</string>
<string name="new_version_available">Нова версія: %s доступна, натисніть, щоб завантажити</string>
<string name="failed_to_update_sepolicy">Не вдалося оновити правила SELinux для: %s</string>
<string name="module_changelog">Журнал змін</string>
<string name="require_kernel_version">Поточна версія KernelSU %d занадто низька, щоб менеджер міг працювати належним чином. Будь ласка, оновіть до версії %d або вище!</string>
<string name="app_profile_template_import_success">Успішно імпортовано</string>
<string name="app_profile_export_to_clipboard">Експортувати в буфер обміну</string>
<string name="app_profile_template_export_empty">Неможливо знайти локальні шаблони для експорту!</string>

View File

@@ -1,132 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="profile">Hồ sơ ứng dụng</string>
<string name="profile_default">Mặc định</string>
<string name="profile_template">Mẫu</string>
<string name="profile_custom">Tuỳ chỉnh</string>
<string name="profile_name">Tên hồ sơ</string>
<string name="profile_groups">Nhóm</string>
<string name="failed_to_update_app_profile">Không thể cập nhật Hồ sơ ứng dụng cho %s</string>
<string name="settings_umount_modules_default">Ngắt mô-đun theo mặc định</string>
<string name="settings_umount_modules_default_summary">Giá trị mặc định của \"Ngắt mô-đun\" trong Cấu hình ứng dụng. Nếu bật, KernelSU sẽ khôi phục mọi tệp hệ thống đã sửa đổi bởi mô-đun cho các ứng dụng chưa thiết lập Cấu hình.</string>
<string name="profile_umount_modules_summary">Bật tùy chọn này sẽ khôi phục mọi tệp đã sửa đổi bởi các mô-đun cho ứng dụng này.</string>
<string name="module_update">Cập nhật</string>
<string name="module_downloading">Đang tải xuống mô-đun: %s</string>
<string name="module_start_downloading">Bắt đầu tải xuống: %s</string>
<string name="new_version_available">Phiên bản mới: %s đã có, bấm để nâng cấp</string>
<string name="home_learn_kernelsu">Tìm hiểu KernelSU</string>
<string name="home_click_to_learn_kernelsu">Tìm hiểu cách cài đặt KernelSU và sử dụng các mô-đun</string>
<string name="home_support_title">Hỗ trợ chúng tôi</string>
<string name="home_support_content">KernelSU sẽ luôn luôn miễn phí và mã nguồn mở. Tuy nhiên bạn có thể ủng hộ chúng tôi bằng một khoản đóng góp nhỏ.</string>
<string name="about_source_code"><![CDATA[Xem mã nguồn tại %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
<string name="module_magisk_conflict">Các mô-đun bị vô hiệu hóa vì chúng xung đột với Magisk!</string>
<string name="module_uninstall_confirm">Bạn có muốn gỡ cài đặt mô-đun %s không\?</string>
<string name="send_log">Nhật ký báo cáo</string>
<string name="home">Trang chủ</string>
<string name="home_not_installed">Chưa cài đặt</string>
<string name="home_click_to_install">Nhấn để cài đặt</string>
<string name="home_working">Đang hoạt động</string>
<string name="home_working_version">Phiên bản: %d</string>
<string name="home_unsupported">Không được hỗ trợ</string>
<string name="home_unsupported_reason">KernelSU hiện tại chỉ hỗ trợ kernel GKI</string>
<string name="home_kernel">Kernel</string>
<string name="home_manager_version">Phiên bản Manager</string>
<string name="home_fingerprint">Fingerprint</string>
<string name="home_selinux_status">Trạng thái SELinux</string>
<string name="selinux_status_disabled">Vô hiệu hóa</string>
<string name="selinux_status_enforcing">Thực thi</string>
<string name="selinux_status_permissive">Cho phép</string>
<string name="selinux_status_unknown">Không rõ</string>
<string name="superuser">SuperUser</string>
<string name="module_failed_to_enable">Không thể kích hoạt mô-đun: %s</string>
<string name="module_failed_to_disable">Không thể vô hiệu hóa mô-đun: %s</string>
<string name="module_empty">Chưa cài đặt mô-đun nào</string>
<string name="module">Mô-đun</string>
<string name="uninstall">Gỡ cài đặt</string>
<string name="module_install">Cài đặt</string>
<string name="install">Cài đặt</string>
<string name="reboot">Khởi động lại</string>
<string name="settings">Thiết đặt</string>
<string name="reboot_userspace">Khởi động mềm</string>
<string name="reboot_recovery">Khởi động lại vào Recovery</string>
<string name="reboot_bootloader">Khởi động lại vào Bootloader</string>
<string name="reboot_download">Khởi động lại vào Download Mode</string>
<string name="reboot_edl">Khởi động lại vào EDL</string>
<string name="about">Giới thiệu</string>
<string name="module_uninstall_success">%s được gỡ cài đặt</string>
<string name="module_uninstall_failed">Lỗi khi gỡ cài đặt: %s</string>
<string name="module_version">Phiên bản</string>
<string name="module_author">Tác giả</string>
<string name="refresh">Làm mới</string>
<string name="show_system_apps">Hiển thị ứng dụng hệ thống</string>
<string name="hide_system_apps">Ẩn ứng dụng hệ thống</string>
<string name="safe_mode">Chế độ an toàn</string>
<string name="reboot_to_apply">Khởi động lại để có hiệu lực</string>
<string name="home_learn_kernelsu_url">https://kernelsu.org/vi_VN/guide/what-is-kernelsu.html</string>
<string name="home_superuser_count">Số superuser: %d</string>
<string name="home_module_count">Số mô-đun: %d</string>
<string name="profile_selinux_domain">Phạm vi</string>
<string name="profile_selinux_rules">Quy định</string>
<string name="launch_app">Khởi chạy</string>
<string name="restart_app">Khởi động lại</string>
<string name="profile_namespace">Gắn namespace</string>
<string name="profile_capabilities">Quyền</string>
<string name="failed_to_update_sepolicy">Không thể cập nhật quy định SELinux cho: %s</string>
<string name="force_stop_app">Buộc dừng</string>
<string name="profile_namespace_inherited">Thừa hưởng</string>
<string name="profile_namespace_global">Chung</string>
<string name="profile_namespace_individual">Riêng</string>
<string name="profile_selinux_context">Bối cảnh SELinux</string>
<string name="profile_umount_modules">Ngắt mô-đun</string>
<string name="require_kernel_version">KernelSU phiên bản %d quá thấp để trình quản lý hoạt động, hãy cập nhật lên %d hoặc mới hơn!</string>
<string name="app_profile_template_import_success">Đã nhập thành công</string>
<string name="app_profile_export_to_clipboard">Xuất từ khay nhớ tạm</string>
<string name="app_profile_template_export_empty">Không thể tìm thấy mẫu cục bộ để xuất!</string>
<string name="app_profile_template_id_exist">id bản mẫu đã tồn tại!</string>
<string name="module_changelog">Nhật ký thay đổi</string>
<string name="app_profile_import_from_clipboard">Nhập từ khay nhớ tạm</string>
<string name="module_changelog_failed">Không nạp được nhật ký thay đổi: %s</string>
<string name="app_profile_template_name">Tên</string>
<string name="app_profile_template_id_invalid">Id mẫu không hợp lệ</string>
<string name="app_profile_template_sync">Đồng bộ hóa các mẫu trực tuyến</string>
<string name="app_profile_template_create">Tạo Bản Mẫu</string>
<string name="app_profile_import_export">Nhập/Xuất</string>
<string name="app_profile_template_save_failed">Không lưu được mẫu</string>
<string name="app_profile_template_edit">Sửa Bản Mẫu</string>
<string name="settings_profile_template">Mẫu Hồ Sơ Ứng Dụng</string>
<string name="app_profile_template_description">Mô tả</string>
<string name="app_profile_template_save">Lưu</string>
<string name="settings_profile_template_summary">Quản lý mẫu Hồ sơ Ứng dụng cục bộ và trực tuyến</string>
<string name="app_profile_template_delete">Xóa</string>
<string name="app_profile_template_import_empty">Clipboard trống!</string>
<string name="app_profile_template_view">Xem Bản Mẫu</string>
<string name="app_profile_template_readonly">chỉ đọc</string>
<string name="app_profile_template_id">id</string>
<string name="enable_web_debugging">Bật gỡ lỗi WebView</string>
<string name="enable_web_debugging_summary">Có thể được sử dụng để gỡ lỗi WebUI, vui lòng chỉ bật khi cần.</string>
<string name="grant_root_failed">Không cấp được quyền root!</string>
<string name="settings_check_update">Kiểm tra cập nhật</string>
<string name="settings_check_update_summary">Tự động kiểm tra cập nhật khi mở ứng dụng</string>
<string name="open">Mở</string>
<string name="install_inactive_slot">Cài đặt vào khe không hoạt động (Sau OTA)</string>
<string name="install_inactive_slot_warning">Thiết bị của bạn sẽ **BẮT BUỘC** khởi động vào khe không hoạt động hiện tại sau khi khởi động lại!
\nChỉ sử dụng tùy chọn này sau khi OTA hoàn tất.
\nTiếp tục?</string>
<string name="settings_uninstall_temporary_message">Tạm thời gỡ cài đặt KernelSU, khôi phục về trạng thái ban đầu sau lần khởi động lại tiếp theo.</string>
<string name="select_kmi">Chọn KMI</string>
<string name="install_next">Kế tiếp</string>
<string name="direct_install">Cài đặt trực tiếp (Được khuyến nghị)</string>
<string name="select_file">Chọn một tệp</string>
<string name="settings_uninstall">Gỡ cài đặt</string>
<string name="settings_uninstall_temporary">Gỡ cài đặt tạm thời</string>
<string name="settings_uninstall_permanent">Gỡ cài đặt vĩnh viễn</string>
<string name="settings_restore_stock_image">Khôi phục hình ảnh gốc</string>
<string name="settings_uninstall_permanent_message">Gỡ cài đặt KernelSU (Root và tất cả các mô-đun) hoàn toàn và vĩnh viễn.</string>
<string name="settings_restore_stock_image_message">Khôi phục hình ảnh gốc của nhà máy (nếu có bản sao lưu), thường được sử dụng trước OTA; nếu bạn cần gỡ cài đặt KernelSU, vui lòng sử dụng \"Gỡ cài đặt vĩnh viễn\".</string>
<string name="flashing">Đang cài</string>
<string name="flash_success">Cài thành công</string>
<string name="flash_failed">Cài thất bại</string>
<string name="selected_lkm">Đã chọn lkm: %s</string>
<string name="select_file_tip">Nên sử dụng hình ảnh phân vùng %1$s</string>
<string name="save_log">Lưu Nhật Ký</string>
</resources>
<resources></resources>

View File

@@ -7,8 +7,11 @@
<string name="home_working_version">版本:%d</string>
<string name="home_superuser_count">超级用户数:%d</string>
<string name="home_unsupported">不支持</string>
<string name="home_unsupported_reason">KernelSU 现在只支持 GKI 内核</string>
<string name="home_unsupported_reason">内核上未检测到 KernelSU 驱动程序,内核错误?</string>
<string name="home_kernel">内核版本</string>
<string name="home_susfs">SuSFS%s</string>
<string name="home_susfs_version">SuSFS 版本</string>
<string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">管理器版本</string>
<string name="home_fingerprint">系统指纹</string>
<string name="home_selinux_status">SELinux 状态</string>
@@ -53,7 +56,7 @@
<string name="home_click_to_learn_kernelsu">了解如何安装 KernelSU 以及如何开发模块</string>
<string name="home_support_title">支持开发</string>
<string name="home_support_content">KernelSU 将保持免费开源,向开发者捐赠以表示支持。</string>
<string name="about_source_code"><![CDATA[在 %1$s 查看源码<br/>加入我们的 %2$s 频道<br/>加入我们的 <b><a href="https://pd.qq.com/s/8lipl1brp">QQ 频道</a></b>]]></string>
<string name="about_source_code"><![CDATA[在 %1$s 查看源码<br/>加入我们的 %2$s 频道<br/>加入我们的 <b><a href="https://qm.qq.com/q/WbrOMxgLG8">QQ 群聊</a></b>]]></string>
<string name="profile_default">默认</string>
<string name="profile_template">模版</string>
<string name="profile_custom">自定义</string>
@@ -67,9 +70,11 @@
<string name="profile_selinux_context">SELinux</string>
<string name="profile_umount_modules">卸载模块</string>
<string name="failed_to_update_app_profile">为 %s 更新 App Profile 失败</string>
<string name="require_kernel_version">当前 KernelSU 版本 %d 过低,管理器无法正常工作,请将内核 KernelSU 版本升级至 %d 或以上!</string>
<string name="require_kernel_version" formatted="false">当前 KernelSU 版本 %d 过低,管理器无法正常工作,请将内核 KernelSU 版本升级至 %d 或以上!</string>
<string name="settings_umount_modules_default">默认卸载模块</string>
<string name="settings_umount_modules_default_summary">App Profile 中“卸载模块”的全局默认值,如果启用,将会为没有设置 Profile 的应用移除所有模块针对系统的修改。</string>
<string name="settings_susfs_toggle">隐藏 Kprobe 钩子</string>
<string name="settings_susfs_toggle_summary">禁用由 KSU 创建的 Kprobe 钩子,并使用非 Kprobe 内联钩子代替,实现方式类似于不支持 Kprobe 的非 GKI 内核。</string>
<string name="profile_umount_modules_summary">启用该选项后将允许 KernelSU 为本应用还原被模块修改过的文件。</string>
<string name="profile_selinux_domain"></string>
<string name="profile_selinux_rules">规则</string>
@@ -78,10 +83,9 @@
<string name="module_start_downloading">开始下载:%s</string>
<string name="new_version_available">发现新版本:%s点击升级。</string>
<string name="launch_app">启动</string>
<string name="force_stop_app">强制停止</string>
<string name="force_stop_app" formatted="false">强制停止</string>
<string name="restart_app">重新启动</string>
<string name="failed_to_update_sepolicy"> %s 更新 SELinux 策略失败</string>
<string name="su_not_allowed">不允许授予:%s 超级用户权限</string>
<string name="failed_to_update_sepolicy">%s 更新翻译失败</string>
<string name="module_changelog">更新日志</string>
<string name="settings_profile_template">App Profile 模版</string>
<string name="settings_profile_template_summary">管理本地和在线的 App Profile 模版</string>
@@ -132,6 +136,82 @@
<string name="selected_lkm">选择的 LKM%s</string>
<string name="save_log">保存日志</string>
<string name="log_saved">日志已保存</string>
<string name="settings_disable_su">关闭 su 兼容</string>
<string name="settings_disable_su_summary">临时禁止任何应用通过 su 命令获取 root 权限(已运行的 root 进程不受影响)</string>
</resources>
<string name="status_supported">支持</string>
<string name="status_not_supported">不支持</string>
<string name="status_unknown">未知</string>
<string name="sus_su_mode">SuS SU 模式:</string>
<!-- Module related -->
<string name="module_install_confirm">确认安装模块 %1$s</string>
<string name="unknown_module">未知模块</string>
<!-- Restore related -->
<string name="restore_confirm_title">确认还原模块</string>
<string name="restore_confirm_message">此操作将覆盖所有现有模块,是否继续?</string>
<string name="confirm">确定</string>
<string name="cancel">取消</string>
<!-- Backup related -->
<string name="backup_success">备份成功 (tar.gz)</string>
<string name="backup_failed">备份失败:%1$s</string>
<string name="backup_modules">备份模块</string>
<string name="restore_modules">恢复模块</string>
<!-- Restore related messages -->
<string name="restore_success">模块已成功还原,需重启生效</string>
<string name="restore_failed">还原失败:%1$s</string>
<string name="restart_now">立即重启</string>
<string name="unknown_error">未知错误</string>
<!-- Command related -->
<string name="command_execution_failed">命令执行失败:%1$s</string>
<!-- Allowlist related -->
<string name="allowlist_backup_success">应用列表备份成功</string>
<string name="allowlist_backup_failed">应用列表备份失败:%1$s</string>
<string name="allowlist_restore_confirm_title">确认还原应用列表</string>
<string name="allowlist_restore_confirm_message">此操作将覆盖当前的应用列表,是否继续?</string>
<string name="allowlist_restore_success">应用列表还原成功</string>
<string name="allowlist_restore_failed">应用列表还原失败:%1$s</string>
<string name="backup_allowlist">备份应用列表</string>
<string name="restore_allowlist">还原应用列表</string>
<string name="settings_custom_background">自定义背景</string>
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
<string name="settings_card_manage">卡片管理</string>
<string name="settings_card_alpha">卡片透明度</string>
<string name="settings_restore_default">恢复默认</string>
<string name="home_android_version">Android 版本</string>
<string name="home_device_model">设备</string>
<string name="su_not_allowed">不允许授予 %s 超级用户权限</string>
<string name="settings_disable_su">禁用 su 兼容性</string>
<string name="settings_disable_su_summary">临时禁止任何应用程序通过 su 命令获取 Root 权限(现有的 Root 进程不受影响)</string>
<string name="using_mksu_manager">你正在使用的是MKSU第三方管理器</string>
<string name="module_install_multiple_confirm">确定要安装选择的 %d 个模块吗?</string>
<string name="module_install_multiple_confirm_with_names">确定要安装以下 %1$d 个模块吗?\n\n%2$s</string>
<string name="more_settings">更多设置</string>
<string name="selinux">SELinux</string>
<string name="selinux_enabled">强制执行</string>
<string name="selinux_disabled">兼容模式</string>
<string name="simple_mode">简洁模式</string>
<string name="simple_mode_summary">开启后将隐藏不必要的卡片</string>
<string name="theme_mode">主题模式</string>
<string name="theme_follow_system">跟随系统</string>
<string name="theme_light">浅色</string>
<string name="theme_dark">深色</string>
<string name="manual_hook">Manual Hook</string>
<string name="dynamic_color_title">动态颜色</string>
<string name="dynamic_color_summary">使用系统主题的动态颜色</string>
<string name="choose_theme_color">选择主题色</string>
<string name="color_default">黄色</string>
<string name="color_blue">蓝色</string>
<string name="color_green">绿色</string>
<string name="color_purple">紫色</string>
<string name="color_orange">橙色</string>
<string name="color_pink">粉色</string>
<string name="color_gray">高级灰</string>
<string name="color_ivory">象牙白</string>
<string name="flash_option">刷入选项</string>
<string name="flash_option_tip">选择要刷入的文件</string>
<string name="horizon_kernel">Anykernel3 刷写</string>
<string name="root_required">需要 root 权限</string>
<string name="copy_failed">文件复制失败</string>
<string name="reboot_complete_title">刷写完成</string>
<string name="reboot_complete_msg">是否立即重启?</string>
<string name="yes"></string>
<string name="no"></string>
<string name="failed_reboot">重启失败</string>
</resources>

View File

@@ -50,7 +50,6 @@
<string name="home_click_to_learn_kernelsu">瞭解如何安裝 KernelSU 以及如何開發模組</string>
<string name="home_support_title">支援開發</string>
<string name="home_support_content">KernelSU 將保持免費和開源,您可以考慮向開發人員贊助以表示支持。</string>
<string name="about_source_code"><![CDATA[在 %1$s 中檢視原始碼<br/>加入我們的 %2$s 頻道]]></string>
<string name="profile_default">預設</string>
<string name="profile_name">設定檔名稱</string>
<string name="profile_template">範本</string>
@@ -60,7 +59,7 @@
<string name="profile_umount_modules">卸載模組</string>
<string name="failed_to_update_app_profile">無法更新 %s 應用程式設定檔</string>
<string name="profile_selinux_rules">規則</string>
<string name="require_kernel_version">目前 KernelSU 版本 %d 過低,管理器無法正常運作。請升級至 %d 或更高版本!</string>
<string name="require_kernel_version" formatted="false">目前 KernelSU 版本 %d 過低,管理器無法正常運作。請升級至 %d 或更高版本!</string>
<string name="settings_umount_modules_default_summary">應用程式設定檔中「解除安裝模組」的全域預設值,如果啟用,將會為沒有設定檔的應用程式移除所有模組針對系統的修改。</string>
<string name="profile_umount_modules_summary">啟用此選項將允許 KernelSU 為這個應用程式還原任何被模組修改過的檔案。</string>
<string name="profile_selinux_domain">網域</string>
@@ -75,7 +74,7 @@
<string name="module_start_downloading">開始下載:%s</string>
<string name="new_version_available">新版本:%s 已可供使用,按一下以升級</string>
<string name="launch_app">啟動</string>
<string name="force_stop_app">強制停止</string>
<string name="force_stop_app" formatted="false">強制停止</string>
<string name="restart_app">重新啟動</string>
<string name="failed_to_update_sepolicy">無法為 %s 更新 SELinux 規則</string>
<string name="module_changelog">變更記錄</string>

View File

@@ -50,10 +50,9 @@
<string name="home_click_to_learn_kernelsu">知曉安裝、使用 KernelSU 本體與其模組功能的方法</string>
<string name="home_support_title">協助發展</string>
<string name="home_support_content">KernelSU 一向以免費製品與開放原始碼自居,矢志不渝。若想協助我們,請以小額捐款表達你對專案發展的大力支持。</string>
<string name="about_source_code"><![CDATA[前往 %1$s 檢閱原始碼<br/>前往 %2$s 加入頻道]]></string>
<string name="profile_umount_modules">解除掛載模組功能</string>
<string name="failed_to_update_app_profile">無法更新「%s」App Profile</string>
<string name="require_kernel_version">管理工具無法以老舊的 KernelSU %d 版本正常運作。請升級至 %d 以上的版本!</string>
<string name="require_kernel_version" formatted="false">管理工具無法以老舊的 KernelSU %d 版本正常運作。請升級至 %d 以上的版本!</string>
<string name="settings_umount_modules_default">預設解除掛載模組功能</string>
<string name="settings_umount_modules_default_summary">將 App Profile 的全域預設行為設作「解除掛載模組功能」。啟用後,將向未指派 Profile 的應用程式移除模組功能。</string>
<string name="profile_umount_modules_summary">啟用選項後KernelSU 會將應用程式內遭模組修改的檔案恢復原狀。</string>
@@ -61,7 +60,7 @@
<string name="profile_custom">自訂</string>
<string name="profile_capabilities">權限</string>
<string name="profile_selinux_rules">規則</string>
<string name="module_downloading">正在下載模組:%s</string>
<string name="module_downloading" formatted="false">正在下載模組:%s</string>
<string name="restart_app">重新執行</string>
<string name="profile_template">範本</string>
<string name="profile_name">Profile 名稱</string>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="app_name" translatable="false">SukiSU</string>
<string name="home">Home</string>
<string name="home_not_installed">Not installed</string>
<string name="home_click_to_install">Click to install</string>
@@ -9,8 +9,11 @@
<string name="home_superuser_count">Superusers: %d</string>
<string name="home_module_count">Modules: %d</string>
<string name="home_unsupported">Unsupported</string>
<string name="home_unsupported_reason">KernelSU only supports GKI kernels now</string>
<string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string>
<string name="home_kernel">Kernel</string>
<string name="home_susfs">SuSFS: %s</string>
<string name="home_susfs_version">Version de SuSFS</string>
<string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">Manager version</string>
<string name="home_fingerprint">Fingerprint</string>
<string name="home_selinux_status">SELinux status</string>
@@ -23,10 +26,8 @@
<string name="module_failed_to_disable">Failed to disable module: %s</string>
<string name="module_empty">No module installed</string>
<string name="module">Module</string>
<string name="module_install_prompt_with_name">The following modules will be installed: %1$s</string>
<string name="module_sort_action_first">Sort (Action first)</string>
<string name="module_sort_enabled_first">Sort (Enabled first)</string>
<string name="confirm">Confirm</string>
<string name="uninstall">Uninstall</string>
<string name="restore">Restore</string>
<string name="module_install">Install</string>
@@ -71,9 +72,11 @@
<string name="profile_selinux_context">SELinux context</string>
<string name="profile_umount_modules">Umount modules</string>
<string name="failed_to_update_app_profile">Failed to update App Profile for %s</string>
<string name="require_kernel_version">The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher!</string>
<string name="require_kernel_version" formatted="false">The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher!</string>
<string name="settings_umount_modules_default">Umount modules by default</string>
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profile. If enabled, it will remove all module modifications to the system for apps that don\'t have a profile set.</string>
<string name="settings_susfs_toggle">Cacher les hooks kprobe</string>
<string name="settings_susfs_toggle_summary">Désactive les hooks kprobe créés par KSU et, à la place, active les hooks non-kprobe intégrés, implémentant les mêmes fonctionnalités qui seraient appliquées à un kernel non-GKI, qui ne supportent krpobe.</string>
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this app.</string>
<string name="profile_selinux_domain">Domain</string>
<string name="profile_selinux_rules">Rules</string>
@@ -82,10 +85,9 @@
<string name="module_start_downloading">Start downloading: %s</string>
<string name="new_version_available">New version %s is available, click to upgrade.</string>
<string name="launch_app">Launch</string>
<string name="force_stop_app">Force stop</string>
<string name="force_stop_app" formatted="false">Force stop</string>
<string name="restart_app">Restart</string>
<string name="failed_to_update_sepolicy">Failed to update SELinux rules for %s</string>
<string name="su_not_allowed">Granting superuser to %s is not allowed</string>
<string name="module_changelog">Changelog</string>
<string name="settings_profile_template">App Profile Template</string>
<string name="settings_profile_template_summary">Manage local and online template of App Profile</string>
@@ -136,6 +138,82 @@
<string name="selected_lkm">Selected LKM: %s</string>
<string name="save_log">Save logs</string>
<string name="log_saved">Logs saved</string>
<string name="status_supported">Supported</string>
<string name="status_not_supported">Not Supported</string>
<string name="status_unknown">Unknown</string>
<string name="sus_su_mode">SuS SU mode:</string>
<!-- Module related -->
<string name="module_install_confirm">confirm install module %1$s</string>
<string name="unknown_module">unknown module</string>
<!-- Restore related -->
<string name="restore_confirm_title">Confirm Module Restoration</string>
<string name="restore_confirm_message">This operation will overwrite all existing modules. Continue?</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<!-- Backup related -->
<string name="backup_success">Backup successful (tar.gz)</string>
<string name="backup_failed">Backup failed: %1$s</string>
<string name="backup_modules">backup modules</string>
<string name="restore_modules">restore modules</string>
<!-- Restore related messages -->
<string name="restore_success">Modules restored successfully, restart required</string>
<string name="restore_failed">Restore failed: %1$s</string>
<string name="restart_now">Restart Now</string>
<string name="unknown_error">Unknown error</string>
<!-- Command related -->
<string name="command_execution_failed">Command execution failed: %1$s</string>
<!-- Allowlist related -->
<string name="allowlist_backup_success">Allowlist backup successful</string>
<string name="allowlist_backup_failed">Allowlist backup failed: %1$s</string>
<string name="allowlist_restore_confirm_title">Confirm Allowlist Restoration</string>
<string name="allowlist_restore_confirm_message">This operation will overwrite the current allowlist. Continue?</string>
<string name="allowlist_restore_success">Allowlist restored successfully</string>
<string name="allowlist_restore_failed">Allowlist restore failed: %1$s</string>
<string name="backup_allowlist">Backup Allowlist</string>
<string name="restore_allowlist">Restore Allowlist</string>
<string name="settings_custom_background">settings custom background</string>
<string name="settings_custom_background_summary">settings custom background summary</string>
<string name="settings_card_manage">card manage</string>
<string name="settings_card_alpha">card alpha</string>
<string name="settings_restore_default">restore default</string>
<string name="home_android_version">Android version</string>
<string name="home_device_model">device model</string>
<string name="su_not_allowed">Granting superuser to %s is not allowed</string>
<string name="settings_disable_su">Disable su compatibility</string>
<string name="settings_disable_su_summary">Temporarily disable the ability of any app to gain root privileges via the su command (existing root processes won\'t be affected).</string>
<string name="settings_disable_su_summary">Temporarily disable any applications from obtaining root privileges via the su command (existing root processes will not be affected).</string>
<string name="using_mksu_manager">You are using the MKSU third-party manager</string>
<string name="module_install_multiple_confirm">Are you sure you want to install the selected %d modules?</string>
<string name="module_install_multiple_confirm_with_names">Sure you want to install the following %1$d modules? \n\n%2$s</string>
<string name="more_settings">more settings</string>
<string name="selinux">SELinux</string>
<string name="selinux_enabled">Enabled</string>
<string name="selinux_disabled">Disabled</string>
<string name="simple_mode">simplicity mode</string>
<string name="simple_mode_summary">Hides unnecessary cards when turned on</string>
<string name="theme_mode">Theme Mode</string>
<string name="theme_follow_system">follow-up system</string>
<string name="theme_light">light color</string>
<string name="theme_dark">dark colored</string>
<string name="manual_hook">Manual Hook</string>
<string name="dynamic_color_title">Dynamic colours</string>
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
<string name="choose_theme_color">Choose a theme colour</string>
<string name="color_default">yellow</string>
<string name="color_blue">blue</string>
<string name="color_green">green</string>
<string name="color_purple">purple</string>
<string name="color_orange">orange</string>
<string name="color_pink">pink</string>
<string name="color_gray">gray</string>
<string name="color_ivory">ivory</string>
<string name="flash_option">Brush Options</string>
<string name="flash_option_tip">Select the file to be flashed</string>
<string name="horizon_kernel">Anykernel3 Flush</string>
<string name="root_required">Requires root privileges</string>
<string name="copy_failed">File Copy Failure</string>
<string name="reboot_complete_title">Scrubbing complete</string>
<string name="reboot_complete_msg">Whether to reboot immediately</string>
<string name="yes">yes</string>
<string name="no">no</string>
<string name="failed_reboot">Reboot Failed</string>
</resources>