diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt
index 963e1c11..8cbd839f 100644
--- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt
+++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Settings.kt
@@ -3,16 +3,20 @@ package me.weishu.kernelsu.ui.screen
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Compress
import androidx.compose.material.icons.filled.ContactPage
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.DeveloperMode
import androidx.compose.material.icons.filled.Fence
import androidx.compose.material.icons.filled.RemoveModerator
@@ -31,10 +35,17 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.FileProvider
+import com.maxkeppeker.sheets.core.models.base.Header
+import com.maxkeppeker.sheets.core.models.base.IconSource
+import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
+import com.maxkeppeler.sheets.list.ListDialog
+import com.maxkeppeler.sheets.list.models.ListOption
+import com.maxkeppeler.sheets.list.models.ListSelection
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
@@ -46,6 +57,7 @@ import me.weishu.kernelsu.Natives
import me.weishu.kernelsu.R
import me.weishu.kernelsu.ui.component.AboutDialog
import me.weishu.kernelsu.ui.component.ConfirmResult
+import me.weishu.kernelsu.ui.component.DialogHandle
import me.weishu.kernelsu.ui.component.SwitchItem
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
import me.weishu.kernelsu.ui.component.rememberCustomDialog
@@ -200,6 +212,13 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
)
+ val lkmMode = Natives.version >= Natives.MINIMAL_SUPPORTED_KERNEL_LKM && Natives.isLkmMode
+ if (lkmMode) {
+ UninstallItem {
+ loadingDialog.withLoading(it)
+ }
+ }
+
val about = stringResource(id = R.string.about)
ListItem(
leadingContent = {
@@ -217,6 +236,101 @@ fun SettingScreen(navigator: DestinationsNavigator) {
}
}
+@Composable
+fun UninstallItem(withLoading: suspend (suspend () -> Unit) -> Unit) {
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ val uninstallConfirmDialog = rememberConfirmDialog()
+ val showTodo = {
+ Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show()
+ }
+ val uninstallDialog = rememberUninstallDialog { uninstallType ->
+ scope.launch {
+ val result = uninstallConfirmDialog.awaitConfirm(
+ title = context.getString(uninstallType.title),
+ content = context.getString(uninstallType.message)
+ )
+ if (result == ConfirmResult.Confirmed) {
+ withLoading {
+ when (uninstallType) {
+ UninstallType.TEMPORARY -> showTodo()
+ UninstallType.PERMANENT -> showTodo()
+ UninstallType.RESTORE_STOCK_IMAGE -> showTodo()
+ UninstallType.NONE -> Unit
+ }
+ }
+ }
+ }
+ }
+ val uninstall = stringResource(id = R.string.settings_uninstall)
+ ListItem(
+ leadingContent = {
+ Icon(
+ Icons.Filled.Delete,
+ uninstall
+ )
+ },
+ headlineContent = { Text(uninstall) },
+ modifier = Modifier.clickable {
+ uninstallDialog.show()
+ }
+ )
+}
+
+enum class UninstallType(val title: Int, val message: Int, val icon: ImageVector) {
+ TEMPORARY(
+ R.string.settings_uninstall_temporary,
+ R.string.settings_uninstall_temporary_message,
+ Icons.Filled.Delete
+ ),
+ PERMANENT(
+ R.string.settings_uninstall_permanent,
+ R.string.settings_uninstall_permanent_message,
+ Icons.Filled.DeleteForever
+ ),
+ RESTORE_STOCK_IMAGE(
+ R.string.settings_restore_stock_image,
+ R.string.settings_restore_stock_image_message,
+ Icons.AutoMirrored.Filled.Undo
+ ),
+ NONE(0, 0, Icons.Filled.Delete)
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun rememberUninstallDialog(onSelected: (UninstallType) -> Unit): DialogHandle {
+ return rememberCustomDialog { dismiss ->
+ val options = listOf(
+ UninstallType.TEMPORARY,
+ UninstallType.PERMANENT,
+ UninstallType.RESTORE_STOCK_IMAGE
+ )
+ val listOptions = options.map {
+ ListOption(
+ titleText = stringResource(it.title),
+ subtitleText = if (it.message != 0) stringResource(it.message) else null,
+ icon = IconSource(it.icon)
+ )
+ }
+
+ var selection = UninstallType.NONE
+ ListDialog(state = rememberUseCaseState(visible = true, onFinishedRequest = {
+ if (selection != UninstallType.NONE) {
+ onSelected(selection)
+ }
+ }, onCloseRequest = {
+ dismiss()
+ }), header = Header.Default(
+ title = stringResource(R.string.settings_uninstall),
+ ), selection = ListSelection.Single(
+ showRadioButtons = false,
+ options = listOptions,
+ ) { index, _ ->
+ selection = options[index]
+ })
+ }
+}
+
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(onBack: () -> Unit = {}) {
diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml
index 6d6b3c6e..3e1a8814 100644
--- a/manager/app/src/main/res/values-zh-rCN/strings.xml
+++ b/manager/app/src/main/res/values-zh-rCN/strings.xml
@@ -117,4 +117,11 @@
选择 KMI
最小化稀疏文件
将模块所在的稀疏文件镜像调整为其实际大小,注意这可能导致模块工作异常,请仅在必要时(如备份)使用。
+ 卸载
+ 临时卸载
+ 永久卸载
+ 恢复原厂镜像
+ 临时卸载 KernelSU,下次重启后恢复
+ 完全并永久移除 KernelSU 和所有模块
+ 恢复原厂镜像,一般在 OTA 前使用;如需卸载请使用“永久卸载”
\ No newline at end of file
diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml
index cf7c88fe..91299724 100644
--- a/manager/app/src/main/res/values/strings.xml
+++ b/manager/app/src/main/res/values/strings.xml
@@ -119,4 +119,11 @@
Select KMI
Minimize sparse image
Resize the sparse image where the module is located to its actual size. Note that this may cause the module to work abnormally, so please only use when necessary (such as for backup)
+ Uninstall
+ Uninstall Temporarily
+ Uninstall Permanently
+ Restore Stock Image
+ Temporarily uninstall KernelSU, restore to original state after next reboot.
+ Uninstalling KernelSU(Root and all modules) completely and permanently.
+ Restore the stock factory image (if a backup exists), usually used before OTA; if you need to uninstall KernelSU, please use \"Permanent Uninstall\".
\ No newline at end of file