From 4f564ff7b647d6c7d8865d9cac4710cdb1eea9d3 Mon Sep 17 00:00:00 2001 From: Nourhan Bakry Date: Sun, 15 Mar 2026 22:03:38 +0200 Subject: [PATCH 1/5] improvements on card type: rename: show red text when card type name used remove (:) from text field label add dialog with text field for card type name when adding new card type and with add button edit remove card title and button --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 52 ++++++++++++----- .../ichi2/anki/dialogs/ConfirmationDialog.kt | 15 ++++- .../ichi2/anki/notetype/AddCardTypeDialog.kt | 58 +++++++++++++++++++ .../anki/notetype/RenameCardTemplateDialog.kt | 23 +++++++- .../com/ichi2/anki/utils/ext/Translations.kt | 3 + AnkiDroid/src/main/res/values/01-core.xml | 4 ++ 6 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 4d5c013d6d2f..0412ff9f5f39 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -87,6 +87,7 @@ import com.ichi2.anki.libanki.getStockNotetype import com.ichi2.anki.libanki.getStockNotetypeKinds import com.ichi2.anki.libanki.utils.append import com.ichi2.anki.model.SelectableDeck +import com.ichi2.anki.notetype.AddCardTypeDialog import com.ichi2.anki.notetype.RenameCardTemplateDialog import com.ichi2.anki.notetype.RepositionCardTemplateDialog import com.ichi2.anki.observability.undoableOp @@ -775,9 +776,15 @@ open class CardTemplateEditor : val ordinal = templateEditor.mainBinding.cardTemplateEditorPager.currentItem val template = templateEditor.tempNoteType!!.getTemplate(ordinal) + val existingNames = + templateEditor.tempNoteType!! + .notetype.templates + .map { it.name } + RenameCardTemplateDialog.showInstance( requireContext(), prefill = template.name, + existingNames = existingNames, ) { newName -> template.name = newName Timber.i("updated card template name") @@ -915,17 +922,26 @@ open class CardTemplateEditor : Timber.w("addCardTemplate attempted on cloze note type") return } - // Show confirmation dialog - val ordinal = templateEditor.mainBinding.cardTemplateEditorPager.currentItem - // isOrdinalPendingAdd method will check if there are any new card types added or not, - // if TempModel has new card type then numAffectedCards will be 0 by default. - val numAffectedCards = - if (!CardTemplateNotetype.isOrdinalPendingAdd(templateEditor.tempNoteType!!, ordinal)) { - templateEditor.getColUnsafe.notetypes.tmplUseCount(templateEditor.tempNoteType!!.notetype, ordinal) - } else { - 0 - } - confirmAddCards(templateEditor.tempNoteType!!.notetype, numAffectedCards) + // Show new card type name dialog + val templates = templateEditor.tempNoteType!!.notetype.templates + val defaultName = newCardName(templates) + val existingNames = templates.map { it.name } + AddCardTypeDialog.showInstance(requireContext(), defaultName, existingNames) { enteredName -> + // Show confirmation dialog + val ordinal = templateEditor.mainBinding.cardTemplateEditorPager.currentItem + // isOrdinalPendingAdd method will check if there are any new card types added or not, + // if TempModel has new card type then numAffectedCards will be 0 by default. + val numAffectedCards = + if (!CardTemplateNotetype.isOrdinalPendingAdd(templateEditor.tempNoteType!!, ordinal)) { + templateEditor.getColUnsafe.notetypes.tmplUseCount(templateEditor.tempNoteType!!.notetype, ordinal) + } else { + 0 + } +// confirmAddCards(templateEditor.tempNoteType!!.notetype, numAffectedCards, enteredName) + executeWithSyncCheck( + Runnable { addNewTemplate(templateEditor.tempNoteType!!.notetype, enteredName) }, + ) + } } fun saveNoteType(): Boolean { @@ -1309,7 +1325,9 @@ open class CardTemplateEditor : numAffectedCards, tmpl.jsonObject.optString("name"), ) - d.setArgs(msg) + val title = resources.getString(R.string.delete_card_type_title) + d.setArgs(title, msg) + d.setPositiveButton(R.string.dialog_positive_delete) val deleteCard = Runnable { deleteTemplate(tmpl, notetype) } val confirm = Runnable { executeWithSyncCheck(deleteCard) } @@ -1325,6 +1343,7 @@ open class CardTemplateEditor : private fun confirmAddCards( notetype: NotetypeJson, numAffectedCards: Int, + enteredName: String, ) { val d = ConfirmationDialog() val msg = @@ -1337,7 +1356,7 @@ open class CardTemplateEditor : ) d.setArgs(msg) - val addCard = Runnable { addNewTemplate(notetype) } + val addCard = Runnable { addNewTemplate(notetype, enteredName) } val confirm = Runnable { executeWithSyncCheck(addCard) } d.setConfirm(confirm) templateEditor.showDialogFragment(d) @@ -1411,12 +1430,15 @@ open class CardTemplateEditor : * Add new template to a given note type * @param noteType note type to add new template to */ - private fun addNewTemplate(noteType: NotetypeJson) { + private fun addNewTemplate( + noteType: NotetypeJson, + name: String, + ) { // Build new template val oldCardIndex = requireArguments().getInt(CARD_INDEX) val templates = noteType.templates val oldTemplate = templates[oldCardIndex] - val newTemplate = Notetypes.newTemplate(newCardName(templates)) + val newTemplate = Notetypes.newTemplate(name) // Set up question & answer formats newTemplate.qfmt = oldTemplate.qfmt newTemplate.afmt = oldTemplate.afmt diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt index 0eeadb924181..e8d3d2f0a481 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt @@ -17,6 +17,7 @@ package com.ichi2.anki.dialogs import android.os.Bundle +import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import com.ichi2.anki.R @@ -32,7 +33,17 @@ import com.ichi2.utils.title */ class ConfirmationDialog : DialogFragment() { private var confirm = Runnable {} // Do nothing by default - private var cancel = Runnable {} // Do nothing by default + private var cancel = Runnable {} + + // Do nothing by default + @StringRes + private var positiveButtonTextRes: Int = R.string.dialog_ok + + fun setPositiveButton( + @StringRes resId: Int, + ) { + this.positiveButtonTextRes = resId + } fun setArgs(message: String?) { setArgs("", message) @@ -63,7 +74,7 @@ class ConfirmationDialog : DialogFragment() { return AlertDialog.Builder(requireContext()).create { title(text = (if ("" == title) res.getString(R.string.app_name) else title)!!) message(text = requireArguments().getString("message")!!) - positiveButton(R.string.dialog_ok) { + positiveButton(positiveButtonTextRes) { confirm.run() } negativeButton(R.string.dialog_cancel) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt new file mode 100644 index 000000000000..a3942c2f6747 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt @@ -0,0 +1,58 @@ + +package com.ichi2.anki.notetype + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import androidx.core.widget.doOnTextChanged +import com.ichi2.anki.R +import com.ichi2.utils.getInputField +import com.ichi2.utils.getInputTextLayout +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 + +class AddCardTypeDialog { + companion object { + fun showInstance( + context: Context, + prefill: String, + existingNames: List, + block: (newName: String) -> Unit, + ) { + AlertDialog + .Builder(context) + .show { + title(R.string.add_card_type) + positiveButton(R.string.menu_add) { } + negativeButton(R.string.dialog_cancel) + setView(R.layout.dialog_generic_text_input) + }.input( + hint = context.getString(R.string.card_type_name), + displayKeyboard = true, + allowEmpty = false, + prefill = prefill, + waitForPositiveButton = true, + callback = { dialog, result -> + block(result.toString()) + dialog.dismiss() + }, + ).apply { + val field = getInputField() + val layout = getInputTextLayout() + field.doOnTextChanged { text, _, _, _ -> + val name = text.toString().trim() + val isDuplicate = existingNames.any { it.equals(name, ignoreCase = true) } + if (isDuplicate) { + layout.error = context.getString(R.string.card_type_name_used) + positiveButton.isEnabled = false + } else { + layout.error = null + positiveButton.isEnabled = name.isNotEmpty() + } + } + } + } + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTemplateDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTemplateDialog.kt index 386507cf6243..50f6252b401b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTemplateDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTemplateDialog.kt @@ -18,8 +18,12 @@ package com.ichi2.anki.notetype import android.content.Context import androidx.appcompat.app.AlertDialog +import androidx.core.widget.doOnTextChanged import com.ichi2.anki.CollectionManager import com.ichi2.anki.R +import com.ichi2.anki.utils.ext.actionsNewNameClean +import com.ichi2.utils.getInputField +import com.ichi2.utils.getInputTextLayout import com.ichi2.utils.input import com.ichi2.utils.negativeButton import com.ichi2.utils.positiveButton @@ -31,6 +35,7 @@ class RenameCardTemplateDialog { fun showInstance( context: Context, prefill: String, + existingNames: List, block: (result: String) -> Unit, ) { AlertDialog @@ -41,7 +46,7 @@ class RenameCardTemplateDialog { negativeButton(R.string.dialog_cancel) setView(R.layout.dialog_generic_text_input) }.input( - hint = CollectionManager.TR.actionsNewName(), + hint = CollectionManager.TR.actionsNewNameClean(), displayKeyboard = true, allowEmpty = false, prefill = prefill, @@ -50,7 +55,21 @@ class RenameCardTemplateDialog { block(result.toString()) dialog.dismiss() }, - ) + ).apply { + val field = getInputField() + val layout = getInputTextLayout() + field.doOnTextChanged { text, _, _, _ -> + val name = text.toString().trim() + val isDuplicate = name != prefill && existingNames.any { it.equals(name, ignoreCase = true) } + if (isDuplicate) { + layout.error = context.getString(R.string.card_type_name_used) + positiveButton.isEnabled = false + } else { + layout.error = null + positiveButton.isEnabled = name.isNotEmpty() + } + } + } } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Translations.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Translations.kt index afe94930036b..38994c8b536b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Translations.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Translations.kt @@ -28,6 +28,9 @@ fun Translations.cardStatsNoCardClean(): String { return cardStatsNoCard().replace("[().]".toRegex(), "") } +/** New name (Removes colon for use as a label) */ +fun Translations.actionsNewNameClean(): String = actionsNewName().removeSuffix(":") + /** Previous Card (Study) */ fun Translations.previousCardStudy() = cardStatsPreviousCard(decksStudy()) diff --git a/AnkiDroid/src/main/res/values/01-core.xml b/AnkiDroid/src/main/res/values/01-core.xml index 30d895e39c5e..e7365aeafeb3 100644 --- a/AnkiDroid/src/main/res/values/01-core.xml +++ b/AnkiDroid/src/main/res/values/01-core.xml @@ -131,6 +131,7 @@ Card types + Delete card type Front template Back template Styling @@ -139,6 +140,9 @@ Insert field Select field At least one card type is required + Add card type + Card type name + Card type name already used This will create %1$d card. Proceed? This will create %1$d cards. Proceed? From 1dd68860a961bfcac4f22557d9201489cb263d56 Mon Sep 17 00:00:00 2001 From: Nourhan Bakry Date: Sun, 15 Mar 2026 23:12:33 +0200 Subject: [PATCH 2/5] fix kotlin lint --- .../src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt index e8d3d2f0a481..3282194c9e66 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt @@ -35,7 +35,7 @@ class ConfirmationDialog : DialogFragment() { private var confirm = Runnable {} // Do nothing by default private var cancel = Runnable {} - // Do nothing by default + // Do nothing by default @StringRes private var positiveButtonTextRes: Int = R.string.dialog_ok From 8494e2bd4e3688f82bc9ee58a10c64676b78c2d8 Mon Sep 17 00:00:00 2001 From: Nourhan Bakry Date: Sun, 15 Mar 2026 23:43:23 +0200 Subject: [PATCH 3/5] fix ktlint formatting From 0e773c7fc032f11c7d85c25c9e28030215991c27 Mon Sep 17 00:00:00 2001 From: Nourhan Bakry Date: Mon, 16 Mar 2026 00:04:55 +0200 Subject: [PATCH 4/5] fix ktlint spacing --- .../main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt index 3282194c9e66..51bde9a54f83 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt @@ -33,9 +33,9 @@ import com.ichi2.utils.title */ class ConfirmationDialog : DialogFragment() { private var confirm = Runnable {} // Do nothing by default - private var cancel = Runnable {} - // Do nothing by default + private var cancel = Runnable {} // Do nothing by default + @StringRes private var positiveButtonTextRes: Int = R.string.dialog_ok From 6f72e443f0640d58bac700c6c9b0e2bcc53a7035 Mon Sep 17 00:00:00 2001 From: Nourhan Bakry Date: Mon, 16 Mar 2026 01:04:01 +0200 Subject: [PATCH 5/5] fix kotlin lint --- .../com/ichi2/anki/notetype/AddCardTypeDialog.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt index a3942c2f6747..d6b7bf78dbf4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt @@ -1,4 +1,18 @@ - +/* + * Copyright (c) 2015 Timothy Rae + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ package com.ichi2.anki.notetype import android.content.Context