diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt index d8afd15e6f3c..1939dd8a1eca 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt @@ -29,13 +29,9 @@ import android.widget.EditText import android.widget.TextView import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.core.content.edit import androidx.fragment.app.DialogFragment -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.WhichButton -import com.afollestad.materialdialogs.actions.getActionButton -import com.afollestad.materialdialogs.customview.customView -import com.afollestad.materialdialogs.list.listItems import com.ichi2.anki.* import com.ichi2.anki.UIUtils.showThemedToast import com.ichi2.anki.analytics.AnalyticsDialogFragment @@ -53,6 +49,12 @@ import com.ichi2.libanki.Deck import com.ichi2.libanki.DeckId import com.ichi2.utils.HashUtil.hashMapInit import com.ichi2.utils.KotlinCleanup +import com.ichi2.utils.cancelable +import com.ichi2.utils.customView +import com.ichi2.utils.listItems +import com.ichi2.utils.negativeButton +import com.ichi2.utils.positiveButton +import com.ichi2.utils.title import net.ankiweb.rsdroid.exceptions.BackendDeckIsFilteredException import org.json.JSONArray import org.json.JSONObject @@ -100,15 +102,16 @@ class CustomStudyDialog(private val collection: Collection, private val customSt * Build a context menu for custom study * @param id the id type of the dialog */ - private fun buildContextMenu(id: Int): MaterialDialog { + private fun buildContextMenu(id: Int): AlertDialog { val listIds = getListIds(ContextMenuConfiguration.fromInt(id)).map { it.value }.toIntArray() val jumpToReviewer = requireArguments().getBoolean("jumpToReviewer") + val items = getValuesFromKeys(keyValueMap, listIds).toList().map { it as CharSequence } - return MaterialDialog(requireActivity()) + return AlertDialog.Builder(requireActivity()) .title(R.string.custom_study) .cancelable(true) - .listItems(items = getValuesFromKeys(keyValueMap, listIds).toList().map { it as CharSequence }) { _: MaterialDialog, _: Int, charSequence: CharSequence -> - when (ContextMenuOption.fromString(resources, charSequence.toString())) { + .listItems(items = items) { _, index -> + when (ContextMenuOption.fromString(resources, items[index].toString())) { DECK_OPTIONS -> { // User asked to permanently change the deck options val deckId = requireArguments().getLong("did") @@ -144,14 +147,14 @@ class CustomStudyDialog(private val collection: Collection, private val customSt // User asked for a standard custom study option val d = CustomStudyDialog(collection, customStudyListener) .withArguments( - ContextMenuOption.fromString(resources, charSequence.toString()), + ContextMenuOption.fromString(resources, items[index].toString()), requireArguments().getLong("did"), jumpToReviewer ) customStudyListener?.showDialogFragment(d) } } - } + }.create() } @KotlinCleanup("make this use enum instead of Int") @@ -167,7 +170,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt * Build an input dialog that is used to get a parameter related to custom study from the user * @param contextMenuOption the option of the dialog */ - private fun buildInputDialog(contextMenuOption: ContextMenuOption): MaterialDialog { + private fun buildInputDialog(contextMenuOption: ContextMenuOption): AlertDialog { /* TODO: Try to change to a standard input dialog (currently the thing holding us back is having the extra TODO: hint line for the number of cards available, and having the pre-filled text selected by default) @@ -194,8 +197,8 @@ class CustomStudyDialog(private val collection: Collection, private val customSt // Whether or not to jump straight to the reviewer val jumpToReviewer = requireArguments().getBoolean("jumpToReviewer") // Set material dialog parameters - val dialog = MaterialDialog(requireActivity()) - .customView(view = v, scrollable = true, noVerticalPadding = false, horizontalPadding = true) + val dialog = AlertDialog.Builder(requireActivity()) + .customView(view = v, paddingLeft = 64, paddingRight = 64, paddingTop = 32, paddingBottom = 32) .positiveButton(R.string.dialog_ok) { // Get the value selected by user val n: Int = try { @@ -277,16 +280,17 @@ class CustomStudyDialog(private val collection: Collection, private val customSt .negativeButton(R.string.dialog_cancel) { customStudyListener?.dismissAllDialogFragments() } + .create() // Added .create() because we wanted to access alertDialog positive button enable state editText.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun afterTextChanged(editable: Editable) { try { editText.text.toString().toInt() - dialog.getActionButton(WhichButton.POSITIVE).isEnabled = true + dialog.positiveButton.isEnabled = true } catch (e: Exception) { Timber.w(e) - dialog.getActionButton(WhichButton.POSITIVE).isEnabled = false + dialog.positiveButton.isEnabled = false } } }) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.kt index c8b7b79ad34c..c9e295d3384e 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.kt @@ -15,26 +15,22 @@ */ package com.ichi2.anki.dialogs +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.testing.FragmentScenario import androidx.lifecycle.Lifecycle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.WhichButton -import com.afollestad.materialdialogs.actions.getActionButton -import com.ichi2.anki.R import com.ichi2.anki.RobolectricTest import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory +import com.ichi2.anki.dialogs.utils.performPositiveClick import com.ichi2.libanki.Collection import com.ichi2.libanki.sched.Scheduler import com.ichi2.testutils.ParametersUtils import com.ichi2.testutils.isJsonEqual -import com.ichi2.testutils.items import com.ichi2.utils.KotlinCleanup import org.hamcrest.CoreMatchers.notNullValue import org.hamcrest.MatcherAssert -import org.hamcrest.Matchers import org.hamcrest.core.IsNull import org.json.JSONObject import org.junit.After @@ -44,6 +40,7 @@ import org.mockito.Mockito import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import org.robolectric.annotation.Config +import kotlin.test.assertNotNull @RunWith(AndroidJUnit4::class) class CustomStudyDialogTest : RobolectricTest() { @@ -66,12 +63,11 @@ class CustomStudyDialogTest : RobolectricTest() { .withArguments(CustomStudyDialog.ContextMenuOption.STUDY_AHEAD, 1) .arguments val factory = CustomStudyDialogFactory({ this.col }, mockListener) - val scenario = FragmentScenario.launch(CustomStudyDialog::class.java, args, factory) - scenario.moveToState(Lifecycle.State.STARTED) + val scenario = FragmentScenario.launch(CustomStudyDialog::class.java, args, androidx.appcompat.R.style.Theme_AppCompat, factory) + scenario.moveToState(Lifecycle.State.RESUMED) scenario.onFragment { f: CustomStudyDialog -> - val dialog = f.dialog as MaterialDialog? - MatcherAssert.assertThat(dialog, IsNull.notNullValue()) - dialog!!.getActionButton(WhichButton.POSITIVE).callOnClick() + val dialog = assertNotNull(f.dialog as AlertDialog?) + dialog.performPositiveClick() } val customStudy = col.decks.current() MatcherAssert.assertThat("Custom Study should be dynamic", customStudy.isFiltered) @@ -120,9 +116,8 @@ class CustomStudyDialogTest : RobolectricTest() { val scenario = FragmentScenario.launch(CustomStudyDialog::class.java, args, androidx.appcompat.R.style.Theme_AppCompat, factory) scenario.moveToState(Lifecycle.State.STARTED) scenario.onFragment { f: CustomStudyDialog -> - val dialog = f.dialog as MaterialDialog? + val dialog = f.dialog as AlertDialog? MatcherAssert.assertThat(dialog, IsNull.notNullValue()) - MatcherAssert.assertThat(dialog!!.items, Matchers.not(Matchers.hasItem(getResourceString(R.string.custom_study_increase_new_limit)))) } } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/AlertDialogUtils.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/AlertDialogUtils.kt index 1e8178eb40bd..894b7b5f102f 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/AlertDialogUtils.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/AlertDialogUtils.kt @@ -16,9 +16,15 @@ package com.ichi2.anki.dialogs.utils +import android.content.DialogInterface import android.widget.TextView import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import androidx.test.platform.app.InstrumentationRegistry +import com.ichi2.utils.HandlerUtils.executeFunctionUsingHandler import com.ichi2.utils.getInputField +import org.hamcrest.MatcherAssert.* +import kotlin.test.assertNotNull var AlertDialog.input get() = getInputField().text.toString() @@ -28,3 +34,12 @@ val AlertDialog.title get() = requireNotNull(this.findViewById(androidx.appcompat.R.id.alertTitle)) { "androidx.appcompat.R.id.alertTitle not found" }.text.toString() + +fun AlertDialog.performPositiveClick() { + // This exists as callOnClick did not call the listener + val positiveButton = assertNotNull(getButton(DialogInterface.BUTTON_POSITIVE), message = "positive button") + assertThat("button is visible", positiveButton.isVisible) + assertThat("button is enalbed", positiveButton.isEnabled) + executeFunctionUsingHandler { positiveButton.callOnClick() } + InstrumentationRegistry.getInstrumentation().waitForIdleSync() +}