Merge some files and rewrite the update history
This commit is contained in:
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// IKsuInterface.aidl
|
||||
package me.weishu.kernelsu;
|
||||
package shirkneko.zako.sukisu;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
BIN
manager/app/src/main/assets/mkbootfs
Normal file
BIN
manager/app/src/main/assets/mkbootfs
Normal file
Binary file not shown.
@@ -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})
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package me.weishu.kernelsu.ui.util.module
|
||||
|
||||
data class LatestVersionInfo(
|
||||
val versionCode : Int = 0,
|
||||
val downloadUrl : String = "",
|
||||
val changelog : String = ""
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.weishu.kernelsu
|
||||
package shirkneko.zako.sukisu
|
||||
|
||||
import android.system.Os
|
||||
|
||||
@@ -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.
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.weishu.kernelsu.profile
|
||||
package shirkneko.zako.sukisu.profile
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
@@ -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,
|
||||
@@ -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
|
||||
@@ -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 = "" }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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(
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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
|
||||
@@ -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("[H[J")) { // 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)
|
||||
}
|
||||
@@ -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)})"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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, "", {}, {}, {}, {})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.weishu.kernelsu.ui.util;
|
||||
package shirkneko.zako.sukisu.ui.util;
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = ""
|
||||
)
|
||||
@@ -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) {
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ->
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package me.weishu.kernelsu.ui.webui;
|
||||
package shirkneko.zako.sukisu.ui.webui;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package me.weishu.kernelsu.ui.webui;
|
||||
package shirkneko.zako.sukisu.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
manager/app/src/main/jniLibs/.gitignore
vendored
3
manager/app/src/main/jniLibs/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
libksud.so
|
||||
libzakomk.so
|
||||
libzakomksd.so
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -33,31 +33,27 @@ val androidCompileSdkVersion = 35
|
||||
val androidCompileNdkVersion = "28.0.13004108"
|
||||
val androidSourceCompatibility = JavaVersion.VERSION_21
|
||||
val androidTargetCompatibility = JavaVersion.VERSION_21
|
||||
val managerVersionCode by extra(getVersionCode())
|
||||
val managerVersionName by extra(getVersionName())
|
||||
val managerVersionCode by extra(1 * 12000 + getGitCommitCount() + 500)
|
||||
val managerVersionName by extra(getGitDescribe())
|
||||
|
||||
fun getGitCommitCount(): Int {
|
||||
val out = ByteArrayOutputStream()
|
||||
exec {
|
||||
return providers.exec {
|
||||
commandLine("git", "rev-list", "--count", "HEAD")
|
||||
standardOutput = out
|
||||
}
|
||||
return out.toString().trim().toInt()
|
||||
}.standardOutput.asText.get().trim().toInt()
|
||||
}
|
||||
|
||||
fun getGitDescribe(): String {
|
||||
val out = ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine("git", "describe", "--tags", "--always")
|
||||
standardOutput = out
|
||||
}
|
||||
return out.toString().trim()
|
||||
return providers.exec {
|
||||
commandLine("git", "describe", "--tags", "--always", "--abbrev=0")
|
||||
}.standardOutput.asText.get().trim()
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getVersionCode(): Int {
|
||||
val commitCount = getGitCommitCount()
|
||||
val major = 1
|
||||
return major * 10000 + commitCount + 200
|
||||
return major * 12000 + commitCount + 500
|
||||
}
|
||||
|
||||
fun getVersionName(): String {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user