diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt
index a68f30b1..cbb8c656 100644
--- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt
+++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/Module.kt
@@ -37,6 +37,7 @@ import androidx.compose.material.icons.outlined.PlayArrow
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.AlertDialog
+import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
@@ -71,6 +72,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -104,6 +106,7 @@ import me.weishu.kernelsu.ui.util.LocalSnackbarHost
import me.weishu.kernelsu.ui.util.download
import me.weishu.kernelsu.ui.util.hasMagisk
import me.weishu.kernelsu.ui.util.reboot
+import me.weishu.kernelsu.ui.util.restoreModule
import me.weishu.kernelsu.ui.util.toggleModule
import me.weishu.kernelsu.ui.util.uninstallModule
import me.weishu.kernelsu.ui.util.getFileName
@@ -398,26 +401,34 @@ private fun ModuleList(
}
}
- suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
- val confirmResult = confirmDialog.awaitConfirm(
- moduleStr,
- content = moduleUninstallConfirm.format(module.name),
- confirm = uninstall,
- dismiss = cancel
- )
- if (confirmResult != ConfirmResult.Confirmed) {
- return
+ suspend fun onModuleUninstallClicked(module: ModuleViewModel.ModuleInfo) {
+ val isUninstall = !module.remove
+ if (isUninstall) {
+ val confirmResult = confirmDialog.awaitConfirm(
+ moduleStr,
+ content = moduleUninstallConfirm.format(module.name),
+ confirm = uninstall,
+ dismiss = cancel
+ )
+ if (confirmResult != ConfirmResult.Confirmed) {
+ return
+ }
}
val success = loadingDialog.withLoading {
withContext(Dispatchers.IO) {
- uninstallModule(module.dirId)
+ if (isUninstall) {
+ uninstallModule(module.dirId)
+ } else {
+ restoreModule(module.dirId)
+ }
}
}
if (success) {
viewModel.fetchModuleList()
}
+ if (!isUninstall) return
val message = if (success) {
successUninstall.format(module.name)
} else {
@@ -484,8 +495,8 @@ private fun ModuleList(
navigator = navigator,
module = module,
updateUrl = updatedModule.first,
- onUninstall = {
- scope.launch { onModuleUninstall(module) }
+ onUninstallClicked = {
+ scope.launch { onModuleUninstallClicked(module) }
},
onCheckChanged = {
scope.launch {
@@ -543,7 +554,7 @@ fun ModuleItem(
navigator: DestinationsNavigator,
module: ModuleViewModel.ModuleInfo,
updateUrl: String,
- onUninstall: (ModuleViewModel.ModuleInfo) -> Unit,
+ onUninstallClicked: (ModuleViewModel.ModuleInfo) -> Unit,
onCheckChanged: (Boolean) -> Unit,
onUpdate: (ModuleViewModel.ModuleInfo) -> Unit,
onClick: (ModuleViewModel.ModuleInfo) -> Unit
@@ -731,21 +742,28 @@ fun ModuleItem(
FilledTonalButton(
modifier = Modifier.defaultMinSize(52.dp, 32.dp),
- enabled = !module.remove,
- onClick = { onUninstall(module) },
+ onClick = { onUninstallClicked(module) },
contentPadding = ButtonDefaults.TextButtonContentPadding
) {
- Icon(
- modifier = Modifier.size(20.dp),
- imageVector = Icons.Outlined.Delete,
- contentDescription = null
- )
+ if (!module.remove) {
+ Icon(
+ modifier = Modifier.size(20.dp),
+ imageVector = Icons.Outlined.Delete,
+ contentDescription = null,
+ )
+ } else {
+ Icon(
+ modifier = Modifier.size(20.dp).rotate(180f),
+ imageVector = Icons.Outlined.Refresh,
+ contentDescription = null,
+ )
+ }
if (!module.hasActionScript && !module.hasWebUi && updateUrl.isEmpty()) {
Text(
modifier = Modifier.padding(start = 7.dp),
fontFamily = MaterialTheme.typography.labelMedium.fontFamily,
fontSize = MaterialTheme.typography.labelMedium.fontSize,
- text = stringResource(R.string.uninstall)
+ text = stringResource(if (!module.remove) R.string.uninstall else R.string.restore)
)
}
}
diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt
index 49e29ba5..49ecf06d 100644
--- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt
+++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/KsuCli.kt
@@ -147,6 +147,13 @@ fun uninstallModule(id: String): Boolean {
return result
}
+fun restoreModule(id: String): Boolean {
+ val cmd = "module restore $id"
+ val result = execKsud(cmd, true)
+ Log.i(TAG, "restore module $id result: $result")
+ return result
+}
+
private fun flashWithIO(
cmd: String,
onStdout: (String) -> 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 94fb97ec..d5ec58c0 100644
--- a/manager/app/src/main/res/values-zh-rCN/strings.xml
+++ b/manager/app/src/main/res/values-zh-rCN/strings.xml
@@ -24,6 +24,7 @@
排序(可执行优先)
排序(已启用优先)
卸载
+ 还原
安装
安装
重启
diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml
index deeb7d69..2d568411 100644
--- a/manager/app/src/main/res/values/strings.xml
+++ b/manager/app/src/main/res/values/strings.xml
@@ -28,6 +28,7 @@
Sort (Enabled first)
Confirm
Uninstall
+ Restore
Install
Install
Reboot
diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs
index 225552c0..a336cbe4 100644
--- a/userspace/ksud/src/cli.rs
+++ b/userspace/ksud/src/cli.rs
@@ -204,6 +204,12 @@ enum Module {
id: String,
},
+ /// Restore module
+ Restore {
+ /// module id
+ id: String,
+ },
+
/// enable module
Enable {
/// module id
@@ -303,6 +309,7 @@ pub fn run() -> Result<()> {
match command {
Module::Install { zip } => module::install_module(&zip),
Module::Uninstall { id } => module::uninstall_module(&id),
+ Module::Restore { id } => module::restore_uninstall_module(&id),
Module::Enable { id } => module::enable_module(&id),
Module::Disable { id } => module::disable_module(&id),
Module::Action { id } => module::run_action(&id),
diff --git a/userspace/ksud/src/module.rs b/userspace/ksud/src/module.rs
index bb540a05..9cb56e8c 100644
--- a/userspace/ksud/src/module.rs
+++ b/userspace/ksud/src/module.rs
@@ -394,6 +394,10 @@ pub fn uninstall_module(id: &str) -> Result<()> {
mark_module_state(id, defs::REMOVE_FILE_NAME, true)
}
+pub fn restore_uninstall_module(id: &str) -> Result<()> {
+ mark_module_state(id, defs::REMOVE_FILE_NAME, false)
+}
+
pub fn run_action(id: &str) -> Result<()> {
let action_script_path = format!("/data/adb/modules/{}/action.sh", id);
exec_script(&action_script_path, true)
@@ -456,17 +460,12 @@ fn _list_modules(path: &str) -> Vec> {
module_prop_map.insert(k, v);
});
+ let dir_id = entry.file_name().to_string_lossy().to_string();
+ module_prop_map.insert("dir_id".to_owned(), dir_id.clone());
+
if !module_prop_map.contains_key("id") || module_prop_map["id"].is_empty() {
- match entry.file_name().to_str() {
- Some(id) => {
- info!("Use dir name as module id: {}", id);
- module_prop_map.insert("id".to_owned(), id.to_owned());
- }
- _ => {
- info!("Failed to get module id: {:?}", module_prop);
- continue;
- }
- }
+ info!("Use dir name as module id: {dir_id}");
+ module_prop_map.insert("id".to_owned(), dir_id.clone());
}
// Add enabled, update, remove flags