From 057330c68f8f9204e93872b6e5d012801f61039b Mon Sep 17 00:00:00 2001 From: TinyHai <34483077+TinyHai@users.noreply.github.com> Date: Sat, 22 Apr 2023 18:40:11 +0800 Subject: [PATCH] manager: refine dialog component and make a small fix in AboutCard (#422) Co-authored-by: weishu --- .../weishu/kernelsu/ui/component/AboutCard.kt | 82 +++--- .../me/weishu/kernelsu/ui/component/Dialog.kt | 260 ++++++++++++------ .../me/weishu/kernelsu/ui/screen/Module.kt | 6 +- .../me/weishu/kernelsu/ui/screen/Settings.kt | 43 +-- .../me/weishu/kernelsu/ui/screen/SuperUser.kt | 6 +- 5 files changed, 238 insertions(+), 159 deletions(-) diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt index e1659ad1..973de2db 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AboutCard.kt @@ -3,16 +3,23 @@ package me.weishu.kernelsu.ui.component import android.text.method.LinkMovementMethod import android.widget.TextView import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -59,46 +66,43 @@ private fun AboutCardContent() { modifier = Modifier .fillMaxWidth() ) { - CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodyMedium) { + val drawable = ResourcesCompat.getDrawable( + LocalContext.current.resources, + R.mipmap.ic_launcher, + LocalContext.current.theme + ) - val drawable = ResourcesCompat.getDrawable( - LocalContext.current.resources, - R.mipmap.ic_launcher, - LocalContext.current.theme + Row { + Image( + painter = rememberDrawablePainter(drawable), + contentDescription = "icon", + modifier = Modifier.size(40.dp) ) - Row { - Image( - painter = rememberDrawablePainter(drawable), - contentDescription = "icon", - modifier = Modifier.size(40.dp) + Spacer(modifier = Modifier.width(12.dp)) + + Column { + + Text( + stringResource(id = R.string.app_name), + style = MaterialTheme.typography.titleSmall, + fontSize = 18.sp + ) + Text( + BuildConfig.VERSION_NAME, + style = MaterialTheme.typography.bodySmall, + fontSize = 14.sp ) - Spacer(modifier = Modifier.width(12.dp)) + Spacer(modifier = Modifier.height(8.dp)) - Column { - - Text( - stringResource(id = R.string.app_name), - style = MaterialTheme.typography.titleSmall, - fontSize = 18.sp + HtmlText( + html = stringResource( + id = R.string.about_source_code, + "GitHub", + "Telegram" ) - Text( - BuildConfig.VERSION_NAME, - style = MaterialTheme.typography.bodySmall, - fontSize = 14.sp - ) - - Spacer(modifier = Modifier.height(8.dp)) - - HtmlText( - html = stringResource( - id = R.string.about_source_code, - "GitHub", - "Telegram" - ) - ) - } + ) } } } @@ -106,6 +110,7 @@ private fun AboutCardContent() { @Composable fun HtmlText(html: String, modifier: Modifier = Modifier) { + val contentColor = LocalContentColor.current AndroidView( modifier = modifier, factory = { context -> @@ -113,6 +118,9 @@ fun HtmlText(html: String, modifier: Modifier = Modifier) { it.movementMethod = LinkMovementMethod.getInstance() } }, - update = { it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) } + update = { + it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) + it.setTextColor(contentColor.toArgb()) + } ) } \ No newline at end of file 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 index 722dba01..1e726bca 100644 --- 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 @@ -1,56 +1,113 @@ package me.weishu.kernelsu.ui.component +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.AlertDialog +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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 kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch 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 -interface DialogVisuals { +interface LoadingDialogVisuals : DialogVisuals + +interface PromptDialogVisuals : DialogVisuals { val title: String val content: String +} + +interface ConfirmDialogVisuals : PromptDialogVisuals { val confirm: String? val dismiss: String? } -interface DialogData { + +sealed interface DialogData { val visuals: DialogVisuals +} - fun confirm() - +interface LoadingDialogData : DialogData { + override val visuals: LoadingDialogVisuals fun dismiss() } +interface PromptDialogData : DialogData { + override val visuals: PromptDialogVisuals + fun dismiss() +} + +interface ConfirmDialogData : PromptDialogData { + override val visuals: ConfirmDialogVisuals + fun confirm() +} + +sealed interface ConfirmResult { + object Confirmed : ConfirmResult + object Canceled : ConfirmResult +} + class DialogHostState { - private data class DialogVisualsImpl( + private object LoadingDialogVisualsImpl : LoadingDialogVisuals + + private data class PromptDialogVisualsImpl( + override val title: String, + override val content: String + ) : PromptDialogVisuals + + private data class ConfirmDialogVisualsImpl( override val title: String, override val content: String, override val confirm: String?, override val dismiss: String? - ) : DialogVisuals + ) : ConfirmDialogVisuals - private data class DialogDataImpl( - override val visuals: DialogVisuals, - val continuation: CancellableContinuation - ) : DialogData { + private data class LoadingDialogDataImpl( + override val visuals: LoadingDialogVisuals, + private val continuation: CancellableContinuation, + ) : LoadingDialogData { + override fun dismiss() { + if (continuation.isActive) continuation.resume(Unit) + } + } + + private data class PromptDialogDataImpl( + override val visuals: PromptDialogVisuals, + private val continuation: CancellableContinuation, + ) : PromptDialogData { + override fun dismiss() { + if (continuation.isActive) continuation.resume(Unit) + } + } + + private data class ConfirmDialogDataImpl( + override val visuals: ConfirmDialogVisuals, + private val continuation: CancellableContinuation + ) : ConfirmDialogData { override fun confirm() { - if (continuation.isActive) continuation.resume(DialogResult.Confirmed) + if (continuation.isActive) continuation.resume(ConfirmResult.Confirmed) } override fun dismiss() { - if (continuation.isActive) continuation.resume(DialogResult.Dismissed) + if (continuation.isActive) continuation.resume(ConfirmResult.Canceled) } } @@ -59,16 +116,58 @@ class DialogHostState { var currentDialogData by mutableStateOf(null) private set - suspend fun showDialog( + suspend fun showLoading() { + try { + mutex.withLock { + suspendCancellableCoroutine { continuation -> + currentDialogData = LoadingDialogDataImpl( + visuals = LoadingDialogVisualsImpl, + continuation = continuation + ) + } + } + } finally { + currentDialogData = null + } + } + + suspend fun withLoading(block: suspend () -> R) = coroutineScope { + val showLoading = launch { + showLoading() + } + + val result = block() + + showLoading.cancel() + + result + } + + suspend fun showPrompt(title: String, content: String) { + try { + mutex.withLock { + suspendCancellableCoroutine { continuation -> + currentDialogData = PromptDialogDataImpl( + visuals = PromptDialogVisualsImpl(title, content), + continuation = continuation + ) + } + } + } finally { + currentDialogData = null + } + } + + suspend fun showConfirm( title: String, content: String, confirm: String? = null, dismiss: String? = null - ): DialogResult = mutex.withLock { + ): ConfirmResult = mutex.withLock { try { return@withLock suspendCancellableCoroutine { continuation -> - currentDialogData = DialogDataImpl( - visuals = DialogVisualsImpl(title, content, confirm, dismiss), + currentDialogData = ConfirmDialogDataImpl( + visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss), continuation = continuation ) } @@ -85,82 +184,85 @@ fun rememberDialogHostState(): 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) - } - ) +private inline fun DialogData?.tryInto(): T? { + return when (this) { + is T -> this + else -> null + } } @Composable -fun SimpleDialog( +fun LoadingDialog( state: DialogHostState = LocalDialogHost.current, - content: @Composable (String) -> Unit ) { - BaseDialog( - state = state, + state.currentDialogData.tryInto() ?: return + val dialogProperties = remember { + DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false) + } + Dialog(onDismissRequest = {}, properties = dialogProperties) { + Surface( + modifier = Modifier + .size(100.dp), + shape = RoundedCornerShape(8.dp) + ) { + Box( + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator() + } + } + } +} + +@Composable +fun PromptDialog( + state: DialogHostState = LocalDialogHost.current, +) { + val promptDialogData = state.currentDialogData.tryInto() ?: return + + val visuals = promptDialogData.visuals + AlertDialog( + onDismissRequest = { + promptDialogData.dismiss() + }, title = { - Text(text = it) + Text(text = visuals.title) }, - confirmButton = { text, confirm -> - text?.let { - TextButton(onClick = confirm) { - Text(text = it) - } + text = { + Text(text = visuals.content) + }, + confirmButton = { + TextButton(onClick = { promptDialogData.dismiss() }) { + Text(text = stringResource(id = android.R.string.ok)) } }, - dismissButton = { text, dismiss -> - text?.let { - TextButton(onClick = dismiss) { - Text(text = it) - } - } - }, - content = content + dismissButton = null, ) } @Composable fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) { - BaseDialog( - state = state, - title = { - Text(text = it) + val confirmDialogData = state.currentDialogData.tryInto() ?: return + + val visuals = confirmDialogData.visuals + AlertDialog( + onDismissRequest = { + confirmDialogData.dismiss() }, - confirmButton = { text, confirm -> - text?.let { - TextButton(onClick = confirm) { - Text(text = it) - } + title = { + Text(text = visuals.title) + }, + text = { + Text(text = visuals.content) + }, + confirmButton = { + TextButton(onClick = { confirmDialogData.dismiss() }) { + Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok)) } }, - dismissButton = { text, dismiss -> - text?.let { - TextButton(onClick = dismiss) { - Text(text = it) - } + dismissButton = { + TextButton(onClick = { confirmDialogData.dismiss() }) { + Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel)) } }, ) 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 f466880c..8b36f4d0 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 @@ -34,7 +34,7 @@ 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.ConfirmResult import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination import me.weishu.kernelsu.ui.util.* import me.weishu.kernelsu.ui.viewmodel.ModuleViewModel @@ -143,13 +143,13 @@ private fun ModuleList(viewModel: ModuleViewModel, modifier: Modifier = Modifier val snackBarHost = LocalSnackbarHost.current suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) { - val dialogResult = dialogHost.showDialog( + val confirmResult = dialogHost.showConfirm( moduleStr, content = moduleUninstallConfirm.format(module.name), confirm = uninstall, dismiss = cancel ) - if (dialogResult != DialogResult.Confirmed) { + if (confirmResult != ConfirmResult.Confirmed) { return } 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 e3176ef7..d2159c95 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 @@ -2,21 +2,14 @@ package me.weishu.kernelsu.ui.screen import android.content.Intent import android.net.Uri -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.* import androidx.compose.runtime.* -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color.Companion.White import androidx.compose.ui.platform.LocalContext 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 androidx.core.content.FileProvider import com.alorma.compose.settings.ui.* import com.ramcosta.composedestinations.annotation.Destination @@ -27,6 +20,7 @@ import kotlinx.coroutines.withContext import me.weishu.kernelsu.BuildConfig import me.weishu.kernelsu.R import me.weishu.kernelsu.ui.component.AboutDialog +import me.weishu.kernelsu.ui.component.LoadingDialog import me.weishu.kernelsu.ui.util.LocalDialogHost import me.weishu.kernelsu.ui.util.getBugreportFile @@ -46,13 +40,11 @@ fun SettingScreen(navigator: DestinationsNavigator) { }) } ) { paddingValues -> + LoadingDialog() val showAboutDialog = remember { mutableStateOf(false) } AboutDialog(showAboutDialog) - var showLoadingDialog by remember { mutableStateOf(false) } - LoadingDialog(showLoadingDialog) - Column(modifier = Modifier.padding(paddingValues)) { val context = LocalContext.current @@ -64,14 +56,12 @@ fun SettingScreen(navigator: DestinationsNavigator) { }, onClick = { scope.launch { - showLoadingDialog = true - - val bugreport = withContext(Dispatchers.IO) { - getBugreportFile(context) + val bugreport = dialogHost.withLoading { + withContext(Dispatchers.IO) { + getBugreportFile(context) + } } - showLoadingDialog = false - val uri: Uri = FileProvider.getUriForFile( context, @@ -118,25 +108,4 @@ private fun TopBar(onBack: () -> Unit = {}) { ) { Icon(Icons.Filled.ArrowBack, contentDescription = null) } }, ) -} - -@Composable -fun LoadingDialog(showLoadingDialog: Boolean) { - if (!showLoadingDialog) { - return - } - - Dialog( - onDismissRequest = { }, - DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false) - ) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .size(100.dp) - .background(White, shape = RoundedCornerShape(8.dp)) - ) { - CircularProgressIndicator() - } - } } \ No newline at end of file diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt index e4f84b87..1d0874d1 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/screen/SuperUser.kt @@ -25,7 +25,7 @@ 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.ConfirmResult import me.weishu.kernelsu.ui.component.SearchAppBar import me.weishu.kernelsu.ui.util.LocalDialogHost import me.weishu.kernelsu.ui.util.LocalSnackbarHost @@ -119,13 +119,13 @@ fun SuperUserScreen() { AppItem(app, isChecked) { checked -> scope.launch { if (checked) { - val dialogResult = dialogHost.showDialog( + val confirmResult = dialogHost.showConfirm( app.label, content = content, confirm = confirm, dismiss = cancel ) - if (dialogResult != DialogResult.Confirmed) { + if (confirmResult != ConfirmResult.Confirmed) { return@launch } }