manager: show confirm dialog before uninstall module (#257)

manager: Add Dialog component, show confirm dialog before uninstall
module, fix a bug in listModules
This commit is contained in:
TinyHai
2023-02-20 10:52:23 +08:00
committed by GitHub
parent 93bcd78f89
commit 6ec0c25173
7 changed files with 211 additions and 25 deletions

View File

@@ -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<DialogResult>
) : 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<DialogData?>(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)
}
}
},
)
}

View File

@@ -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()

View File

@@ -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)
}
}
)
}

View File

@@ -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<SnackbarHostState> {
error("CompositionLocal LocalSnackbarController not present")
}
val LocalDialogHost = compositionLocalOf<DialogHostState> {
error("CompositionLocal LocalDialogController not present")
}

View File

@@ -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
}

View File

@@ -41,6 +41,7 @@
<string name="settings_system_rw_summary">使用 overlayfs 使系统分区可写, 重启生效</string>
<string name="about">关于</string>
<string name="require_kernel_version_8">需要 KernelSU 版本 8+</string>
<string name="module_uninstall_confirm">确定要卸载模块 %s 吗?</string>
<string name="module_uninstall_success">%s 已卸载</string>
<string name="module_uninstall_failed">卸载失败: %s</string>
<string name="module_version">版本</string>

View File

@@ -45,6 +45,7 @@
<string name="settings_system_rw_summary">Use overlayfs to make system partition writable, reboot to take effect.</string>
<string name="about">About</string>
<string name="require_kernel_version_8">Require KernelSU version 8+</string>
<string name="module_uninstall_confirm">Are you sure you want to uninstall module %s?</string>
<string name="module_uninstall_success">%s uninstalled</string>
<string name="module_uninstall_failed">Failed to uninstall: %s</string>
<string name="module_version">Version</string>