Skip to content

Commit

Permalink
card-template-editor: keyboard shortcuts
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
SanjaySargam committed Jul 12, 2024
1 parent a9fd2c5 commit 41d7402
Showing 1 changed file with 163 additions and 79 deletions.
242 changes: 163 additions & 79 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
}
Expand All @@ -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
Expand Down Expand Up @@ -553,99 +603,133 @@ 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<View>(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,
Lifecycle.State.RESUMED
)
}

// 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<View>(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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 41d7402

Please sign in to comment.