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)
|
**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
|
- [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
|
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
|
||||||
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks
|
- [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)
|
**日本語** | [简体中文](README.md) | [English](README-en.md)
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ SukiSU と susfs をベースにコンパイルされたプロジェクトです
|
|||||||
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
|
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
|
||||||
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
|
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
|
||||||
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
|
- [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) 请我喝奶茶也不错
|
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
|
||||||
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
|
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
|
||||||
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
|
- [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'.
|
# .git is a text file while the module is imported by 'git submodule add'.
|
||||||
ifeq ($(shell test -e $(srctree)/$(src)/../.git; echo $$?),0)
|
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)
|
$(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
|
# ksu_version: major * 10000 + git version + 606 for historical reasons
|
||||||
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
|
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
|
||||||
$(info -- KernelSU version: $(KSU_VERSION))
|
$(info -- KernelSU version: $(KSU_VERSION))
|
||||||
@@ -42,13 +42,26 @@ endif
|
|||||||
|
|
||||||
ifdef KSU_MANAGER_PACKAGE
|
ifdef KSU_MANAGER_PACKAGE
|
||||||
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(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
|
endif
|
||||||
|
|
||||||
$(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
$(info -- SukiSU Manager signature size: $(KSU_EXPECTED_SIZE))
|
||||||
$(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
$(info -- SukiSU Manager signature hash: $(KSU_EXPECTED_HASH))
|
||||||
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
|
||||||
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
|
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))
|
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
|
||||||
ifeq ($(CONFIG_KPM),y)
|
ifeq ($(CONFIG_KPM),y)
|
||||||
$(info -- KPM is enabled)
|
$(info -- KPM is enabled)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ apksign {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "zako.zako.zako"
|
namespace = "com.sukisu.ultra"
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako;
|
package com.sukisu.zako;
|
||||||
|
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import rikka.parcelablelist.ParcelableListSlice;
|
import rikka.parcelablelist.ParcelableListSlice;
|
||||||
Binary file not shown.
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
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 cpkg = env->GetStringUTFChars(pkg, nullptr);
|
||||||
auto result = become_manager(cpkg);
|
auto result = become_manager(cpkg);
|
||||||
env->ReleaseStringUTFChars(pkg, cpkg);
|
env->ReleaseStringUTFChars(pkg, cpkg);
|
||||||
@@ -21,13 +21,13 @@ Java_zako_zako_zako_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_zako_zako_zako_Natives_getVersion(JNIEnv *env, jobject) {
|
Java_com_sukisu_ultra_Natives_getVersion(JNIEnv *env, jobject) {
|
||||||
return get_version();
|
return get_version();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jintArray JNICALL
|
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 uids[1024];
|
||||||
int size = 0;
|
int size = 0;
|
||||||
bool result = get_allow_list(uids, &size);
|
bool result = get_allow_list(uids, &size);
|
||||||
@@ -42,13 +42,13 @@ Java_zako_zako_zako_Natives_getAllowList(JNIEnv *env, jobject) {
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
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();
|
return is_safe_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
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();
|
return is_lkm_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
|
|||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jobject JNICALL
|
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) {
|
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
|
||||||
return nullptr;
|
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);
|
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 constructor = env->GetMethodID(cls, "<init>", "()V");
|
||||||
auto obj = env->NewObject(cls, constructor);
|
auto obj = env->NewObject(cls, constructor);
|
||||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
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"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_zako_zako_zako_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
Java_com_sukisu_ultra_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
|
||||||
auto cls = env->FindClass("zako/zako/zako/Natives$Profile");
|
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile");
|
||||||
|
|
||||||
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
|
||||||
auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
|
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"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
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);
|
return uid_should_umount(uid);
|
||||||
}
|
}
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
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();
|
return is_su_enabled();
|
||||||
}
|
}
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT jboolean JNICALL
|
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);
|
return set_su_enabled(enabled);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako
|
package com.sukisu.ultra
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako
|
package com.sukisu.ultra
|
||||||
|
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako
|
package com.sukisu.ultra
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
@@ -16,14 +16,14 @@ object Natives {
|
|||||||
// 10946: add capabilities
|
// 10946: add capabilities
|
||||||
// 10977: change groups_count and groups to avoid overflow write
|
// 10977: change groups_count and groups to avoid overflow write
|
||||||
// 11071: Fix the issue of failing to set a custom SELinux type.
|
// 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
|
// 11640: Support query working mode, LKM or GKI
|
||||||
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
|
// 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
|
// 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 KERNEL_SU_DOMAIN = "u:r:su:s0"
|
||||||
|
|
||||||
const val ROOT_UID = 0
|
const val ROOT_UID = 0
|
||||||
@@ -91,6 +91,14 @@ object Natives {
|
|||||||
return version < MINIMAL_SUPPORTED_KERNEL
|
return version < MINIMAL_SUPPORTED_KERNEL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isKsuValid(pkgName: String?): Boolean {
|
||||||
|
if (becomeManager(pkgName)) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Keep
|
@Keep
|
||||||
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
|
* @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
|
* 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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import zako.zako.zako.IKsuInterface;
|
import com.sukisu.zako.IKsuInterface;
|
||||||
import rikka.parcelablelist.ParcelableListSlice;
|
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.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.EnterTransition
|
|
||||||
import androidx.compose.animation.ExitTransition
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.foundation.layout.displayCutout
|
import androidx.compose.runtime.*
|
||||||
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.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
@@ -37,21 +20,16 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||||
import com.ramcosta.composedestinations.generated.NavGraphs
|
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.isRouteOnBackStackAsState
|
||||||
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
|
||||||
import io.zako.zako.UltraToolInstall
|
import io.sukisu.ultra.UltraToolInstall
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import zako.zako.zako.ui.screen.BottomBarDestination
|
import com.sukisu.ultra.ui.screen.BottomBarDestination
|
||||||
import zako.zako.zako.ui.theme.CardConfig
|
import com.sukisu.ultra.ui.theme.*
|
||||||
import zako.zako.zako.ui.theme.KernelSUTheme
|
import com.sukisu.ultra.ui.util.*
|
||||||
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
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -89,7 +67,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
) {
|
) {
|
||||||
DestinationsNavHost(
|
DestinationsNavHost(
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
navGraph = NavGraphs.root,
|
navGraph = NavGraphs.root as NavHostGraphSpec,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
defaultTransitions = object : NavHostAnimatedDestinationStyle() {
|
||||||
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
|
||||||
@@ -136,7 +114,7 @@ private fun BottomBar(navController: NavHostController) {
|
|||||||
navigator.popBackStack(destination.direction, false)
|
navigator.popBackStack(destination.direction, false)
|
||||||
}
|
}
|
||||||
navigator.navigate(destination.direction) {
|
navigator.navigate(destination.direction) {
|
||||||
popUpTo(NavGraphs.root) {
|
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||||
saveState = true
|
saveState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
@@ -152,7 +130,7 @@ private fun BottomBar(navController: NavHostController) {
|
|||||||
},
|
},
|
||||||
label = { Text(stringResource(destination.label)) },
|
label = { Text(stringResource(destination.label)) },
|
||||||
alwaysShowLabel = false,
|
alwaysShowLabel = false,
|
||||||
colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
|
colors = NavigationBarItemDefaults.colors(
|
||||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -167,7 +145,7 @@ private fun BottomBar(navController: NavHostController) {
|
|||||||
navigator.popBackStack(destination.direction, false)
|
navigator.popBackStack(destination.direction, false)
|
||||||
}
|
}
|
||||||
navigator.navigate(destination.direction) {
|
navigator.navigate(destination.direction) {
|
||||||
popUpTo(NavGraphs.root) {
|
popUpTo(NavGraphs.root as RouteOrDirection) {
|
||||||
saveState = true
|
saveState = true
|
||||||
}
|
}
|
||||||
launchSingleTop = 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.Image
|
||||||
import androidx.compose.foundation.layout.Column
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import zako.zako.zako.BuildConfig
|
import com.sukisu.ultra.BuildConfig
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.component
|
package com.sukisu.ultra.ui.component
|
||||||
|
|
||||||
import android.graphics.text.LineBreaker
|
import android.graphics.text.LineBreaker
|
||||||
import android.os.Build
|
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.focusable
|
||||||
import androidx.compose.foundation.layout.Box
|
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 android.util.Log
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
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.text.input.ImeAction
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
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"
|
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.LocalIndication
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
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.Icon
|
||||||
import androidx.compose.material3.ListItem
|
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.foundation.layout.Column
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.SwitchItem
|
import com.sukisu.ultra.ui.component.SwitchItem
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppProfileConfig(
|
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.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
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.ListDialog
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
import com.maxkeppeler.sheets.list.models.ListOption
|
||||||
import com.maxkeppeler.sheets.list.models.ListSelection
|
import com.maxkeppeler.sheets.list.models.ListSelection
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.profile.Capabilities
|
import com.sukisu.ultra.profile.Capabilities
|
||||||
import zako.zako.zako.profile.Groups
|
import com.sukisu.ultra.profile.Groups
|
||||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||||
import zako.zako.zako.ui.util.isSepolicyValid
|
import com.sukisu.ultra.ui.util.isSepolicyValid
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@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.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.util.listAppProfileTemplates
|
import com.sukisu.ultra.ui.util.listAppProfileTemplates
|
||||||
import zako.zako.zako.ui.util.setSepolicy
|
import com.sukisu.ultra.ui.util.setSepolicy
|
||||||
import zako.zako.zako.ui.viewmodel.getTemplateInfoById
|
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.animation.Crossfade
|
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.generated.destinations.TemplateEditorScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.SwitchItem
|
import com.sukisu.ultra.ui.component.SwitchItem
|
||||||
import zako.zako.zako.ui.component.profile.AppProfileConfig
|
import com.sukisu.ultra.ui.component.profile.AppProfileConfig
|
||||||
import zako.zako.zako.ui.component.profile.RootProfileConfig
|
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||||
import zako.zako.zako.ui.component.profile.TemplateConfig
|
import com.sukisu.ultra.ui.component.profile.TemplateConfig
|
||||||
import zako.zako.zako.ui.util.LocalSnackbarHost
|
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||||
import zako.zako.zako.ui.util.forceStopApp
|
import com.sukisu.ultra.ui.util.forceStopApp
|
||||||
import zako.zako.zako.ui.util.getSepolicy
|
import com.sukisu.ultra.ui.util.getSepolicy
|
||||||
import zako.zako.zako.ui.util.launchApp
|
import com.sukisu.ultra.ui.util.launchApp
|
||||||
import zako.zako.zako.ui.util.restartApp
|
import com.sukisu.ultra.ui.util.restartApp
|
||||||
import zako.zako.zako.ui.util.setSepolicy
|
import com.sukisu.ultra.ui.util.setSepolicy
|
||||||
import zako.zako.zako.ui.viewmodel.SuperUserViewModel
|
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||||
import zako.zako.zako.ui.viewmodel.getTemplateInfoById
|
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.material.icons.Icons
|
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.SettingScreenDestination
|
||||||
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
|
||||||
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
|
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
|
|
||||||
enum class BottomBarDestination(
|
enum class BottomBarDestination(
|
||||||
val direction: DirectionDestinationSpec,
|
val direction: DirectionDestinationSpec,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.KeyEventBlocker
|
import com.sukisu.ultra.ui.component.KeyEventBlocker
|
||||||
import zako.zako.zako.ui.util.LocalSnackbarHost
|
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||||
import zako.zako.zako.ui.util.runModuleAction
|
import com.sukisu.ultra.ui.util.runModuleAction
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
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.net.Uri
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
@@ -30,9 +30,9 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import zako.zako.zako.ui.component.KeyEventBlocker
|
import com.sukisu.ultra.ui.component.KeyEventBlocker
|
||||||
import zako.zako.zako.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -45,10 +45,6 @@ enum class FlashingStatus {
|
|||||||
|
|
||||||
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
|
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
|
||||||
|
|
||||||
fun getFlashingStatus(): FlashingStatus {
|
|
||||||
return currentFlashingStatus.value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setFlashingStatus(status: FlashingStatus) {
|
fun setFlashingStatus(status: FlashingStatus) {
|
||||||
currentFlashingStatus.value = status
|
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.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -33,15 +33,15 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
|
|||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import zako.zako.zako.*
|
import com.sukisu.ultra.*
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import zako.zako.zako.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
import zako.zako.zako.ui.util.module.LatestVersionInfo
|
import com.sukisu.ultra.ui.util.module.LatestVersionInfo
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import zako.zako.zako.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import zako.zako.zako.ui.theme.getCardElevation
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -49,7 +49,8 @@ import androidx.compose.animation.AnimatedVisibility
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
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 androidx.core.content.edit
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
@@ -92,11 +93,9 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
val isManager = Natives.becomeManager(ksuApp.packageName)
|
val isManager = Natives.becomeManager(ksuApp.packageName)
|
||||||
val deviceModel = getDeviceModel(context)
|
val deviceModel = getDeviceModel(context)
|
||||||
val ksuVersion = if (isManager) Natives.version else null
|
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 isVersion = ksuVersion == 12777
|
||||||
val isManagerVersionValid = managerVersion > (ksuVersion ?: 0) + 33
|
val shouldTriggerRestart = zako && kernelVersion.isGKI() && (isVersion)
|
||||||
val shouldTriggerRestart = Zako && kernelVersion.isGKI() && (isVersion || isManagerVersionValid)
|
|
||||||
|
|
||||||
LaunchedEffect(shouldTriggerRestart) {
|
LaunchedEffect(shouldTriggerRestart) {
|
||||||
if (shouldTriggerRestart) {
|
if (shouldTriggerRestart) {
|
||||||
@@ -285,14 +284,18 @@ private fun TopBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var showDropdown by remember { mutableStateOf(false) }
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
|
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||||
IconButton(onClick = { showDropdown = true }) {
|
IconButton(onClick = { showDropdown = true }) {
|
||||||
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
|
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
|
||||||
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
|
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?
|
val pm =
|
||||||
|
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
|
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_userspace, reason = "userspace")
|
||||||
@@ -303,12 +306,14 @@ private fun TopBar(
|
|||||||
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun StatusCard(
|
private fun StatusCard(
|
||||||
kernelVersion: KernelVersion,
|
kernelVersion: KernelVersion,
|
||||||
@@ -557,6 +562,7 @@ fun DonateCard() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoCard() {
|
private fun InfoCard() {
|
||||||
|
val lkmMode = Natives.isLkmMode
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
.getBoolean("is_simple_mode", false)
|
.getBoolean("is_simple_mode", false)
|
||||||
@@ -568,7 +574,7 @@ private fun InfoCard() {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.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@{
|
) withContext@{
|
||||||
val contents = StringBuilder()
|
val contents = StringBuilder()
|
||||||
val uname = Os.uname()
|
val uname = Os.uname()
|
||||||
@@ -577,42 +583,44 @@ private fun InfoCard() {
|
|||||||
fun InfoCardItem(
|
fun InfoCardItem(
|
||||||
label: String,
|
label: String,
|
||||||
content: String,
|
content: String,
|
||||||
|
icon: ImageVector = Icons.Default.Info
|
||||||
) {
|
) {
|
||||||
contents.appendLine(label).appendLine(content).appendLine()
|
contents.appendLine(label).appendLine(content).appendLine()
|
||||||
|
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 = label, style = MaterialTheme.typography.bodyLarge)
|
||||||
Text(text = content, style = MaterialTheme.typography.bodyMedium)
|
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) {
|
if (!isSimpleMode) {
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
val androidVersion = Build.VERSION.RELEASE
|
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))
|
Spacer(Modifier.height(16.dp))
|
||||||
val deviceModel = getDeviceModel(context)
|
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))
|
Spacer(Modifier.height(16.dp))
|
||||||
val managerVersion = getManagerVersion(context)
|
val managerVersion = getManagerVersion(context)
|
||||||
InfoCardItem(
|
InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings)
|
||||||
stringResource(R.string.home_manager_version),
|
|
||||||
"${managerVersion.first} (${managerVersion.second})"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
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) {
|
if (!isSimpleMode) {
|
||||||
|
if (lkmMode != true) {
|
||||||
val kpmVersion = getKpmVersion()
|
val kpmVersion = getKpmVersion()
|
||||||
var displayVersion: String
|
var displayVersion: String
|
||||||
val isKpmConfigured = checkKpmConfigured()
|
val isKpmConfigured = checkKpmConfigured()
|
||||||
@@ -628,7 +636,8 @@ private fun InfoCard() {
|
|||||||
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
|
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion)
|
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion, icon = Icons.Default.Code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
@@ -653,9 +662,7 @@ private fun InfoCard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
InfoCardItem(
|
InfoCardItem(
|
||||||
stringResource(R.string.home_susfs_version),
|
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage)
|
||||||
infoText
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -709,7 +716,7 @@ private fun getDeviceModel(context: Context): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Build.DEVICE
|
Build.DEVICE
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Build.DEVICE
|
Build.DEVICE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
package zako.zako.zako.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.StringRes
|
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.LocalIndication
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
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.semantics.Role
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
import com.maxkeppeler.sheets.list.models.ListOption
|
import com.maxkeppeler.sheets.list.models.ListOption
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
||||||
import zako.zako.zako.ui.component.DialogHandle
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.component.DialogHandle
|
||||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
import com.sukisu.ultra.ui.component.SlotSelectionDialog
|
||||||
import zako.zako.zako.ui.theme.ThemeConfig
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import zako.zako.zako.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.component.rememberCustomDialog
|
||||||
import zako.zako.zako.ui.theme.getCardElevation
|
import com.sukisu.ultra.flash.HorizonKernelFlashProgress
|
||||||
import zako.zako.zako.ui.util.*
|
import com.sukisu.ultra.flash.HorizonKernelState
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.flash.HorizonKernelWorker
|
||||||
import zako.zako.zako.utils.AssetsUtil
|
import com.sukisu.ultra.ui.theme.CardConfig
|
||||||
import java.io.File
|
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||||
import java.io.FileOutputStream
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import java.io.IOException
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
|
import com.sukisu.ultra.ui.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -60,8 +63,12 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
|
||||||
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
|
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var showRebootDialog by remember { mutableStateOf(false) }
|
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 = {
|
val onFlashComplete = {
|
||||||
showRebootDialog = true
|
showRebootDialog = true
|
||||||
@@ -79,7 +86,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
writer.write("svc power reboot\n")
|
writer.write("svc power reboot\n")
|
||||||
writer.write("exit\n")
|
writer.write("exit\n")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +98,11 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
when (method) {
|
when (method) {
|
||||||
is InstallMethod.HorizonKernel -> {
|
is InstallMethod.HorizonKernel -> {
|
||||||
method.uri?.let { uri ->
|
method.uri?.let { uri ->
|
||||||
val worker = HorizonKernelWorker(context)
|
val worker = HorizonKernelWorker(
|
||||||
|
context = context,
|
||||||
|
state = horizonKernelState,
|
||||||
|
slot = method.slot
|
||||||
|
)
|
||||||
worker.uri = uri
|
worker.uri = uri
|
||||||
worker.setOnFlashCompleteListener(onFlashComplete)
|
worker.setOnFlashCompleteListener(onFlashComplete)
|
||||||
worker.start()
|
worker.start()
|
||||||
@@ -110,6 +121,22 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
Unit
|
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 = "") {
|
val currentKmi by produceState(initialValue = "") {
|
||||||
value = getCurrentKmi()
|
value = getCurrentKmi()
|
||||||
}
|
}
|
||||||
@@ -165,9 +192,25 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
SelectInstallMethod { method ->
|
SelectInstallMethod(
|
||||||
|
onSelected = { method ->
|
||||||
|
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
|
||||||
|
tempKernelUri = method.uri
|
||||||
|
showSlotSelectionDialog = true
|
||||||
|
} else {
|
||||||
installMethod = method
|
installMethod = method
|
||||||
}
|
}
|
||||||
|
horizonKernelState.reset()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = flashState.isFlashing && installMethod is InstallMethod.HorizonKernel,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically()
|
||||||
|
) {
|
||||||
|
HorizonKernelFlashProgress(flashState)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -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(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = installMethod != null,
|
enabled = installMethod != null && !flashState.isFlashing,
|
||||||
onClick = onClickNext
|
onClick = onClickNext
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -197,7 +251,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RebootDialog(
|
private fun RebootDialog(
|
||||||
show: Boolean,
|
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 {
|
sealed class InstallMethod {
|
||||||
data class SelectFile(
|
data class SelectFile(
|
||||||
val uri: Uri? = null,
|
val uri: Uri? = null,
|
||||||
@@ -369,6 +295,7 @@ sealed class InstallMethod {
|
|||||||
|
|
||||||
data class HorizonKernel(
|
data class HorizonKernel(
|
||||||
val uri: Uri? = null,
|
val uri: Uri? = null,
|
||||||
|
val slot: String? = null,
|
||||||
@StringRes override val label: Int = R.string.horizon_kernel,
|
@StringRes override val label: Int = R.string.horizon_kernel,
|
||||||
override val summary: String? = null
|
override val summary: String? = null
|
||||||
) : InstallMethod()
|
) : InstallMethod()
|
||||||
@@ -381,6 +308,7 @@ sealed class InstallMethod {
|
|||||||
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
||||||
val rootAvailable = rootAvailable()
|
val rootAvailable = rootAvailable()
|
||||||
val isAbDevice = isAbDevice()
|
val isAbDevice = isAbDevice()
|
||||||
|
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
|
||||||
val selectFileTip = stringResource(
|
val selectFileTip = stringResource(
|
||||||
id = R.string.select_file_tip,
|
id = R.string.select_file_tip,
|
||||||
if (isInitBoot()) "init_boot" else "boot"
|
if (isInitBoot()) "init_boot" else "boot"
|
||||||
@@ -395,7 +323,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|||||||
if (isAbDevice) {
|
if (isAbDevice) {
|
||||||
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
|
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) }
|
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
|
||||||
@@ -408,7 +336,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
|
|||||||
it.data?.data?.let { uri ->
|
it.data?.data?.let { uri ->
|
||||||
val option = when (currentSelectingMethod) {
|
val option = when (currentSelectingMethod) {
|
||||||
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
|
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
|
else -> null
|
||||||
}
|
}
|
||||||
option?.let {
|
option?.let {
|
||||||
@@ -580,8 +508,15 @@ private fun TopBar(
|
|||||||
onLkmUpload: () -> Unit = {},
|
onLkmUpload: () -> Unit = {},
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
|
val cardColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
val cardAlpha = CardConfig.cardAlpha
|
||||||
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.install)) },
|
title = { Text(stringResource(R.string.install)) },
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = cardColor.copy(alpha = cardAlpha),
|
||||||
|
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
|
||||||
|
),
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
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.app.Activity.RESULT_OK
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -25,30 +25,23 @@ import com.ramcosta.composedestinations.annotation.RootGraph
|
|||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import zako.zako.zako.ui.component.ConfirmResult
|
import com.sukisu.ultra.ui.component.*
|
||||||
import zako.zako.zako.ui.component.SearchAppBar
|
import com.sukisu.ultra.ui.theme.*
|
||||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.viewmodel.KpmViewModel
|
||||||
import zako.zako.zako.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.util.*
|
||||||
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 java.io.File
|
import java.io.File
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import zako.zako.zako.ui.theme.ThemeConfig
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.rememberCustomDialog
|
import java.io.BufferedReader
|
||||||
import zako.zako.zako.ui.component.ConfirmDialogHandle
|
import java.io.FileInputStream
|
||||||
import zako.zako.zako.R
|
import java.io.InputStreamReader
|
||||||
import java.net.URLDecoder
|
import java.net.*
|
||||||
import java.net.URLEncoder
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KPM 管理界面
|
* KPM 管理界面
|
||||||
* 以下内核模块功能由KernelPatch开发,经过修改后加入SukiSU Ultra的内核模块功能
|
* 以下内核模块功能由KernelPatch开发,经过修改后加入SukiSU Ultra的内核模块功能
|
||||||
* 开发者:zako, Liaokong
|
* 开发者:ShirkNeko, Liaokong
|
||||||
*/
|
*/
|
||||||
var globalModuleFileName: String = ""
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
@@ -66,6 +59,11 @@ fun KpmScreen(
|
|||||||
MaterialTheme.colorScheme.secondaryContainer
|
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 scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
|
||||||
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
|
||||||
@@ -78,13 +76,33 @@ fun KpmScreen(
|
|||||||
val kpmInstallMode = stringResource(R.string.kpm_install_mode)
|
val kpmInstallMode = stringResource(R.string.kpm_install_mode)
|
||||||
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
|
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
|
||||||
val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed)
|
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 invalidFileTypeMessage = stringResource(R.string.invalid_file_type)
|
||||||
val confirmTitle = stringResource(R.string.confirm_uninstall_title_with_filename)
|
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) }
|
var tempFileForInstall by remember { mutableStateOf<File?>(null) }
|
||||||
val installModeDialog = rememberCustomDialog { dismiss ->
|
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(
|
AlertDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
dismiss()
|
dismiss()
|
||||||
@@ -92,7 +110,7 @@ fun KpmScreen(
|
|||||||
tempFileForInstall = null
|
tempFileForInstall = null
|
||||||
},
|
},
|
||||||
title = { Text(kpmInstallMode) },
|
title = { Text(kpmInstallMode) },
|
||||||
text = { Text(kpmInstallModeDescription) },
|
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Column {
|
Column {
|
||||||
Button(
|
Button(
|
||||||
@@ -170,15 +188,30 @@ fun KpmScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tempFile.name.endsWith(".kpm")) {
|
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(
|
snackBarHost.showSnackbar(
|
||||||
message = invalidFileTypeMessage,
|
message = invalidFileTypeMessage,
|
||||||
duration = SnackbarDuration.Short
|
duration = SnackbarDuration.Short
|
||||||
)
|
)
|
||||||
|
}
|
||||||
tempFile.delete()
|
tempFile.delete()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
tempFileForInstall = tempFile
|
tempFileForInstall = tempFile
|
||||||
installModeDialog.show()
|
installModeDialog.show()
|
||||||
}
|
}
|
||||||
@@ -217,7 +250,7 @@ fun KpmScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
selectPatchLauncher.launch(
|
selectPatchLauncher.launch(
|
||||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
type = "*/*"
|
type = "application/octet-stream"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -281,6 +314,7 @@ fun KpmScreen(
|
|||||||
module = module,
|
module = module,
|
||||||
onUninstall = {
|
onUninstall = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
val confirmContent = moduleConfirmContentMap[module.id] ?: ""
|
||||||
handleModuleUninstall(
|
handleModuleUninstall(
|
||||||
module = module,
|
module = module,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -315,8 +349,25 @@ private suspend fun handleModuleInstall(
|
|||||||
kpmInstallSuccess: String,
|
kpmInstallSuccess: String,
|
||||||
kpmInstallFailed: String
|
kpmInstallFailed: String
|
||||||
) {
|
) {
|
||||||
val moduleId = extractModuleId(tempFile.name)
|
var moduleId: String? = null
|
||||||
if (moduleId == 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}")
|
Log.e("KsuCli", "Failed to extract module ID from file: ${tempFile.name}")
|
||||||
snackBarHost.showSnackbar(
|
snackBarHost.showSnackbar(
|
||||||
message = kpmInstallFailed,
|
message = kpmInstallFailed,
|
||||||
@@ -358,18 +409,6 @@ private suspend fun handleModuleInstall(
|
|||||||
tempFile.delete()
|
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(
|
private suspend fun handleModuleUninstall(
|
||||||
module: KpmViewModel.ModuleInfo,
|
module: KpmViewModel.ModuleInfo,
|
||||||
viewModel: KpmViewModel,
|
viewModel: KpmViewModel,
|
||||||
@@ -384,7 +423,6 @@ private suspend fun handleModuleUninstall(
|
|||||||
confirmDialog: ConfirmDialogHandle
|
confirmDialog: ConfirmDialogHandle
|
||||||
) {
|
) {
|
||||||
val moduleFileName = "${module.id}.kpm"
|
val moduleFileName = "${module.id}.kpm"
|
||||||
globalModuleFileName = moduleFileName
|
|
||||||
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
|
||||||
|
|
||||||
val fileExists = try {
|
val fileExists = try {
|
||||||
@@ -554,3 +592,40 @@ 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.app.Activity.*
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -88,32 +88,32 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.ui.component.ConfirmResult
|
import com.sukisu.ultra.ui.component.ConfirmResult
|
||||||
import zako.zako.zako.ui.component.SearchAppBar
|
import com.sukisu.ultra.ui.component.SearchAppBar
|
||||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.component.rememberConfirmDialog
|
||||||
import zako.zako.zako.ui.component.rememberLoadingDialog
|
import com.sukisu.ultra.ui.component.rememberLoadingDialog
|
||||||
import zako.zako.zako.ui.util.DownloadListener
|
import com.sukisu.ultra.ui.util.DownloadListener
|
||||||
import zako.zako.zako.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
import zako.zako.zako.ui.util.download
|
import com.sukisu.ultra.ui.util.download
|
||||||
import zako.zako.zako.ui.util.hasMagisk
|
import com.sukisu.ultra.ui.util.hasMagisk
|
||||||
import zako.zako.zako.ui.util.reboot
|
import com.sukisu.ultra.ui.util.reboot
|
||||||
import zako.zako.zako.ui.util.restoreModule
|
import com.sukisu.ultra.ui.util.restoreModule
|
||||||
import zako.zako.zako.ui.util.toggleModule
|
import com.sukisu.ultra.ui.util.toggleModule
|
||||||
import zako.zako.zako.ui.util.uninstallModule
|
import com.sukisu.ultra.ui.util.uninstallModule
|
||||||
import zako.zako.zako.ui.webui.WebUIActivity
|
import com.sukisu.ultra.ui.webui.WebUIActivity
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import zako.zako.zako.ui.util.ModuleModify
|
import com.sukisu.ultra.ui.util.ModuleModify
|
||||||
import zako.zako.zako.ui.theme.getCardColors
|
import com.sukisu.ultra.ui.theme.getCardColors
|
||||||
import zako.zako.zako.ui.theme.getCardElevation
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
import zako.zako.zako.ui.viewmodel.ModuleViewModel
|
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import zako.zako.zako.ui.theme.ThemeConfig
|
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@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 androidx.compose.animation.AnimatedVisibility
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -26,8 +26,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
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.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
@@ -37,21 +35,13 @@ import com.topjohnwu.superuser.Shell
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import zako.zako.zako.ui.component.SwitchItem
|
import com.sukisu.ultra.ui.component.ImageEditorDialog
|
||||||
import zako.zako.zako.ui.theme.CardConfig
|
import com.sukisu.ultra.ui.component.SwitchItem
|
||||||
import zako.zako.zako.ui.theme.ThemeColors
|
import com.sukisu.ultra.ui.theme.*
|
||||||
import zako.zako.zako.ui.theme.ThemeConfig
|
import com.sukisu.ultra.ui.util.*
|
||||||
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 androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
|
import com.sukisu.ultra.*
|
||||||
|
|
||||||
|
|
||||||
fun saveCardConfig(context: Context) {
|
fun saveCardConfig(context: Context) {
|
||||||
@@ -151,6 +141,10 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
mutableStateOf(ThemeConfig.customBackgroundUri != null)
|
mutableStateOf(ThemeConfig.customBackgroundUri != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 图片编辑状态
|
||||||
|
var showImageEditor by remember { mutableStateOf(false) }
|
||||||
|
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
|
|
||||||
// 初始化卡片配置
|
// 初始化卡片配置
|
||||||
val systemIsDark = isSystemInDarkTheme()
|
val systemIsDark = isSystemInDarkTheme()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -187,17 +181,36 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var showThemeColorDialog by remember { mutableStateOf(false) }
|
var showThemeColorDialog by remember { mutableStateOf(false) }
|
||||||
|
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
||||||
|
|
||||||
// 图片选择器
|
// 图片选择器
|
||||||
val pickImageLauncher = rememberLauncherForActivityResult(
|
val pickImageLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.GetContent()
|
ActivityResultContracts.GetContent()
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
uri?.let {
|
uri?.let {
|
||||||
context.saveCustomBackground(it)
|
selectedImageUri = it
|
||||||
|
showImageEditor = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示图片编辑对话框
|
||||||
|
if (showImageEditor && selectedImageUri != null) {
|
||||||
|
ImageEditorDialog(
|
||||||
|
imageUri = selectedImageUri!!,
|
||||||
|
onDismiss = {
|
||||||
|
showImageEditor = false
|
||||||
|
selectedImageUri = null
|
||||||
|
},
|
||||||
|
onConfirm = { transformedUri ->
|
||||||
|
context.saveAndApplyCustomBackground(transformedUri)
|
||||||
isCustomBackgroundEnabled = true
|
isCustomBackgroundEnabled = true
|
||||||
CardConfig.cardElevation = 0.dp
|
CardConfig.cardElevation = 0.dp
|
||||||
|
CardConfig.isCustomBackgroundEnabled = true
|
||||||
saveCardConfig(context)
|
saveCardConfig(context)
|
||||||
|
showImageEditor = false
|
||||||
|
selectedImageUri = null
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -220,6 +233,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
.padding(top = 12.dp)
|
.padding(top = 12.dp)
|
||||||
) {
|
) {
|
||||||
// SELinux 开关
|
// SELinux 开关
|
||||||
|
if (ksuIsValid) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
icon = Icons.Filled.Security,
|
icon = Icons.Filled.Security,
|
||||||
title = stringResource(R.string.selinux),
|
title = stringResource(R.string.selinux),
|
||||||
@@ -233,6 +247,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
if (result.isSuccess) selinuxEnabled = enabled
|
if (result.isSuccess) selinuxEnabled = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var isExpanded by remember { mutableStateOf(false) }
|
var isExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -447,6 +462,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
|
|||||||
CardConfig.cardElevation = CardConfig.defaultElevation
|
CardConfig.cardElevation = CardConfig.defaultElevation
|
||||||
CardConfig.cardAlpha = 0.45f
|
CardConfig.cardAlpha = 0.45f
|
||||||
CardConfig.isCustomAlphaSet = false
|
CardConfig.isCustomAlphaSet = false
|
||||||
|
CardConfig.isCustomBackgroundEnabled = false
|
||||||
saveCardConfig(context)
|
saveCardConfig(context)
|
||||||
cardAlpha = 0.35f
|
cardAlpha = 0.35f
|
||||||
themeMode = 0
|
themeMode = 0
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -7,32 +7,14 @@ import android.widget.Toast
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.*
|
||||||
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.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.Undo
|
import androidx.compose.material.icons.automirrored.filled.Undo
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.*
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -64,22 +46,14 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import zako.zako.zako.BuildConfig
|
import com.sukisu.ultra.BuildConfig
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.AboutDialog
|
import com.sukisu.ultra.*
|
||||||
import zako.zako.zako.ui.component.ConfirmResult
|
import com.sukisu.ultra.ui.component.*
|
||||||
import zako.zako.zako.ui.component.DialogHandle
|
import com.sukisu.ultra.ui.theme.*
|
||||||
import zako.zako.zako.ui.component.SwitchItem
|
import com.sukisu.ultra.ui.util.LocalSnackbarHost
|
||||||
import zako.zako.zako.ui.component.rememberConfirmDialog
|
import com.sukisu.ultra.ui.util.getBugreportFile
|
||||||
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 java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
@@ -95,6 +69,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
// region 界面基础设置
|
// region 界面基础设置
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
|
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -142,6 +117,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
// region 配置项列表
|
// region 配置项列表
|
||||||
// 配置文件模板入口
|
// 配置文件模板入口
|
||||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||||
|
if (ksuIsValid) {
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
|
||||||
headlineContent = { Text(profileTemplate) },
|
headlineContent = { Text(profileTemplate) },
|
||||||
@@ -150,10 +126,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
navigator.navigate(AppProfileTemplateScreenDestination)
|
navigator.navigate(AppProfileTemplateScreenDestination)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
// 卸载模块开关
|
// 卸载模块开关
|
||||||
var umountChecked by rememberSaveable {
|
var umountChecked by rememberSaveable {
|
||||||
mutableStateOf(Natives.isDefaultUmountModules())
|
mutableStateOf(Natives.isDefaultUmountModules())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ksuIsValid) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
icon = Icons.Filled.FolderDelete,
|
icon = Icons.Filled.FolderDelete,
|
||||||
title = stringResource(id = R.string.settings_umount_modules_default),
|
title = stringResource(id = R.string.settings_umount_modules_default),
|
||||||
@@ -164,7 +143,9 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
umountChecked = it
|
umountChecked = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// SU 禁用开关(仅在兼容版本显示)
|
// SU 禁用开关(仅在兼容版本显示)
|
||||||
|
if (ksuIsValid) {
|
||||||
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
|
||||||
var isSuDisabled by rememberSaveable {
|
var isSuDisabled by rememberSaveable {
|
||||||
mutableStateOf(!Natives.isSuEnabled())
|
mutableStateOf(!Natives.isSuEnabled())
|
||||||
@@ -181,6 +162,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
@@ -206,6 +188,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
prefs.getBoolean("enable_web_debugging", false)
|
prefs.getBoolean("enable_web_debugging", false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (Natives.isKsuValid(ksuApp.packageName)) {
|
||||||
SwitchItem(
|
SwitchItem(
|
||||||
icon = Icons.Filled.DeveloperMode,
|
icon = Icons.Filled.DeveloperMode,
|
||||||
title = stringResource(id = R.string.enable_web_debugging),
|
title = stringResource(id = R.string.enable_web_debugging),
|
||||||
@@ -215,6 +198,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
prefs.edit { putBoolean("enable_web_debugging", it) }
|
prefs.edit { putBoolean("enable_web_debugging", it) }
|
||||||
enableWebDebugging = it
|
enableWebDebugging = it
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 更多设置
|
// 更多设置
|
||||||
val newButtonTitle = stringResource(id = R.string.more_settings)
|
val newButtonTitle = stringResource(id = R.string.more_settings)
|
||||||
ListItem(
|
ListItem(
|
||||||
@@ -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.background
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
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.generated.destinations.AppProfileScreenDestination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.ui.component.SearchAppBar
|
import com.sukisu.ultra.ui.component.SearchAppBar
|
||||||
import zako.zako.zako.ui.util.ModuleModify
|
import com.sukisu.ultra.ui.util.ModuleModify
|
||||||
import zako.zako.zako.ui.viewmodel.SuperUserViewModel
|
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@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 android.widget.Toast
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -44,11 +46,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
import androidx.lifecycle.compose.dropUnlessResumed
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
@@ -59,9 +60,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
|
|||||||
import com.ramcosta.composedestinations.result.getOr
|
import com.ramcosta.composedestinations.result.getOr
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.theme.ThemeConfig
|
import com.sukisu.ultra.ui.theme.ThemeConfig
|
||||||
import zako.zako.zako.ui.viewmodel.TemplateViewModel
|
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author weishu
|
* @author weishu
|
||||||
@@ -99,8 +100,8 @@ fun AppProfileTemplateScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val clipboardManager = context.getSystemService<ClipboardManager>()
|
||||||
val showToast = fun(msg: String) {
|
val showToast = fun(msg: String) {
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
|
||||||
@@ -112,21 +113,21 @@ fun AppProfileTemplateScreen(
|
|||||||
scope.launch { viewModel.fetchTemplates(true) }
|
scope.launch { viewModel.fetchTemplates(true) }
|
||||||
},
|
},
|
||||||
onImport = {
|
onImport = {
|
||||||
clipboardManager.getText()?.text?.let {
|
|
||||||
if (it.isEmpty()) {
|
|
||||||
showToast(context.getString(R.string.app_profile_template_import_empty))
|
|
||||||
return@let
|
|
||||||
}
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
val clipboardText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString()
|
||||||
|
if (clipboardText.isNullOrEmpty()) {
|
||||||
|
showToast(context.getString(R.string.app_profile_template_import_empty))
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
viewModel.importTemplates(
|
viewModel.importTemplates(
|
||||||
it, {
|
clipboardText,
|
||||||
|
{
|
||||||
showToast(context.getString(R.string.app_profile_template_import_success))
|
showToast(context.getString(R.string.app_profile_template_import_success))
|
||||||
viewModel.fetchTemplates(false)
|
viewModel.fetchTemplates(false)
|
||||||
},
|
},
|
||||||
showToast
|
showToast
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onExport = {
|
onExport = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -134,8 +135,8 @@ fun AppProfileTemplateScreen(
|
|||||||
{
|
{
|
||||||
showToast(context.getString(R.string.app_profile_template_export_empty))
|
showToast(context.getString(R.string.app_profile_template_export_empty))
|
||||||
}
|
}
|
||||||
) {
|
) { text ->
|
||||||
clipboardManager.setText(AnnotatedString(it))
|
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 android.widget.Toast
|
||||||
import androidx.activity.compose.BackHandler
|
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.Destination
|
||||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import zako.zako.zako.ui.component.profile.RootProfileConfig
|
import com.sukisu.ultra.ui.component.profile.RootProfileConfig
|
||||||
import zako.zako.zako.ui.util.deleteAppProfileTemplate
|
import com.sukisu.ultra.ui.util.deleteAppProfileTemplate
|
||||||
import zako.zako.zako.ui.util.getAppProfileTemplate
|
import com.sukisu.ultra.ui.util.getAppProfileTemplate
|
||||||
import zako.zako.zako.ui.util.setAppProfileTemplate
|
import com.sukisu.ultra.ui.util.setAppProfileTemplate
|
||||||
import zako.zako.zako.ui.viewmodel.TemplateViewModel
|
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel
|
||||||
import zako.zako.zako.ui.viewmodel.toJSON
|
import com.sukisu.ultra.ui.viewmodel.toJSON
|
||||||
import androidx.lifecycle.compose.dropUnlessResumed
|
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 android.content.Context
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
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
|
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.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -27,11 +27,14 @@ import androidx.compose.ui.zIndex
|
|||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import com.sukisu.ultra.ui.util.BackgroundTransformation
|
||||||
|
import com.sukisu.ultra.ui.util.saveTransformedBackground
|
||||||
|
|
||||||
object ThemeConfig {
|
object ThemeConfig {
|
||||||
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
var customBackgroundUri by mutableStateOf<Uri?>(null)
|
||||||
@@ -88,29 +91,6 @@ private fun getLightColorScheme() = lightColorScheme(
|
|||||||
outlineVariant = Color.Black.copy(alpha = 0.12f)
|
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
|
@Composable
|
||||||
fun KernelSUTheme(
|
fun KernelSUTheme(
|
||||||
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
|
||||||
@@ -131,7 +111,6 @@ fun KernelSUTheme(
|
|||||||
if (darkTheme) {
|
if (darkTheme) {
|
||||||
val originalScheme = dynamicDarkColorScheme(context)
|
val originalScheme = dynamicDarkColorScheme(context)
|
||||||
originalScheme.copy(
|
originalScheme.copy(
|
||||||
// 调整按钮相关颜色
|
|
||||||
primary = adjustColor(originalScheme.primary),
|
primary = adjustColor(originalScheme.primary),
|
||||||
onPrimary = adjustColor(originalScheme.onPrimary),
|
onPrimary = adjustColor(originalScheme.onPrimary),
|
||||||
primaryContainer = adjustColor(originalScheme.primaryContainer),
|
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?) {
|
fun Context.saveCustomBackground(uri: Uri?) {
|
||||||
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
val newUri = uri?.let { copyImageToInternalStorage(it) }
|
||||||
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
|
||||||
@@ -249,6 +270,10 @@ fun Context.saveCustomBackground(uri: Uri?) {
|
|||||||
putString("custom_background", newUri?.toString())
|
putString("custom_background", newUri?.toString())
|
||||||
}
|
}
|
||||||
ThemeConfig.customBackgroundUri = newUri
|
ThemeConfig.customBackgroundUri = newUri
|
||||||
|
if (uri != null) {
|
||||||
|
CardConfig.cardElevation = 0.dp
|
||||||
|
CardConfig.isCustomBackgroundEnabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.loadCustomBackground() {
|
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.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
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.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
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.annotation.SuppressLint
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
@@ -12,7 +12,8 @@ import android.util.Log
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.core.content.ContextCompat
|
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
|
* @author weishu
|
||||||
@@ -42,14 +43,14 @@ fun download(
|
|||||||
onDownloading()
|
onDownloading()
|
||||||
return
|
return
|
||||||
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
} else if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
onDownloaded(Uri.parse(localUri))
|
onDownloaded(localUri.toUri())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val request = DownloadManager.Request(Uri.parse(url))
|
val request = DownloadManager.Request(url.toUri())
|
||||||
.setDestinationInExternalPublicDir(
|
.setDestinationInExternalPublicDir(
|
||||||
Environment.DIRECTORY_DOWNLOADS,
|
Environment.DIRECTORY_DOWNLOADS,
|
||||||
fileName
|
fileName
|
||||||
@@ -140,7 +141,7 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
|
|||||||
val uri = cursor.getString(
|
val uri = cursor.getString(
|
||||||
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
|
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
|
* 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.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.material3.MaterialTheme
|
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.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import zako.zako.zako.BuildConfig
|
import com.sukisu.ultra.BuildConfig
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -436,7 +436,7 @@ fun restartApp(packageName: String) {
|
|||||||
launchApp(packageName)
|
launchApp(packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSuSFSDaemonPath(): String {
|
fun getSuSFSDaemonPath(): String {
|
||||||
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
|
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.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.ui.screen.getManagerVersion
|
import com.sukisu.ultra.ui.screen.getManagerVersion
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
import java.io.PrintWriter
|
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.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStreamReader
|
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.runtime.Composable
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import zako.zako.zako.R
|
import com.sukisu.ultra.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun getSELinuxStatus(): String {
|
fun getSELinuxStatus(): String {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.util.module
|
package com.sukisu.ultra.ui.util.module
|
||||||
|
|
||||||
data class LatestVersionInfo(
|
data class LatestVersionInfo(
|
||||||
val versionCode : Int = 0,
|
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 android.util.Log
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -9,7 +9,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import zako.zako.zako.ui.util.*
|
import com.sukisu.ultra.ui.util.*
|
||||||
|
|
||||||
class KpmViewModel : ViewModel() {
|
class KpmViewModel : ViewModel() {
|
||||||
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
var moduleList by mutableStateOf(emptyList<ModuleInfo>())
|
||||||
@@ -143,17 +143,6 @@ class KpmViewModel : ViewModel() {
|
|||||||
return result
|
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(
|
data class ModuleInfo(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: 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.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import zako.zako.zako.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import zako.zako.zako.ui.util.listModules
|
import com.sukisu.ultra.ui.util.listModules
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
@@ -40,13 +40,6 @@ class ModuleViewModel : ViewModel() {
|
|||||||
val dirId: String, // real module id (dir name)
|
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)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
var search by mutableStateOf("")
|
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.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -18,12 +18,12 @@ import com.topjohnwu.superuser.Shell
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import zako.zako.zako.IKsuInterface
|
import com.sukisu.zako.IKsuInterface
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import zako.zako.zako.ui.KsuService
|
import com.sukisu.ultra.ui.KsuService
|
||||||
import zako.zako.zako.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import zako.zako.zako.ui.util.KsuCli
|
import com.sukisu.ultra.ui.util.KsuCli
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
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.os.Parcelable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -10,12 +10,12 @@ import androidx.lifecycle.ViewModel
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import zako.zako.zako.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import zako.zako.zako.profile.Capabilities
|
import com.sukisu.ultra.profile.Capabilities
|
||||||
import zako.zako.zako.profile.Groups
|
import com.sukisu.ultra.profile.Groups
|
||||||
import zako.zako.zako.ui.util.getAppProfileTemplate
|
import com.sukisu.ultra.ui.util.getAppProfileTemplate
|
||||||
import zako.zako.zako.ui.util.listAppProfileTemplates
|
import com.sukisu.ultra.ui.util.listAppProfileTemplates
|
||||||
import zako.zako.zako.ui.util.setAppProfileTemplate
|
import com.sukisu.ultra.ui.util.setAppProfileTemplate
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package zako.zako.zako.ui.webui;
|
package com.sukisu.ultra.ui.webui;
|
||||||
|
|
||||||
import java.net.URLConnection;
|
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.content.Context;
|
||||||
import android.util.Log;
|
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.annotation.SuppressLint
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.webkit.WebViewAssetLoader
|
import androidx.webkit.WebViewAssetLoader
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import zako.zako.zako.ui.util.createRootShell
|
import com.sukisu.ultra.ui.util.createRootShell
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.ui.webui
|
package com.sukisu.ultra.ui.webui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -14,13 +14,13 @@ import androidx.core.view.WindowInsetsControllerCompat
|
|||||||
import com.topjohnwu.superuser.CallbackList
|
import com.topjohnwu.superuser.CallbackList
|
||||||
import com.topjohnwu.superuser.ShellUtils
|
import com.topjohnwu.superuser.ShellUtils
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||||
import zako.zako.zako.ui.util.createRootShell
|
import com.sukisu.ultra.ui.util.createRootShell
|
||||||
import zako.zako.zako.ui.util.listModules
|
import com.sukisu.ultra.ui.util.listModules
|
||||||
import zako.zako.zako.ui.util.withNewRootShell
|
import com.sukisu.ultra.ui.util.withNewRootShell
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import zako.zako.zako.ui.util.controlKpmModule
|
import com.sukisu.ultra.ui.util.controlKpmModule
|
||||||
import zako.zako.zako.ui.util.listKpmModules
|
import com.sukisu.ultra.ui.util.listKpmModules
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package zako.zako.zako.utils
|
package com.sukisu.ultra.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package io.zako.zako;
|
package io.sukisu.ultra;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import zako.zako.zako.ui.util.KsuCli;
|
import com.sukisu.ultra.ui.util.KsuCli;
|
||||||
|
|
||||||
public class UltraShellHelper {
|
public class UltraShellHelper {
|
||||||
public static String runCmd(String cmds) {
|
public static String runCmd(String cmds) {
|
||||||
@@ -19,7 +19,7 @@ public class UltraShellHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPathExists(String path) {
|
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) {
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
manager/app/src/main/jniLibs/.gitignore
vendored
2
manager/app/src/main/jniLibs/.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
libzakozako.so
|
libzakozako.so
|
||||||
libzakozakozako.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_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
|
||||||
<string name="home_support_title">از ما حمایت کنید</string>
|
<string name="home_support_title">از ما حمایت کنید</string>
|
||||||
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
|
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
|
||||||
<string name="profile">پروفایل برنامه</string>
|
|
||||||
<string name="profile_default">پیشفرض</string>
|
<string name="profile_default">پیشفرض</string>
|
||||||
<string name="profile_template">قالب</string>
|
<string name="profile_template">قالب</string>
|
||||||
<string name="profile_custom">شخصی سازی شده</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_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string>
|
||||||
<string name="home_support_title">Dukung Kami</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="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_default">Bawaan</string>
|
||||||
<string name="profile_template">Templat</string>
|
<string name="profile_template">Templat</string>
|
||||||
<string name="profile_custom">Khusus</string>
|
<string name="profile_custom">Khusus</string>
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
<string name="reboot">再起動</string>
|
<string name="reboot">再起動</string>
|
||||||
<string name="settings">設定</string>
|
<string name="settings">設定</string>
|
||||||
<string name="reboot_userspace">ソフトリブート</string>
|
<string name="reboot_userspace">ソフトリブート</string>
|
||||||
<string name="reboot_recovery">リカバリーへ再起動</string>
|
<string name="reboot_recovery">リカバリーで再起動</string>
|
||||||
<string name="reboot_bootloader">ブートローダーへ再起動</string>
|
<string name="reboot_bootloader">ブートローダーで再起動</string>
|
||||||
<string name="reboot_download">ダウンロードモードへ再起動</string>
|
<string name="reboot_download">ダウンロードモードで再起動</string>
|
||||||
<string name="reboot_edl">EDL へ再起動</string>
|
<string name="reboot_edl">EDL で再起動</string>
|
||||||
<string name="about">アプリについて</string>
|
<string name="about">アプリについて</string>
|
||||||
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
|
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
|
||||||
<string name="module_uninstall_success">%s はアンインストールされました</string>
|
<string name="module_uninstall_success">%s はアンインストールされました</string>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</string>
|
<string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</string>
|
||||||
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
|
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
|
||||||
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string>
|
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</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">Kprobe フックを非表示にする</string>
|
||||||
<string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string>
|
<string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string>
|
||||||
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
|
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
|
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
|
||||||
<string name="module_changelog">変更履歴</string>
|
<string name="module_changelog">変更履歴</string>
|
||||||
<string name="settings_profile_template">アプリプロファイルのテンプレート</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_create">テンプレートの作成</string>
|
||||||
<string name="app_profile_template_edit">テンプレートの編集</string>
|
<string name="app_profile_template_edit">テンプレートの編集</string>
|
||||||
<string name="app_profile_template_id">ID</string>
|
<string name="app_profile_template_id">ID</string>
|
||||||
@@ -110,12 +110,12 @@
|
|||||||
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
<string name="app_profile_template_import_empty">クリップボードが空です!</string>
|
||||||
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
|
||||||
<string name="settings_check_update">更新を確認する</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="grant_root_failed">root の付与に失敗しました!</string>
|
||||||
<string name="action">アクション</string>
|
<string name="action">アクション</string>
|
||||||
<string name="open">開く</string>
|
<string name="open">開く</string>
|
||||||
<string name="enable_web_debugging">WebView デバッグを有効化する</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="direct_install">直接インストール (推奨)</string>
|
||||||
<string name="select_file">ファイルを選択</string>
|
<string name="select_file">ファイルを選択</string>
|
||||||
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
|
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
|
||||||
@@ -172,9 +172,8 @@
|
|||||||
<string name="backup_allowlist">許可リストをバックアップ</string>
|
<string name="backup_allowlist">許可リストをバックアップ</string>
|
||||||
<string name="restore_allowlist">許可リストを復元</string>
|
<string name="restore_allowlist">許可リストを復元</string>
|
||||||
<string name="settings_custom_background">カスタム背景を設定</string>
|
<string name="settings_custom_background">カスタム背景を設定</string>
|
||||||
<string name="settings_custom_background_summary">カスタム背景を設定します。</string>
|
<string name="settings_custom_background_summary">カスタム背景を設定します</string>
|
||||||
<string name="settings_card_manage">カードの管理</string>
|
<string name="settings_card_alpha">ナビゲーションバーの透過</string>
|
||||||
<string name="settings_card_alpha">カードのアルファ</string>
|
|
||||||
<string name="settings_restore_default">デフォルトに復元</string>
|
<string name="settings_restore_default">デフォルトに復元</string>
|
||||||
<string name="home_android_version">Android のバージョン</string>
|
<string name="home_android_version">Android のバージョン</string>
|
||||||
<string name="home_device_model">デバイスモデル</string>
|
<string name="home_device_model">デバイスモデル</string>
|
||||||
@@ -189,13 +188,13 @@
|
|||||||
<string name="selinux_enabled">有効</string>
|
<string name="selinux_enabled">有効</string>
|
||||||
<string name="selinux_disabled">無効</string>
|
<string name="selinux_disabled">無効</string>
|
||||||
<string name="simple_mode">シンプルモード</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">カーネルのバージョンを非表示にする</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">その他の情報を非表示にする</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">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_mode">テーマモード</string>
|
||||||
<string name="theme_follow_system">システムに従う</string>
|
<string name="theme_follow_system">システムに従う</string>
|
||||||
<string name="theme_light">ライトカラー</string>
|
<string name="theme_light">ライトカラー</string>
|
||||||
@@ -215,6 +214,7 @@
|
|||||||
<string name="flash_option">ブラシの設定</string>
|
<string name="flash_option">ブラシの設定</string>
|
||||||
<string name="flash_option_tip">フラッシュするファイルを選択</string>
|
<string name="flash_option_tip">フラッシュするファイルを選択</string>
|
||||||
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
|
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
|
||||||
|
<string name="horizon_kernel_summary">AnyKernel3 をフラッシュします</string>
|
||||||
<string name="root_required">root 権限が必要です</string>
|
<string name="root_required">root 権限が必要です</string>
|
||||||
<string name="copy_failed">ファイルのコピーに失敗しました</string>
|
<string name="copy_failed">ファイルのコピーに失敗しました</string>
|
||||||
<string name="reboot_complete_title">スクラブが完了しました</string>
|
<string name="reboot_complete_title">スクラブが完了しました</string>
|
||||||
@@ -251,12 +251,12 @@
|
|||||||
<string name="home_kpm_module">KPM モジュール数: %d </string>
|
<string name="home_kpm_module">KPM モジュール数: %d </string>
|
||||||
<string name="kpm_invalid_file">無効な KPM ファイル</string>
|
<string name="kpm_invalid_file">無効な KPM ファイル</string>
|
||||||
<string name="kernel_patched">カーネルはパッチされていません</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="custom_settings">カスタム設定</string>
|
||||||
<string name="kpm_install_mode">インストール</string>
|
<string name="kpm_install_mode">インストール</string>
|
||||||
<string name="kpm_install_mode_load">読み込む</string>
|
<string name="kpm_install_mode_load">読み込む</string>
|
||||||
<string name="kpm_install_mode_embed">埋め込む</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="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
|
||||||
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
|
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
|
||||||
<string name="confirm_uninstall_title">アンインストールを確認</string>
|
<string name="confirm_uninstall_title">アンインストールを確認</string>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">KernelSU</string>
|
|
||||||
<string name="home">Strona główna</string>
|
<string name="home">Strona główna</string>
|
||||||
<string name="home_not_installed">Nie zainstalowano</string>
|
<string name="home_not_installed">Nie zainstalowano</string>
|
||||||
<string name="home_click_to_install">Kliknij, aby zainstalować</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_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_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="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_default">Domyślny</string>
|
||||||
<string name="profile_template">Szablon</string>
|
<string name="profile_template">Szablon</string>
|
||||||
<string name="profile_custom">Własny</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_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_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="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_default">Padrão</string>
|
||||||
<string name="profile_template">Modelo</string>
|
<string name="profile_template">Modelo</string>
|
||||||
<string name="profile_custom">Personalizado</string>
|
<string name="profile_custom">Personalizado</string>
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
<string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
|
<string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
|
||||||
<string name="home_support_title">Поддержите нас</string>
|
<string name="home_support_title">Поддержите нас</string>
|
||||||
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
|
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
|
||||||
<string name="profile" translatable="false">App Profile</string>
|
|
||||||
<!--Don't translate this string!-->
|
<!--Don't translate this string!-->
|
||||||
<string name="profile_default">По умолчанию</string>
|
<string name="profile_default">По умолчанию</string>
|
||||||
<string name="profile_template">Шаблон</string>
|
<string name="profile_template">Шаблон</string>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">KernelSU</string>
|
|
||||||
<string name="home">Ana Sayfa</string>
|
<string name="home">Ana Sayfa</string>
|
||||||
<string name="home_not_installed">Kurulmadı</string>
|
<string name="home_not_installed">Kurulmadı</string>
|
||||||
<string name="home_click_to_install">Kurmak için tıklayın</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_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_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="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_default">Varsayılan</string>
|
||||||
<string name="profile_template">Şablon</string>
|
<string name="profile_template">Şablon</string>
|
||||||
<string name="profile_custom">Özel</string>
|
<string name="profile_custom">Özel</string>
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
<string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
|
<string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
|
||||||
<string name="home_support_title">Підтримати нас</string>
|
<string name="home_support_title">Підтримати нас</string>
|
||||||
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
|
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
|
||||||
<string name="profile">Профіль додатка</string>
|
|
||||||
<string name="profile_default">Типовий</string>
|
<string name="profile_default">Типовий</string>
|
||||||
<string name="profile_template">Шаблон</string>
|
<string name="profile_template">Шаблон</string>
|
||||||
<string name="profile_custom">Власний</string>
|
<string name="profile_custom">Власний</string>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">SukiSU Ultra</string>
|
|
||||||
<string name="home">Home</string>
|
<string name="home">Home</string>
|
||||||
<string name="home_not_installed">Chưa cài đặt</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>
|
<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_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="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="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_default">Mặc định</string>
|
||||||
<string name="profile_template">Bản mẫu</string>
|
<string name="profile_template">Bản mẫu</string>
|
||||||
<string name="profile_custom">Tùy chỉnh</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="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">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_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_card_alpha">Thẻ alpha</string>
|
||||||
<string name="settings_restore_default">Khôi phục mặc định</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>
|
<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">Cài đặt</string>
|
||||||
<string name="kpm_install_mode_load">Tải</string>
|
<string name="kpm_install_mode_load">Tải</string>
|
||||||
<string name="kpm_install_mode_embed">Nhúng</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="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="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>
|
<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="restore_allowlist">还原应用列表</string>
|
||||||
<string name="settings_custom_background">自定义背景</string>
|
<string name="settings_custom_background">自定义背景</string>
|
||||||
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
|
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
|
||||||
<string name="settings_card_manage">卡片管理</string>
|
|
||||||
<string name="settings_card_alpha">卡片透明度</string>
|
<string name="settings_card_alpha">卡片透明度</string>
|
||||||
<string name="settings_restore_default">恢复默认</string>
|
<string name="settings_restore_default">恢复默认</string>
|
||||||
<string name="home_android_version">Android 版本</string>
|
<string name="home_android_version">Android 版本</string>
|
||||||
@@ -214,6 +213,7 @@
|
|||||||
<string name="flash_option">刷入选项</string>
|
<string name="flash_option">刷入选项</string>
|
||||||
<string name="flash_option_tip">选择要刷入的文件</string>
|
<string name="flash_option_tip">选择要刷入的文件</string>
|
||||||
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
|
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
|
||||||
|
<string name="horizon_kernel_summary">刷入 Anykernel3 内核</string>
|
||||||
<string name="root_required">需要 root 权限</string>
|
<string name="root_required">需要 root 权限</string>
|
||||||
<string name="copy_failed">文件复制失败</string>
|
<string name="copy_failed">文件复制失败</string>
|
||||||
<string name="reboot_complete_title">刷写完成</string>
|
<string name="reboot_complete_title">刷写完成</string>
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
<string name="kpm_install_success">加载 kpm 模块成功</string>
|
<string name="kpm_install_success">加载 kpm 模块成功</string>
|
||||||
<string name="kpm_install_failed">加载 kpm 模块失败</string>
|
<string name="kpm_install_failed">加载 kpm 模块失败</string>
|
||||||
<string name="home_kpm_version">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="close_notice">关闭</string>
|
||||||
<string name="kpm_control_success">成功</string>
|
<string name="kpm_control_success">成功</string>
|
||||||
<string name="kpm_control_failed">错误</string>
|
<string name="kpm_control_failed">错误</string>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
<string name="kpm_install_mode">安装</string>
|
<string name="kpm_install_mode">安装</string>
|
||||||
<string name="kpm_install_mode_load">加载</string>
|
<string name="kpm_install_mode_load">加载</string>
|
||||||
<string name="kpm_install_mode_embed">嵌入</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="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string>
|
||||||
<string name="confirm_uninstall_title">确认卸载</string>
|
<string name="confirm_uninstall_title">确认卸载</string>
|
||||||
<string name="confirm_uninstall_confirm">删除</string>
|
<string name="confirm_uninstall_confirm">删除</string>
|
||||||
@@ -262,4 +262,34 @@
|
|||||||
<string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string>
|
<string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string>
|
||||||
<string name="confirm_uninstall_title_with_filename">卸载</string>
|
<string name="confirm_uninstall_title_with_filename">卸载</string>
|
||||||
<string name="confirm_uninstall_content">将卸载以下 kpm 模块:\n%s</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>
|
</resources>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string>
|
<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_kernel">Kernel version</string>
|
||||||
<string name="home_susfs">SuSFS: %s</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_susfs_sus_su">SuS SU</string>
|
||||||
<string name="home_manager_version">Manager version</string>
|
<string name="home_manager_version">Manager version</string>
|
||||||
<string name="home_fingerprint">Fingerprint</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="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">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_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">Disable kprobe hooks</string>
|
||||||
<string name="settings_susfs_toggle_summary">Désactive les hooks kprobe créés par KSU et, à la place, active les hooks non-kprobe intégrés, implémentant les mêmes fonctionnalités qui seraient appliquées à un kernel non-GKI, qui ne supportent krpobe.</string>
|
|
||||||
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this app.</string>
|
<string name="profile_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_domain">Domain</string>
|
||||||
<string name="profile_selinux_rules">Rules</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="allowlist_restore_failed">Allowlist restore failed: %1$s</string>
|
||||||
<string name="backup_allowlist">Backup Allowlist</string>
|
<string name="backup_allowlist">Backup Allowlist</string>
|
||||||
<string name="restore_allowlist">Restore Allowlist</string>
|
<string name="restore_allowlist">Restore Allowlist</string>
|
||||||
<string name="settings_custom_background">settings custom background</string>
|
<string name="settings_custom_background">Custom App Background</string>
|
||||||
<string name="settings_custom_background_summary">settings custom background summary</string>
|
<string name="settings_custom_background_summary">Select an image as background</string>
|
||||||
<string name="settings_card_manage">card manage</string>
|
<string name="settings_card_alpha">Navigation bar transparency</string>
|
||||||
<string name="settings_card_alpha">card alpha</string>
|
<string name="settings_restore_default">Restore default</string>
|
||||||
<string name="settings_restore_default">restore default</string>
|
|
||||||
<string name="home_android_version">Android version</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="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">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="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="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">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="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">SELinux</string>
|
||||||
<string name="selinux_enabled">Enabled</string>
|
<string name="selinux_enabled">Enabled</string>
|
||||||
<string name="selinux_disabled">Disabled</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="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">Hide kernel version</string>
|
||||||
<string name="hide_kernel_kernelsu_version_summary">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_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">Hide SuSFS status</string>
|
||||||
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</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_mode">Theme</string>
|
||||||
<string name="theme_follow_system">follow-up system</string>
|
<string name="theme_follow_system">Follow system</string>
|
||||||
<string name="theme_light">light color</string>
|
<string name="theme_light">Light</string>
|
||||||
<string name="theme_dark">dark colored</string>
|
<string name="theme_dark">Dark</string>
|
||||||
<string name="manual_hook">Manual Hook</string>
|
<string name="manual_hook">Manual Hook</string>
|
||||||
<string name="dynamic_color_title">Dynamic colours</string>
|
<string name="dynamic_color_title">Dynamic colours</string>
|
||||||
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
|
<string name="dynamic_color_summary">Dynamic colours using system themes</string>
|
||||||
<string name="choose_theme_color">Choose a theme colour</string>
|
<string name="choose_theme_color">Choose a theme colour</string>
|
||||||
<string name="color_default">White</string>
|
<string name="color_default">White</string>
|
||||||
<string name="color_blue">blue</string>
|
<string name="color_blue">Blue</string>
|
||||||
<string name="color_green">green</string>
|
<string name="color_green">Green</string>
|
||||||
<string name="color_purple">purple</string>
|
<string name="color_purple">Purple</string>
|
||||||
<string name="color_orange">orange</string>
|
<string name="color_orange">Orange</string>
|
||||||
<string name="color_pink">pink</string>
|
<string name="color_pink">Pink</string>
|
||||||
<string name="color_gray">gray</string>
|
<string name="color_gray">Gray</string>
|
||||||
<string name="color_ivory">ivory</string>
|
<string name="color_ivory">Ivory</string>
|
||||||
<string name="flash_option">Brush Options</string>
|
<string name="flash_option">Brush Options</string>
|
||||||
<string name="flash_option_tip">Select the file to be flashed</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="root_required">Requires root privileges</string>
|
||||||
<string name="copy_failed">File Copy Failure</string>
|
<string name="copy_failed">File Copy Failure</string>
|
||||||
<string name="reboot_complete_title">Scrubbing complete</string>
|
<string name="reboot_complete_title">Scrubbing complete</string>
|
||||||
<string name="reboot_complete_msg">Whether to reboot immediately?</string>
|
<string name="reboot_complete_msg">Whether to reboot immediately?</string>
|
||||||
<string name="yes">yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">no</string>
|
<string name="no">No</string>
|
||||||
<string name="failed_reboot">Reboot Failed</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="batch_cancel_authorization">Batch cancel authorization</string>
|
||||||
<string name="backup">Backup</string>
|
<string name="backup">Backup</string>
|
||||||
<string name="color_yellow">Yellow</string>
|
<string name="color_yellow">Yellow</string>
|
||||||
<string name="kpm">kpm module</string>
|
<string name="kpm">Kernel Module</string>
|
||||||
<string name="kpm_title">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_empty">No installed kernel modules at this time</string>
|
||||||
<string name="kpm_version">releases</string>
|
<string name="kpm_version">Version</string>
|
||||||
<string name="kpm_author">author</string>
|
<string name="kpm_author">Author</string>
|
||||||
<string name="kpm_uninstall">uninstallation</string>
|
<string name="kpm_uninstall">Uninstall</string>
|
||||||
<string name="kpm_uninstall_success">Uninstalled successfully</string>
|
<string name="kpm_uninstall_success">Uninstalled successfully</string>
|
||||||
<string name="kpm_uninstall_failed">Failed to uninstall</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_success">Load of kpm module successful</string>
|
||||||
<string name="kpm_install_failed">Load of kpm module failed</string>
|
<string name="kpm_install_failed">Load of kpm module failed</string>
|
||||||
<string name="kpm_args">kpm parameters</string>
|
<string name="kpm_args">Parameters</string>
|
||||||
<string name="kpm_control">fulfillment</string>
|
<string name="kpm_control">Execute</string>
|
||||||
<string name="home_kpm_version">KPM Version</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="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="home_ContributionCard_kernelsu">SukiSU Ultra Look forward to</string>
|
||||||
<string name="kpm_control_success">success</string>
|
<string name="kpm_control_success">Success</string>
|
||||||
<string name="kpm_control_failed">failed</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="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="not_supported">Unsupported</string>
|
||||||
<string name="supported">supported</string>
|
<string name="supported">Supported</string>
|
||||||
<string name="home_kpm_module">Number of KPM modules:%d </string>
|
<string name="home_kpm_module">"Number of KPM modules: %d "</string>
|
||||||
<string name="kpm_invalid_file">Invalid KPM file</string>
|
<string name="kpm_invalid_file">Invalid KPM file</string>
|
||||||
<string name="kernel_patched">Kernel not patched</string>
|
<string name="kernel_patched">Kernel not patched</string>
|
||||||
<string name="kernel_not_enabled">Kernel not configured</string>
|
<string name="kernel_not_enabled">Kernel not configured</string>
|
||||||
<string name="custom_settings">Custom settings</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_load">Load</string>
|
||||||
<string name="kpm_install_mode_embed">embed</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_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="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="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_title">Confirm uninstallation</string>
|
||||||
<string name="confirm_uninstall_confirm">removing</string>
|
<string name="confirm_uninstall_confirm">Uninstall</string>
|
||||||
<string name="confirm_uninstall_dismiss">abolish</string>
|
<string name="confirm_uninstall_dismiss">Cancel</string>
|
||||||
<string name="theme_color">Theme Color</string>
|
<string name="theme_color">Theme Color</string>
|
||||||
<string name="invalid_file_type">Incorrect file type, select .kpm file</string>
|
<string name="invalid_file_type">Incorrect file type! Please select .kpm file.</string>
|
||||||
<string name="confirm_uninstall_title_with_filename">uninstallation</string>
|
<string name="confirm_uninstall_title_with_filename">Uninstall</string>
|
||||||
<string name="confirm_uninstall_content">The following kpm modules will be uninstalled:\n%s</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>
|
</resources>
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ cmaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val androidMinSdkVersion = 26
|
val androidMinSdkVersion = 26
|
||||||
val androidTargetSdkVersion = 35
|
val androidTargetSdkVersion = 36
|
||||||
val androidCompileSdkVersion = 35
|
val androidCompileSdkVersion = 36
|
||||||
val androidCompileNdkVersion = "28.0.13004108"
|
val androidCompileNdkVersion = "28.0.13004108"
|
||||||
val androidSourceCompatibility = JavaVersion.VERSION_21
|
val androidSourceCompatibility = JavaVersion.VERSION_21
|
||||||
val androidTargetCompatibility = JavaVersion.VERSION_21
|
val androidTargetCompatibility = JavaVersion.VERSION_21
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.9.1"
|
agp = "8.9.2"
|
||||||
kotlin = "2.1.10"
|
kotlin = "2.1.10"
|
||||||
ksp = "2.1.10-1.0.30"
|
ksp = "2.1.10-1.0.30"
|
||||||
compose-bom = "2025.02.00"
|
compose-bom = "2025.04.01"
|
||||||
lifecycle = "2.8.7"
|
lifecycle = "2.8.7"
|
||||||
navigation = "2.8.7"
|
navigation = "2.8.9"
|
||||||
activity-compose = "1.10.0"
|
activity-compose = "1.10.1"
|
||||||
kotlinx-coroutines = "1.10.1"
|
kotlinx-coroutines = "1.10.2"
|
||||||
coil-compose = "2.7.0"
|
coil-compose = "2.7.0"
|
||||||
compose-destination = "2.1.0-beta16"
|
compose-destination = "2.1.0"
|
||||||
sheets-compose-dialogs = "1.3.0"
|
sheets-compose-dialogs = "1.3.0"
|
||||||
markdown = "4.6.2"
|
markdown = "4.6.2"
|
||||||
webkit = "1.12.1"
|
webkit = "1.13.0"
|
||||||
appiconloader-coil = "1.5.0"
|
appiconloader-coil = "1.5.0"
|
||||||
parcelablelist = "2.0.1"
|
parcelablelist = "2.0.1"
|
||||||
libsu = "6.0.0"
|
libsu = "6.0.0"
|
||||||
apksign = "1.4"
|
apksign = "1.4"
|
||||||
cmaker = "1.2"
|
cmaker = "1.2"
|
||||||
compose-material = "1.7.8"
|
compose-material = "1.8.0"
|
||||||
compose-material3 = "1.3.1"
|
compose-material3 = "1.3.2"
|
||||||
compose-ui = "1.7.8"
|
compose-ui = "1.8.0"
|
||||||
compose-foundation = "1.7.8"
|
compose-foundation = "1.7.8"
|
||||||
documentfile = "1.0.1"
|
documentfile = "1.0.1"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user