manager: refine dialog component & add an animation to UpdateCard (#1429)
This commit is contained in:
@@ -5,13 +5,7 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.NavigationBar
|
|
||||||
import androidx.compose.material3.NavigationBarItem
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -27,11 +21,9 @@ import com.ramcosta.composedestinations.navigation.popBackStack
|
|||||||
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
|
||||||
import me.weishu.kernelsu.Natives
|
import me.weishu.kernelsu.Natives
|
||||||
import me.weishu.kernelsu.ksuApp
|
import me.weishu.kernelsu.ksuApp
|
||||||
import me.weishu.kernelsu.ui.component.rememberDialogHostState
|
|
||||||
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
import me.weishu.kernelsu.ui.screen.BottomBarDestination
|
||||||
import me.weishu.kernelsu.ui.screen.NavGraphs
|
import me.weishu.kernelsu.ui.screen.NavGraphs
|
||||||
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
import me.weishu.kernelsu.ui.theme.KernelSUTheme
|
||||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
|
||||||
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
import me.weishu.kernelsu.ui.util.LocalSnackbarHost
|
||||||
import me.weishu.kernelsu.ui.util.rootAvailable
|
import me.weishu.kernelsu.ui.util.rootAvailable
|
||||||
|
|
||||||
@@ -54,7 +46,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalSnackbarHost provides snackbarHostState,
|
LocalSnackbarHost provides snackbarHostState,
|
||||||
LocalDialogHost provides rememberDialogHostState(),
|
|
||||||
) {
|
) {
|
||||||
DestinationsNavHost(
|
DestinationsNavHost(
|
||||||
modifier = Modifier.padding(innerPadding),
|
modifier = Modifier.padding(innerPadding),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -52,13 +53,11 @@ fun AboutCard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AboutDialog(showAboutDialog: MutableState<Boolean>) {
|
fun AboutDialog(dismiss: () -> Unit) {
|
||||||
if (showAboutDialog.value) {
|
Dialog(onDismissRequest = { dismiss() }) {
|
||||||
Dialog(onDismissRequest = { showAboutDialog.value = false }) {
|
|
||||||
AboutCard()
|
AboutCard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AboutCardContent() {
|
private fun AboutCardContent() {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package me.weishu.kernelsu.ui.component
|
package me.weishu.kernelsu.ui.component
|
||||||
|
|
||||||
import android.graphics.text.LineBreaker
|
import android.graphics.text.LineBreaker
|
||||||
|
import android.os.Parcelable
|
||||||
import android.text.Layout
|
import android.text.Layout
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -10,14 +12,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
@@ -28,48 +26,48 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.utils.NoCopySpannableFactory
|
import io.noties.markwon.utils.NoCopySpannableFactory
|
||||||
import kotlinx.coroutines.CancellableContinuation
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.flow.onEach
|
||||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
import kotlinx.parcelize.Parcelize
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
interface DialogVisuals
|
private const val TAG = "DialogComponent"
|
||||||
|
|
||||||
interface LoadingDialogVisuals : DialogVisuals
|
interface ConfirmDialogVisuals : Parcelable {
|
||||||
|
|
||||||
interface PromptDialogVisuals : DialogVisuals {
|
|
||||||
val title: String
|
val title: String
|
||||||
val content: String
|
val content: String
|
||||||
}
|
val isMarkdown: Boolean
|
||||||
|
|
||||||
interface ConfirmDialogVisuals : PromptDialogVisuals {
|
|
||||||
val confirm: String?
|
val confirm: String?
|
||||||
val dismiss: String?
|
val dismiss: String?
|
||||||
val isMarkdown: Boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
sealed interface DialogData {
|
private data class ConfirmDialogVisualsImpl(
|
||||||
val visuals: DialogVisuals
|
override val title: String,
|
||||||
|
override val content: String,
|
||||||
|
override val isMarkdown: Boolean,
|
||||||
|
override val confirm: String?,
|
||||||
|
override val dismiss: String?,
|
||||||
|
) : ConfirmDialogVisuals {
|
||||||
|
companion object {
|
||||||
|
val Empty: ConfirmDialogVisuals = ConfirmDialogVisualsImpl("", "", false, null, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadingDialogData : DialogData {
|
interface DialogHandle {
|
||||||
override val visuals: LoadingDialogVisuals
|
val isShown: Boolean
|
||||||
fun dismiss()
|
val dialogType: String
|
||||||
|
fun show()
|
||||||
|
fun hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PromptDialogData : DialogData {
|
interface LoadingDialogHandle : DialogHandle {
|
||||||
override val visuals: PromptDialogVisuals
|
suspend fun <R> withLoading(block: suspend () -> R): R
|
||||||
fun dismiss()
|
fun showLoading()
|
||||||
}
|
|
||||||
|
|
||||||
interface ConfirmDialogData : PromptDialogData {
|
|
||||||
override val visuals: ConfirmDialogVisuals
|
|
||||||
fun confirm()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface ConfirmResult {
|
sealed interface ConfirmResult {
|
||||||
@@ -77,143 +75,313 @@ sealed interface ConfirmResult {
|
|||||||
object Canceled : ConfirmResult
|
object Canceled : ConfirmResult
|
||||||
}
|
}
|
||||||
|
|
||||||
class DialogHostState {
|
interface ConfirmDialogHandle : DialogHandle {
|
||||||
|
val visuals: ConfirmDialogVisuals
|
||||||
|
|
||||||
private object LoadingDialogVisualsImpl : LoadingDialogVisuals
|
fun showConfirm(
|
||||||
|
|
||||||
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?,
|
|
||||||
override val isMarkdown: Boolean,
|
|
||||||
) : ConfirmDialogVisuals
|
|
||||||
|
|
||||||
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(ConfirmResult.Confirmed)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dismiss() {
|
|
||||||
if (continuation.isActive) continuation.resume(ConfirmResult.Canceled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
|
|
||||||
var currentDialogData by mutableStateOf<DialogData?>(null)
|
|
||||||
private set
|
|
||||||
|
|
||||||
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,
|
title: String,
|
||||||
content: String,
|
content: String,
|
||||||
markdown: Boolean = false,
|
markdown: Boolean = false,
|
||||||
confirm: String? = null,
|
confirm: String? = null,
|
||||||
dismiss: String? = null
|
dismiss: String? = null
|
||||||
): ConfirmResult = mutex.withLock {
|
)
|
||||||
|
|
||||||
|
suspend fun awaitConfirm(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
markdown: Boolean = false,
|
||||||
|
confirm: String? = null,
|
||||||
|
dismiss: String? = null
|
||||||
|
): ConfirmResult
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class DialogHandleBase(
|
||||||
|
protected val visible: MutableState<Boolean>,
|
||||||
|
protected val coroutineScope: CoroutineScope
|
||||||
|
) : DialogHandle {
|
||||||
|
override val isShown: Boolean
|
||||||
|
get() = visible.value
|
||||||
|
|
||||||
|
override fun show() {
|
||||||
|
coroutineScope.launch {
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun hide() {
|
||||||
|
coroutineScope.launch {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return dialogType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LoadingDialogHandleImpl(
|
||||||
|
visible: MutableState<Boolean>,
|
||||||
|
coroutineScope: CoroutineScope
|
||||||
|
) : LoadingDialogHandle, DialogHandleBase(visible, coroutineScope) {
|
||||||
|
override suspend fun <R> withLoading(block: suspend () -> R): R {
|
||||||
|
return coroutineScope.async {
|
||||||
try {
|
try {
|
||||||
return@withLock suspendCancellableCoroutine { continuation ->
|
visible.value = true
|
||||||
currentDialogData = ConfirmDialogDataImpl(
|
block()
|
||||||
visuals = ConfirmDialogVisualsImpl(title, content, confirm, dismiss, markdown),
|
} finally {
|
||||||
continuation = continuation
|
visible.value = false
|
||||||
|
}
|
||||||
|
}.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showLoading() {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val dialogType: String get() = "LoadingDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias NullableCallback = (() -> Unit)?
|
||||||
|
|
||||||
|
interface ConfirmCallback {
|
||||||
|
|
||||||
|
val onConfirm: NullableCallback
|
||||||
|
|
||||||
|
val onDismiss: NullableCallback
|
||||||
|
|
||||||
|
val isEmpty: Boolean get() = onConfirm == null && onDismiss == null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke(onConfirmProvider: () -> NullableCallback, onDismissProvider: () -> NullableCallback): ConfirmCallback {
|
||||||
|
return object : ConfirmCallback {
|
||||||
|
override val onConfirm: NullableCallback
|
||||||
|
get() = onConfirmProvider()
|
||||||
|
override val onDismiss: NullableCallback
|
||||||
|
get() = onDismissProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConfirmDialogHandleImpl(
|
||||||
|
visible: MutableState<Boolean>,
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
callback: ConfirmCallback,
|
||||||
|
override var visuals: ConfirmDialogVisuals = ConfirmDialogVisualsImpl.Empty,
|
||||||
|
private val resultFlow: ReceiveChannel<ConfirmResult>
|
||||||
|
) : ConfirmDialogHandle, DialogHandleBase(visible, coroutineScope) {
|
||||||
|
private class ResultCollector(
|
||||||
|
private val callback: ConfirmCallback
|
||||||
|
) : FlowCollector<ConfirmResult> {
|
||||||
|
fun handleResult(result: ConfirmResult) {
|
||||||
|
Log.d(TAG, "handleResult: ${result.javaClass.simpleName}")
|
||||||
|
when (result) {
|
||||||
|
ConfirmResult.Confirmed -> onConfirm()
|
||||||
|
ConfirmResult.Canceled -> onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onConfirm() {
|
||||||
|
callback.onConfirm?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDismiss() {
|
||||||
|
callback.onDismiss?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun emit(value: ConfirmResult) {
|
||||||
|
handleResult(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val resultCollector = ResultCollector(callback)
|
||||||
|
|
||||||
|
private var awaitContinuation: CancellableContinuation<ConfirmResult>? = null
|
||||||
|
|
||||||
|
private val isCallbackEmpty = callback.isEmpty
|
||||||
|
|
||||||
|
init {
|
||||||
|
coroutineScope.launch {
|
||||||
|
resultFlow
|
||||||
|
.consumeAsFlow()
|
||||||
|
.onEach { result ->
|
||||||
|
awaitContinuation?.let {
|
||||||
|
awaitContinuation = null
|
||||||
|
if (it.isActive) {
|
||||||
|
it.resume(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEach { hide() }
|
||||||
|
.collect(resultCollector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun awaitResult(): ConfirmResult {
|
||||||
|
return suspendCancellableCoroutine {
|
||||||
|
awaitContinuation = it.apply {
|
||||||
|
if (isCallbackEmpty) {
|
||||||
|
invokeOnCancellation {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateVisuals(visuals: ConfirmDialogVisuals) {
|
||||||
|
this.visuals = visuals
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun show() {
|
||||||
|
if (visuals !== ConfirmDialogVisualsImpl.Empty) {
|
||||||
|
super.show()
|
||||||
|
} else {
|
||||||
|
throw UnsupportedOperationException("can't show confirm dialog with the Empty visuals")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showConfirm(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
markdown: Boolean,
|
||||||
|
confirm: String?,
|
||||||
|
dismiss: String?
|
||||||
|
) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun awaitConfirm(
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
markdown: Boolean,
|
||||||
|
confirm: String?,
|
||||||
|
dismiss: String?
|
||||||
|
): ConfirmResult {
|
||||||
|
coroutineScope.launch {
|
||||||
|
updateVisuals(ConfirmDialogVisualsImpl(title, content, markdown, confirm, dismiss))
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
return awaitResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val dialogType: String get() = "ConfirmDialog"
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "${super.toString()}(visuals: $visuals)"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun Saver(
|
||||||
|
visible: MutableState<Boolean>,
|
||||||
|
coroutineScope: CoroutineScope,
|
||||||
|
callback: ConfirmCallback,
|
||||||
|
resultChannel: ReceiveChannel<ConfirmResult>
|
||||||
|
) = Saver<ConfirmDialogHandle, ConfirmDialogVisuals>(
|
||||||
|
save = {
|
||||||
|
it.visuals
|
||||||
|
},
|
||||||
|
restore = {
|
||||||
|
Log.d(TAG, "ConfirmDialog restore, visuals: $it")
|
||||||
|
ConfirmDialogHandleImpl(visible, coroutineScope, callback, it, resultChannel)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
currentDialogData = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CustomDialogHandleImpl(
|
||||||
|
visible: MutableState<Boolean>,
|
||||||
|
coroutineScope: CoroutineScope
|
||||||
|
) : DialogHandleBase(visible, coroutineScope) {
|
||||||
|
override val dialogType: String get() = "CustomDialog"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberDialogHostState(): DialogHostState {
|
fun rememberLoadingDialog(): LoadingDialogHandle {
|
||||||
|
val visible = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
if (visible.value) {
|
||||||
|
LoadingDialog()
|
||||||
|
}
|
||||||
|
|
||||||
return remember {
|
return remember {
|
||||||
DialogHostState()
|
LoadingDialogHandleImpl(visible, coroutineScope)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T : DialogData> DialogData?.tryInto(): T? {
|
|
||||||
return when (this) {
|
|
||||||
is T -> this
|
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoadingDialog(
|
private fun rememberConfirmDialog(visuals: ConfirmDialogVisuals, callback: ConfirmCallback): ConfirmDialogHandle {
|
||||||
state: DialogHostState = LocalDialogHost.current,
|
val visible = rememberSaveable {
|
||||||
) {
|
mutableStateOf(false)
|
||||||
state.currentDialogData.tryInto<LoadingDialogData>() ?: return
|
|
||||||
val dialogProperties = remember {
|
|
||||||
DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
|
|
||||||
}
|
}
|
||||||
Dialog(onDismissRequest = {}, properties = dialogProperties) {
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val resultChannel = remember {
|
||||||
|
Channel<ConfirmResult>()
|
||||||
|
}
|
||||||
|
|
||||||
|
val handle = rememberSaveable(
|
||||||
|
saver = ConfirmDialogHandleImpl.Saver(visible, coroutineScope, callback, resultChannel),
|
||||||
|
init = {
|
||||||
|
ConfirmDialogHandleImpl(visible, coroutineScope, callback, visuals, resultChannel)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (visible.value) {
|
||||||
|
ConfirmDialog(
|
||||||
|
handle.visuals,
|
||||||
|
confirm = { coroutineScope.launch { resultChannel.send(ConfirmResult.Confirmed) } },
|
||||||
|
dismiss = { coroutineScope.launch { resultChannel.send(ConfirmResult.Canceled) } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberConfirmCallback(onConfirm: NullableCallback, onDismiss: NullableCallback): ConfirmCallback {
|
||||||
|
val currentOnConfirm by rememberUpdatedState(newValue = onConfirm)
|
||||||
|
val currentOnDismiss by rememberUpdatedState(newValue = onDismiss)
|
||||||
|
return remember {
|
||||||
|
ConfirmCallback({ currentOnConfirm }, { currentOnDismiss })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberConfirmDialog(onConfirm: NullableCallback = null, onDismiss: NullableCallback = null): ConfirmDialogHandle {
|
||||||
|
return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle {
|
||||||
|
return rememberConfirmDialog(ConfirmDialogVisualsImpl.Empty, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberCustomDialog(composable: @Composable (dismiss: () -> Unit) -> Unit): DialogHandle {
|
||||||
|
val visible = rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
if (visible.value) {
|
||||||
|
composable { visible.value = false }
|
||||||
|
}
|
||||||
|
return remember {
|
||||||
|
CustomDialogHandleImpl(visible, coroutineScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LoadingDialog() {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)
|
modifier = Modifier.size(100.dp), shape = RoundedCornerShape(8.dp)
|
||||||
) {
|
) {
|
||||||
@@ -227,41 +395,10 @@ fun LoadingDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PromptDialog(
|
private fun ConfirmDialog(visuals: ConfirmDialogVisuals, confirm: () -> Unit, dismiss: () -> Unit) {
|
||||||
state: DialogHostState = LocalDialogHost.current,
|
|
||||||
) {
|
|
||||||
val promptDialogData = state.currentDialogData.tryInto<PromptDialogData>() ?: return
|
|
||||||
|
|
||||||
val visuals = promptDialogData.visuals
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
promptDialogData.dismiss()
|
dismiss()
|
||||||
},
|
|
||||||
title = {
|
|
||||||
Text(text = visuals.title)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(text = visuals.content)
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { promptDialogData.dismiss() }) {
|
|
||||||
Text(text = stringResource(id = android.R.string.ok))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
|
||||||
val confirmDialogData = state.currentDialogData.tryInto<ConfirmDialogData>() ?: return
|
|
||||||
|
|
||||||
val visuals = confirmDialogData.visuals
|
|
||||||
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = {
|
|
||||||
confirmDialogData.dismiss()
|
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(text = visuals.title)
|
Text(text = visuals.title)
|
||||||
@@ -274,17 +411,18 @@ fun ConfirmDialog(state: DialogHostState = LocalDialogHost.current) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { confirmDialogData.confirm() }) {
|
TextButton(onClick = confirm) {
|
||||||
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
|
Text(text = visuals.confirm ?: stringResource(id = android.R.string.ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { confirmDialogData.dismiss() }) {
|
TextButton(onClick = dismiss) {
|
||||||
Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
|
Text(text = visuals.dismiss ?: stringResource(id = android.R.string.cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MarkdownContent(content: String) {
|
private fun MarkdownContent(content: String) {
|
||||||
val contentColor = LocalContentColor.current
|
val contentColor = LocalContentColor.current
|
||||||
@@ -307,5 +445,6 @@ private fun MarkdownContent(content: String) {
|
|||||||
update = {
|
update = {
|
||||||
Markwon.create(it.context).setMarkdown(it, content)
|
Markwon.create(it.context).setMarkdown(it, content)
|
||||||
it.setTextColor(contentColor.toArgb())
|
it.setTextColor(contentColor.toArgb())
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -53,6 +54,7 @@ import me.weishu.kernelsu.Natives
|
|||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.profile.Capabilities
|
import me.weishu.kernelsu.profile.Capabilities
|
||||||
import me.weishu.kernelsu.profile.Groups
|
import me.weishu.kernelsu.profile.Groups
|
||||||
|
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
||||||
import me.weishu.kernelsu.ui.util.isSepolicyValid
|
import me.weishu.kernelsu.ui.util.isSepolicyValid
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -187,10 +189,7 @@ fun RootProfileConfig(
|
|||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
|
fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>) -> Unit) {
|
||||||
|
val selectGroupsDialog = rememberCustomDialog { dismiss: () -> Unit ->
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (showDialog) {
|
|
||||||
val groups = Groups.values().sortedWith(
|
val groups = Groups.values().sortedWith(
|
||||||
compareBy<Groups> { if (selected.contains(it)) 0 else 1 }
|
compareBy<Groups> { if (selected.contains(it)) 0 else 1 }
|
||||||
.then(compareBy {
|
.then(compareBy {
|
||||||
@@ -217,7 +216,7 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
|
|||||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||||
closeSelection(selection)
|
closeSelection(selection)
|
||||||
}, onCloseRequest = {
|
}, onCloseRequest = {
|
||||||
showDialog = false
|
dismiss()
|
||||||
}),
|
}),
|
||||||
header = Header.Default(
|
header = Header.Default(
|
||||||
title = stringResource(R.string.profile_groups),
|
title = stringResource(R.string.profile_groups),
|
||||||
@@ -241,7 +240,7 @@ fun GroupsPanel(selected: List<Groups>, closeSelection: (selection: Set<Groups>)
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
showDialog = true
|
selectGroupsDialog.show()
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
@@ -265,10 +264,7 @@ fun CapsPanel(
|
|||||||
selected: Collection<Capabilities>,
|
selected: Collection<Capabilities>,
|
||||||
closeSelection: (selection: Set<Capabilities>) -> Unit
|
closeSelection: (selection: Set<Capabilities>) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val selectCapabilitiesDialog = rememberCustomDialog { dismiss ->
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (showDialog) {
|
|
||||||
val caps = Capabilities.values().sortedWith(
|
val caps = Capabilities.values().sortedWith(
|
||||||
compareBy<Capabilities> { if (selected.contains(it)) 0 else 1 }
|
compareBy<Capabilities> { if (selected.contains(it)) 0 else 1 }
|
||||||
.then(compareBy { it.name })
|
.then(compareBy { it.name })
|
||||||
@@ -286,7 +282,7 @@ fun CapsPanel(
|
|||||||
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
state = rememberUseCaseState(visible = true, onFinishedRequest = {
|
||||||
closeSelection(selection)
|
closeSelection(selection)
|
||||||
}, onCloseRequest = {
|
}, onCloseRequest = {
|
||||||
showDialog = false
|
dismiss()
|
||||||
}),
|
}),
|
||||||
header = Header.Default(
|
header = Header.Default(
|
||||||
title = stringResource(R.string.profile_capabilities),
|
title = stringResource(R.string.profile_capabilities),
|
||||||
@@ -309,7 +305,7 @@ fun CapsPanel(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.clickable {
|
.clickable {
|
||||||
showDialog = true
|
selectCapabilitiesDialog.show()
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
@@ -377,8 +373,7 @@ private fun SELinuxPanel(
|
|||||||
profile: Natives.Profile,
|
profile: Natives.Profile,
|
||||||
onSELinuxChange: (domain: String, rules: String) -> Unit
|
onSELinuxChange: (domain: String, rules: String) -> Unit
|
||||||
) {
|
) {
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
val editSELinuxDialog = rememberCustomDialog { dismiss ->
|
||||||
if (showDialog) {
|
|
||||||
var domain by remember { mutableStateOf(profile.context) }
|
var domain by remember { mutableStateOf(profile.context) }
|
||||||
var rules by remember { mutableStateOf(profile.rules) }
|
var rules by remember { mutableStateOf(profile.rules) }
|
||||||
|
|
||||||
@@ -430,7 +425,7 @@ private fun SELinuxPanel(
|
|||||||
onSELinuxChange(domain, rules)
|
onSELinuxChange(domain, rules)
|
||||||
},
|
},
|
||||||
onCloseRequest = {
|
onCloseRequest = {
|
||||||
showDialog = false
|
dismiss()
|
||||||
}),
|
}),
|
||||||
header = Header.Default(
|
header = Header.Default(
|
||||||
title = stringResource(R.string.profile_selinux_context),
|
title = stringResource(R.string.profile_selinux_context),
|
||||||
@@ -449,7 +444,7 @@ private fun SELinuxPanel(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
showDialog = true
|
editSELinuxDialog.show()
|
||||||
},
|
},
|
||||||
enabled = false,
|
enabled = false,
|
||||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ private fun AppProfileInner(
|
|||||||
} else {
|
} else {
|
||||||
Mode.Custom
|
Mode.Custom
|
||||||
}
|
}
|
||||||
var mode by remember {
|
var mode by rememberSaveable {
|
||||||
mutableStateOf(initialMode)
|
mutableStateOf(initialMode)
|
||||||
}
|
}
|
||||||
ProfileBox(mode, true) {
|
ProfileBox(mode, true) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Build
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@@ -29,12 +30,10 @@ import com.ramcosta.composedestinations.annotation.Destination
|
|||||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.weishu.kernelsu.*
|
import me.weishu.kernelsu.*
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.SettingScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.*
|
import me.weishu.kernelsu.ui.util.*
|
||||||
|
|
||||||
@@ -84,7 +83,6 @@ fun HomeScreen(navigator: DestinationsNavigator) {
|
|||||||
DonateCard()
|
DonateCard()
|
||||||
LearnMoreCard()
|
LearnMoreCard()
|
||||||
Spacer(Modifier)
|
Spacer(Modifier)
|
||||||
ConfirmDialog()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,28 +97,28 @@ fun UpdateCard() {
|
|||||||
val newVersionCode = newVersion.first
|
val newVersionCode = newVersion.first
|
||||||
val newVersionUrl = newVersion.second
|
val newVersionUrl = newVersion.second
|
||||||
val changelog = newVersion.third
|
val changelog = newVersion.third
|
||||||
if (newVersionCode <= currentVersionCode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val dialogHost = LocalDialogHost.current
|
|
||||||
val title = stringResource(id = R.string.module_changelog)
|
val title = stringResource(id = R.string.module_changelog)
|
||||||
val updateText = stringResource(id = R.string.module_update)
|
val updateText = stringResource(id = R.string.module_update)
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = newVersionCode >= currentVersionCode,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = shrinkVertically() + fadeOut()
|
||||||
|
) {
|
||||||
|
val updateDialog = rememberConfirmDialog(onConfirm = { uriHandler.openUri(newVersionUrl) })
|
||||||
WarningCard(
|
WarningCard(
|
||||||
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
message = stringResource(id = R.string.new_version_available).format(newVersionCode),
|
||||||
MaterialTheme.colorScheme.outlineVariant
|
MaterialTheme.colorScheme.outlineVariant
|
||||||
) {
|
) {
|
||||||
scope.launch {
|
if (changelog.isNotEmpty()) {
|
||||||
if (changelog.isEmpty() || dialogHost.showConfirm(
|
updateDialog.showConfirm(
|
||||||
title = title,
|
title = title,
|
||||||
content = changelog,
|
content = changelog,
|
||||||
markdown = true,
|
markdown = true,
|
||||||
confirm = updateText,
|
confirm = updateText
|
||||||
) == ConfirmResult.Confirmed
|
)
|
||||||
) {
|
|
||||||
uriHandler.openUri(newVersionUrl)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import me.weishu.kernelsu.Natives
|
import me.weishu.kernelsu.Natives
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
|
||||||
import me.weishu.kernelsu.ui.component.ConfirmResult
|
import me.weishu.kernelsu.ui.component.ConfirmResult
|
||||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
import me.weishu.kernelsu.ui.component.rememberConfirmDialog
|
||||||
|
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.InstallScreenDestination
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.WebScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.*
|
import me.weishu.kernelsu.ui.util.*
|
||||||
@@ -101,10 +101,6 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
}) { innerPadding ->
|
}) { innerPadding ->
|
||||||
|
|
||||||
ConfirmDialog()
|
|
||||||
|
|
||||||
LoadingDialog()
|
|
||||||
|
|
||||||
when {
|
when {
|
||||||
hasMagisk -> {
|
hasMagisk -> {
|
||||||
Box(
|
Box(
|
||||||
@@ -162,17 +158,19 @@ private fun ModuleList(
|
|||||||
val startDownloadingText = stringResource(R.string.module_start_downloading)
|
val startDownloadingText = stringResource(R.string.module_start_downloading)
|
||||||
val fetchChangeLogFailed = stringResource(R.string.module_changelog_failed)
|
val fetchChangeLogFailed = stringResource(R.string.module_changelog_failed)
|
||||||
|
|
||||||
val dialogHost = LocalDialogHost.current
|
|
||||||
val snackBarHost = LocalSnackbarHost.current
|
val snackBarHost = LocalSnackbarHost.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val loadingDialog = rememberLoadingDialog()
|
||||||
|
val confirmDialog = rememberConfirmDialog()
|
||||||
|
|
||||||
suspend fun onModuleUpdate(
|
suspend fun onModuleUpdate(
|
||||||
module: ModuleViewModel.ModuleInfo,
|
module: ModuleViewModel.ModuleInfo,
|
||||||
changelogUrl: String,
|
changelogUrl: String,
|
||||||
downloadUrl: String,
|
downloadUrl: String,
|
||||||
fileName: String
|
fileName: String
|
||||||
) {
|
) {
|
||||||
val changelogResult = dialogHost.withLoading {
|
val changelogResult = loadingDialog.withLoading {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
runCatching {
|
runCatching {
|
||||||
OkHttpClient().newCall(
|
OkHttpClient().newCall(
|
||||||
@@ -201,7 +199,7 @@ private fun ModuleList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// changelog is not empty, show it and wait for confirm
|
// changelog is not empty, show it and wait for confirm
|
||||||
val confirmResult = dialogHost.showConfirm(
|
val confirmResult = confirmDialog.awaitConfirm(
|
||||||
changelogText,
|
changelogText,
|
||||||
content = changelog,
|
content = changelog,
|
||||||
markdown = true,
|
markdown = true,
|
||||||
@@ -232,7 +230,7 @@ private fun ModuleList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
|
suspend fun onModuleUninstall(module: ModuleViewModel.ModuleInfo) {
|
||||||
val confirmResult = dialogHost.showConfirm(
|
val confirmResult = confirmDialog.awaitConfirm(
|
||||||
moduleStr,
|
moduleStr,
|
||||||
content = moduleUninstallConfirm.format(module.name),
|
content = moduleUninstallConfirm.format(module.name),
|
||||||
confirm = uninstall,
|
confirm = uninstall,
|
||||||
@@ -242,7 +240,7 @@ private fun ModuleList(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val success = dialogHost.withLoading {
|
val success = loadingDialog.withLoading {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
uninstallModule(module.id)
|
uninstallModule(module.id)
|
||||||
}
|
}
|
||||||
@@ -327,7 +325,7 @@ private fun ModuleList(
|
|||||||
scope.launch { onModuleUninstall(module) }
|
scope.launch { onModuleUninstall(module) }
|
||||||
}, onCheckChanged = {
|
}, onCheckChanged = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val success = dialogHost.withLoading {
|
val success = loadingDialog.withLoading {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
toggleModule(module.id, !isChecked)
|
toggleModule(module.id, !isChecked)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,10 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.filled.BugReport
|
|
||||||
import androidx.compose.material.icons.filled.ContactPage
|
|
||||||
import androidx.compose.material.icons.filled.DeveloperMode
|
|
||||||
import androidx.compose.material.icons.filled.Fence
|
|
||||||
import androidx.compose.material.icons.filled.RemoveModerator
|
|
||||||
import androidx.compose.material.icons.filled.Update
|
|
||||||
import androidx.compose.material.icons.filled.Upgrade
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
@@ -30,10 +24,10 @@ import me.weishu.kernelsu.BuildConfig
|
|||||||
import me.weishu.kernelsu.Natives
|
import me.weishu.kernelsu.Natives
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.AboutDialog
|
import me.weishu.kernelsu.ui.component.AboutDialog
|
||||||
import me.weishu.kernelsu.ui.component.LoadingDialog
|
|
||||||
import me.weishu.kernelsu.ui.component.SwitchItem
|
import me.weishu.kernelsu.ui.component.SwitchItem
|
||||||
|
import me.weishu.kernelsu.ui.component.rememberCustomDialog
|
||||||
|
import me.weishu.kernelsu.ui.component.rememberLoadingDialog
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.AppProfileTemplateScreenDestination
|
||||||
import me.weishu.kernelsu.ui.util.LocalDialogHost
|
|
||||||
import me.weishu.kernelsu.ui.util.getBugreportFile
|
import me.weishu.kernelsu.ui.util.getBugreportFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +37,6 @@ import me.weishu.kernelsu.ui.util.getBugreportFile
|
|||||||
@Destination
|
@Destination
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingScreen(navigator: DestinationsNavigator) {
|
fun SettingScreen(navigator: DestinationsNavigator) {
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopBar(onBack = {
|
TopBar(onBack = {
|
||||||
@@ -51,16 +44,15 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
LoadingDialog()
|
val aboutDialog = rememberCustomDialog {
|
||||||
|
AboutDialog(it)
|
||||||
val showAboutDialog = remember { mutableStateOf(false) }
|
}
|
||||||
AboutDialog(showAboutDialog)
|
val loadingDialog = rememberLoadingDialog()
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(paddingValues)) {
|
Column(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val dialogHost = LocalDialogHost.current
|
|
||||||
|
|
||||||
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
val profileTemplate = stringResource(id = R.string.settings_profile_template)
|
||||||
ListItem(
|
ListItem(
|
||||||
@@ -128,7 +120,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
headlineContent = { Text(stringResource(id = R.string.send_log)) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val bugreport = dialogHost.withLoading {
|
val bugreport = loadingDialog.withLoading {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
getBugreportFile(context)
|
getBugreportFile(context)
|
||||||
}
|
}
|
||||||
@@ -166,7 +158,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
|
|||||||
},
|
},
|
||||||
headlineContent = { Text(about) },
|
headlineContent = { Text(about) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
showAboutDialog.value = true
|
aboutDialog.show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.weishu.kernelsu.Natives
|
import me.weishu.kernelsu.Natives
|
||||||
import me.weishu.kernelsu.R
|
import me.weishu.kernelsu.R
|
||||||
import me.weishu.kernelsu.ui.component.ConfirmDialog
|
|
||||||
import me.weishu.kernelsu.ui.component.SearchAppBar
|
import me.weishu.kernelsu.ui.component.SearchAppBar
|
||||||
import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
|
import me.weishu.kernelsu.ui.screen.destinations.AppProfileScreenDestination
|
||||||
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
|
||||||
@@ -95,9 +94,6 @@ fun SuperUserScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|
||||||
ConfirmDialog()
|
|
||||||
|
|
||||||
val refreshState = rememberPullRefreshState(
|
val refreshState = rememberPullRefreshState(
|
||||||
refreshing = viewModel.isRefreshing,
|
refreshing = viewModel.isRefreshing,
|
||||||
onRefresh = { scope.launch { viewModel.fetchAppList() } },
|
onRefresh = { scope.launch { viewModel.fetchAppList() } },
|
||||||
|
|||||||
@@ -2,12 +2,7 @@ package me.weishu.kernelsu.ui.util
|
|||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import me.weishu.kernelsu.ui.component.DialogHostState
|
|
||||||
|
|
||||||
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
|
val LocalSnackbarHost = compositionLocalOf<SnackbarHostState> {
|
||||||
error("CompositionLocal LocalSnackbarController not present")
|
error("CompositionLocal LocalSnackbarController not present")
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalDialogHost = compositionLocalOf<DialogHostState> {
|
|
||||||
error("CompositionLocal LocalDialogController not present")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user