From 41d7402375c103973a94e3823dcfd4325a7e5a39 Mon Sep 17 00:00:00 2001 From: SanjaySargam Date: Thu, 11 Jul 2024 23:01:40 +0530 Subject: [PATCH] card-template-editor: keyboard shortcuts - Implemented keyboard shortcuts: - Ctrl+P: Perform Preview - Ctrl+1: Navigate to Front Edit - Ctrl+2: Navigate to Back Edit - Ctrl+3: Navigate to Styling Edit - Ctrl+S: Save Note - Ctrl+R: Rename template - Ctrl+I: Show Insert Field Dialog - Ctrl+A: Add template - Ctrl+B: Open Browser Appearance - Ctrl+D: Delete template - Ctrl+O: Display Deck Override Dialog - Ctrl+M: Copy Markdown Template to Clipboard --- .../java/com/ichi2/anki/CardTemplateEditor.kt | 242 ++++++++++++------ 1 file changed, 163 insertions(+), 79 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt index 898db039a2a8..c19d825be603 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt @@ -251,13 +251,61 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { - if (keyCode == KeyEvent.KEYCODE_P) { - if (event.isCtrlPressed) { - val currentFragment = currentFragment - currentFragment?.performPreview() + val currentFragment = currentFragment ?: return super.onKeyUp(keyCode, event) + if (event.isCtrlPressed) { + when (keyCode) { + KeyEvent.KEYCODE_P -> { + Timber.i("Ctrl+P: Perform preview from keypress") + currentFragment.performPreview() + } + KeyEvent.KEYCODE_1 -> { + Timber.i("Ctrl+1: Edit front template from keypress") + currentFragment.bottomNavigation.selectedItemId = R.id.front_edit + } + KeyEvent.KEYCODE_2 -> { + Timber.i("Ctrl+2: Edit back template from keypress") + currentFragment.bottomNavigation.selectedItemId = R.id.back_edit + } + KeyEvent.KEYCODE_3 -> { + Timber.i("Ctrl+3: Edit styling from keypress") + currentFragment.bottomNavigation.selectedItemId = R.id.styling_edit + } + KeyEvent.KEYCODE_S -> { + Timber.i("Ctrl+S: Save note from keypress") + currentFragment.saveNoteType() + } + KeyEvent.KEYCODE_I -> { + Timber.i("Ctrl+I: Insert field from keypress") + currentFragment.showInsertFieldDialog() + } + KeyEvent.KEYCODE_A -> { + Timber.i("Ctrl+A: Add card template from keypress") + currentFragment.addCardTemplate() + } + KeyEvent.KEYCODE_R -> { + Timber.i("Ctrl+R: Rename card from keypress") + currentFragment.showRenameDialog() + } + KeyEvent.KEYCODE_B -> { + Timber.i("Ctrl+B: Open browser appearance from keypress") + currentFragment.openBrowserAppearance() + } + KeyEvent.KEYCODE_D -> { + Timber.i("Ctrl+D: Delete card from keypress") + currentFragment.deleteCardTemplate() + } + KeyEvent.KEYCODE_O -> { + Timber.i("Ctrl+O: Display deck override dialog from keypress") + currentFragment.displayDeckOverrideDialog(currentFragment.tempModel) + } + KeyEvent.KEYCODE_M -> { + Timber.i("Ctrl+M: Copy markdown from keypress") + currentFragment.copyMarkdownTemplateToClipboard() + } + else -> return super.onKeyUp(keyCode, event) } } - return super.onKeyUp(keyCode, event) + return true } @get:VisibleForTesting @@ -311,16 +359,18 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { private lateinit var templateEditor: CardTemplateEditor private var tabLayoutMediator: TabLayoutMediator? = null + lateinit var tempModel: CardTemplateNotetype + lateinit var bottomNavigation: BottomNavigationView override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // Storing a reference to the templateEditor allows us to use member variables templateEditor = activity as CardTemplateEditor val mainView = inflater.inflate(R.layout.card_template_editor_item, container, false) val cardIndex = requireArguments().getInt(CARD_INDEX) - val tempModel = templateEditor.tempModel + tempModel = templateEditor.tempModel!! // Load template val template: JSONObject = try { - tempModel!!.getTemplate(cardIndex) + tempModel.getTemplate(cardIndex) } catch (e: JSONException) { Timber.d(e, "Exception loading template in CardTemplateFragment. Probably stale fragment.") return mainView @@ -332,7 +382,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { editorEditText.customInsertionActionModeCallback = ActionModeCallback() - val bottomNavigation: BottomNavigationView = mainView.findViewById(R.id.card_template_editor_bottom_navigation) + bottomNavigation = mainView.findViewById(R.id.card_template_editor_bottom_navigation) bottomNavigation.setOnItemSelectedListener { item: MenuItem -> val currentSelectedId = item.itemId templateEditor.tabToViewId[cardIndex] = currentSelectedId @@ -429,7 +479,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { @NeedsTest( "the kotlin migration made this method crash due to a recursive call when the dialog would return its data" ) - private fun showInsertFieldDialog() { + fun showInsertFieldDialog() { templateEditor.fieldNames?.let { fieldNames -> templateEditor.showDialogFragment(InsertFieldDialog.newInstance(fieldNames)) } @@ -439,7 +489,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { @NeedsTest("Prefill is correct") @NeedsTest("Does not work for Cloze/Occlusion") @NeedsTest("UI is updated on success") - private fun showRenameDialog() { + fun showRenameDialog() { if (noteTypeCreatesDynamicNumberOfNotes()) { Timber.w("attempted to rename a dynamic note type") return @@ -553,92 +603,58 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { } override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - val col = templateEditor.getColUnsafe - val tempModel = templateEditor.tempModel - when (menuItem.itemId) { + return when (menuItem.itemId) { R.id.action_add -> { Timber.i("CardTemplateEditor:: Add template button pressed") - // Show confirmation dialog - val ordinal = templateEditor.viewPager.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(tempModel!!, ordinal)) { - col.notetypes.tmplUseCount(tempModel.notetype, ordinal) - } else { - 0 - } - confirmAddCards(tempModel.notetype, numAffectedCards) + addCardTemplate() + return true + } + R.id.action_reposition -> { + showRepositionDialog() + return true + } + R.id.action_rename -> { + Timber.i("CardTemplateEditor:: Rename button pressed") + showRenameDialog() + return true + } + R.id.action_copy_as_markdown -> { + Timber.i("CardTemplateEditor:: Copy markdown button pressed") + copyMarkdownTemplateToClipboard() + return true + } + R.id.action_insert_field -> { + Timber.i("CardTemplateEditor:: Insert field button pressed") + showInsertFieldDialog() return true } - R.id.action_reposition -> showRepositionDialog() - R.id.action_rename -> showRenameDialog() - R.id.action_copy_as_markdown -> copyMarkdownTemplateToClipboard() - R.id.action_insert_field -> showInsertFieldDialog() R.id.action_delete -> { - Timber.i("CardTemplateEditor:: Delete template button pressed") - val res = resources - val ordinal = templateEditor.viewPager.currentItem - val template = tempModel!!.getTemplate(ordinal) - // Don't do anything if only one template - if (tempModel.templateCount < 2) { - templateEditor.showSimpleMessageDialog(res.getString(R.string.card_template_editor_cant_delete)) - return true - } - - if (deletionWouldOrphanNote(col, tempModel, ordinal)) { - return true - } - - // Show confirmation dialog - val numAffectedCards = if (!CardTemplateNotetype.isOrdinalPendingAdd(tempModel, ordinal)) { - Timber.d("Ordinal is not a pending add, so we'll get the current card count for confirmation") - col.notetypes.tmplUseCount(tempModel.notetype, ordinal) - } else { - 0 - } - confirmDeleteCards(template, tempModel.notetype, numAffectedCards) + Timber.i("CardTemplateEditor:: Delete button pressed") + deleteCardTemplate() return true } R.id.action_add_deck_override -> { - displayDeckOverrideDialog(tempModel!!) + Timber.i("CardTemplateEditor:: Deck override button pressed") + displayDeckOverrideDialog(tempModel) return true } R.id.action_preview -> { + Timber.i("CardTemplateEditor:: Preview button pressed") performPreview() return true } R.id.action_confirm -> { - Timber.i("CardTemplateEditor:: Save model button pressed") - if (modelHasChanged()) { - val confirmButton = templateEditor.findViewById(R.id.action_confirm) - if (confirmButton != null) { - if (!confirmButton.isEnabled) { - Timber.d("CardTemplateEditor::discarding extra click after button disabled") - return true - } - confirmButton.isEnabled = false - } - launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) { - requireActivity().withProgress(resources.getString(R.string.saving_model)) { - withCol { tempModel!!.saveToDatabase() } - } - onModelSaved() - } - } else { - Timber.d("CardTemplateEditor:: model has not changed, exiting") - templateEditor.finish() - } - - return true + Timber.i("CardTemplateEditor:: Save button pressed") + saveNoteType() } R.id.action_card_browser_appearance -> { - Timber.i("CardTemplateEditor::Card Browser Template button pressed") - val currentTemplate = getCurrentTemplate() - currentTemplate?.let { launchCardBrowserAppearance(it) } - return true + Timber.i("CardTemplateEditor:: Card browser appearance button pressed") + openBrowserAppearance() + } + else -> { + false } } - return false } }, viewLifecycleOwner, @@ -646,6 +662,74 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { ) } + // TODO: Use withCol {} instead + fun deleteCardTemplate() { + val col = templateEditor.getColUnsafe + val tempModel = templateEditor.tempModel + val ordinal = templateEditor.viewPager.currentItem + val template = tempModel!!.getTemplate(ordinal) + // Don't do anything if only one template + if (tempModel.templateCount < 2) { + templateEditor.showSimpleMessageDialog(resources.getString(R.string.card_template_editor_cant_delete)) + return + } + + if (deletionWouldOrphanNote(col, tempModel, ordinal)) { + return + } + + // Show confirmation dialog + val numAffectedCards = if (!CardTemplateNotetype.isOrdinalPendingAdd(tempModel, ordinal)) { + Timber.d("Ordinal is not a pending add, so we'll get the current card count for confirmation") + col.notetypes.tmplUseCount(tempModel.notetype, ordinal) + } else { + 0 + } + confirmDeleteCards(template, tempModel.notetype, numAffectedCards) + } + + fun openBrowserAppearance(): Boolean { + val currentTemplate = getCurrentTemplate() + currentTemplate?.let { launchCardBrowserAppearance(it) } + return true + } + + fun addCardTemplate() { + // Show confirmation dialog + val ordinal = templateEditor.viewPager.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.tempModel!!, ordinal)) { + templateEditor.getColUnsafe.notetypes.tmplUseCount(templateEditor.tempModel!!.notetype, ordinal) + } else { + 0 + } + confirmAddCards(templateEditor.tempModel!!.notetype, numAffectedCards) + } + + fun saveNoteType(): Boolean { + if (modelHasChanged()) { + val confirmButton = templateEditor.findViewById(R.id.action_confirm) + if (confirmButton != null) { + if (!confirmButton.isEnabled) { + Timber.d("CardTemplateEditor::discarding extra click after button disabled") + return true + } + confirmButton.isEnabled = false + } + launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) { + requireActivity().withProgress(resources.getString(R.string.saving_model)) { + withCol { templateEditor.tempModel!!.saveToDatabase() } + } + onModelSaved() + } + } else { + Timber.d("CardTemplateEditor:: model has not changed, exiting") + templateEditor.finish() + } + return true + } + private val currentTemplate: CardTemplate? get() = try { val tempModel = templateEditor.tempModel @@ -661,7 +745,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { } /** Copies the template to clipboard in markdown format */ - private fun copyMarkdownTemplateToClipboard() { + fun copyMarkdownTemplateToClipboard() { // A number of users who post their templates to Reddit/Discord have these badly formatted // It makes it much easier for people to understand if these are provided as markdown val template = currentTemplate ?: return @@ -707,7 +791,7 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener { } } - private fun displayDeckOverrideDialog(tempModel: CardTemplateNotetype) = launchCatchingTask { + fun displayDeckOverrideDialog(tempModel: CardTemplateNotetype) = launchCatchingTask { val activity = requireActivity() as AnkiActivity if (tempModel.notetype.isCloze) { showSnackbar(getString(R.string.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)