From ee969367d5079225ab12cbbcbb6ca3cebc3c102c Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sat, 28 Mar 2026 08:32:22 -0700 Subject: [PATCH 01/10] Review ahead dialog: - Add a title "Review ahead". - Change the description with "Review cards due in the next:". - Added a unit "day"/"days" to the box. - Change "OK" with "Create". - Show a warning when the input is invalid, limit the digits, and prevent leading zeros. --- .../dialogs/customstudy/CustomStudyDialog.kt | 93 +++++++++++++++++++ .../main/res/layout/fragment_custom_study.xml | 6 +- AnkiDroid/src/main/res/values/03-dialogs.xml | 5 +- 3 files changed, 101 insertions(+), 3 deletions(-) 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 5c317d7efd07..a6909d927834 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 @@ -22,6 +22,8 @@ import android.app.Dialog import android.content.res.Resources import android.os.Bundle import android.os.Parcelable +import android.text.InputFilter +import android.text.Spanned import android.util.TypedValue import android.view.WindowManager import android.view.inputmethod.EditorInfo @@ -88,6 +90,7 @@ import com.ichi2.utils.setPaddingRelative import com.ichi2.utils.textAsIntOrNull import com.ichi2.utils.title import kotlinx.coroutines.Deferred +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.parcelize.Parcelize import net.ankiweb.rsdroid.BackendException @@ -335,10 +338,22 @@ class CustomStudyDialog : AnalyticsDialogFragment() { if (contextMenuOption == EXTEND_NEW || contextMenuOption == EXTEND_REV) { inputType = EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_SIGNED } + if (contextMenuOption == STUDY_AHEAD) { + filters = arrayOf(InputFilter.LengthFilter(9), NoLeadingZeroFilter()) + val initialValue = defaultValue.toIntOrNull() ?: 1 + binding.detailsEditText2Layout.suffixText = + resources.getQuantityString( + R.plurals.set_due_date_label_suffix, + initialValue, + initialValue, + ) + } } val positiveBtnLabel = if (contextMenuOption == STUDY_TAGS) { TR.customStudyChooseTags().toSentenceCase(R.string.sentence_choose_tags) + } else if (contextMenuOption == STUDY_AHEAD) { + getString(R.string.dialog_positive_create) } else { getString(R.string.dialog_ok) } @@ -423,6 +438,44 @@ class CustomStudyDialog : AnalyticsDialogFragment() { binding.detailsEditText2.doAfterTextChanged { dialog.positiveButton.isEnabled = userInputValue != null && userInputValue != 0 + val value = it?.toString()?.toIntOrNull() + + if (contextMenuOption == STUDY_AHEAD) { + if (userInputValue == null) { + dialog.positiveButton.isEnabled = false + binding.detailsEditText2Layout.error = getString(R.string.custom_study_ahead_Invalid_number) + return@doAfterTextChanged + } + if (userInputValue == 0) { + binding.detailsEditText2Layout.error = getString(R.string.custom_study_ahead_prevent_leading_zeros) + dialog.positiveButton.isEnabled = false + return@doAfterTextChanged + } + + val safeValue = value ?: return@doAfterTextChanged + + binding.detailsEditText2Layout.suffixText = + resources.getQuantityString( + R.plurals.set_due_date_label_suffix, + safeValue, + safeValue, + ) + + val currentInput = userInputValue + lifecycleScope.launch { + val hasCards = hasMatchingCards(contextMenuOption, userInputValue) + + if (currentInput != userInputValue) return@launch + + if (hasCards) { + binding.detailsEditText2Layout.error = null + dialog.positiveButton.isEnabled = true + } else { + binding.detailsEditText2Layout.error = "No cards matched the criteria you provided" + dialog.positiveButton.isEnabled = false + } + } + } } // Show soft keyboard @@ -430,6 +483,26 @@ class CustomStudyDialog : AnalyticsDialogFragment() { return dialog } + @SuppressLint("CheckResult") + private suspend fun hasMatchingCards( + option: ContextMenuOption, + input: Int?, + ): Boolean = + try { + withCol { + val currentDeckName = decks.name(viewModel.deckId) + val query = + when (option) { + STUDY_AHEAD -> "deck:\"$currentDeckName\" prop:due<=$input" + else -> "deck:\"$currentDeckName\"" + } + findCards(query).isNotEmpty() + } + } catch (e: Exception) { + Timber.e(e) + true + } + // TODO cram kind and the included/excluded tags lists are only relevant for STUDY_TAGS and // should be included in the option to not leak in the method's api private suspend fun customStudy( @@ -510,6 +583,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() { EXTEND_REV -> deferredDefaults.getCompleted().labelForReviewQueueAvailable() STUDY_FORGOT, STUDY_AHEAD, + -> resources.getString(R.string.custom_study_ahead_title) STUDY_PREVIEW, STUDY_TAGS, null, @@ -560,6 +634,25 @@ class CustomStudyDialog : AnalyticsDialogFragment() { } } + class NoLeadingZeroFilter : InputFilter { + override fun filter( + source: CharSequence?, + start: Int, + end: Int, + dest: Spanned?, + dstart: Int, + dend: Int, + ): CharSequence? { + val newText = dest?.replaceRange(dstart, dend, source?.subSequence(start, end) ?: "") + + return if (newText != null && newText.length > 1 && newText.startsWith("0")) { + "" + } else { + null + } + } + } + /** * Represents actions for managing custom study sessions and extending study limits. * These actions are passed between fragments and activities via the FragmentResult API. diff --git a/AnkiDroid/src/main/res/layout/fragment_custom_study.xml b/AnkiDroid/src/main/res/layout/fragment_custom_study.xml index 679d1d78a8a2..fcd74bff2431 100644 --- a/AnkiDroid/src/main/res/layout/fragment_custom_study.xml +++ b/AnkiDroid/src/main/res/layout/fragment_custom_study.xml @@ -55,13 +55,15 @@ + android:layout_height="wrap_content" + app:errorEnabled="true" + app:suffixTextAppearance="@style/TextAppearance.MaterialComponents.Body1" + > diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index 33a12e1a3bd9..55ecc69a88a7 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -47,7 +47,10 @@ Increase/decrease (“-3”) today’s new card limit by Increase/decrease (“-3”) today’s review limit by Review cards forgotten in last x days: - Review ahead by x days: + Review cards due in the next: + Review ahead + Invalid number + Value must be greater than 0 Preview new cards added in the last x days: Select x cards from the deck: From 45dc020942d6649c2da138ba638430aa1915f970 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sat, 28 Mar 2026 08:42:36 -0700 Subject: [PATCH 02/10] Update strings --- .../com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt | 2 +- AnkiDroid/src/main/res/values/02-strings.xml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 a6909d927834..b22ca3c39e12 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 @@ -471,7 +471,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() { binding.detailsEditText2Layout.error = null dialog.positiveButton.isEnabled = true } else { - binding.detailsEditText2Layout.error = "No cards matched the criteria you provided" + binding.detailsEditText2Layout.error = getString(R.string.no_cards_matched_the_criteria_you_provided) dialog.positiveButton.isEnabled = false } } diff --git a/AnkiDroid/src/main/res/values/02-strings.xml b/AnkiDroid/src/main/res/values/02-strings.xml index 33edc4fa9bda..826869bdf000 100644 --- a/AnkiDroid/src/main/res/values/02-strings.xml +++ b/AnkiDroid/src/main/res/values/02-strings.xml @@ -458,5 +458,7 @@ opening the system text to speech settings fails">Failed to open text to speech Name already exists + + No cards matched the criteria you provided From 3e40dfdcc44072ca0c7e3eb8aa1dde3051ba00f8 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sat, 28 Mar 2026 10:42:18 -0700 Subject: [PATCH 03/10] Add Review ahead in test Translation --- AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt index 04d025118344..1437942b2f44 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt @@ -282,6 +282,7 @@ class TranslationTest : RobolectricTest() { "Reposition", // R.string.card_editor_reposition_card, R.string.card_template_reposition_template // TR.actionsReposition() "Reschedule", // R.string.card_editor_reschedule_card | TR.browsingReschedule() + "Review ahead", // R.string.custom_study_ahead_title | TR.customStudyReviewAhead() "Reviews", // R.string.pref_controls_reviews_tab // TR.schedulingReviews() // TR.cardStatsReviewCount() From 3d8c6f5b034eb14751f9da53342a998b688b6e91 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sat, 28 Mar 2026 10:49:54 -0700 Subject: [PATCH 04/10] fix: replace duplicate string with TR translation for review ahead title. --- .../com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt | 2 +- AnkiDroid/src/main/res/values/03-dialogs.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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 b22ca3c39e12..cd21a69f1c3c 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 @@ -583,7 +583,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() { EXTEND_REV -> deferredDefaults.getCompleted().labelForReviewQueueAvailable() STUDY_FORGOT, STUDY_AHEAD, - -> resources.getString(R.string.custom_study_ahead_title) + -> TR.customStudyReviewAhead() STUDY_PREVIEW, STUDY_TAGS, null, diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index 55ecc69a88a7..6af293660b30 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -48,7 +48,6 @@ Increase/decrease (“-3”) today’s review limit by Review cards forgotten in last x days: Review cards due in the next: - Review ahead Invalid number Value must be greater than 0 Preview new cards added in the last x days: From bcf5006b53cdf73578ec41c5da694b56283eda7f Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sat, 28 Mar 2026 11:25:36 -0700 Subject: [PATCH 05/10] fix: remove stale baseline entry after migrating to TR.customStudyReviewAhead() --- AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt index 1437942b2f44..04d025118344 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/TranslationTest.kt @@ -282,7 +282,6 @@ class TranslationTest : RobolectricTest() { "Reposition", // R.string.card_editor_reposition_card, R.string.card_template_reposition_template // TR.actionsReposition() "Reschedule", // R.string.card_editor_reschedule_card | TR.browsingReschedule() - "Review ahead", // R.string.custom_study_ahead_title | TR.customStudyReviewAhead() "Reviews", // R.string.pref_controls_reviews_tab // TR.schedulingReviews() // TR.cardStatsReviewCount() From f878b386d65076a50eb31cfb2530eebcff503865 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sat, 28 Mar 2026 18:35:16 -0700 Subject: [PATCH 06/10] Improve title for review ahead dialog(size and place). --- .../com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt | 4 ++-- AnkiDroid/src/main/res/layout/fragment_custom_study.xml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) 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 cd21a69f1c3c..bc5bfe440dac 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 @@ -334,6 +334,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() { // Give EditText focus and show keyboard setSelectAllOnFocus(true) requestFocus() + inputType = EditorInfo.TYPE_CLASS_NUMBER // a user may enter a negative value when extending limits if (contextMenuOption == EXTEND_NEW || contextMenuOption == EXTEND_REV) { inputType = EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_SIGNED @@ -581,9 +582,8 @@ class CustomStudyDialog : AnalyticsDialogFragment() { when (selectedSubDialog) { EXTEND_NEW -> deferredDefaults.getCompleted().labelForNewQueueAvailable() EXTEND_REV -> deferredDefaults.getCompleted().labelForReviewQueueAvailable() + STUDY_AHEAD -> TR.customStudyReviewAhead() STUDY_FORGOT, - STUDY_AHEAD, - -> TR.customStudyReviewAhead() STUDY_PREVIEW, STUDY_TAGS, null, diff --git a/AnkiDroid/src/main/res/layout/fragment_custom_study.xml b/AnkiDroid/src/main/res/layout/fragment_custom_study.xml index fcd74bff2431..407be4e4116b 100644 --- a/AnkiDroid/src/main/res/layout/fragment_custom_study.xml +++ b/AnkiDroid/src/main/res/layout/fragment_custom_study.xml @@ -37,7 +37,9 @@ android:layout_marginRight="4dip" android:gravity="start" android:text="" - android:textSize="16sp" /> + android:textSize="18sp" + android:textStyle="bold" + /> Date: Sun, 29 Mar 2026 04:31:42 -0700 Subject: [PATCH 07/10] fix: Improve strings and backend message. --- .../com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt | 4 ++-- AnkiDroid/src/main/res/values/02-strings.xml | 2 -- AnkiDroid/src/main/res/values/03-dialogs.xml | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) 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 bc5bfe440dac..c86654614dc0 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 @@ -444,7 +444,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() { if (contextMenuOption == STUDY_AHEAD) { if (userInputValue == null) { dialog.positiveButton.isEnabled = false - binding.detailsEditText2Layout.error = getString(R.string.custom_study_ahead_Invalid_number) + binding.detailsEditText2Layout.error = getString(R.string.custom_study_ahead_invalid_number) return@doAfterTextChanged } if (userInputValue == 0) { @@ -472,7 +472,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() { binding.detailsEditText2Layout.error = null dialog.positiveButton.isEnabled = true } else { - binding.detailsEditText2Layout.error = getString(R.string.no_cards_matched_the_criteria_you_provided) + binding.detailsEditText2Layout.error = TR.customStudyNoCardsMatchedTheCriteriaYou() dialog.positiveButton.isEnabled = false } } diff --git a/AnkiDroid/src/main/res/values/02-strings.xml b/AnkiDroid/src/main/res/values/02-strings.xml index 826869bdf000..33edc4fa9bda 100644 --- a/AnkiDroid/src/main/res/values/02-strings.xml +++ b/AnkiDroid/src/main/res/values/02-strings.xml @@ -458,7 +458,5 @@ opening the system text to speech settings fails">Failed to open text to speech Name already exists - - No cards matched the criteria you provided diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index 6af293660b30..bf33192b55be 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -48,7 +48,7 @@ Increase/decrease (“-3”) today’s review limit by Review cards forgotten in last x days: Review cards due in the next: - Invalid number + Invalid number Value must be greater than 0 Preview new cards added in the last x days: Select x cards from the deck: From 04b927c8c92055c597d4bcf4dd892b6d87015c73 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Mon, 30 Mar 2026 07:24:39 -0700 Subject: [PATCH 08/10] =?UTF-8?q?fix:=20I've=20updated=20the=20filter=20to?= =?UTF-8?q?=20allow=20full=20replacement=20(e.g.,=20"01"=20=E2=86=92=20"1"?= =?UTF-8?q?)=20while=20still=20preventing=20leading=20zeros=20in=20other?= =?UTF-8?q?=20cases.=20fix:=20Code=20Enhancement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dialogs/customstudy/CustomStudyDialog.kt | 101 ++++++++++++------ 1 file changed, 68 insertions(+), 33 deletions(-) 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 c86654614dc0..056f422765bf 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 @@ -22,6 +22,7 @@ import android.app.Dialog import android.content.res.Resources import android.os.Bundle import android.os.Parcelable +import android.text.Editable import android.text.InputFilter import android.text.Spanned import android.util.TypedValue @@ -441,47 +442,75 @@ class CustomStudyDialog : AnalyticsDialogFragment() { dialog.positiveButton.isEnabled = userInputValue != null && userInputValue != 0 val value = it?.toString()?.toIntOrNull() - if (contextMenuOption == STUDY_AHEAD) { - if (userInputValue == null) { - dialog.positiveButton.isEnabled = false - binding.detailsEditText2Layout.error = getString(R.string.custom_study_ahead_invalid_number) - return@doAfterTextChanged - } - if (userInputValue == 0) { - binding.detailsEditText2Layout.error = getString(R.string.custom_study_ahead_prevent_leading_zeros) - dialog.positiveButton.isEnabled = false - return@doAfterTextChanged - } + // Prevent invalid inputs like leading zeros (e.g. "01") by normalizing the value. + if (replaceZeroWithNextNumber(it)) return@doAfterTextChanged + // Delegate STUDY_AHEAD-specific validation, suffix handling (day/days), + // and async card matching check to a dedicated function for better separation of concerns. + studyAheadCase(contextMenuOption, dialog, value) + } - val safeValue = value ?: return@doAfterTextChanged + // Show soft keyboard + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + return dialog + } - binding.detailsEditText2Layout.suffixText = - resources.getQuantityString( - R.plurals.set_due_date_label_suffix, - safeValue, - safeValue, - ) + private fun studyAheadCase( + contextMenuOption: ContextMenuOption, + dialog: AlertDialog, + value: Int?, + ) { + if (contextMenuOption == STUDY_AHEAD) { + if (userInputValue == null) { + dialog.positiveButton.isEnabled = false + return + } + if (userInputValue == 0) { + binding.detailsEditText2Layout.error = + getString(R.string.custom_study_ahead_prevent_leading_zeros) + dialog.positiveButton.isEnabled = false + return + } - val currentInput = userInputValue - lifecycleScope.launch { - val hasCards = hasMatchingCards(contextMenuOption, userInputValue) + val safeValue = value ?: return - if (currentInput != userInputValue) return@launch + binding.detailsEditText2Layout.suffixText = + resources.getQuantityString( + R.plurals.set_due_date_label_suffix, + safeValue, + safeValue, + ) - if (hasCards) { - binding.detailsEditText2Layout.error = null - dialog.positiveButton.isEnabled = true - } else { - binding.detailsEditText2Layout.error = TR.customStudyNoCardsMatchedTheCriteriaYou() - dialog.positiveButton.isEnabled = false - } + val currentInput = userInputValue + lifecycleScope.launch { + val hasCards = hasMatchingCards(contextMenuOption, userInputValue) + + if (currentInput != userInputValue) return@launch + + if (hasCards) { + binding.detailsEditText2Layout.error = null + dialog.positiveButton.isEnabled = true + } else { + binding.detailsEditText2Layout.error = + TR.customStudyNoCardsMatchedTheCriteriaYou() + dialog.positiveButton.isEnabled = false } } } + } - // Show soft keyboard - dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) - return dialog + private fun replaceZeroWithNextNumber(editable: Editable?): Boolean { + val text = editable?.toString() ?: return true + + if (text.length > 1 && text.startsWith("0")) { + val newText = text.trimStart('0') + + val finalText = newText.ifEmpty { "0" } + + binding.detailsEditText2.setText(finalText) + binding.detailsEditText2.setSelection(finalText.length) + return true + } + return false } @SuppressLint("CheckResult") @@ -645,7 +674,13 @@ class CustomStudyDialog : AnalyticsDialogFragment() { ): CharSequence? { val newText = dest?.replaceRange(dstart, dend, source?.subSequence(start, end) ?: "") - return if (newText != null && newText.length > 1 && newText.startsWith("0")) { + if (dest?.toString() == "0") return null + + return if ( + newText != null && + newText.length > 1 && + newText.startsWith("0") + ) { "" } else { null From 4e895898114f02d9647267f9937912b25d7dd720 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Mon, 30 Mar 2026 07:34:29 -0700 Subject: [PATCH 09/10] fix: improve strings. --- AnkiDroid/src/main/res/values/03-dialogs.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml index bf33192b55be..848ad550d989 100644 --- a/AnkiDroid/src/main/res/values/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values/03-dialogs.xml @@ -48,7 +48,6 @@ Increase/decrease (“-3”) today’s review limit by Review cards forgotten in last x days: Review cards due in the next: - Invalid number Value must be greater than 0 Preview new cards added in the last x days: Select x cards from the deck: From 34671ee4def1696fa12fb0f998c61ffee3ce9eb1 Mon Sep 17 00:00:00 2001 From: Galal-20 Date: Sun, 5 Apr 2026 13:33:27 -0700 Subject: [PATCH 10/10] fix: - Code refactored to use early return for better readability. - Improve the comments by add a comment for documentation and remove unnecessary comments. - Update limit prevent excessively long numeric input. --- .../dialogs/customstudy/CustomStudyDialog.kt | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) 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 056f422765bf..3a0351f27a57 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 @@ -341,7 +341,8 @@ class CustomStudyDialog : AnalyticsDialogFragment() { inputType = EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_SIGNED } if (contextMenuOption == STUDY_AHEAD) { - filters = arrayOf(InputFilter.LengthFilter(9), NoLeadingZeroFilter()) + // UI safeguard: prevent excessively long numeric input + filters = arrayOf(InputFilter.LengthFilter(5), NoLeadingZeroFilter()) val initialValue = defaultValue.toIntOrNull() ?: 1 binding.detailsEditText2Layout.suffixText = resources.getQuantityString( @@ -444,8 +445,6 @@ class CustomStudyDialog : AnalyticsDialogFragment() { // Prevent invalid inputs like leading zeros (e.g. "01") by normalizing the value. if (replaceZeroWithNextNumber(it)) return@doAfterTextChanged - // Delegate STUDY_AHEAD-specific validation, suffix handling (day/days), - // and async card matching check to a dedicated function for better separation of concerns. studyAheadCase(contextMenuOption, dialog, value) } @@ -459,41 +458,43 @@ class CustomStudyDialog : AnalyticsDialogFragment() { dialog: AlertDialog, value: Int?, ) { - if (contextMenuOption == STUDY_AHEAD) { - if (userInputValue == null) { - dialog.positiveButton.isEnabled = false - return - } - if (userInputValue == 0) { - binding.detailsEditText2Layout.error = - getString(R.string.custom_study_ahead_prevent_leading_zeros) - dialog.positiveButton.isEnabled = false - return - } + if (contextMenuOption != STUDY_AHEAD) return - val safeValue = value ?: return + if (userInputValue == null) { + binding.detailsEditText2Layout.error = null + dialog.positiveButton.isEnabled = false + return + } - binding.detailsEditText2Layout.suffixText = - resources.getQuantityString( - R.plurals.set_due_date_label_suffix, - safeValue, - safeValue, - ) + if (userInputValue == 0) { + binding.detailsEditText2Layout.error = + getString(R.string.custom_study_ahead_prevent_leading_zeros) + dialog.positiveButton.isEnabled = false + return + } - val currentInput = userInputValue - lifecycleScope.launch { - val hasCards = hasMatchingCards(contextMenuOption, userInputValue) + val safeValue = value ?: return - if (currentInput != userInputValue) return@launch + binding.detailsEditText2Layout.suffixText = + resources.getQuantityString( + R.plurals.set_due_date_label_suffix, + safeValue, + safeValue, + ) - if (hasCards) { - binding.detailsEditText2Layout.error = null - dialog.positiveButton.isEnabled = true - } else { - binding.detailsEditText2Layout.error = - TR.customStudyNoCardsMatchedTheCriteriaYou() - dialog.positiveButton.isEnabled = false - } + val currentInput = userInputValue + lifecycleScope.launch { + val hasCards = hasMatchingCards(contextMenuOption, userInputValue) + + if (currentInput != userInputValue) return@launch + + if (hasCards) { + binding.detailsEditText2Layout.error = null + dialog.positiveButton.isEnabled = true + } else { + binding.detailsEditText2Layout.error = + TR.customStudyNoCardsMatchedTheCriteriaYou() + dialog.positiveButton.isEnabled = false } } }