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