From b0b2d52f4f93645fcca2628c0336b530c721d9a1 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Sun, 21 Jan 2024 17:53:46 +0000 Subject: [PATCH] refactor: convert IntegerDialog to AlertDialog adds parameters to input() * hint * inputType * maxLength * waitForPositiveButton waitForPositiveButton matches the material dialog UI, but is questionable `allowEmpty` potentially has a bug if called alongside `waitForPositiveButton` Issue 13315 --- .../ichi2/anki/dialogs/CreateDeckDialog.kt | 2 +- .../com/ichi2/anki/dialogs/IntegerDialog.kt | 39 ++++++++-------- .../java/com/ichi2/utils/AlertDialogFacade.kt | 46 ++++++++++++++++--- .../com/ichi2/utils/MaterialBuilderUtil.kt | 21 --------- 4 files changed, 59 insertions(+), 49 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt index dacc7215cc61..3c92eb83aa20 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt @@ -81,7 +81,7 @@ class CreateDeckDialog( positiveButton(R.string.dialog_ok) { onPositiveButtonClicked() } negativeButton(R.string.dialog_cancel) setView(R.layout.dialog_generic_text_input) - }.input(prefill = initialDeckName, displayKeyboard = true) { dialog, text -> + }.input(prefill = initialDeckName, displayKeyboard = true, waitForPositiveButton = false) { dialog, text -> // we need the fully-qualified name for subdecks val maybeDeckName = fullyQualifyDeckName(dialogText = text) // if the name is empty, it seems distracting to show an error diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt index 677774852f7c..d23970f252e2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt @@ -16,17 +16,16 @@ package com.ichi2.anki.dialogs -import android.annotation.SuppressLint import android.os.Bundle import android.text.InputType -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.callbacks.onShow -import com.afollestad.materialdialogs.input.getInputField -import com.afollestad.materialdialogs.input.input +import androidx.appcompat.app.AlertDialog import com.ichi2.anki.R import com.ichi2.anki.analytics.AnalyticsDialogFragment -import com.ichi2.utils.contentNullable -import com.ichi2.utils.displayKeyboard +import com.ichi2.utils.input +import com.ichi2.utils.negativeButton +import com.ichi2.utils.positiveButton +import com.ichi2.utils.show +import com.ichi2.utils.title import java.util.function.Consumer open class IntegerDialog : AnalyticsDialogFragment() { @@ -45,24 +44,22 @@ open class IntegerDialog : AnalyticsDialogFragment() { arguments = args } - override fun onCreateDialog(savedInstanceState: Bundle?): MaterialDialog { + override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog { super.onCreateDialog(savedInstanceState) - @SuppressLint("CheckResult") - val show = MaterialDialog(requireActivity()).show { + return AlertDialog.Builder(requireActivity()).show { title(text = requireArguments().getString("title")!!) positiveButton(R.string.dialog_ok) negativeButton(R.string.dialog_cancel) - input( - hint = requireArguments().getString("prompt"), - inputType = InputType.TYPE_CLASS_NUMBER, - maxLength = requireArguments().getInt("digits") - ) { _: MaterialDialog?, text: CharSequence -> consumer!!.accept(text.toString().toInt()) } - contentNullable(requireArguments().getString("content")) - onShow { - displayKeyboard(getInputField()) - } + setMessage(requireArguments().getString("content")) + setView(R.layout.dialog_generic_text_input) + }.input( + hint = requireArguments().getString("prompt"), + inputType = InputType.TYPE_CLASS_NUMBER, + maxLength = requireArguments().getInt("digits"), + displayKeyboard = true + ) { _, text: CharSequence -> + consumer!!.accept(text.toString().toInt()) + dismiss() } - - return show } } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt b/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt index 17fdffe3c320..b462ebe48635 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt +++ b/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt @@ -20,6 +20,7 @@ package com.ichi2.utils import android.content.DialogInterface import android.content.DialogInterface.OnClickListener +import android.text.InputFilter import android.view.LayoutInflater import android.view.View import android.widget.Button @@ -202,30 +203,63 @@ fun AlertDialog.Builder.customListAdapter(adapter: RecyclerView.Adapter<*>) { } /** + * @param hint The hint text to be displayed to the user * @param prefill The text to initially appear in the [EditText] * @param allowEmpty If true, [DialogInterface.BUTTON_POSITIVE] is disabled if the [EditText] is empty * @param displayKeyboard Whether to open the keyboard when the dialog appears - * @param callback called whenever the text is changed + * @param callback if [waitForPositiveButton], called when [positiveButton] is pressed, otherwise + * called whenever the text is changed + * @param maxLength if set, the user may not enter more than the supplied number of digits + * @param inputType see [EditText.setInputType] + * @param waitForPositiveButton MaterialDialog compat: if `false` [callback] is called on input + * if `true` [callback] is called when [positiveButton] is pressed */ fun AlertDialog.input( + hint: String? = null, + inputType: Int? = null, prefill: CharSequence? = null, allowEmpty: Boolean = false, + maxLength: Int? = null, displayKeyboard: Boolean = false, + waitForPositiveButton: Boolean = true, callback: (AlertDialog, CharSequence) -> Unit ): AlertDialog { + // Builder.setView() may not be called before show() + if (!this.isShowing) throw IllegalStateException("input() requires .show()") + + getInputTextLayout().hint = hint + getInputField().apply { if (displayKeyboard) { AndroidUiUtils.setFocusAndOpenKeyboard(this, window!!) } - doOnTextChanged { text, _, _, _ -> - callback(this@input, text ?: "") - // called after the callback so allowEmpty takes priority - if (!allowEmpty && text.isNullOrEmpty()) { - this@input.positiveButton.isEnabled = false + inputType?.let { this.inputType = it } + + if (!waitForPositiveButton) { + doOnTextChanged { text, _, _, _ -> + callback(this@input, text ?: "") } + } else { + positiveButton.setOnClickListener { callback(this@input, this.text.toString()) } } + if (!allowEmpty) { + // this is called after callback() so allowEmpty takes priority + doOnTextChanged { text, _, _, _ -> + if (waitForPositiveButton) { + // this is the only validation filter we apply - toggle on or off + this@input.positiveButton.isEnabled = !text.isNullOrEmpty() + } else if (text.isNullOrEmpty()) { + // potentially other filters in `waitForPositiveButton`. + // WARN: this could be buggy as it does not toggle the button back on + this@input.positiveButton.isEnabled = false + } + } + } + + maxLength?.let { filters += InputFilter.LengthFilter(it) } + requestFocus() // this calls callback(this, prefill). positiveButton may be disabled if there's no prefill setText(prefill) diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/MaterialBuilderUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/MaterialBuilderUtil.kt index 9d7e5a52c032..72f8de25ee8c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/MaterialBuilderUtil.kt +++ b/AnkiDroid/src/main/java/com/ichi2/utils/MaterialBuilderUtil.kt @@ -16,10 +16,7 @@ package com.ichi2.utils -import android.widget.EditText -import androidx.annotation.DrawableRes import com.afollestad.materialdialogs.MaterialDialog -import com.ichi2.themes.Themes // Extension methods for MaterialDialog workarounds in Kotlin // Previously the methods accepted null into a non-null parameter, @@ -29,21 +26,3 @@ fun MaterialDialog.contentNullable(message: CharSequence?): MaterialDialog { message?.let { this.message(text = it) } return this } - -/** - * Method to display keyboard when dialog is shown. - * - * @param editText EditText present in the dialog. - */ -fun MaterialDialog.displayKeyboard(editText: EditText) { - AndroidUiUtils.setFocusAndOpenKeyboard(editText, window!!) -} - -/** - * Shows an icon to the left of the dialog title. - */ -fun MaterialDialog.iconAttr( - @DrawableRes res: Int -): MaterialDialog = apply { - this.icon(Themes.getResFromAttr(this.context, res)) -}