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 434728de990c..2c9a727d8973 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 @@ -56,6 +56,7 @@ import com.ichi2.libanki.Consts.DynPriority import com.ichi2.libanki.Deck import com.ichi2.libanki.DeckId import com.ichi2.utils.BundleUtils.getNullableInt +import com.ichi2.utils.KotlinCleanup import com.ichi2.utils.cancelable import com.ichi2.utils.customView import com.ichi2.utils.listItems @@ -69,22 +70,59 @@ import timber.log.Timber import java.util.Locale /** - * Key for the ordinal of the [ContextMenuOption] to display. May be not set. + * Implements custom studying either by: + * 1. modifying the limits of the current selected deck for when the user has reached the daily deck limits and wishes to study more + * 2. creating a filtered "Custom study deck" where a user can study outside the typical schedule + * + * + * ## UI + * [CustomStudyDialog] represents either: + * * A [main menu][buildContextMenu], offering [methods of custom study][ContextMenuOption]: + * * An [input dialog][buildInputDialog] to input additional constraints for a [ContextMenuOption] + * * Example: changing the number of new cards + * + * #### Not Implemented + * Anki Desktop contains the following items which are not yet implemented + * * Study by card state or tags + * * New cards only + * * Due cards only + * * All review cards in random order + * * All cards in random order (don't reschedule) + * + * ## Nomenclature + * Filtered decks were previously known as 'dynamic' decks, and before that: 'cram' decks + * + * ## Links + * * [https://docs.ankiweb.net/filtered-decks.html#custom-study](https://docs.ankiweb.net/filtered-decks.html#custom-study) + * * [com.ichi2.libanki.sched.Scheduler.customStudyDefaults] + * * [sched.proto: CustomStudyDefaultsResponse](https://github.com/search?q=repo%3Aankitects%2Fanki+CustomStudyDefaultsResponse+language%3A%22Protocol+Buffer%22&type=code&l=Protocol+Buffer) + * * [com.ichi2.libanki.sched.Scheduler.customStudy] + * * [sched.proto: CustomStudyRequest](https://github.com/search?q=repo%3Aankitects%2Fanki+CustomStudyRequest+language%3A%22Protocol+Buffer%22&type=code&l=Protocol+Buffer) + * * [https://github.com/ankitects/anki/blob/main/qt/aqt/customstudy.py](https://github.com/ankitects/anki/blob/main/qt/aqt/customstudy.py) */ -private const val ID = "id" - -/** - * Key for the id of the deck this dialog deals with - */ -private const val DID = "did" - -class CustomStudyDialog(private val collection: Collection, private val customStudyListener: CustomStudyListener?) : AnalyticsDialogFragment(), TagsDialogListener { +@KotlinCleanup("remove 'collection' parameter and use withCol { }") +@KotlinCleanup("remove 'customStudyListener' parameter and use FragmentResult") +class CustomStudyDialog( + private val collection: Collection, + private val customStudyListener: CustomStudyListener? +) : AnalyticsDialogFragment(), TagsDialogListener { interface CustomStudyListener { fun onCreateCustomStudySession() fun onExtendStudyLimits() } + /** ID of the [Deck] which this dialog was created for */ + private val dialogDeckId: DeckId + get() = requireArguments().getLong(ARG_DID) + + /** + * `null` initially when the main view is shown + * otherwise, the [ContextMenuOption] representing the current sub-dialog + */ + private val selectedSubDialog: ContextMenuOption? + get() = requireArguments().getNullableInt(ARG_SUB_DIALOG_ID)?.let { ContextMenuOption.entries[it] } + fun withArguments( did: DeckId, contextMenuAttribute: ContextMenuOption? = null @@ -92,9 +130,9 @@ class CustomStudyDialog(private val collection: Collection, private val customSt val args = this.arguments ?: Bundle() args.apply { if (contextMenuAttribute != null) { - putInt(ID, contextMenuAttribute.ordinal) + putInt(ARG_SUB_DIALOG_ID, contextMenuAttribute.ordinal) } - putLong(DID, did) + putLong(ARG_DID, did) } this.arguments = args return this @@ -107,11 +145,11 @@ class CustomStudyDialog(private val collection: Collection, private val customSt override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { super.onCreate(savedInstanceState) - val option = getOption() + val option = selectedSubDialog return if (option == null) { Timber.i("Showing Custom Study main menu") // Select the specified deck - collection.decks.select(requireArguments().getLong(DID)) + collection.decks.select(dialogDeckId) buildContextMenu() } else { Timber.i("Showing Custom Study dialog: $option") @@ -134,13 +172,11 @@ class CustomStudyDialog(private val collection: Collection, private val customSt * number, it is necessary to collect a list of tags. This case handles the creation * of that Dialog. */ - val currentDeck = requireArguments().getLong(DID) - val dialogFragment = TagsDialog().withArguments( context = requireContext(), type = TagsDialog.DialogType.CUSTOM_STUDY_TAGS, checkedTags = ArrayList(), - allTags = ArrayList(collection.tags.byDeck(currentDeck)) + allTags = ArrayList(collection.tags.byDeck(dialogDeckId)) ) requireActivity().showDialogFragment(dialogFragment) } @@ -152,10 +188,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt STUDY_PREVIEW -> { // User asked for a standard custom study option val d = CustomStudyDialog(collection, customStudyListener) - .withArguments( - requireArguments().getLong(DID), - listIds[index] - ) + .withArguments(dialogDeckId, listIds[index]) requireActivity().showDialogFragment(d) } } @@ -188,8 +221,6 @@ class CustomStudyDialog(private val collection: Collection, private val customSt if (contextMenuOption == STUDY_NEW || contextMenuOption == STUDY_REV) { editText.inputType = EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_SIGNED } - // deck id - val did = requireArguments().getLong(DID) // Set material dialog parameters val dialog = AlertDialog.Builder(requireActivity()) .customView(view = v, paddingLeft = 64, paddingRight = 64, paddingTop = 32, paddingBottom = 32) @@ -205,7 +236,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt when (contextMenuOption) { STUDY_NEW -> { requireActivity().sharedPrefs().edit { putInt("extendNew", n) } - val deck = collection.decks.get(did)!! + val deck = collection.decks.get(dialogDeckId)!! deck.put("extendNew", n) collection.decks.save(deck) collection.sched.extendLimits(n, 0) @@ -213,7 +244,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt } STUDY_REV -> { requireActivity().sharedPrefs().edit { putInt("extendRev", n) } - val deck = collection.decks.get(did)!! + val deck = collection.decks.get(dialogDeckId)!! deck.put("extendRev", n) collection.decks.save(deck) collection.sched.extendLimits(0, n) @@ -331,15 +362,10 @@ class CustomStudyDialog(private val collection: Collection, private val customSt } } - /** - * The ContextMenuOption saved in [ID]. It may be unset for the standard study dialog. - */ - private fun getOption() = requireArguments().getNullableInt(ID)?. let { ContextMenuOption.entries[it] } - private val text1: String get() { val res = resources - return when (getOption()) { + return when (selectedSubDialog) { STUDY_NEW -> res.getString(R.string.custom_study_new_total_new, collection.sched.totalNewForCurrentDeck()) STUDY_REV -> res.getString(R.string.custom_study_rev_total_rev, collection.sched.totalRevForCurrentDeck()) STUDY_FORGOT, @@ -353,7 +379,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt private val text2: String get() { val res = resources - return when (getOption()) { + return when (selectedSubDialog) { STUDY_NEW -> res.getString(R.string.custom_study_new_extend) STUDY_REV -> res.getString(R.string.custom_study_rev_extend) STUDY_FORGOT -> res.getString(R.string.custom_study_forgotten) @@ -367,7 +393,7 @@ class CustomStudyDialog(private val collection: Collection, private val customSt private val defaultValue: String get() { val prefs = requireActivity().sharedPrefs() - return when (getOption()) { + return when (selectedSubDialog) { STUDY_NEW -> prefs.getInt("extendNew", 10).toString() STUDY_REV -> prefs.getInt("extendRev", 50).toString() STUDY_FORGOT -> prefs.getInt("forgottenDays", 1).toString() @@ -387,10 +413,9 @@ class CustomStudyDialog(private val collection: Collection, private val customSt */ private fun createCustomStudySession(delays: JSONArray, terms: Array, resched: Boolean) { val dyn: Deck - val did = requireArguments().getLong(DID) val decks = collection.decks - val deckToStudyName = decks.name(did) + val deckToStudyName = decks.name(dialogDeckId) val customStudyDeck = resources.getString(R.string.custom_study_deck_name) val cur = decks.byName(customStudyDeck) if (cur != null) { @@ -473,4 +498,18 @@ class CustomStudyDialog(private val collection: Collection, private val customSt STUDY_PREVIEW(R.string.custom_study_preview_new), STUDY_TAGS(R.string.custom_study_limit_tags) } + + companion object { + /** + * (required) Key for the [DeckId] this dialog deals with. + * @see CustomStudyDialog.dialogDeckId + */ + private const val ARG_DID = "did" + + /** + * (optional) Key for the ordinal of the [ContextMenuOption] to display. + * @see CustomStudyDialog.selectedSubDialog + */ + private const val ARG_SUB_DIALOG_ID = "subDialogId" + } }