Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GSoC'24] CardTemplateEditor: Implementation of Keyboard Shortcuts #16722

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Arthur-Milchior marked this conversation as resolved.
Show resolved Hide resolved
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 -> {

This comment was marked as outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DIdn't removed

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a "todo" stating to use withCol? I don't request a change right now because you are just moving code and that's unrelated to your change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it was not clear, this is the kind of remark that should be applied everywhere. We should not use getColUnsafe
It's currently used, so I won't require you to rewrite this part of the code, your commit is an improvement to our codebase. Still, each getColUnsafe should have its todo

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created #16789. The user should be told why it's not done. But this an already existing bug, so it's not blocking

}

// 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