From 1bfda836659b50c74ca0dbac038e025d015e4d70 Mon Sep 17 00:00:00 2001 From: tarsin Date: Wed, 2 Aug 2023 10:14:41 +0800 Subject: [PATCH] Introduce DialogState.awaitInputText --- .../ehviewer/ui/scene/GalleryDetailScene.kt | 18 ++--- .../ehviewer/ui/scene/GalleryListScene.kt | 43 ++++++----- .../com/hippo/ehviewer/ui/tools/Dialog.kt | 71 ++++++++++++++++--- 3 files changed, 88 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt index bbfbc7f52..a1bca14e0 100755 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.kt @@ -144,7 +144,6 @@ import com.hippo.ehviewer.ui.addToFavorites import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder import com.hippo.ehviewer.ui.legacy.CheckBoxDialogBuilder import com.hippo.ehviewer.ui.legacy.CoilImageGetter -import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder import com.hippo.ehviewer.ui.legacy.GalleryRatingBar.OnUserRateListener import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount import com.hippo.ehviewer.ui.main.EhPreviewItem @@ -361,16 +360,13 @@ class GalleryDetailScene : BaseScene() { showTip(R.string.sign_in_first, LENGTH_LONG) return } - val builder = - EditTextDialogBuilder(requireContext(), "", getString(R.string.action_add_tag_tip)) - builder.setPositiveButton(android.R.string.ok, null) - val dialog = builder.setTitle(R.string.action_add_tag) - .show() - dialog.getButton(DialogInterface.BUTTON_POSITIVE) - .setOnClickListener { - voteTag(builder.text.trim { it <= ' ' }, 1) - dialog.dismiss() - } + lifecycleScope.launchIO { + val text = dialogState.awaitInputText( + title = getString(R.string.action_add_tag), + hint = getString(R.string.action_add_tag_tip), + ) + voteTag(text.trim(), 1) + } } override fun onCreateView( diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt index c20539759..1819b7312 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable -import android.text.InputType import android.view.LayoutInflater import android.view.MenuItem import android.view.View @@ -628,31 +627,31 @@ class GalleryListScene : SearchBarScene() { } private fun showGoToDialog() { - val context = context ?: return mAdapter ?: return if (mIsTopList) { - val page = 1 val pages = 200 - val hint = getString(R.string.go_to_hint, page, pages) - val builder = EditTextDialogBuilder(context, null, hint) - builder.editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL - val dialog = builder.setTitle(R.string.go_to).setPositiveButton(android.R.string.ok, null).show() - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { - val text = builder.text.trim { it <= ' ' } - val goTo: Int = try { - text.toInt() - 1 - } catch (e: NumberFormatException) { - builder.setError(getString(R.string.error_invalid_number)) - return@setOnClickListener - } - if (goTo < 0 || goTo >= pages) { - builder.setError(getString(R.string.error_out_of_range)) - return@setOnClickListener - } - builder.setError(null) - mUrlBuilder.setJumpTo(goTo.toString()) + val title = getString(R.string.go_to) + val hint = getString(R.string.go_to_hint, 1, 200) + lifecycleScope.launch { + val text = dialogState.awaitInputText( + title = title, + hint = hint, + isNumber = true, + ) { oriText -> + val text = oriText.trim() + val goTo = runCatching { + text.toInt() - 1 + }.onFailure { + return@awaitInputText getString(R.string.error_invalid_number) + }.getOrThrow() + if (goTo < 0 || goTo >= pages) { + getString(R.string.error_out_of_range) + } else { + null + } + }.trim().toInt() - 1 + mUrlBuilder.setJumpTo(text.toString()) mAdapter?.refresh() - dialog.dismiss() } } else { val local = LocalDateTime.of(2007, 3, 21, 0, 0) diff --git a/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt b/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt index d1ee6c179..c001e425c 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/tools/Dialog.kt @@ -14,8 +14,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Info import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.Icon @@ -23,6 +25,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -43,8 +46,10 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.jamal.composeprefs3.ui.ifNotNullThen import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @@ -69,20 +74,23 @@ class DialogState { suspend inline fun dialog(crossinline block: @Composable (CancellableContinuation) -> Unit) = suspendCancellableCoroutine { cont -> cont.invokeOnCancellation { dismiss() } - content = { block(cont) } + val realContinuation = object : CancellableContinuation by cont { + override fun resumeWith(result: Result) { + dismiss() + cont.resumeWith(result) + } + } + content = { block(realContinuation) } } - suspend fun show(initial: R, @StringRes title: Int? = null, block: @Composable DialogScope.() -> Unit): R { + suspend fun awaitResult(initial: R, @StringRes title: Int? = null, block: @Composable DialogScope.() -> Unit): R { return dialog { cont -> val state = remember(cont) { mutableStateOf(initial) } val impl = remember(cont) { object : DialogScope { override var expectedValue by state } } AlertDialog( onDismissRequest = { cont.cancel() }, confirmButton = { - TextButton(onClick = { - dismiss() - cont.resume(state.value) - }) { + TextButton(onClick = { cont.resume(state.value) }) { Text(text = stringResource(id = android.R.string.ok)) } }, @@ -92,6 +100,51 @@ class DialogState { } } + suspend fun awaitInputText( + initial: String = "", + title: String? = null, + hint: String? = null, + isNumber: Boolean = false, + invalidator: ((String) -> String?)? = null, + ): String { + return dialog { cont -> + var state by remember(cont) { mutableStateOf(initial) } + var error by remember(cont) { mutableStateOf(null) } + AlertDialog( + onDismissRequest = { cont.cancel() }, + confirmButton = { + TextButton(onClick = { + error = invalidator?.invoke(state) + error ?: cont.resume(state) + }) { + Text(text = stringResource(id = android.R.string.ok)) + } + }, + title = title?.let { { Text(text = title) } }, + text = { + OutlinedTextField( + value = state, + onValueChange = { state = it }, + label = hint.ifNotNullThen { + Text(text = hint!!) + }, + trailingIcon = error.ifNotNullThen { + Icon( + imageVector = Icons.Filled.Info, + contentDescription = null, + ) + }, + supportingText = error.ifNotNullThen { + Text(text = error!!) + }, + isError = error != null, + keyboardOptions = if (isNumber) KeyboardOptions(keyboardType = KeyboardType.Number) else KeyboardOptions.Default, + ) + }, + ) + } + } + suspend fun awaitPermissionOrCancel( @StringRes confirmText: Int? = null, @StringRes dismissText: Int? = null, @@ -102,10 +155,7 @@ class DialogState { AlertDialog( onDismissRequest = { cont.cancel() }, confirmButton = { - TextButton(onClick = { - dismiss() - cont.resume(Unit) - }) { + TextButton(onClick = { cont.resume(Unit) }) { Text(text = stringResource(id = confirmText ?: android.R.string.ok)) } }, @@ -126,7 +176,6 @@ class DialogState { return dialog { cont -> val impl = remember(cont) { DismissDialogScope { - dismiss() cont.resume(it) } }