diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 3f0166691147..9658362689e4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -89,6 +89,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 @@ -833,9 +834,15 @@ open class CardTemplateEditor : val ordinal = templateEditor.ord 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") @@ -971,16 +978,26 @@ open class CardTemplateEditor : Timber.w("addCardTemplate attempted on cloze note type") return } - // Show confirmation dialog - // 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!!, templateEditor.ord)) { - templateEditor.getColUnsafe.notetypes.tmplUseCount(templateEditor.tempNoteType!!.notetype, templateEditor.ord) - } 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 { @@ -1360,7 +1377,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) } @@ -1376,6 +1395,7 @@ open class CardTemplateEditor : private fun confirmAddCards( notetype: NotetypeJson, numAffectedCards: Int, + enteredName: String, ) { val d = ConfirmationDialog() val msg = @@ -1388,7 +1408,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) @@ -1462,12 +1482,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..51bde9a54f83 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,8 +33,18 @@ import com.ichi2.utils.title */ class ConfirmationDialog : DialogFragment() { private var confirm = 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..d6b7bf78dbf4 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddCardTypeDialog.kt @@ -0,0 +1,72 @@ +/* + * 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 +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 352f6c646d9a..71f25eff1bce 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?