Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56b4664ec7 | ||
|
|
70f7c75a92 | ||
|
|
e414b4de92 | ||
|
|
79e68f473f | ||
|
|
6656604809 | ||
|
|
85b4d11912 | ||
|
|
7769a23f59 | ||
|
|
c442f43090 | ||
|
|
d73670bf43 | ||
|
|
dd1967f0d0 | ||
|
|
e3d2fc64ac | ||
|
|
e07f20bf29 | ||
|
|
34f216181f | ||
|
|
8aef775474 | ||
|
|
f669ad92b6 | ||
|
|
cc0b272770 | ||
|
|
9ea6de340d | ||
|
|
be37f8a2a3 | ||
|
|
8a12fac39f | ||
|
|
0242fe12e3 | ||
|
|
acf2e1a5ec | ||
|
|
626db4be56 | ||
|
|
5941fa1ec7 | ||
|
|
1dd8651a1a | ||
|
|
33dd0ca16b |
@@ -1,4 +1,4 @@
|
||||
# SukiSU
|
||||
# SukiSU Ultra
|
||||
|
||||
**English** | [简体中文](README.md) | [日本語](README-ja.md)
|
||||
|
||||
@@ -100,6 +100,7 @@ Projects compiled based on Sukisu and susfs
|
||||
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
|
||||
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
|
||||
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks
|
||||
- [DARKWWEE](https://github.com/DARKWWEE) Thanks for the 100 USDT Lao
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SukiSU
|
||||
# SukiSU Ultra
|
||||
|
||||
**日本語** | [简体中文](README.md) | [English](README-en.md)
|
||||
|
||||
@@ -93,6 +93,7 @@ SukiSU と susfs をベースにコンパイルされたプロジェクトです
|
||||
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
|
||||
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
|
||||
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
|
||||
- [DARKWWEE](https://github.com/DARKWWEE) ラオウ100USDTありがとう!
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
|
||||
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
|
||||
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
|
||||
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
|
||||
- [DARKWWEE](https://github.com/DARKWWEE) 感谢老哥的 100 USDT
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ obj-$(CONFIG_KPM) += kpm/
|
||||
# .git is a text file while the module is imported by 'git submodule add'.
|
||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
||||
$(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin [ -f ../.git/shallow ] && git fetch --unshallow)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count HEAD)
|
||||
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count main)
|
||||
# ksu_version: major * 10000 + git version + 606 for historical reasons
|
||||
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
|
||||
$(info -- KernelSU version: $(KSU_VERSION))
|
||||
@@ -42,13 +42,26 @@ endif
|
||||
|
||||
ifdef KSU_MANAGER_PACKAGE
|
||||
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
|
||||
$(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE))
|
||||
endif
|
||||
|
||||
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
$(info -- SukiSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||
$(info -- SukiSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
||||
KERNEL_TYPE := Non-GKI
|
||||
# Check for GKI 2.0 (5.10+ or 6.x+)
|
||||
ifneq ($(shell test \( $(VERSION) -ge 5 -a $(PATCHLEVEL) -ge 10 \) -o $(VERSION) -ge 6; echo $$?),0)
|
||||
# Check for GKI 1.0 (5.4)
|
||||
ifeq ($(shell test $(VERSION)-$(PATCHLEVEL) = 5-4; echo $$?),0)
|
||||
KERNEL_TYPE := GKI 1.0
|
||||
endif
|
||||
else
|
||||
KERNEL_TYPE := GKI 2.0
|
||||
endif
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
|
||||
|
||||
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||
ifeq ($(CONFIG_KPM),y)
|
||||
$(info -- KPM is enabled)
|
||||
|
||||
@@ -25,7 +25,7 @@ apksign {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "zako.zako.zako"
|
||||
namespace = "com.sukisu.ultra"
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako;
|
||||
package com.sukisu.zako;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
Binary file not shown.
@@ -12,7 +12,7 @@
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
||||
Java_com_sukisu_ultra_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_zako_zako_zako_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_zako_zako_zako_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
Java_com_sukisu_ultra_Natives_getVersion(JNIEnv *env, jobject) {
|
||||
return get_version();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jintArray JNICALL
|
||||
Java_zako_zako_zako_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
Java_com_sukisu_ultra_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
int uids[1024];
|
||||
int size = 0;
|
||||
bool result = get_allow_list(uids, &size);
|
||||
@@ -42,13 +42,13 @@ Java_zako_zako_zako_Natives_getAllowList(JNIEnv *env, jobject) {
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
||||
Java_com_sukisu_ultra_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
|
||||
return is_safe_mode();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
|
||||
Java_com_sukisu_ultra_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_zako_zako_zako_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
|
||||
Java_com_sukisu_ultra_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
|
||||
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ Java_zako_zako_zako_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jin
|
||||
|
||||
bool useDefaultProfile = !get_app_profile(key, &profile);
|
||||
|
||||
auto cls = env->FindClass("zako/zako/zako/Natives$Profile");
|
||||
auto cls = env->FindClass("com/sukisu/ultra/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_zako_zako_zako_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jin
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
||||
auto cls = env->FindClass("zako/zako/zako/Natives$Profile");
|
||||
Java_com_sukisu_ultra_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
||||
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile");
|
||||
|
||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
||||
@@ -293,16 +293,16 @@ Java_zako_zako_zako_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject pr
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
||||
Java_com_sukisu_ultra_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
|
||||
return uid_should_umount(uid);
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
|
||||
Java_com_sukisu_ultra_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
|
||||
return is_su_enabled();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_zako_zako_zako_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
||||
Java_com_sukisu_ultra_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
||||
return set_su_enabled(enabled);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako
|
||||
package com.sukisu.ultra
|
||||
|
||||
import android.app.Application
|
||||
import coil.Coil
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako
|
||||
package com.sukisu.ultra
|
||||
|
||||
import android.system.Os
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako
|
||||
package com.sukisu.ultra
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
@@ -16,14 +16,14 @@ object Natives {
|
||||
// 10946: add capabilities
|
||||
// 10977: change groups_count and groups to avoid overflow write
|
||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 11071
|
||||
const val MINIMAL_SUPPORTED_KERNEL = 12800
|
||||
|
||||
// 11640: Support query working mode, LKM or GKI
|
||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
|
||||
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12800
|
||||
|
||||
// 12040: Support disable sucompat mode
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
|
||||
const val MINIMAL_SUPPORTED_SU_COMPAT = 12800
|
||||
const val KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||
|
||||
const val ROOT_UID = 0
|
||||
@@ -90,6 +90,14 @@ object Natives {
|
||||
fun requireNewKernel(): Boolean {
|
||||
return version < MINIMAL_SUPPORTED_KERNEL
|
||||
}
|
||||
|
||||
fun isKsuValid(pkgName: String?): Boolean {
|
||||
if (becomeManager(pkgName)) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Parcelize
|
||||
423
manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt
Normal file
423
manager/app/src/main/java/com/sukisu/ultra/flash/KernelFlash.kt
Normal file
@@ -0,0 +1,423 @@
|
||||
package com.sukisu.ultra.flash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
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.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.utils.AssetsUtil
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
data class FlashState(
|
||||
val isFlashing: Boolean = false,
|
||||
val isCompleted: Boolean = false,
|
||||
val progress: Float = 0f,
|
||||
val currentStep: String = "",
|
||||
val logs: List<String> = emptyList(),
|
||||
val error: String = ""
|
||||
)
|
||||
|
||||
class HorizonKernelState {
|
||||
private val _state = MutableStateFlow(FlashState())
|
||||
val state: StateFlow<FlashState> = _state.asStateFlow()
|
||||
|
||||
fun updateProgress(progress: Float) {
|
||||
_state.update { it.copy(progress = progress) }
|
||||
}
|
||||
|
||||
fun updateStep(step: String) {
|
||||
_state.update { it.copy(currentStep = step) }
|
||||
}
|
||||
|
||||
fun addLog(log: String) {
|
||||
_state.update {
|
||||
it.copy(logs = it.logs + log)
|
||||
}
|
||||
}
|
||||
|
||||
fun setError(error: String) {
|
||||
_state.update { it.copy(error = error) }
|
||||
}
|
||||
|
||||
fun startFlashing() {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isFlashing = true,
|
||||
isCompleted = false,
|
||||
progress = 0f,
|
||||
currentStep = "under preparation...",
|
||||
logs = emptyList(),
|
||||
error = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun completeFlashing() {
|
||||
_state.update { it.copy(isCompleted = true, progress = 1f) }
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
_state.value = FlashState()
|
||||
}
|
||||
}
|
||||
|
||||
class HorizonKernelWorker(
|
||||
private val context: Context,
|
||||
private val state: HorizonKernelState,
|
||||
private val slot: String? = null
|
||||
) : Thread() {
|
||||
var uri: Uri? = null
|
||||
private lateinit var filePath: String
|
||||
private lateinit var binaryPath: String
|
||||
|
||||
private var onFlashComplete: (() -> Unit)? = null
|
||||
private var originalSlot: String? = null
|
||||
|
||||
fun setOnFlashCompleteListener(listener: () -> Unit) {
|
||||
onFlashComplete = listener
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
state.startFlashing()
|
||||
state.updateStep(context.getString(R.string.horizon_preparing))
|
||||
|
||||
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
|
||||
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
|
||||
|
||||
try {
|
||||
state.updateStep(context.getString(R.string.horizon_cleaning_files))
|
||||
state.updateProgress(0.1f)
|
||||
cleanup()
|
||||
|
||||
if (!rootAvailable()) {
|
||||
state.setError(context.getString(R.string.root_required))
|
||||
return
|
||||
}
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_copying_files))
|
||||
state.updateProgress(0.2f)
|
||||
copy()
|
||||
|
||||
if (!File(filePath).exists()) {
|
||||
state.setError(context.getString(R.string.horizon_copy_failed))
|
||||
return
|
||||
}
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_extracting_tool))
|
||||
state.updateProgress(0.4f)
|
||||
getBinary()
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_patching_script))
|
||||
state.updateProgress(0.6f)
|
||||
patch()
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_flashing))
|
||||
state.updateProgress(0.7f)
|
||||
|
||||
// 获取原始槽位信息
|
||||
if (slot != null) {
|
||||
state.updateStep(context.getString(R.string.horizon_getting_original_slot))
|
||||
state.updateProgress(0.72f)
|
||||
originalSlot = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
|
||||
}
|
||||
|
||||
// 设置目标槽位
|
||||
if (!slot.isNullOrEmpty()) {
|
||||
state.updateStep(context.getString(R.string.horizon_setting_target_slot))
|
||||
state.updateProgress(0.74f)
|
||||
runCommand(true, "resetprop -n ro.boot.slot_suffix _$slot")
|
||||
}
|
||||
|
||||
flash()
|
||||
|
||||
// 恢复原始槽位
|
||||
if (!originalSlot.isNullOrEmpty()) {
|
||||
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
||||
state.updateProgress(0.8f)
|
||||
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
||||
}
|
||||
|
||||
state.updateStep(context.getString(R.string.horizon_flash_complete_status))
|
||||
state.completeFlashing()
|
||||
|
||||
(context as? Activity)?.runOnUiThread {
|
||||
onFlashComplete?.invoke()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
state.setError(e.message ?: context.getString(R.string.horizon_unknown_error))
|
||||
|
||||
// 恢复原始槽位
|
||||
if (!originalSlot.isNullOrEmpty()) {
|
||||
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
|
||||
state.updateProgress(0.8f)
|
||||
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// 写入槽位信息到临时文件
|
||||
slot?.let { selectedSlot ->
|
||||
writer.write("echo \"$selectedSlot\" > ${context.filesDir.absolutePath}/bootslot\n")
|
||||
}
|
||||
|
||||
// 构建刷写命令
|
||||
val flashCommand = buildString {
|
||||
append("sh $binaryPath 3 1 \"$filePath\"")
|
||||
if (slot != null) {
|
||||
append(" \"$(cat ${context.filesDir.absolutePath}/bootslot)\"")
|
||||
}
|
||||
append(" && touch ${context.filesDir.absolutePath}/done\n")
|
||||
}
|
||||
|
||||
writer.write(flashCommand)
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.lineSequence().forEach { line ->
|
||||
if (line.startsWith("ui_print")) {
|
||||
val logMessage = line.removePrefix("ui_print").trim()
|
||||
state.addLog(logMessage)
|
||||
|
||||
when {
|
||||
logMessage.contains("extracting", ignoreCase = true) -> {
|
||||
state.updateProgress(0.75f)
|
||||
}
|
||||
logMessage.contains("installing", ignoreCase = true) -> {
|
||||
state.updateProgress(0.85f)
|
||||
}
|
||||
logMessage.contains("complete", ignoreCase = true) -> {
|
||||
state.updateProgress(0.95f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
|
||||
if (!File("${context.filesDir.absolutePath}/done").exists()) {
|
||||
throw IOException(context.getString(R.string.flash_failed_message))
|
||||
}
|
||||
}
|
||||
|
||||
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 runCommandGetOutput(su: Boolean, cmd: String): String {
|
||||
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.inputStream.bufferedReader().use { reader ->
|
||||
reader.readText().trim()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun rootAvailable(): Boolean {
|
||||
return try {
|
||||
val process = Runtime.getRuntime().exec("su -c id")
|
||||
val exitValue = process.waitFor()
|
||||
exitValue == 0
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HorizonKernelFlashProgress(state: FlashState) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.horizon_flash_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
progress = { state.progress },
|
||||
)
|
||||
|
||||
Text(
|
||||
text = state.currentStep,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
)
|
||||
|
||||
if (state.logs.isNotEmpty()) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.horizon_logs_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Start)
|
||||
.padding(top = 8.dp, bottom = 4.dp)
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 150.dp)
|
||||
.padding(vertical = 4.dp),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 1.dp,
|
||||
shape = MaterialTheme.shapes.small
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
state.logs.forEach { log ->
|
||||
Text(
|
||||
text = log,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(vertical = 2.dp),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.error.isNotEmpty()) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Error,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = state.error,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
} else if (state.isCompleted) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CheckCircle,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.horizon_flash_complete),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.profile
|
||||
package com.sukisu.ultra.profile
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.profile
|
||||
package com.sukisu.ultra.profile
|
||||
|
||||
/**
|
||||
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui;
|
||||
package com.sukisu.ultra.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 zako.zako.zako.IKsuInterface;
|
||||
import com.sukisu.zako.IKsuInterface;
|
||||
import rikka.parcelablelist.ParcelableListSlice;
|
||||
|
||||
/**
|
||||
@@ -1,34 +1,17 @@
|
||||
package zako.zako.zako.ui
|
||||
package com.sukisu.ultra.ui
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.only
|
||||
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
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
@@ -37,21 +20,16 @@ import androidx.navigation.compose.rememberNavController
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
||||
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
|
||||
import com.ramcosta.composedestinations.spec.RouteOrDirection
|
||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||
import io.zako.zako.UltraToolInstall
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.ksuApp
|
||||
import zako.zako.zako.ui.screen.BottomBarDestination
|
||||
import zako.zako.zako.ui.theme.CardConfig
|
||||
import zako.zako.zako.ui.theme.KernelSUTheme
|
||||
import zako.zako.zako.ui.theme.loadCustomBackground
|
||||
import zako.zako.zako.ui.theme.loadThemeMode
|
||||
import zako.zako.zako.ui.util.LocalSnackbarHost
|
||||
import zako.zako.zako.ui.util.getKpmVersion
|
||||
import zako.zako.zako.ui.util.rootAvailable
|
||||
import zako.zako.zako.ui.util.install
|
||||
|
||||
import io.sukisu.ultra.UltraToolInstall
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -89,7 +67,7 @@ class MainActivity : ComponentActivity() {
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
navGraph = NavGraphs.root,
|
||||
navGraph = NavGraphs.root as NavHostGraphSpec,
|
||||
navController = navController,
|
||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
||||
@@ -136,7 +114,7 @@ private fun BottomBar(navController: NavHostController) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root) {
|
||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
@@ -152,7 +130,7 @@ private fun BottomBar(navController: NavHostController) {
|
||||
},
|
||||
label = { Text(stringResource(destination.label)) },
|
||||
alwaysShowLabel = false,
|
||||
colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
)
|
||||
@@ -167,7 +145,7 @@ private fun BottomBar(navController: NavHostController) {
|
||||
navigator.popBackStack(destination.direction, false)
|
||||
}
|
||||
navigator.navigate(destination.direction) {
|
||||
popUpTo(NavGraphs.root) {
|
||||
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component
|
||||
package com.sukisu.ultra.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 zako.zako.zako.BuildConfig
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.graphics.text.LineBreaker
|
||||
import android.os.Build
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import kotlin.math.max
|
||||
|
||||
@Composable
|
||||
fun ImageEditorDialog(
|
||||
imageUri: Uri,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (Uri) -> Unit
|
||||
) {
|
||||
var scale by remember { mutableFloatStateOf(1f) }
|
||||
var offsetX by remember { mutableFloatStateOf(0f) }
|
||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val density = LocalDensity.current
|
||||
var lastScale by remember { mutableFloatStateOf(1f) }
|
||||
var lastOffsetX by remember { mutableFloatStateOf(0f) }
|
||||
var lastOffsetY by remember { mutableFloatStateOf(0f) }
|
||||
var imageSize by remember { mutableStateOf(Size.Zero) }
|
||||
var screenSize by remember { mutableStateOf(Size.Zero) }
|
||||
val animatedScale by animateFloatAsState(
|
||||
targetValue = scale,
|
||||
label = "ScaleAnimation"
|
||||
)
|
||||
val animatedOffsetX by animateFloatAsState(
|
||||
targetValue = offsetX,
|
||||
label = "OffsetXAnimation"
|
||||
)
|
||||
val animatedOffsetY by animateFloatAsState(
|
||||
targetValue = offsetY,
|
||||
label = "OffsetYAnimation"
|
||||
)
|
||||
val updateTransformation = remember {
|
||||
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
|
||||
val scaleDiff = kotlin.math.abs(newScale - lastScale)
|
||||
val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX)
|
||||
val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY)
|
||||
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
|
||||
scale = newScale
|
||||
offsetX = newOffsetX
|
||||
offsetY = newOffsetY
|
||||
lastScale = newScale
|
||||
lastOffsetX = newOffsetX
|
||||
lastOffsetY = newOffsetY
|
||||
}
|
||||
}
|
||||
}
|
||||
val scaleToFullScreen = remember {
|
||||
{
|
||||
if (imageSize.height > 0 && screenSize.height > 0) {
|
||||
val newScale = screenSize.height / imageSize.height
|
||||
updateTransformation(newScale, 0f, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = false,
|
||||
usePlatformDefaultWidth = false
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.9f))
|
||||
.onSizeChanged { size ->
|
||||
screenSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||
}
|
||||
) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(imageUri)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = stringResource(R.string.settings_custom_background),
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(
|
||||
scaleX = animatedScale,
|
||||
scaleY = animatedScale,
|
||||
translationX = animatedOffsetX,
|
||||
translationY = animatedOffsetY
|
||||
)
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures { _, pan, zoom, _ ->
|
||||
scope.launch {
|
||||
try {
|
||||
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
|
||||
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
|
||||
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
|
||||
val newOffsetX = if (maxOffsetX > 0) {
|
||||
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val newOffsetY = if (maxOffsetY > 0) {
|
||||
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
updateTransformation(newScale, newOffsetX, newOffsetY)
|
||||
} catch (e: Exception) {
|
||||
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onSizeChanged { size ->
|
||||
imageSize = Size(size.width.toFloat(), size.height.toFloat())
|
||||
}
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.align(Alignment.TopCenter),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = stringResource(R.string.cancel),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { scaleToFullScreen() },
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Fullscreen,
|
||||
contentDescription = stringResource(R.string.reprovision),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
try {
|
||||
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
|
||||
val savedUri = context.saveTransformedBackground(imageUri, transformation)
|
||||
savedUri?.let { onConfirm(it) }
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.confirm),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color.Black.copy(alpha = 0.6f))
|
||||
.padding(16.dp)
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.image_editor_hint),
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
@@ -42,7 +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 zako.zako.zako.ui.theme.CardConfig
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
|
||||
private const val TAG = "SearchBar"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
|
||||
/**
|
||||
* 槽位选择对话框组件
|
||||
* 用于HorizonKernel刷写时选择目标槽位
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SlotSelectionDialog(
|
||||
show: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSlotSelected: (String) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var currentSlot by remember { mutableStateOf<String?>(null) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
currentSlot = getCurrentSlot(context)
|
||||
errorMessage = null
|
||||
} catch (e: Exception) {
|
||||
errorMessage = e.message
|
||||
currentSlot = null
|
||||
}
|
||||
}
|
||||
|
||||
if (show) {
|
||||
val backgroundColor = if (!ThemeConfig.useDynamicColor) {
|
||||
ThemeConfig.currentTheme.ButtonContrast.copy(alpha = 1.0f)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1.0f)
|
||||
}
|
||||
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Card(
|
||||
shape = MaterialTheme.shapes.medium.copy(
|
||||
topStart = CornerSize(16.dp),
|
||||
topEnd = CornerSize(16.dp),
|
||||
bottomEnd = CornerSize(16.dp),
|
||||
bottomStart = CornerSize(16.dp)
|
||||
),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = backgroundColor
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(24.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.select_slot_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
if (errorMessage != null) {
|
||||
Text(
|
||||
text = "Error: $errorMessage",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.current_slot,
|
||||
currentSlot ?: "Unknown"
|
||||
),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.select_slot_description),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
val isDefaultSlotA = currentSlot == "_a" || currentSlot == "a"
|
||||
Button(
|
||||
onClick = { onSlotSelected("a") },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isDefaultSlotA)
|
||||
MaterialTheme.colorScheme.primary
|
||||
else
|
||||
MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = if (isDefaultSlotA)
|
||||
MaterialTheme.colorScheme.onPrimary
|
||||
else
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.slot_a),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
|
||||
val isDefaultSlotB = currentSlot == "_b" || currentSlot == "b"
|
||||
Button(
|
||||
onClick = { onSlotSelected("b") },
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isDefaultSlotB)
|
||||
MaterialTheme.colorScheme.secondary
|
||||
else
|
||||
MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = if (isDefaultSlotB)
|
||||
MaterialTheme.colorScheme.onSecondary
|
||||
else
|
||||
MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.slot_b),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
thickness = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
currentSlot?.let { onSlotSelected(it) }
|
||||
onDismiss()
|
||||
},
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(id = android.R.string.ok))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前槽位信息
|
||||
private fun getCurrentSlot(context: Context): String? {
|
||||
return runCommandGetOutput(true, "getprop ro.boot.slot_suffix")?.let {
|
||||
if (it.startsWith("_")) it.substring(1) else it
|
||||
}
|
||||
}
|
||||
|
||||
private fun runCommandGetOutput(su: Boolean, cmd: String): String? {
|
||||
return try {
|
||||
val process = ProcessBuilder(if (su) "su" else "sh").start()
|
||||
process.outputStream.bufferedWriter().use { writer ->
|
||||
writer.write("$cmd\n")
|
||||
writer.write("exit\n")
|
||||
writer.flush()
|
||||
}
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.readText().trim()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component.profile
|
||||
package com.sukisu.ultra.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 zako.zako.zako.Natives
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.component.SwitchItem
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.SwitchItem
|
||||
|
||||
@Composable
|
||||
fun AppProfileConfig(
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component.profile
|
||||
package com.sukisu.ultra.ui.component.profile
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -42,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 zako.zako.zako.Natives
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.profile.Capabilities
|
||||
import zako.zako.zako.profile.Groups
|
||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
||||
import zako.zako.zako.ui.util.isSepolicyValid
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.profile.Capabilities
|
||||
import com.sukisu.ultra.profile.Groups
|
||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||
import com.sukisu.ultra.ui.util.isSepolicyValid
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.component.profile
|
||||
package com.sukisu.ultra.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 zako.zako.zako.Natives
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.util.listAppProfileTemplates
|
||||
import zako.zako.zako.ui.util.setSepolicy
|
||||
import zako.zako.zako.ui.viewmodel.getTemplateInfoById
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.util.listAppProfileTemplates
|
||||
import com.sukisu.ultra.ui.util.setSepolicy
|
||||
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.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 zako.zako.zako.Natives
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.component.SwitchItem
|
||||
import zako.zako.zako.ui.component.profile.AppProfileConfig
|
||||
import zako.zako.zako.ui.component.profile.RootProfileConfig
|
||||
import zako.zako.zako.ui.component.profile.TemplateConfig
|
||||
import zako.zako.zako.ui.util.LocalSnackbarHost
|
||||
import zako.zako.zako.ui.util.forceStopApp
|
||||
import zako.zako.zako.ui.util.getSepolicy
|
||||
import zako.zako.zako.ui.util.launchApp
|
||||
import zako.zako.zako.ui.util.restartApp
|
||||
import zako.zako.zako.ui.util.setSepolicy
|
||||
import zako.zako.zako.ui.viewmodel.SuperUserViewModel
|
||||
import zako.zako.zako.ui.viewmodel.getTemplateInfoById
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.SwitchItem
|
||||
import com.sukisu.ultra.ui.component.profile.AppProfileConfig
|
||||
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||
import com.sukisu.ultra.ui.component.profile.TemplateConfig
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.forceStopApp
|
||||
import com.sukisu.ultra.ui.util.getSepolicy
|
||||
import com.sukisu.ultra.ui.util.launchApp
|
||||
import com.sukisu.ultra.ui.util.restartApp
|
||||
import com.sukisu.ultra.ui.util.setSepolicy
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -11,7 +11,7 @@ import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDe
|
||||
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
|
||||
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
|
||||
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
enum class BottomBarDestination(
|
||||
val direction: DirectionDestinationSpec,
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.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 zako.zako.zako.R
|
||||
import zako.zako.zako.ui.component.KeyEventBlocker
|
||||
import zako.zako.zako.ui.util.LocalSnackbarHost
|
||||
import zako.zako.zako.ui.util.runModuleAction
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.KeyEventBlocker
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.runModuleAction
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
@@ -30,9 +30,9 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import zako.zako.zako.ui.component.KeyEventBlocker
|
||||
import zako.zako.zako.ui.util.*
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.ui.component.KeyEventBlocker
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.R
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -45,10 +45,6 @@ enum class FlashingStatus {
|
||||
|
||||
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
|
||||
|
||||
fun getFlashingStatus(): FlashingStatus {
|
||||
return currentFlashingStatus.value
|
||||
}
|
||||
|
||||
fun setFlashingStatus(status: FlashingStatus) {
|
||||
currentFlashingStatus.value = status
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
@@ -33,15 +33,15 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import zako.zako.zako.*
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
||||
import zako.zako.zako.ui.util.*
|
||||
import zako.zako.zako.ui.util.module.LatestVersionInfo
|
||||
import com.sukisu.ultra.*
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import zako.zako.zako.ui.theme.getCardColors
|
||||
import zako.zako.zako.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -49,7 +49,8 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import zako.zako.zako.ui.theme.CardConfig
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
import androidx.core.content.edit
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
@@ -92,11 +93,9 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||
val deviceModel = getDeviceModel(context)
|
||||
val ksuVersion = if (isManager) Natives.version else null
|
||||
val managerVersion = getManagerVersion(context).second
|
||||
val Zako = "一.*加.*A.*c.*e.*5.*P.*r.*o".toRegex().matches(deviceModel)
|
||||
val zako = "一.*加.*A.*c.*e.*5.*P.*r.*o".toRegex().matches(deviceModel)
|
||||
val isVersion = ksuVersion == 12777
|
||||
val isManagerVersionValid = managerVersion > (ksuVersion ?: 0) + 33
|
||||
val shouldTriggerRestart = Zako && kernelVersion.isGKI() && (isVersion || isManagerVersionValid)
|
||||
val shouldTriggerRestart = zako && kernelVersion.isGKI() && (isVersion)
|
||||
|
||||
LaunchedEffect(shouldTriggerRestart) {
|
||||
if (shouldTriggerRestart) {
|
||||
@@ -285,22 +284,27 @@ private fun TopBar(
|
||||
}
|
||||
|
||||
var showDropdown by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { showDropdown = true }) {
|
||||
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
|
||||
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
|
||||
) {
|
||||
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||
IconButton(onClick = { showDropdown = true }) {
|
||||
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
|
||||
DropdownMenu(
|
||||
expanded = showDropdown,
|
||||
onDismissRequest = { showDropdown = false }
|
||||
) {
|
||||
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
RebootDropdownItem(id = R.string.reboot)
|
||||
|
||||
val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
val pm =
|
||||
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||
@Suppress("DEPRECATION")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
||||
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
|
||||
}
|
||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
}
|
||||
RebootDropdownItem(id = R.string.reboot_recovery, reason = "recovery")
|
||||
RebootDropdownItem(id = R.string.reboot_bootloader, reason = "bootloader")
|
||||
RebootDropdownItem(id = R.string.reboot_download, reason = "download")
|
||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -309,6 +313,7 @@ private fun TopBar(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun StatusCard(
|
||||
kernelVersion: KernelVersion,
|
||||
@@ -557,6 +562,7 @@ fun DonateCard() {
|
||||
|
||||
@Composable
|
||||
private fun InfoCard() {
|
||||
val lkmMode = Natives.isLkmMode
|
||||
val context = LocalContext.current
|
||||
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
.getBoolean("is_simple_mode", false)
|
||||
@@ -568,7 +574,7 @@ private fun InfoCard() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
|
||||
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
) withContext@{
|
||||
val contents = StringBuilder()
|
||||
val uname = Os.uname()
|
||||
@@ -577,58 +583,61 @@ private fun InfoCard() {
|
||||
fun InfoCardItem(
|
||||
label: String,
|
||||
content: String,
|
||||
icon: ImageVector = Icons.Default.Info
|
||||
) {
|
||||
contents.appendLine(label).appendLine(content).appendLine()
|
||||
Text(text = label, style = MaterialTheme.typography.bodyLarge)
|
||||
Text(text = content, style = MaterialTheme.typography.bodyMedium)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = label,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
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, icon = Icons.Default.Memory)
|
||||
|
||||
if (!isSimpleMode) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val androidVersion = Build.VERSION.RELEASE
|
||||
InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
|
||||
InfoCardItem(stringResource(R.string.home_android_version), androidVersion, icon = Icons.Default.Android)
|
||||
}
|
||||
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val deviceModel = getDeviceModel(context)
|
||||
InfoCardItem(stringResource(R.string.home_device_model), deviceModel)
|
||||
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_device_model), deviceModel, icon = Icons.Default.PhoneAndroid)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
val managerVersion = getManagerVersion(context)
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_manager_version),
|
||||
"${managerVersion.first} (${managerVersion.second})"
|
||||
)
|
||||
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings)
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
|
||||
|
||||
|
||||
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security)
|
||||
|
||||
if (!isSimpleMode) {
|
||||
val kpmVersion = getKpmVersion()
|
||||
var displayVersion: String
|
||||
val isKpmConfigured = checkKpmConfigured()
|
||||
if (lkmMode != true) {
|
||||
val kpmVersion = getKpmVersion()
|
||||
var displayVersion: String
|
||||
val isKpmConfigured = checkKpmConfigured()
|
||||
|
||||
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
|
||||
val statusText = if (isKpmConfigured) {
|
||||
stringResource(R.string.kernel_patched)
|
||||
if (kpmVersion.isEmpty() || kpmVersion.startsWith("Error")) {
|
||||
val statusText = if (isKpmConfigured) {
|
||||
stringResource(R.string.kernel_patched)
|
||||
} else {
|
||||
stringResource(R.string.kernel_not_enabled)
|
||||
}
|
||||
displayVersion = "${stringResource(R.string.not_supported)} ($statusText)"
|
||||
} else {
|
||||
stringResource(R.string.kernel_not_enabled)
|
||||
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
|
||||
}
|
||||
displayVersion = "${stringResource(R.string.not_supported)} ($statusText)"
|
||||
} else {
|
||||
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion, icon = Icons.Default.Code)
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion)
|
||||
}
|
||||
|
||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
@@ -653,9 +662,7 @@ private fun InfoCard() {
|
||||
}
|
||||
}
|
||||
InfoCardItem(
|
||||
stringResource(R.string.home_susfs_version),
|
||||
infoText
|
||||
)
|
||||
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -709,7 +716,7 @@ private fun getDeviceModel(context: Context): String {
|
||||
}
|
||||
}
|
||||
Build.DEVICE
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
Build.DEVICE
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity
|
||||
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.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -28,26 +32,25 @@ 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.maxkeppeler.sheets.list.models.ListOption
|
||||
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 zako.zako.zako.ui.component.DialogHandle
|
||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
||||
import zako.zako.zako.ui.theme.ThemeConfig
|
||||
import zako.zako.zako.ui.theme.getCardColors
|
||||
import zako.zako.zako.ui.theme.getCardElevation
|
||||
import zako.zako.zako.ui.util.*
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.utils.AssetsUtil
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.DialogHandle
|
||||
import com.sukisu.ultra.ui.component.SlotSelectionDialog
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||
import com.sukisu.ultra.flash.HorizonKernelFlashProgress
|
||||
import com.sukisu.ultra.flash.HorizonKernelState
|
||||
import com.sukisu.ultra.flash.HorizonKernelWorker
|
||||
import com.sukisu.ultra.ui.theme.CardConfig
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -60,8 +63,12 @@ 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) }
|
||||
var showSlotSelectionDialog by remember { mutableStateOf(false) }
|
||||
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
|
||||
val horizonKernelState = remember { HorizonKernelState() }
|
||||
val flashState by horizonKernelState.state.collectAsState()
|
||||
val summary = stringResource(R.string.horizon_kernel_summary)
|
||||
|
||||
val onFlashComplete = {
|
||||
showRebootDialog = true
|
||||
@@ -79,7 +86,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
writer.write("svc power reboot\n")
|
||||
writer.write("exit\n")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
} catch (_: Exception) {
|
||||
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
@@ -91,7 +98,11 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
when (method) {
|
||||
is InstallMethod.HorizonKernel -> {
|
||||
method.uri?.let { uri ->
|
||||
val worker = HorizonKernelWorker(context)
|
||||
val worker = HorizonKernelWorker(
|
||||
context = context,
|
||||
state = horizonKernelState,
|
||||
slot = method.slot
|
||||
)
|
||||
worker.uri = uri
|
||||
worker.setOnFlashCompleteListener(onFlashComplete)
|
||||
worker.start()
|
||||
@@ -110,6 +121,22 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
Unit
|
||||
}
|
||||
|
||||
// 槽位选择
|
||||
SlotSelectionDialog(
|
||||
show = showSlotSelectionDialog,
|
||||
onDismiss = { showSlotSelectionDialog = false },
|
||||
onSlotSelected = { slot ->
|
||||
showSlotSelectionDialog = false
|
||||
val horizonMethod = InstallMethod.HorizonKernel(
|
||||
uri = tempKernelUri,
|
||||
slot = slot,
|
||||
summary = summary
|
||||
)
|
||||
installMethod = horizonMethod
|
||||
onInstall()
|
||||
}
|
||||
)
|
||||
|
||||
val currentKmi by produceState(initialValue = "") {
|
||||
value = getCurrentKmi()
|
||||
}
|
||||
@@ -165,8 +192,24 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SelectInstallMethod { method ->
|
||||
installMethod = method
|
||||
SelectInstallMethod(
|
||||
onSelected = { method ->
|
||||
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
|
||||
tempKernelUri = method.uri
|
||||
showSlotSelectionDialog = true
|
||||
} else {
|
||||
installMethod = method
|
||||
}
|
||||
horizonKernelState.reset()
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = flashState.isFlashing && installMethod is InstallMethod.HorizonKernel,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically()
|
||||
) {
|
||||
HorizonKernelFlashProgress(flashState)
|
||||
}
|
||||
|
||||
Column(
|
||||
@@ -182,9 +225,20 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
)
|
||||
}
|
||||
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
|
||||
if (method.slot != null) {
|
||||
Text(
|
||||
stringResource(
|
||||
id = R.string.selected_slot,
|
||||
if (method.slot == "a") stringResource(id = R.string.slot_a)
|
||||
else stringResource(id = R.string.slot_b)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = installMethod != null,
|
||||
enabled = installMethod != null && !flashState.isFlashing,
|
||||
onClick = onClickNext
|
||||
) {
|
||||
Text(
|
||||
@@ -197,7 +251,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun RebootDialog(
|
||||
show: Boolean,
|
||||
@@ -223,133 +276,6 @@ private fun RebootDialog(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
@@ -369,6 +295,7 @@ sealed class InstallMethod {
|
||||
|
||||
data class HorizonKernel(
|
||||
val uri: Uri? = null,
|
||||
val slot: String? = null,
|
||||
@StringRes override val label: Int = R.string.horizon_kernel,
|
||||
override val summary: String? = null
|
||||
) : InstallMethod()
|
||||
@@ -381,6 +308,7 @@ sealed class InstallMethod {
|
||||
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
val rootAvailable = rootAvailable()
|
||||
val isAbDevice = isAbDevice()
|
||||
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
||||
val selectFileTip = stringResource(
|
||||
id = R.string.select_file_tip,
|
||||
if (isInitBoot()) "init_boot" else "boot"
|
||||
@@ -395,7 +323,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
if (isAbDevice) {
|
||||
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
||||
}
|
||||
radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
|
||||
radioOptions.add(InstallMethod.HorizonKernel(summary = horizonKernelSummary))
|
||||
}
|
||||
|
||||
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
||||
@@ -408,7 +336,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||
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")
|
||||
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = horizonKernelSummary)
|
||||
else -> null
|
||||
}
|
||||
option?.let {
|
||||
@@ -580,8 +508,15 @@ private fun TopBar(
|
||||
onLkmUpload: () -> Unit = {},
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||
) {
|
||||
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
val cardAlpha = CardConfig.cardAlpha
|
||||
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.install)) },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Context
|
||||
@@ -25,30 +25,23 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import zako.zako.zako.ui.component.ConfirmResult
|
||||
import zako.zako.zako.ui.component.SearchAppBar
|
||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
||||
import zako.zako.zako.ui.theme.getCardColors
|
||||
import zako.zako.zako.ui.theme.getCardElevation
|
||||
import zako.zako.zako.ui.viewmodel.KpmViewModel
|
||||
import zako.zako.zako.ui.util.loadKpmModule
|
||||
import zako.zako.zako.ui.util.unloadKpmModule
|
||||
import com.sukisu.ultra.ui.component.*
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.viewmodel.KpmViewModel
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import java.io.File
|
||||
import androidx.core.content.edit
|
||||
import zako.zako.zako.ui.theme.ThemeConfig
|
||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
||||
import zako.zako.zako.ui.component.ConfirmDialogHandle
|
||||
import zako.zako.zako.R
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import com.sukisu.ultra.R
|
||||
import java.io.BufferedReader
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.*
|
||||
|
||||
/**
|
||||
* KPM 管理界面
|
||||
* 以下内核模块功能由KernelPatch开发,经过修改后加入SukiSU Ultra的内核模块功能
|
||||
* 开发者:zako, Liaokong
|
||||
* 开发者:ShirkNeko, Liaokong
|
||||
*/
|
||||
var globalModuleFileName: String = ""
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
@@ -66,6 +59,11 @@ fun KpmScreen(
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
}
|
||||
|
||||
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
||||
val moduleFileName = module.id
|
||||
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
|
||||
}
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
||||
@@ -78,13 +76,33 @@ fun KpmScreen(
|
||||
val kpmInstallMode = stringResource(R.string.kpm_install_mode)
|
||||
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
|
||||
val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed)
|
||||
val kpmInstallModeDescription = stringResource(R.string.kpm_install_mode_description)
|
||||
val invalidFileTypeMessage = stringResource(R.string.invalid_file_type)
|
||||
val confirmTitle = stringResource(R.string.confirm_uninstall_title_with_filename)
|
||||
val confirmContent = stringResource(R.string.confirm_uninstall_content, globalModuleFileName)
|
||||
|
||||
var tempFileForInstall by remember { mutableStateOf<File?>(null) }
|
||||
val installModeDialog = rememberCustomDialog { dismiss ->
|
||||
var moduleName by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(tempFileForInstall) {
|
||||
tempFileForInstall?.let { tempFile ->
|
||||
try {
|
||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val inputStream = process.inputStream
|
||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line!!.startsWith("name=")) {
|
||||
moduleName = line.substringAfter("name=").trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
process.waitFor()
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to get module name: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
dismiss()
|
||||
@@ -92,7 +110,7 @@ fun KpmScreen(
|
||||
tempFileForInstall = null
|
||||
},
|
||||
title = { Text(kpmInstallMode) },
|
||||
text = { Text(kpmInstallModeDescription) },
|
||||
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } },
|
||||
confirmButton = {
|
||||
Column {
|
||||
Button(
|
||||
@@ -170,15 +188,30 @@ fun KpmScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (!tempFile.name.endsWith(".kpm")) {
|
||||
snackBarHost.showSnackbar(
|
||||
message = invalidFileTypeMessage,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
val mimeType = context.contentResolver.getType(uri)
|
||||
val isCorrectMimeType = mimeType == null || mimeType.contains("application/octet-stream")
|
||||
|
||||
if (!isCorrectMimeType) {
|
||||
var shouldShowSnackbar = true
|
||||
try {
|
||||
val matchCount = checkStringsCommand(tempFile)
|
||||
val isElf = isElfFile(tempFile)
|
||||
|
||||
if (matchCount >= 1 || isElf) {
|
||||
shouldShowSnackbar = false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to execute checks: ${e.message}", e)
|
||||
}
|
||||
if (shouldShowSnackbar) {
|
||||
snackBarHost.showSnackbar(
|
||||
message = invalidFileTypeMessage,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
tempFile.delete()
|
||||
return@launch
|
||||
}
|
||||
|
||||
tempFileForInstall = tempFile
|
||||
installModeDialog.show()
|
||||
}
|
||||
@@ -217,7 +250,7 @@ fun KpmScreen(
|
||||
onClick = {
|
||||
selectPatchLauncher.launch(
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
type = "*/*"
|
||||
type = "application/octet-stream"
|
||||
}
|
||||
)
|
||||
},
|
||||
@@ -281,6 +314,7 @@ fun KpmScreen(
|
||||
module = module,
|
||||
onUninstall = {
|
||||
scope.launch {
|
||||
val confirmContent = moduleConfirmContentMap[module.id] ?: ""
|
||||
handleModuleUninstall(
|
||||
module = module,
|
||||
viewModel = viewModel,
|
||||
@@ -315,8 +349,25 @@ private suspend fun handleModuleInstall(
|
||||
kpmInstallSuccess: String,
|
||||
kpmInstallFailed: String
|
||||
) {
|
||||
val moduleId = extractModuleId(tempFile.name)
|
||||
if (moduleId == null) {
|
||||
var moduleId: String? = null
|
||||
try {
|
||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val inputStream = process.inputStream
|
||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (line!!.startsWith("name=")) {
|
||||
moduleId = line.substringAfter("name=").trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
process.waitFor()
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to get module ID from strings command: ${e.message}", e)
|
||||
}
|
||||
|
||||
if (moduleId == null || moduleId.isEmpty()) {
|
||||
Log.e("KsuCli", "Failed to extract module ID from file: ${tempFile.name}")
|
||||
snackBarHost.showSnackbar(
|
||||
message = kpmInstallFailed,
|
||||
@@ -358,18 +409,6 @@ private suspend fun handleModuleInstall(
|
||||
tempFile.delete()
|
||||
}
|
||||
|
||||
private fun extractModuleId(fileName: String): String? {
|
||||
return try {
|
||||
val decodedFileName = URLDecoder.decode(fileName, "UTF-8")
|
||||
val pattern = "([^/]*?)\\.kpm$".toRegex()
|
||||
val matchResult = pattern.find(decodedFileName)
|
||||
matchResult?.groupValues?.get(1)
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to extract module ID: ${e.message}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleModuleUninstall(
|
||||
module: KpmViewModel.ModuleInfo,
|
||||
viewModel: KpmViewModel,
|
||||
@@ -384,7 +423,6 @@ private suspend fun handleModuleUninstall(
|
||||
confirmDialog: ConfirmDialogHandle
|
||||
) {
|
||||
val moduleFileName = "${module.id}.kpm"
|
||||
globalModuleFileName = moduleFileName
|
||||
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
||||
|
||||
val fileExists = try {
|
||||
@@ -553,4 +591,41 @@ private fun KpmModuleItem(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkStringsCommand(tempFile: File): Int {
|
||||
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='")
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val inputStream = process.inputStream
|
||||
val reader = BufferedReader(InputStreamReader(inputStream))
|
||||
var line: String?
|
||||
var matchCount = 0
|
||||
val keywords = listOf("name=", "version=", "license=", "author=")
|
||||
var nameExists = false
|
||||
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
if (!nameExists && line!!.startsWith("name=")) {
|
||||
nameExists = true
|
||||
matchCount++
|
||||
} else if (nameExists) {
|
||||
for (keyword in keywords) {
|
||||
if (line!!.startsWith(keyword)) {
|
||||
matchCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
process.waitFor()
|
||||
|
||||
return if (nameExists) matchCount else 0
|
||||
}
|
||||
|
||||
private fun isElfFile(tempFile: File): Boolean {
|
||||
val elfMagic = byteArrayOf(0x7F, 'E'.code.toByte(), 'L'.code.toByte(), 'F'.code.toByte())
|
||||
val fileBytes = ByteArray(4)
|
||||
FileInputStream(tempFile).use { input ->
|
||||
input.read(fileBytes)
|
||||
}
|
||||
return fileBytes.contentEquals(elfMagic)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.app.Activity.*
|
||||
import android.content.Context
|
||||
@@ -88,32 +88,32 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.ui.component.ConfirmResult
|
||||
import zako.zako.zako.ui.component.SearchAppBar
|
||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
||||
import zako.zako.zako.ui.component.rememberLoadingDialog
|
||||
import zako.zako.zako.ui.util.DownloadListener
|
||||
import zako.zako.zako.ui.util.*
|
||||
import zako.zako.zako.ui.util.download
|
||||
import zako.zako.zako.ui.util.hasMagisk
|
||||
import zako.zako.zako.ui.util.reboot
|
||||
import zako.zako.zako.ui.util.restoreModule
|
||||
import zako.zako.zako.ui.util.toggleModule
|
||||
import zako.zako.zako.ui.util.uninstallModule
|
||||
import zako.zako.zako.ui.webui.WebUIActivity
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||
import com.sukisu.ultra.ui.component.SearchAppBar
|
||||
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||
import com.sukisu.ultra.ui.component.rememberLoadingDialog
|
||||
import com.sukisu.ultra.ui.util.DownloadListener
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import com.sukisu.ultra.ui.util.download
|
||||
import com.sukisu.ultra.ui.util.hasMagisk
|
||||
import com.sukisu.ultra.ui.util.reboot
|
||||
import com.sukisu.ultra.ui.util.restoreModule
|
||||
import com.sukisu.ultra.ui.util.toggleModule
|
||||
import com.sukisu.ultra.ui.util.uninstallModule
|
||||
import com.sukisu.ultra.ui.webui.WebUIActivity
|
||||
import okhttp3.OkHttpClient
|
||||
import zako.zako.zako.ui.util.ModuleModify
|
||||
import zako.zako.zako.ui.theme.getCardColors
|
||||
import zako.zako.zako.ui.theme.getCardElevation
|
||||
import zako.zako.zako.ui.viewmodel.ModuleViewModel
|
||||
import com.sukisu.ultra.ui.util.ModuleModify
|
||||
import com.sukisu.ultra.ui.theme.getCardColors
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.util.zip.ZipInputStream
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import zako.zako.zako.ui.theme.ThemeConfig
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import android.content.Context
|
||||
@@ -26,8 +26,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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
|
||||
@@ -37,21 +35,13 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import zako.zako.zako.ui.component.SwitchItem
|
||||
import zako.zako.zako.ui.theme.CardConfig
|
||||
import zako.zako.zako.ui.theme.ThemeColors
|
||||
import zako.zako.zako.ui.theme.ThemeConfig
|
||||
import zako.zako.zako.ui.theme.saveCustomBackground
|
||||
import zako.zako.zako.ui.theme.saveThemeColors
|
||||
import zako.zako.zako.ui.theme.saveThemeMode
|
||||
import zako.zako.zako.ui.theme.saveDynamicColorState
|
||||
import zako.zako.zako.ui.util.getSuSFS
|
||||
import zako.zako.zako.ui.util.getSuSFSFeatures
|
||||
import zako.zako.zako.ui.util.susfsSUS_SU_0
|
||||
import zako.zako.zako.ui.util.susfsSUS_SU_2
|
||||
import zako.zako.zako.ui.util.susfsSUS_SU_Mode
|
||||
import com.sukisu.ultra.ui.component.ImageEditorDialog
|
||||
import com.sukisu.ultra.ui.component.SwitchItem
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
import androidx.core.content.edit
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.*
|
||||
|
||||
|
||||
fun saveCardConfig(context: Context) {
|
||||
@@ -138,7 +128,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
prefs.edit { putBoolean("is_hide_susfs_status", newValue) }
|
||||
isHideSusfsStatus = newValue
|
||||
}
|
||||
|
||||
|
||||
// SELinux 状态
|
||||
var selinuxEnabled by remember {
|
||||
mutableStateOf(Shell.cmd("getenforce").exec().out.firstOrNull() == "Enforcing")
|
||||
@@ -151,6 +141,10 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
mutableStateOf(ThemeConfig.customBackgroundUri != null)
|
||||
}
|
||||
|
||||
// 图片编辑状态
|
||||
var showImageEditor by remember { mutableStateOf(false) }
|
||||
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 初始化卡片配置
|
||||
val systemIsDark = isSystemInDarkTheme()
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -187,19 +181,38 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
|
||||
var showThemeColorDialog by remember { mutableStateOf(false) }
|
||||
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
||||
|
||||
// 图片选择器
|
||||
val pickImageLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.GetContent()
|
||||
) { uri: Uri? ->
|
||||
uri?.let {
|
||||
context.saveCustomBackground(it)
|
||||
isCustomBackgroundEnabled = true
|
||||
CardConfig.cardElevation = 0.dp
|
||||
saveCardConfig(context)
|
||||
selectedImageUri = it
|
||||
showImageEditor = true
|
||||
}
|
||||
}
|
||||
|
||||
// 显示图片编辑对话框
|
||||
if (showImageEditor && selectedImageUri != null) {
|
||||
ImageEditorDialog(
|
||||
imageUri = selectedImageUri!!,
|
||||
onDismiss = {
|
||||
showImageEditor = false
|
||||
selectedImageUri = null
|
||||
},
|
||||
onConfirm = { transformedUri ->
|
||||
context.saveAndApplyCustomBackground(transformedUri)
|
||||
isCustomBackgroundEnabled = true
|
||||
CardConfig.cardElevation = 0.dp
|
||||
CardConfig.isCustomBackgroundEnabled = true
|
||||
saveCardConfig(context)
|
||||
showImageEditor = false
|
||||
selectedImageUri = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -220,17 +233,19 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
.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
|
||||
if (ksuIsValid) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +462,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
CardConfig.cardElevation = CardConfig.defaultElevation
|
||||
CardConfig.cardAlpha = 0.45f
|
||||
CardConfig.isCustomAlphaSet = false
|
||||
CardConfig.isCustomBackgroundEnabled = false
|
||||
saveCardConfig(context)
|
||||
cardAlpha = 0.35f
|
||||
themeMode = 0
|
||||
@@ -459,45 +475,45 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
}
|
||||
)
|
||||
// 透明度 Slider
|
||||
AnimatedVisibility(
|
||||
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
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
|
||||
CardConfig.isCustomAlphaSet = true
|
||||
prefs.edit { putBoolean("is_custom_alpha_set", true) }
|
||||
prefs.edit { putFloat("card_alpha", newValue) }
|
||||
},
|
||||
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)
|
||||
)
|
||||
// 透明度 Slider
|
||||
AnimatedVisibility(
|
||||
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
) {
|
||||
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
|
||||
CardConfig.isCustomAlphaSet = true
|
||||
prefs.edit { putBoolean("is_custom_alpha_set", true) }
|
||||
prefs.edit { putFloat("card_alpha", newValue) }
|
||||
},
|
||||
onValueChangeFinished = {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
saveCardConfig(context)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
){
|
||||
},
|
||||
valueRange = 0f..1f,
|
||||
colors = getSliderColors(cardAlpha, useCustomColors = true),
|
||||
thumb = {
|
||||
SliderDefaults.Thumb(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
thumbSize = DpSize(0.dp, 0.dp)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = ThemeConfig.customBackgroundUri != null && showCardSettings,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
|
||||
){
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.DarkMode, null) },
|
||||
headlineContent = { Text(stringResource(R.string.theme_mode)) },
|
||||
@@ -506,69 +522,69 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
||||
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)
|
||||
when (index) {
|
||||
2 -> {
|
||||
ThemeConfig.forceDarkMode = true
|
||||
CardConfig.isUserLightModeEnabled = false
|
||||
CardConfig.isUserDarkModeEnabled = true
|
||||
CardConfig.save(context)
|
||||
}
|
||||
1 -> {
|
||||
ThemeConfig.forceDarkMode = false
|
||||
CardConfig.isUserLightModeEnabled = true
|
||||
CardConfig.isUserDarkModeEnabled = false
|
||||
CardConfig.save(context)
|
||||
}
|
||||
0 -> {
|
||||
ThemeConfig.forceDarkMode = null
|
||||
CardConfig.isUserLightModeEnabled = false
|
||||
CardConfig.isUserDarkModeEnabled = false
|
||||
CardConfig.save(context)
|
||||
}
|
||||
}
|
||||
showThemeModeDialog = false
|
||||
// 主题模式选择对话框
|
||||
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)
|
||||
when (index) {
|
||||
2 -> {
|
||||
ThemeConfig.forceDarkMode = true
|
||||
CardConfig.isUserLightModeEnabled = false
|
||||
CardConfig.isUserDarkModeEnabled = true
|
||||
CardConfig.save(context)
|
||||
}
|
||||
.padding(vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = themeMode == index,
|
||||
onClick = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(option)
|
||||
1 -> {
|
||||
ThemeConfig.forceDarkMode = false
|
||||
CardConfig.isUserLightModeEnabled = true
|
||||
CardConfig.isUserDarkModeEnabled = false
|
||||
CardConfig.save(context)
|
||||
}
|
||||
0 -> {
|
||||
ThemeConfig.forceDarkMode = null
|
||||
CardConfig.isUserLightModeEnabled = false
|
||||
CardConfig.isUserDarkModeEnabled = false
|
||||
CardConfig.save(context)
|
||||
}
|
||||
}
|
||||
showThemeModeDialog = false
|
||||
}
|
||||
}
|
||||
.padding(vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = themeMode == index,
|
||||
onClick = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(option)
|
||||
}
|
||||
},
|
||||
confirmButton = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
@@ -612,4 +628,4 @@ private fun getSliderColors(cardAlpha: Float, useCustomColors: Boolean = false):
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -7,32 +7,14 @@ import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.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.Undo
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
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.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -64,22 +46,14 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import zako.zako.zako.BuildConfig
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.component.AboutDialog
|
||||
import zako.zako.zako.ui.component.ConfirmResult
|
||||
import zako.zako.zako.ui.component.DialogHandle
|
||||
import zako.zako.zako.ui.component.SwitchItem
|
||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
||||
import zako.zako.zako.ui.component.rememberLoadingDialog
|
||||
import zako.zako.zako.ui.theme.CardConfig
|
||||
import zako.zako.zako.ui.theme.ThemeConfig
|
||||
import zako.zako.zako.ui.theme.getCardColors
|
||||
import zako.zako.zako.ui.theme.getCardElevation
|
||||
import zako.zako.zako.ui.util.LocalSnackbarHost
|
||||
import zako.zako.zako.ui.util.getBugreportFile
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.*
|
||||
import com.sukisu.ultra.ui.component.*
|
||||
import com.sukisu.ultra.ui.theme.*
|
||||
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||
import com.sukisu.ultra.ui.util.getBugreportFile
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@@ -95,6 +69,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
// region 界面基础设置
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val snackBarHost = LocalSnackbarHost.current
|
||||
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
||||
// endregion
|
||||
|
||||
Scaffold(
|
||||
@@ -142,6 +117,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
// region 配置项列表
|
||||
// 配置文件模板入口
|
||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||
if (ksuIsValid) {
|
||||
ListItem(
|
||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||
headlineContent = { Text(profileTemplate) },
|
||||
@@ -150,34 +126,40 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||
}
|
||||
)
|
||||
}
|
||||
// 卸载模块开关
|
||||
var umountChecked by rememberSaveable {
|
||||
mutableStateOf(Natives.isDefaultUmountModules())
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.FolderDelete,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
}
|
||||
|
||||
if (ksuIsValid) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.FolderDelete,
|
||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||
summary = stringResource(id = R.string.settings_umount_modules_default_summary),
|
||||
checked = umountChecked
|
||||
) {
|
||||
if (Natives.setDefaultUmountModules(it)) {
|
||||
umountChecked = it
|
||||
}
|
||||
}
|
||||
}
|
||||
// SU 禁用开关(仅在兼容版本显示)
|
||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||
var isSuDisabled by rememberSaveable {
|
||||
mutableStateOf(!Natives.isSuEnabled())
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_disable_su),
|
||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||
checked = isSuDisabled,
|
||||
) { checked ->
|
||||
val shouldEnable = !checked
|
||||
if (Natives.setSuEnabled(shouldEnable)) {
|
||||
isSuDisabled = !shouldEnable
|
||||
if (ksuIsValid) {
|
||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||
var isSuDisabled by rememberSaveable {
|
||||
mutableStateOf(!Natives.isSuEnabled())
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.RemoveModerator,
|
||||
title = stringResource(id = R.string.settings_disable_su),
|
||||
summary = stringResource(id = R.string.settings_disable_su_summary),
|
||||
checked = isSuDisabled,
|
||||
) { checked ->
|
||||
val shouldEnable = !checked
|
||||
if (Natives.setSuEnabled(shouldEnable)) {
|
||||
isSuDisabled = !shouldEnable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,14 +188,16 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
||||
prefs.getBoolean("enable_web_debugging", false)
|
||||
)
|
||||
}
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.DeveloperMode,
|
||||
title = stringResource(id = R.string.enable_web_debugging),
|
||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||
checked = enableWebDebugging
|
||||
) {
|
||||
prefs.edit { putBoolean("enable_web_debugging", it) }
|
||||
enableWebDebugging = it
|
||||
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||
SwitchItem(
|
||||
icon = Icons.Filled.DeveloperMode,
|
||||
title = stringResource(id = R.string.enable_web_debugging),
|
||||
summary = stringResource(id = R.string.enable_web_debugging_summary),
|
||||
checked = enableWebDebugging
|
||||
) {
|
||||
prefs.edit { putBoolean("enable_web_debugging", it) }
|
||||
enableWebDebugging = it
|
||||
}
|
||||
}
|
||||
// 更多设置
|
||||
val newButtonTitle = stringResource(id = R.string.more_settings)
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
@@ -24,7 +24,7 @@ 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 zako.zako.zako.R
|
||||
import com.sukisu.ultra.R
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -32,10 +32,10 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.launch
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.ui.component.SearchAppBar
|
||||
import zako.zako.zako.ui.util.ModuleModify
|
||||
import zako.zako.zako.ui.viewmodel.SuperUserViewModel
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.component.SearchAppBar
|
||||
import com.sukisu.ultra.ui.util.ModuleModify
|
||||
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@@ -1,5 +1,7 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -44,11 +46,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
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.core.content.getSystemService
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -59,9 +60,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
|
||||
import com.ramcosta.composedestinations.result.getOr
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.theme.ThemeConfig
|
||||
import zako.zako.zako.ui.viewmodel.TemplateViewModel
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -99,8 +100,8 @@ fun AppProfileTemplateScreen(
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val context = LocalContext.current
|
||||
val clipboardManager = context.getSystemService<ClipboardManager>()
|
||||
val showToast = fun(msg: String) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
||||
@@ -112,20 +113,20 @@ fun AppProfileTemplateScreen(
|
||||
scope.launch { viewModel.fetchTemplates(true) }
|
||||
},
|
||||
onImport = {
|
||||
clipboardManager.getText()?.text?.let {
|
||||
if (it.isEmpty()) {
|
||||
scope.launch {
|
||||
val clipboardText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString()
|
||||
if (clipboardText.isNullOrEmpty()) {
|
||||
showToast(context.getString(R.string.app_profile_template_import_empty))
|
||||
return@let
|
||||
}
|
||||
scope.launch {
|
||||
viewModel.importTemplates(
|
||||
it, {
|
||||
showToast(context.getString(R.string.app_profile_template_import_success))
|
||||
viewModel.fetchTemplates(false)
|
||||
},
|
||||
showToast
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
viewModel.importTemplates(
|
||||
clipboardText,
|
||||
{
|
||||
showToast(context.getString(R.string.app_profile_template_import_success))
|
||||
viewModel.fetchTemplates(false)
|
||||
},
|
||||
showToast
|
||||
)
|
||||
}
|
||||
},
|
||||
onExport = {
|
||||
@@ -134,8 +135,8 @@ fun AppProfileTemplateScreen(
|
||||
{
|
||||
showToast(context.getString(R.string.app_profile_template_export_empty))
|
||||
}
|
||||
) {
|
||||
clipboardManager.setText(AnnotatedString(it))
|
||||
) { text ->
|
||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText("", text))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.screen
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
@@ -47,14 +47,14 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.R
|
||||
import zako.zako.zako.ui.component.profile.RootProfileConfig
|
||||
import zako.zako.zako.ui.util.deleteAppProfileTemplate
|
||||
import zako.zako.zako.ui.util.getAppProfileTemplate
|
||||
import zako.zako.zako.ui.util.setAppProfileTemplate
|
||||
import zako.zako.zako.ui.viewmodel.TemplateViewModel
|
||||
import zako.zako.zako.ui.viewmodel.toJSON
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.R
|
||||
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||
import com.sukisu.ultra.ui.util.deleteAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.util.getAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.util.setAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||
import com.sukisu.ultra.ui.viewmodel.toJSON
|
||||
import androidx.lifecycle.compose.dropUnlessResumed
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
@@ -27,11 +27,14 @@ import androidx.compose.ui.zIndex
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.unit.dp
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||
|
||||
object ThemeConfig {
|
||||
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
||||
@@ -88,29 +91,6 @@ private fun getLightColorScheme() = lightColorScheme(
|
||||
outlineVariant = Color.Black.copy(alpha = 0.12f)
|
||||
)
|
||||
|
||||
// 复制图片到应用内部存储
|
||||
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) {
|
||||
@@ -131,7 +111,6 @@ fun KernelSUTheme(
|
||||
if (darkTheme) {
|
||||
val originalScheme = dynamicDarkColorScheme(context)
|
||||
originalScheme.copy(
|
||||
// 调整按钮相关颜色
|
||||
primary = adjustColor(originalScheme.primary),
|
||||
onPrimary = adjustColor(originalScheme.onPrimary),
|
||||
primaryContainer = adjustColor(originalScheme.primaryContainer),
|
||||
@@ -242,6 +221,48 @@ fun KernelSUTheme(
|
||||
}
|
||||
}
|
||||
|
||||
// 复制图片到应用内部存储
|
||||
private 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
|
||||
}
|
||||
}
|
||||
|
||||
// 保存变换后的背景图片到应用内部存储并更新配置
|
||||
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
|
||||
val finalUri = if (transformation != null) {
|
||||
saveTransformedBackground(uri, transformation)
|
||||
} else {
|
||||
copyImageToInternalStorage(uri)
|
||||
}
|
||||
|
||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||
.edit {
|
||||
putString("custom_background", finalUri?.toString())
|
||||
}
|
||||
|
||||
ThemeConfig.customBackgroundUri = finalUri
|
||||
CardConfig.cardElevation = 0.dp
|
||||
CardConfig.isCustomBackgroundEnabled = true
|
||||
}
|
||||
|
||||
// 保存背景图片到应用内部存储并更新配置
|
||||
fun Context.saveCustomBackground(uri: Uri?) {
|
||||
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||
@@ -249,6 +270,10 @@ fun Context.saveCustomBackground(uri: Uri?) {
|
||||
putString("custom_background", newUri?.toString())
|
||||
}
|
||||
ThemeConfig.customBackgroundUri = newUri
|
||||
if (uri != null) {
|
||||
CardConfig.cardElevation = 0.dp
|
||||
CardConfig.isCustomBackgroundEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.loadCustomBackground() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.theme
|
||||
package com.sukisu.ultra.ui.theme
|
||||
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import androidx.core.graphics.createBitmap
|
||||
|
||||
data class BackgroundTransformation(
|
||||
val scale: Float = 1f,
|
||||
val offsetX: Float = 0f,
|
||||
val offsetY: Float = 0f
|
||||
)
|
||||
|
||||
fun Context.getImageBitmap(uri: Uri): Bitmap? {
|
||||
return try {
|
||||
val contentResolver: ContentResolver = contentResolver
|
||||
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
|
||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||
inputStream.close()
|
||||
bitmap
|
||||
} catch (e: Exception) {
|
||||
Log.e("BackgroundUtils", "Failed to get image bitmap: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.applyTransformationToBitmap(bitmap: Bitmap, transformation: BackgroundTransformation): Bitmap {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
|
||||
// 创建与屏幕比例相同的目标位图
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val screenWidth = displayMetrics.widthPixels
|
||||
val screenHeight = displayMetrics.heightPixels
|
||||
val screenRatio = screenHeight.toFloat() / screenWidth.toFloat()
|
||||
|
||||
// 计算目标宽高
|
||||
val targetWidth: Int
|
||||
val targetHeight: Int
|
||||
if (width.toFloat() / height.toFloat() > screenRatio) {
|
||||
targetHeight = height
|
||||
targetWidth = (height / screenRatio).toInt()
|
||||
} else {
|
||||
targetWidth = width
|
||||
targetHeight = (width * screenRatio).toInt()
|
||||
}
|
||||
|
||||
// 创建与目标相同大小的位图
|
||||
val scaledBitmap = createBitmap(targetWidth, targetHeight)
|
||||
val canvas = Canvas(scaledBitmap)
|
||||
|
||||
val matrix = Matrix()
|
||||
|
||||
// 确保缩放值有效
|
||||
val safeScale = maxOf(0.1f, transformation.scale)
|
||||
matrix.postScale(safeScale, safeScale)
|
||||
|
||||
// 计算中心点
|
||||
val centerX = targetWidth / 2f
|
||||
val centerY = targetHeight / 2f
|
||||
|
||||
// 计算偏移量,确保不会出现负最大值的问题
|
||||
val widthDiff = (bitmap.width * safeScale - targetWidth)
|
||||
val heightDiff = (bitmap.height * safeScale - targetHeight)
|
||||
|
||||
// 安全计算偏移量边界
|
||||
val maxOffsetX = maxOf(0f, widthDiff / 2)
|
||||
val maxOffsetY = maxOf(0f, heightDiff / 2)
|
||||
|
||||
// 限制偏移范围
|
||||
val safeOffsetX = if (maxOffsetX > 0)
|
||||
transformation.offsetX.coerceIn(-maxOffsetX, maxOffsetX) else 0f
|
||||
val safeOffsetY = if (maxOffsetY > 0)
|
||||
transformation.offsetY.coerceIn(-maxOffsetY, maxOffsetY) else 0f
|
||||
|
||||
// 应用偏移量到矩阵
|
||||
val translationX = -widthDiff / 2 + safeOffsetX
|
||||
val translationY = -heightDiff / 2 + safeOffsetY
|
||||
|
||||
matrix.postTranslate(translationX, translationY)
|
||||
|
||||
// 将原始位图绘制到新位图上
|
||||
canvas.drawBitmap(bitmap, matrix, null)
|
||||
|
||||
return scaledBitmap
|
||||
}
|
||||
|
||||
fun Context.saveTransformedBackground(uri: Uri, transformation: BackgroundTransformation): Uri? {
|
||||
try {
|
||||
val bitmap = getImageBitmap(uri) ?: return null
|
||||
val transformedBitmap = applyTransformationToBitmap(bitmap, transformation)
|
||||
|
||||
val fileName = "custom_background_transformed.jpg"
|
||||
val file = File(filesDir, fileName)
|
||||
val outputStream = FileOutputStream(file)
|
||||
|
||||
transformedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
return Uri.fromFile(file)
|
||||
} catch (e: Exception) {
|
||||
Log.e("BackgroundUtils", "Failed to save transformed image: ${e.message}", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
@@ -12,7 +12,8 @@ import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.core.content.ContextCompat
|
||||
import zako.zako.zako.ui.util.module.LatestVersionInfo
|
||||
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||
import androidx.core.net.toUri
|
||||
|
||||
/**
|
||||
* @author weishu
|
||||
@@ -42,14 +43,14 @@ fun download(
|
||||
onDownloading()
|
||||
return
|
||||
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
onDownloaded(Uri.parse(localUri))
|
||||
onDownloaded(localUri.toUri())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val request = DownloadManager.Request(Uri.parse(url))
|
||||
val request = DownloadManager.Request(url.toUri())
|
||||
.setDestinationInExternalPublicDir(
|
||||
Environment.DIRECTORY_DOWNLOADS,
|
||||
fileName
|
||||
@@ -140,7 +141,7 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
||||
val uri = cursor.getString(
|
||||
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
|
||||
)
|
||||
onDownloaded(Uri.parse(uri))
|
||||
onDownloaded(uri.toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util;
|
||||
package com.sukisu.ultra.ui.util;
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.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 zako.zako.zako.BuildConfig
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.ksuApp
|
||||
import com.sukisu.ultra.BuildConfig
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
|
||||
@@ -436,7 +436,7 @@ fun restartApp(packageName: String) {
|
||||
launchApp(packageName)
|
||||
}
|
||||
|
||||
private fun getSuSFSDaemonPath(): String {
|
||||
fun getSuSFSDaemonPath(): String {
|
||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.system.Os
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.ui.screen.getManagerVersion
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ui.screen.getManagerVersion
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.R
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
@@ -1,9 +1,9 @@
|
||||
package zako.zako.zako.ui.util
|
||||
package com.sukisu.ultra.ui.util
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import zako.zako.zako.R
|
||||
import com.sukisu.ultra.R
|
||||
|
||||
@Composable
|
||||
fun getSELinuxStatus(): String {
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.util.module
|
||||
package com.sukisu.ultra.ui.util.module
|
||||
|
||||
data class LatestVersionInfo(
|
||||
val versionCode : Int = 0,
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -9,7 +9,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import zako.zako.zako.ui.util.*
|
||||
import com.sukisu.ultra.ui.util.*
|
||||
|
||||
class KpmViewModel : ViewModel() {
|
||||
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
||||
@@ -143,17 +143,6 @@ class KpmViewModel : ViewModel() {
|
||||
return result
|
||||
}
|
||||
|
||||
fun controlModule(moduleId: String, args: String? = null): Int {
|
||||
return try {
|
||||
val result = controlKpmModule(moduleId, args)
|
||||
Log.d("KsuCli", "Control module $moduleId result: $result")
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e("KsuCli", "Failed to control module $moduleId", e)
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
data class ModuleInfo(
|
||||
val id: String,
|
||||
val name: String,
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
@@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import zako.zako.zako.ui.util.HanziToPinyin
|
||||
import zako.zako.zako.ui.util.listModules
|
||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||
import com.sukisu.ultra.ui.util.listModules
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.text.Collator
|
||||
@@ -40,13 +40,6 @@ class ModuleViewModel : ViewModel() {
|
||||
val dirId: String, // real module id (dir name)
|
||||
)
|
||||
|
||||
data class ModuleUpdateInfo(
|
||||
val version: String,
|
||||
val versionCode: Int,
|
||||
val zipUrl: String,
|
||||
val changelog: String,
|
||||
)
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
var search by mutableStateOf("")
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
@@ -18,12 +18,12 @@ import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import zako.zako.zako.IKsuInterface
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.ksuApp
|
||||
import zako.zako.zako.ui.KsuService
|
||||
import zako.zako.zako.ui.util.HanziToPinyin
|
||||
import zako.zako.zako.ui.util.KsuCli
|
||||
import com.sukisu.zako.IKsuInterface
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.ksuApp
|
||||
import com.sukisu.ultra.ui.KsuService
|
||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||
import com.sukisu.ultra.ui.util.KsuCli
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resume
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.viewmodel
|
||||
package com.sukisu.ultra.ui.viewmodel
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
@@ -10,12 +10,12 @@ import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import zako.zako.zako.Natives
|
||||
import zako.zako.zako.profile.Capabilities
|
||||
import zako.zako.zako.profile.Groups
|
||||
import zako.zako.zako.ui.util.getAppProfileTemplate
|
||||
import zako.zako.zako.ui.util.listAppProfileTemplates
|
||||
import zako.zako.zako.ui.util.setAppProfileTemplate
|
||||
import com.sukisu.ultra.Natives
|
||||
import com.sukisu.ultra.profile.Capabilities
|
||||
import com.sukisu.ultra.profile.Groups
|
||||
import com.sukisu.ultra.ui.util.getAppProfileTemplate
|
||||
import com.sukisu.ultra.ui.util.listAppProfileTemplates
|
||||
import com.sukisu.ultra.ui.util.setAppProfileTemplate
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONArray
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package zako.zako.zako.ui.webui;
|
||||
package com.sukisu.ultra.ui.webui;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.webui;
|
||||
package com.sukisu.ultra.ui.webui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.webui
|
||||
package com.sukisu.ultra.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 zako.zako.zako.ui.util.createRootShell
|
||||
import com.sukisu.ultra.ui.util.createRootShell
|
||||
import java.io.File
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.ui.webui
|
||||
package com.sukisu.ultra.ui.webui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
@@ -14,13 +14,13 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.ShellUtils
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import zako.zako.zako.ui.util.createRootShell
|
||||
import zako.zako.zako.ui.util.listModules
|
||||
import zako.zako.zako.ui.util.withNewRootShell
|
||||
import com.sukisu.ultra.ui.util.createRootShell
|
||||
import com.sukisu.ultra.ui.util.listModules
|
||||
import com.sukisu.ultra.ui.util.withNewRootShell
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import zako.zako.zako.ui.util.controlKpmModule
|
||||
import zako.zako.zako.ui.util.listKpmModules
|
||||
import com.sukisu.ultra.ui.util.controlKpmModule
|
||||
import com.sukisu.ultra.ui.util.listKpmModules
|
||||
import java.io.File
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package zako.zako.zako.utils
|
||||
package com.sukisu.ultra.utils
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.zako.zako;
|
||||
package io.sukisu.ultra;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import zako.zako.zako.ui.util.KsuCli;
|
||||
import com.sukisu.ultra.ui.util.KsuCli;
|
||||
|
||||
public class UltraShellHelper {
|
||||
public static String runCmd(String cmds) {
|
||||
@@ -19,7 +19,7 @@ public class UltraShellHelper {
|
||||
}
|
||||
|
||||
public static boolean isPathExists(String path) {
|
||||
return !runCmd("file " + path).contains("No such file or directory");
|
||||
return runCmd("file " + path).contains("No such file or directory");
|
||||
}
|
||||
|
||||
public static void CopyFileTo(String path, String target) {
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.sukisu.ultra;
|
||||
|
||||
import static com.sukisu.ultra.ui.util.KsuCliKt.getKpmmgrPath;
|
||||
import static com.sukisu.ultra.ui.util.KsuCliKt.getSuSFSDaemonPath;
|
||||
|
||||
public class UltraToolInstall {
|
||||
private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
|
||||
private static final String OUTSIDE_SUSFSD_PATH = "/data/adb/ksu/bin/susfsd";
|
||||
public static void tryToInstall() {
|
||||
String kpmmgrPath = getKpmmgrPath();
|
||||
if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
|
||||
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
|
||||
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
|
||||
}
|
||||
String SuSFSDaemonPath = getSuSFSDaemonPath();
|
||||
if (UltraShellHelper.isPathExists(OUTSIDE_SUSFSD_PATH)) {
|
||||
UltraShellHelper.CopyFileTo(SuSFSDaemonPath, OUTSIDE_SUSFSD_PATH);
|
||||
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_SUSFSD_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package io.zako.zako;
|
||||
|
||||
import static zako.zako.zako.ui.util.KsuCliKt.getKpmmgrPath;
|
||||
|
||||
public class UltraToolInstall {
|
||||
private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
|
||||
public static void tryToInstall() {
|
||||
String kpmmgrPath = getKpmmgrPath();
|
||||
if (!UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
|
||||
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
|
||||
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
manager/app/src/main/jniLibs/.gitignore
vendored
4
manager/app/src/main/jniLibs/.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
libzakozako.so
|
||||
libzakozakozako.so
|
||||
libkpmmgr.so
|
||||
libkpmmgr.so
|
||||
libzako.so
|
||||
libandroidx.graphics.path.so
|
||||
@@ -50,7 +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="profile">پروفایل برنامه</string>
|
||||
<string name="profile_default">پیشفرض</string>
|
||||
<string name="profile_template">قالب</string>
|
||||
<string name="profile_custom">شخصی سازی شده</string>
|
||||
|
||||
@@ -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="profile">Profil Apl</string>
|
||||
<string name="profile_default">Bawaan</string>
|
||||
<string name="profile_template">Templat</string>
|
||||
<string name="profile_custom">Khusus</string>
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
<string name="reboot">再起動</string>
|
||||
<string name="settings">設定</string>
|
||||
<string name="reboot_userspace">ソフトリブート</string>
|
||||
<string name="reboot_recovery">リカバリーへ再起動</string>
|
||||
<string name="reboot_bootloader">ブートローダーへ再起動</string>
|
||||
<string name="reboot_download">ダウンロードモードへ再起動</string>
|
||||
<string name="reboot_edl">EDL へ再起動</string>
|
||||
<string name="reboot_recovery">リカバリーで再起動</string>
|
||||
<string name="reboot_bootloader">ブートローダーで再起動</string>
|
||||
<string name="reboot_download">ダウンロードモードで再起動</string>
|
||||
<string name="reboot_edl">EDL で再起動</string>
|
||||
<string name="about">アプリについて</string>
|
||||
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
|
||||
<string name="module_uninstall_success">%s はアンインストールされました</string>
|
||||
@@ -72,7 +72,7 @@
|
||||
<string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</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">アプリプロファイルの「モジュールのアンマウント」の共通のデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
|
||||
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通となるデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</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>
|
||||
@@ -88,7 +88,7 @@
|
||||
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
|
||||
<string name="module_changelog">変更履歴</string>
|
||||
<string name="settings_profile_template">アプリプロファイルのテンプレート</string>
|
||||
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します。</string>
|
||||
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します</string>
|
||||
<string name="app_profile_template_create">テンプレートの作成</string>
|
||||
<string name="app_profile_template_edit">テンプレートの編集</string>
|
||||
<string name="app_profile_template_id">ID</string>
|
||||
@@ -110,12 +110,12 @@
|
||||
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
||||
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
||||
<string name="settings_check_update">更新を確認する</string>
|
||||
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します。</string>
|
||||
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string>
|
||||
<string name="grant_root_failed">root の付与に失敗しました!</string>
|
||||
<string name="action">アクション</string>
|
||||
<string name="open">開く</string>
|
||||
<string name="enable_web_debugging">WebView デバッグを有効化する</string>
|
||||
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合にのみ有効にしてください。</string>
|
||||
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合でのみ有効化してください</string>
|
||||
<string name="direct_install">直接インストール (推奨)</string>
|
||||
<string name="select_file">ファイルを選択</string>
|
||||
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
|
||||
@@ -172,9 +172,8 @@
|
||||
<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_custom_background_summary">カスタム背景を設定します</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>
|
||||
@@ -189,13 +188,13 @@
|
||||
<string name="selinux_enabled">有効</string>
|
||||
<string name="selinux_disabled">無効</string>
|
||||
<string name="simple_mode">シンプルモード</string>
|
||||
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします。</string>
|
||||
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
|
||||
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします。</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
|
||||
<string name="hide_other_info">その他の情報を非表示にする</string>
|
||||
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします。</string>
|
||||
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
|
||||
<string name="hide_susfs_status">SuSFS ステータスを非表示にする</string>
|
||||
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします。</string>
|
||||
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
|
||||
<string name="theme_mode">テーマモード</string>
|
||||
<string name="theme_follow_system">システムに従う</string>
|
||||
<string name="theme_light">ライトカラー</string>
|
||||
@@ -215,6 +214,7 @@
|
||||
<string name="flash_option">ブラシの設定</string>
|
||||
<string name="flash_option_tip">フラッシュするファイルを選択</string>
|
||||
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
|
||||
<string name="horizon_kernel_summary">AnyKernel3 をフラッシュします</string>
|
||||
<string name="root_required">root 権限が必要です</string>
|
||||
<string name="copy_failed">ファイルのコピーに失敗しました</string>
|
||||
<string name="reboot_complete_title">スクラブが完了しました</string>
|
||||
@@ -251,12 +251,12 @@
|
||||
<string name="home_kpm_module">KPM モジュール数: %d </string>
|
||||
<string name="kpm_invalid_file">無効な KPM ファイル</string>
|
||||
<string name="kernel_patched">カーネルはパッチされていません</string>
|
||||
<string name="kernel_not_enabled">カーネルは設定されていません</string>
|
||||
<string name="kernel_not_enabled">カーネルは未設定です</string>
|
||||
<string name="custom_settings">カスタム設定</string>
|
||||
<string name="kpm_install_mode">インストール</string>
|
||||
<string name="kpm_install_mode_load">読み込む</string>
|
||||
<string name="kpm_install_mode_embed">埋め込む</string>
|
||||
<string name="kpm_install_mode_description">モジュールのインストールモードを選択してください:\n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string>
|
||||
<string name="kpm_install_mode_description">モジュール:%1\$s のインストールモードを選択してください \n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string>
|
||||
<string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
|
||||
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
|
||||
<string name="confirm_uninstall_title">アンインストールを確認</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
<string name="home">Strona główna</string>
|
||||
<string name="home_not_installed">Nie zainstalowano</string>
|
||||
<string name="home_click_to_install">Kliknij, aby zainstalować</string>
|
||||
@@ -51,7 +50,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="profile" translatable="false">Profil aplikacji</string>
|
||||
<string name="profile_default">Domyślny</string>
|
||||
<string name="profile_template">Szablon</string>
|
||||
<string name="profile_custom">Własny</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="profile" translatable="false">Perfil do Aplicativo</string>
|
||||
<string name="profile_default">Padrão</string>
|
||||
<string name="profile_template">Modelo</string>
|
||||
<string name="profile_custom">Personalizado</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="profile" translatable="false">App Profile</string>
|
||||
<!--Don't translate this string!-->
|
||||
<string name="profile_default">По умолчанию</string>
|
||||
<string name="profile_template">Шаблон</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">KernelSU</string>
|
||||
<string name="home">Ana Sayfa</string>
|
||||
<string name="home_not_installed">Kurulmadı</string>
|
||||
<string name="home_click_to_install">Kurmak için tıklayın</string>
|
||||
@@ -51,7 +50,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="profile" translatable="false">Uygulama profili</string>
|
||||
<string name="profile_default">Varsayılan</string>
|
||||
<string name="profile_template">Şablon</string>
|
||||
<string name="profile_custom">Özel</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="profile">Профіль додатка</string>
|
||||
<string name="profile_default">Типовий</string>
|
||||
<string name="profile_template">Шаблон</string>
|
||||
<string name="profile_custom">Власний</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">SukiSU Ultra</string>
|
||||
<string name="home">Home</string>
|
||||
<string name="home_not_installed">Chưa cài đặt</string>
|
||||
<string name="home_click_to_install">Nhấn vào đây để cài đặt</string>
|
||||
@@ -58,7 +57,6 @@
|
||||
<string name="home_support_title">Hỗ trợ chúng tôi</string>
|
||||
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string>
|
||||
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
|
||||
<string name="profile" translatable="false">Hồ sơ ứng dụng</string>
|
||||
<string name="profile_default">Mặc định</string>
|
||||
<string name="profile_template">Bản mẫu</string>
|
||||
<string name="profile_custom">Tùy chỉnh</string>
|
||||
@@ -173,7 +171,6 @@
|
||||
<string name="restore_allowlist">Khôi phục danh sách cho phép</string>
|
||||
<string name="settings_custom_background">Cài đặt nền tùy chỉnh</string>
|
||||
<string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string>
|
||||
<string name="settings_card_manage">Quản lý thẻ</string>
|
||||
<string name="settings_card_alpha">Thẻ alpha</string>
|
||||
<string name="settings_restore_default">Khôi phục mặc định</string>
|
||||
<string name="home_android_version">Phiên bản Android</string>
|
||||
@@ -256,7 +253,7 @@
|
||||
<string name="kpm_install_mode">Cài đặt</string>
|
||||
<string name="kpm_install_mode_load">Tải</string>
|
||||
<string name="kpm_install_mode_embed">Nhúng</string>
|
||||
<string name="kpm_install_mode_description">Vui lòng chọn chế độ cài đặt mô-đun: \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
|
||||
<string name="kpm_install_mode_description">Vui lòng:%1\$s chọn chế độ cài đặt mô-đun \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
|
||||
<string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string>
|
||||
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string>
|
||||
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>
|
||||
|
||||
@@ -172,7 +172,6 @@
|
||||
<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>
|
||||
@@ -214,6 +213,7 @@
|
||||
<string name="flash_option">刷入选项</string>
|
||||
<string name="flash_option_tip">选择要刷入的文件</string>
|
||||
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
|
||||
<string name="horizon_kernel_summary">刷入 Anykernel3 内核</string>
|
||||
<string name="root_required">需要 root 权限</string>
|
||||
<string name="copy_failed">文件复制失败</string>
|
||||
<string name="reboot_complete_title">刷写完成</string>
|
||||
@@ -236,7 +236,7 @@
|
||||
<string name="kpm_install_success">加载 kpm 模块成功</string>
|
||||
<string name="kpm_install_failed">加载 kpm 模块失败</string>
|
||||
<string name="home_kpm_version">KPM 版本</string>
|
||||
<string name="kpm_control">执行</string>
|
||||
<string name="kpm_control">调参</string>
|
||||
<string name="close_notice">关闭</string>
|
||||
<string name="kpm_control_success">成功</string>
|
||||
<string name="kpm_control_failed">错误</string>
|
||||
@@ -253,7 +253,7 @@
|
||||
<string name="kpm_install_mode">安装</string>
|
||||
<string name="kpm_install_mode_load">加载</string>
|
||||
<string name="kpm_install_mode_embed">嵌入</string>
|
||||
<string name="kpm_install_mode_description">请选择模块安装模式:\n\n加载:临时加载模块\n嵌入:永久安装到系统</string>
|
||||
<string name="kpm_install_mode_description">请选择: %1\$s 模块的安装模式 \n\n加载:临时加载模块\n嵌入:永久安装到系统</string>
|
||||
<string name="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string>
|
||||
<string name="confirm_uninstall_title">确认卸载</string>
|
||||
<string name="confirm_uninstall_confirm">删除</string>
|
||||
@@ -262,4 +262,34 @@
|
||||
<string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string>
|
||||
<string name="confirm_uninstall_title_with_filename">卸载</string>
|
||||
<string name="confirm_uninstall_content">将卸载以下 kpm 模块:\n%s</string>
|
||||
<string name="image_editor_title">调整背景图片</string>
|
||||
<string name="image_editor_hint">使用双指缩放图片,单指拖动调整位置</string>
|
||||
<string name="background_image_error">无法加载图片</string>
|
||||
<string name="reprovision">重置</string>
|
||||
<!-- Anykernel3 Kernel刷写进度相关 -->
|
||||
<string name="horizon_flash_title">刷写Kernel</string>
|
||||
<string name="horizon_logs_label">日志:</string>
|
||||
<string name="horizon_flash_complete">刷写完成</string>
|
||||
<!-- 刷写状态相关 -->
|
||||
<string name="horizon_preparing">准备中…</string>
|
||||
<string name="horizon_cleaning_files">清理文件…</string>
|
||||
<string name="horizon_copying_files">复制文件…</string>
|
||||
<string name="horizon_extracting_tool">提取刷写工具…</string>
|
||||
<string name="horizon_patching_script">修补刷写脚本…</string>
|
||||
<string name="horizon_flashing">刷写内核中…</string>
|
||||
<string name="horizon_flash_complete_status">刷写完成</string>
|
||||
<!-- 槽位选择相关字符串 -->
|
||||
<string name="select_slot_title">选择刷写槽位</string>
|
||||
<string name="select_slot_description">请选择要刷写boot的目标槽位</string>
|
||||
<string name="slot_a">A槽位</string>
|
||||
<string name="slot_b">B槽位</string>
|
||||
<string name="selected_slot">已选择槽位: %1$s</string>
|
||||
<!-- 错误信息 -->
|
||||
<string name="horizon_copy_failed">复制失败</string>
|
||||
<string name="horizon_unknown_error">未知错误</string>
|
||||
<string name="horizon_getting_original_slot">获取原有槽位</string>
|
||||
<string name="horizon_setting_target_slot">设置指定槽位</string>
|
||||
<string name="horizon_restoring_original_slot">恢复默认槽位</string>
|
||||
<string name="current_slot">当前槽位:%1$s </string>
|
||||
<string name="flash_failed_message">刷写失败</string>
|
||||
</resources>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string>
|
||||
<string name="home_kernel">Kernel version</string>
|
||||
<string name="home_susfs">SuSFS: %s</string>
|
||||
<string name="home_susfs_version">Version de SuSFS</string>
|
||||
<string name="home_susfs_version">SuSFS Version</string>
|
||||
<string name="home_susfs_sus_su">SuS SU</string>
|
||||
<string name="home_manager_version">Manager version</string>
|
||||
<string name="home_fingerprint">Fingerprint</string>
|
||||
@@ -75,8 +75,7 @@
|
||||
<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="settings_susfs_toggle">Disable kprobe hooks</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>
|
||||
@@ -171,24 +170,23 @@
|
||||
<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="settings_custom_background">Custom App Background</string>
|
||||
<string name="settings_custom_background_summary">Select an image as background</string>
|
||||
<string name="settings_card_alpha">Navigation bar transparency</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="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 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 SukiSU Beta 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="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">Simplicity mode</string>
|
||||
<string name="simple_mode_summary">Hides unnecessary cards when turned on</string>
|
||||
<string name="hide_kernel_kernelsu_version">Hide kernel version</string>
|
||||
<string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string>
|
||||
@@ -196,74 +194,106 @@
|
||||
<string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string>
|
||||
<string name="hide_susfs_status">Hide SuSFS status</string>
|
||||
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</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="theme_mode">Theme</string>
|
||||
<string name="theme_follow_system">Follow system</string>
|
||||
<string name="theme_light">Light</string>
|
||||
<string name="theme_dark">Dark</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">White</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="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 Flash</string>
|
||||
<string name="horizon_kernel">Install Anykernel3</string>
|
||||
<string name="horizon_kernel_summary">Flash AnyKernel3 kernel file</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="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="failed_reboot">Reboot Failed</string>
|
||||
<string name="batch_authorization">bulk license</string>
|
||||
<string name="batch_authorization">Bulk license</string>
|
||||
<string name="batch_cancel_authorization">Batch cancel authorization</string>
|
||||
<string name="backup">Backup</string>
|
||||
<string name="color_yellow">Yellow</string>
|
||||
<string name="kpm">kpm module</string>
|
||||
<string name="kpm_title">kernel module</string>
|
||||
<string name="kpm">Kernel Module</string>
|
||||
<string name="kpm_title">Kernel Module</string>
|
||||
<string name="kpm_empty">No installed kernel modules at this time</string>
|
||||
<string name="kpm_version">releases</string>
|
||||
<string name="kpm_author">author</string>
|
||||
<string name="kpm_uninstall">uninstallation</string>
|
||||
<string name="kpm_version">Version</string>
|
||||
<string name="kpm_author">Author</string>
|
||||
<string name="kpm_uninstall">Uninstall</string>
|
||||
<string name="kpm_uninstall_success">Uninstalled successfully</string>
|
||||
<string name="kpm_uninstall_failed">Failed to uninstall</string>
|
||||
<string name="kpm_install">Select Installation</string>
|
||||
<string name="kpm_install">Install</string>
|
||||
<string name="kpm_install_success">Load of kpm module successful</string>
|
||||
<string name="kpm_install_failed">Load of kpm module failed</string>
|
||||
<string name="kpm_args">kpm parameters</string>
|
||||
<string name="kpm_control">fulfillment</string>
|
||||
<string name="kpm_args">Parameters</string>
|
||||
<string name="kpm_control">Execute</string>
|
||||
<string name="home_kpm_version">KPM Version</string>
|
||||
<string name="close_notice">close</string>
|
||||
<string name="close_notice">Close</string>
|
||||
<string name="kernel_module_notice">The following kernel module functions were developed by KernelPatch and modified to include the kernel module functions of SukiSU Ultra</string>
|
||||
<string name="home_ContributionCard_kernelsu">SukiSU Ultra Look forward to</string>
|
||||
<string name="kpm_control_success">success</string>
|
||||
<string name="kpm_control_failed">failed</string>
|
||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra will be a relatively independent branch of KSU in the future, but thanks to the official KernelSU and MKSU etc. for their contributions!</string>
|
||||
<string name="not_supported">unsupported</string>
|
||||
<string name="supported">supported</string>
|
||||
<string name="home_kpm_module">Number of KPM modules:%d </string>
|
||||
<string name="kpm_control_success">Success</string>
|
||||
<string name="kpm_control_failed">Failed</string>
|
||||
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra will be a relatively independent branch of KSU in the future, but we still appreciate the official KernelSU and MKSU etc. for their contributions!</string>
|
||||
<string name="not_supported">Unsupported</string>
|
||||
<string name="supported">Supported</string>
|
||||
<string name="home_kpm_module">"Number of KPM modules: %d "</string>
|
||||
<string name="kpm_invalid_file">Invalid KPM file</string>
|
||||
<string name="kernel_patched">Kernel not patched</string>
|
||||
<string name="kernel_not_enabled">Kernel not configured</string>
|
||||
<string name="custom_settings">Custom settings</string>
|
||||
<string name="kpm_install_mode">install</string>
|
||||
<string name="kpm_install_mode">KPM Install</string>
|
||||
<string name="kpm_install_mode_load">Load</string>
|
||||
<string name="kpm_install_mode_embed">embed</string>
|
||||
<string name="kpm_install_mode_description">Please select the module installation mode: \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string>
|
||||
<string name="kpm_install_mode_embed">Embed</string>
|
||||
<string name="kpm_install_mode_description">Please select: %1\$s Module Installation Mode \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string>
|
||||
<string name="log_failed_to_check_module_file">Failed to check module file existence</string>
|
||||
<string name="snackbar_failed_to_check_module_file">Unable to check if module file exists</string>
|
||||
<string name="confirm_uninstall_title">Confirm uninstallation</string>
|
||||
<string name="confirm_uninstall_confirm">removing</string>
|
||||
<string name="confirm_uninstall_dismiss">abolish</string>
|
||||
<string name="confirm_uninstall_confirm">Uninstall</string>
|
||||
<string name="confirm_uninstall_dismiss">Cancel</string>
|
||||
<string name="theme_color">Theme Color</string>
|
||||
<string name="invalid_file_type">Incorrect file type, select .kpm file</string>
|
||||
<string name="confirm_uninstall_title_with_filename">uninstallation</string>
|
||||
<string name="confirm_uninstall_content">The following kpm modules will be uninstalled:\n%s</string>
|
||||
<string name="invalid_file_type">Incorrect file type! Please select .kpm file.</string>
|
||||
<string name="confirm_uninstall_title_with_filename">Uninstall</string>
|
||||
<string name="confirm_uninstall_content">The following KPM will be uninstalled: %s</string>
|
||||
<string name="settings_susfs_toggle_summary">Disable kprobe hooks created by KernelSU, using inline hooks instead, which is similar to non-GKI kernel hooking method.</string>
|
||||
<string name="image_editor_title">Adjust background image</string>
|
||||
<string name="image_editor_hint">Use two fingers to zoom the image, and one finger to drag it to adjust the position</string>
|
||||
<string name="background_image_error">Could not load image</string>
|
||||
<string name="reprovision">Reprovision</string>
|
||||
<!-- Kernel Flash Progress Related -->
|
||||
<string name="horizon_flash_title">Kernel Flashing</string>
|
||||
<string name="horizon_logs_label">Logs:</string>
|
||||
<string name="horizon_flash_complete">Flash Complete</string>
|
||||
<!-- Flash Status Related -->
|
||||
<string name="horizon_preparing">Preparing…</string>
|
||||
<string name="horizon_cleaning_files">Cleaning files…</string>
|
||||
<string name="horizon_copying_files">Copying files…</string>
|
||||
<string name="horizon_extracting_tool">Extracting flash tool…</string>
|
||||
<string name="horizon_patching_script">Patching flash script…</string>
|
||||
<string name="horizon_flashing">Flashing kernel…</string>
|
||||
<string name="horizon_flash_complete_status">Flash completed</string>
|
||||
<!-- Slot selection related strings -->
|
||||
<string name="select_slot_title">Select Flash Slot</string>
|
||||
<string name="select_slot_description">Please select the target slot for flashing boot</string>
|
||||
<string name="slot_a">Slot A</string>
|
||||
<string name="slot_b">Slot B</string>
|
||||
<string name="selected_slot">Selected slot: %1$s</string>
|
||||
<!-- Error Messages -->
|
||||
<string name="horizon_copy_failed">Copy failed</string>
|
||||
<string name="horizon_unknown_error">Unknown error</string>
|
||||
<string name="horizon_getting_original_slot">Getting the original slot</string>
|
||||
<string name="horizon_setting_target_slot">Setting the specified slot</string>
|
||||
<string name="horizon_restoring_original_slot">Restore Default Slot</string>
|
||||
<string name="current_slot">Current Slot:%1$s </string>
|
||||
<string name="flash_failed_message">Flash failed</string>
|
||||
</resources>
|
||||
|
||||
@@ -28,8 +28,8 @@ cmaker {
|
||||
}
|
||||
|
||||
val androidMinSdkVersion = 26
|
||||
val androidTargetSdkVersion = 35
|
||||
val androidCompileSdkVersion = 35
|
||||
val androidTargetSdkVersion = 36
|
||||
val androidCompileSdkVersion = 36
|
||||
val androidCompileNdkVersion = "28.0.13004108"
|
||||
val androidSourceCompatibility = JavaVersion.VERSION_21
|
||||
val androidTargetCompatibility = JavaVersion.VERSION_21
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
[versions]
|
||||
agp = "8.9.1"
|
||||
agp = "8.9.2"
|
||||
kotlin = "2.1.10"
|
||||
ksp = "2.1.10-1.0.30"
|
||||
compose-bom = "2025.02.00"
|
||||
compose-bom = "2025.04.01"
|
||||
lifecycle = "2.8.7"
|
||||
navigation = "2.8.7"
|
||||
activity-compose = "1.10.0"
|
||||
kotlinx-coroutines = "1.10.1"
|
||||
navigation = "2.8.9"
|
||||
activity-compose = "1.10.1"
|
||||
kotlinx-coroutines = "1.10.2"
|
||||
coil-compose = "2.7.0"
|
||||
compose-destination = "2.1.0-beta16"
|
||||
compose-destination = "2.1.0"
|
||||
sheets-compose-dialogs = "1.3.0"
|
||||
markdown = "4.6.2"
|
||||
webkit = "1.12.1"
|
||||
webkit = "1.13.0"
|
||||
appiconloader-coil = "1.5.0"
|
||||
parcelablelist = "2.0.1"
|
||||
libsu = "6.0.0"
|
||||
apksign = "1.4"
|
||||
cmaker = "1.2"
|
||||
compose-material = "1.7.8"
|
||||
compose-material3 = "1.3.1"
|
||||
compose-ui = "1.7.8"
|
||||
compose-material = "1.8.0"
|
||||
compose-material3 = "1.3.2"
|
||||
compose-ui = "1.8.0"
|
||||
compose-foundation = "1.7.8"
|
||||
documentfile = "1.0.1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user