From 6ec0c25173304da55f1458099e04a114f1aeb566 Mon Sep 17 00:00:00 2001 From: TinyHai <34483077+TinyHai@users.noreply.github.com> Date: Mon, 20 Feb 2023 10:52:23 +0800 Subject: [PATCH] manager: show confirm dialog before uninstall module (#257) manager: Add Dialog component, show confirm dialog before uninstall module, fix a bug in listModules --- .../me/weishu/kernelsu/ui/component/Dialog.kt | 167 ++++++++++++++++++ .../me/weishu/kernelsu/ui/screen/Module.kt | 22 +++ .../me/weishu/kernelsu/ui/screen/Settings.kt | 38 ++-- .../kernelsu/ui/util/CompositionProvider.kt | 5 + .../kernelsu/ui/viewmodel/ModuleViewModel.kt | 2 +- .../src/main/res/values-zh-rCN/strings.xml | 1 + manager/app/src/main/res/values/strings.xml | 1 + 7 files changed, 211 insertions(+), 25 deletions(-) create mode 100644 manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt new file mode 100644 index 00000000..722dba01 --- /dev/null +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/Dialog.kt @@ -0,0 +1,167 @@ +package me.weishu.kernelsu.ui.component + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.* +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import me.weishu.kernelsu.ui.util.LocalDialogHost +import kotlin.coroutines.resume + +sealed interface DialogResult { + object Confirmed : DialogResult + object Dismissed : DialogResult +} + +interface DialogVisuals { + val title: String + val content: String + val confirm: String? + val dismiss: String? +} + +interface DialogData { + val visuals: DialogVisuals + + fun confirm() + + fun dismiss() +} + +class DialogHostState { + + private data class DialogVisualsImpl( + override val title: String, + override val content: String, + override val confirm: String?, + override val dismiss: String? + ) : DialogVisuals + + private data class DialogDataImpl( + override val visuals: DialogVisuals, + val continuation: CancellableContinuation + ) : DialogData { + + override fun confirm() { + if (continuation.isActive) continuation.resume(DialogResult.Confirmed) + } + + override fun dismiss() { + if (continuation.isActive) continuation.resume(DialogResult.Dismissed) + } + } + + private val mutex = Mutex() + + var currentDialogData by mutableStateOf(null) + private set + + suspend fun showDialog( + title: String, + content: String, + confirm: String? = null, + dismiss: String? = null + ): DialogResult = mutex.withLock { + try { + return@withLock suspendCancellableCoroutine { continuation -> + currentDialogData = DialogDataImpl( + visuals = DialogVisualsImpl(title, content, confirm, dismiss), + continuation = continuation + ) + } + } finally { + currentDialogData = null + } + } +} + +@Composable +fun rememberDialogHostState(): DialogHostState { + return remember { + DialogHostState() + } +} + +@Composable +fun BaseDialog( + state: DialogHostState = LocalDialogHost.current, + title: @Composable (String) -> Unit, + confirmButton: @Composable (String?, () -> Unit) -> Unit, + dismissButton: @Composable (String?, () -> Unit) -> Unit, + content: @Composable (String) -> Unit = { Text(text = it) }, +) { + val currentDialogData = state.currentDialogData ?: return + val visuals = currentDialogData.visuals + AlertDialog( + onDismissRequest = { + currentDialogData.dismiss() + }, + title = { + title(visuals.title) + }, + text = { + content(visuals.content) + }, + confirmButton = { + confirmButton(visuals.confirm, currentDialogData::confirm) + }, + dismissButton = { + dismissButton(visuals.dismiss, currentDialogData::dismiss) + } + ) +} + +@Composable +fun SimpleDialog( + state: DialogHostState = LocalDialogHost.current, + content: @Composable (String) -> Unit +) { + BaseDialog( + state = state, + title = { + Text(text = it) + }, + confirmButton = { text, confirm -> + text?.let { + TextButton(onClick = confirm) { + Text(text = it) + } + } + }, + dismissButton = { text, dismiss -> + text?.let { + TextButton(onClick = dismiss) { + Text(text = it) + } + } + }, + content = content + ) +} + +@Composable +fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) { + BaseDialog( + state = state, + title = { + Text(text = it) + }, + confirmButton = { text, confirm -> + text?.let { + TextButton(onClick = confirm) { + Text(text = it) + } + } + }, + dismissButton = { text, dismiss -> + text?.let { + TextButton(onClick = dismiss) { + Text(text = it) + } + } + }, + ) +} \ No newline at end of file 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 ee0b7902..1cd87428 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 @@ -30,6 +30,9 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator import kotlinx.coroutines.launch import me.weishu.kernelsu.Natives import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.ConfirmDialog +import me.weishu.kernelsu.ui.component.DialogResult +import me.weishu.kernelsu.ui.component.rememberDialogHostState import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination import me.weishu.kernelsu.ui.util.* import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel @@ -84,6 +87,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) { ) } }) { innerPadding -> + + val dialogState = rememberDialogHostState() + ConfirmDialog(dialogState) + val failedEnable = stringResource(R.string.module_failed_to_enable) val failedDisable = stringResource(R.string.module_failed_to_disable) val failedUninstall = stringResource(R.string.module_uninstall_failed) @@ -125,8 +132,23 @@ fun ModuleScreen(navigator: DestinationsNavigator) { var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) } val reboot = stringResource(id = R.string.reboot) val rebootToApply = stringResource(id = R.string.reboot_to_apply) + val moduleStr = stringResource(id = R.string.module) + val uninstall = stringResource(id = R.string.uninstall) + val cancel = stringResource(id = android.R.string.cancel) + val moduleUninstallConfirm = + stringResource(id = R.string.module_uninstall_confirm) ModuleItem(module, isChecked, onUninstall = { scope.launch { + val dialogResult = dialogState.showDialog( + moduleStr, + content = moduleUninstallConfirm.format(module.name), + confirm = uninstall, + dismiss = cancel + ) + if (dialogResult != DialogResult.Confirmed) { + return@launch + } + val success = uninstallModule(module.id) if (success) { viewModel.fetchModuleList() 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 df43e4f4..af4f87c1 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 @@ -16,9 +16,12 @@ import androidx.core.content.FileProvider import com.alorma.compose.settings.ui.* import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlinx.coroutines.launch import me.weishu.kernelsu.ui.util.getBugreportFile import me.weishu.kernelsu.BuildConfig import me.weishu.kernelsu.R +import me.weishu.kernelsu.ui.component.SimpleDialog +import me.weishu.kernelsu.ui.component.rememberDialogHostState import me.weishu.kernelsu.ui.util.LinkifyText @@ -39,29 +42,10 @@ fun SettingScreen(navigator: DestinationsNavigator) { } ) { paddingValues -> - var openDialog by remember { mutableStateOf(false) } + val dialogState = rememberDialogHostState() - if (openDialog) { - AlertDialog( - onDismissRequest = { - openDialog = false - }, - title = { - Text(text = stringResource(id = R.string.about)) - }, - text = { - SupportCard() - }, - confirmButton = { - TextButton( - onClick = { - openDialog = false - } - ) { - Text(stringResource(id = android.R.string.ok)) - } - }, - ) + SimpleDialog(dialogState) { + SupportCard() } Column(modifier = Modifier.padding(paddingValues)) { @@ -98,11 +82,17 @@ fun SettingScreen(navigator: DestinationsNavigator) { ) } ) + + val about = stringResource(id = R.string.about) + val ok = stringResource(id = android.R.string.ok) + val scope = rememberCoroutineScope() SettingsMenuLink(title = { - Text(stringResource(id = R.string.about)) + Text(about) }, onClick = { - openDialog = true + scope.launch { + dialogState.showDialog(about, content = "unused", confirm = ok) + } } ) } diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt index 8811e55a..63b4b520 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/CompositionProvider.kt @@ -2,7 +2,12 @@ package me.weishu.kernelsu.ui.util import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.compositionLocalOf +import me.weishu.kernelsu.ui.component.DialogHostState val LocalSnackbarHost = compositionLocalOf { error("CompositionLocal LocalSnackbarController not present") } + +val LocalDialogHost = compositionLocalOf { + error("CompositionLocal LocalDialogController not present") +} \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt index ae7c0bdc..6d0558a6 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/viewmodel/ModuleViewModel.kt @@ -1,6 +1,5 @@ package me.weishu.kernelsu.ui.viewmodel -import android.content.Context import android.os.SystemClock import android.util.Log import androidx.compose.runtime.derivedStateOf @@ -74,6 +73,7 @@ class ModuleViewModel : ViewModel() { }.toList() }.onFailure { e -> Log.e(TAG, "fetchModuleList: ", e) + isRefreshing = false } 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 e756fae9..2d02e152 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -41,6 +41,7 @@ 使用 overlayfs 使系统分区可写, 重启生效 关于 需要 KernelSU 版本 8+ + 确定要卸载模块 %s 吗? %s 已卸载 卸载失败: %s 版本 diff --git a/manager/app/src/main/res/values/strings.xml b/manager/app/src/main/res/values/strings.xml index 927e46d4..43403f68 100644 --- a/manager/app/src/main/res/values/strings.xml +++ b/manager/app/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ Use overlayfs to make system partition writable, reboot to take effect. About Require KernelSU version 8+ + Are you sure you want to uninstall module %s? %s uninstalled Failed to uninstall: %s Version