manager: refine dialog component and make a small fix in AboutCard (#422)

Co-authored-by: weishu <twsxtd@gmail.com>
This commit is contained in:
TinyHai
2023-04-22 18:40:11 +08:00
committed by GitHub
parent 91c80279bd
commit 057330c68f
5 changed files with 238 additions and 159 deletions

View File

@@ -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,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
)
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,
"<b><a href=\"https://github.com/tiann/KernelSU\">GitHub</a></b>",
"<b><a href=\"https://t.me/KernelSU\">Telegram</a></b>"
)
)
}
)
}
}
}
@@ -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())
}
)
}

View File

@@ -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<DialogResult>
) : DialogData {
private data class LoadingDialogDataImpl(
override val visuals: LoadingDialogVisuals,
private val continuation: CancellableContinuation<Unit>,
) : LoadingDialogData {
override fun dismiss() {
if (continuation.isActive) continuation.resume(Unit)
}
}
private data class PromptDialogDataImpl(
override val visuals: PromptDialogVisuals,
private val continuation: CancellableContinuation<Unit>,
) : PromptDialogData {
override fun dismiss() {
if (continuation.isActive) continuation.resume(Unit)
}
}
private data class ConfirmDialogDataImpl(
override val visuals: ConfirmDialogVisuals,
private val continuation: CancellableContinuation<ConfirmResult>
) : 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<DialogData?>(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 <R> 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 <reified T : DialogData> 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<LoadingDialogData>() ?: 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<PromptDialogData>() ?: 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<ConfirmDialogData>() ?: 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))
}
},
)

View File

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

View File

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

View File

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