Skip to content

Commit

Permalink
feat: keyboard shortcuts helper
Browse files Browse the repository at this point in the history
  • Loading branch information
SanjaySargam committed Sep 1, 2024
1 parent ce4ccc5 commit 52d5b0f
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 6 deletions.
6 changes: 5 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.workarounds.AppLoadedFromBackupWorkaround.showedActivityFailedScreen
import com.ichi2.async.CollectionLoader
import com.ichi2.compat.CompatV24
import com.ichi2.compat.ShortcutGroupProvider
import com.ichi2.compat.customtabs.CustomTabActivityHelper
import com.ichi2.compat.customtabs.CustomTabsFallback
import com.ichi2.compat.customtabs.CustomTabsHelper
Expand All @@ -66,7 +68,7 @@ import androidx.browser.customtabs.CustomTabsIntent.Builder as CustomTabsIntentB

@UiThread
@KotlinCleanup("set activityName")
open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener, ShortcutGroupProvider {

/** The name of the parent class (example: 'Reviewer') */
private val activityName: String
Expand Down Expand Up @@ -587,6 +589,8 @@ open class AnkiActivity : AppCompatActivity, SimpleMessageDialogListener {
return false
}

override val shortcuts: CompatV24.ShortcutGroup? = null

companion object {
const val DIALOG_FRAGMENT_TAG = "dialog"

Expand Down
6 changes: 6 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/AnkiFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import com.ichi2.async.CollectionLoader
import com.ichi2.compat.CompatV24
import com.ichi2.libanki.Collection
import com.ichi2.utils.increaseHorizontalPaddingOfOverflowMenuIcons
import com.ichi2.utils.tintOverflowMenuIcons
Expand Down Expand Up @@ -218,4 +219,9 @@ open class AnkiFragment(@LayoutRes layout: Int) : Fragment(layout) {
requireActivity().finish()
return false
}

/**
* Lists of shortcuts for this fragment, and the IdRes of the name of this shortcut group.
*/
open val shortcuts: CompatV24.ShortcutGroup? = null
}
59 changes: 59 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import android.os.SystemClock
import android.text.TextUtils
import android.util.TypedValue
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
Expand Down Expand Up @@ -115,7 +116,10 @@ import com.ichi2.anki.utils.roundedTimeSpanUnformatted
import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
import com.ichi2.annotations.NeedsTest
import com.ichi2.async.renderBrowserQA
import com.ichi2.compat.CompatHelper
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatV24
import com.ichi2.compat.CompatV24.Shortcut
import com.ichi2.libanki.Card
import com.ichi2.libanki.CardId
import com.ichi2.libanki.ChangeManager
Expand Down Expand Up @@ -147,6 +151,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.ankiweb.rsdroid.RustCleanup
import net.ankiweb.rsdroid.Translations
import timber.log.Timber
import kotlin.math.abs
import kotlin.math.ceil
Expand Down Expand Up @@ -631,6 +636,16 @@ open class CardBrowser :
viewModel.setDeckId(deckId)
}

override fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>,
menu: Menu?,
deviceId: Int
) {
val shortcutGroups = CompatHelper.compat.getShortcuts(this)
data.addAll(shortcutGroups)
super.onProvideKeyboardShortcuts(data, menu, deviceId)
}

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
// This method is called even when the user is typing in the search text field.
// So we must ensure that all shortcuts uses a modifier.
Expand Down Expand Up @@ -673,6 +688,9 @@ open class CardBrowser :
Timber.i("Ctrl+K: Toggle Mark")
toggleMark()
return true
} else if (event.isAltPressed) {
CompatHelper.compat.showKeyboardShortcutsDialog(this)
return true
}
}
KeyEvent.KEYCODE_R -> {
Expand Down Expand Up @@ -789,6 +807,12 @@ open class CardBrowser :
}
}
}

// Show snackbar only if a modifier key is pressed and the keyCode is an unmapped alphabet key
if ((event.isCtrlPressed || event.isAltPressed || event.isShiftPressed) && keyCode in KeyEvent.KEYCODE_A..KeyEvent.KEYCODE_Z) {
showSnackbar(R.string.show_shortcuts_message, Snackbar.LENGTH_SHORT)
}

return super.onKeyUp(keyCode, event)
}

Expand Down Expand Up @@ -2370,6 +2394,41 @@ open class CardBrowser :
}
}

override val shortcuts = CompatV24.ShortcutGroup(
listOf(
Shortcut("Ctrl+Shift+A", R.string.edit_tags_dialog),
Shortcut("Ctrl+A", R.string.card_browser_select_all),
Shortcut("Ctrl+Shift+E", Translations::exportingExport),
Shortcut("Ctrl+E", R.string.menu_add_note),
Shortcut("E", R.string.cardeditor_title_edit_card),
Shortcut("Ctrl+D", R.string.card_browser_change_deck),
Shortcut("Ctrl+K", Translations::browsingToggleMark),
Shortcut("Ctrl+Alt+R", Translations::browsingReschedule),
Shortcut("DEL", R.string.delete_card_title),
Shortcut("Ctrl+Alt+N", R.string.reset_card_dialog_title),
Shortcut("Ctrl+Alt+T", R.string.toggle_cards_notes),
Shortcut("T", R.string.card_browser_search_by_tag),
Shortcut("Ctrl+Shift+S", Translations::actionsReposition),
Shortcut("Ctrl+Alt+S", R.string.card_browser_list_my_searches),
Shortcut("Ctrl+S", R.string.card_browser_list_my_searches_save),
Shortcut("Alt+S", R.string.card_browser_show_suspended),
Shortcut("Ctrl+Shift+J", Translations::browsingToggleBury),
Shortcut("Ctrl+J", Translations::browsingToggleSuspend),
Shortcut("Ctrl+Shift+I", Translations::actionsCardInfo),
Shortcut("Ctrl+O", R.string.show_order_dialog),
Shortcut("Ctrl+M", R.string.card_browser_show_marked),
Shortcut("ESCAPE", R.string.card_browser_select_none),
Shortcut("Ctrl+NUMPAD_1", R.string.gesture_flag_red),
Shortcut("Ctrl+NUMPAD_2", R.string.gesture_flag_orange),
Shortcut("Ctrl+NUMPAD_3", R.string.gesture_flag_green),
Shortcut("Ctrl+NUMPAD_4", R.string.gesture_flag_blue),
Shortcut("Ctrl+NUMPAD_5", R.string.gesture_flag_pink),
Shortcut("Ctrl+NUMPAD_6", R.string.gesture_flag_turquoise),
Shortcut("Ctrl+NUMPAD_7", R.string.gesture_flag_purple)
),
R.string.card_browser_context_menu
)

companion object {
/**
* Argument key to add on change deck dialog,
Expand Down
59 changes: 57 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.text.Editable
import android.text.TextWatcher
import android.view.ActionMode
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
Expand Down Expand Up @@ -72,7 +73,10 @@ import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.ext.isImageOcclusion
import com.ichi2.anki.utils.postDelayed
import com.ichi2.annotations.NeedsTest
import com.ichi2.compat.CompatHelper
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
import com.ichi2.compat.CompatV24
import com.ichi2.compat.CompatV24.Shortcut
import com.ichi2.libanki.Collection
import com.ichi2.libanki.Note
import com.ichi2.libanki.NoteId
Expand All @@ -86,6 +90,7 @@ import com.ichi2.ui.FixedTextView
import com.ichi2.utils.KotlinCleanup
import com.ichi2.utils.copyToClipboard
import com.ichi2.utils.jsonObjectIterable
import net.ankiweb.rsdroid.Translations
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
Expand Down Expand Up @@ -321,62 +326,94 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
invalidateOptionsMenu()
}

override fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>,
menu: Menu?,
deviceId: Int
) {
val shortcutGroups = CompatHelper.compat.getShortcuts(this)
data.addAll(shortcutGroups)
super.onProvideKeyboardShortcuts(data, menu, deviceId)
}

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
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()
return true
}
KeyEvent.KEYCODE_1 -> {
Timber.i("Ctrl+1: Edit front template from keypress")
currentFragment.bottomNavigation.selectedItemId = R.id.front_edit
return true
}
KeyEvent.KEYCODE_2 -> {
Timber.i("Ctrl+2: Edit back template from keypress")
currentFragment.bottomNavigation.selectedItemId = R.id.back_edit
return true
}
KeyEvent.KEYCODE_3 -> {
Timber.i("Ctrl+3: Edit styling from keypress")
currentFragment.bottomNavigation.selectedItemId = R.id.styling_edit
return true
}
KeyEvent.KEYCODE_S -> {
Timber.i("Ctrl+S: Save note from keypress")
currentFragment.saveNoteType()
return true
}
KeyEvent.KEYCODE_I -> {
Timber.i("Ctrl+I: Insert field from keypress")
currentFragment.showInsertFieldDialog()
return true
}
KeyEvent.KEYCODE_A -> {
Timber.i("Ctrl+A: Add card template from keypress")
currentFragment.addCardTemplate()
return true
}
KeyEvent.KEYCODE_R -> {
Timber.i("Ctrl+R: Rename card from keypress")
currentFragment.showRenameDialog()
return true
}
KeyEvent.KEYCODE_B -> {
Timber.i("Ctrl+B: Open browser appearance from keypress")
currentFragment.openBrowserAppearance()
return true
}
KeyEvent.KEYCODE_D -> {
Timber.i("Ctrl+D: Delete card from keypress")
currentFragment.deleteCardTemplate()
return true
}
KeyEvent.KEYCODE_O -> {
Timber.i("Ctrl+O: Display deck override dialog from keypress")
currentFragment.displayDeckOverrideDialog(currentFragment.tempModel)
return true
}
KeyEvent.KEYCODE_M -> {
Timber.i("Ctrl+M: Copy markdown from keypress")
currentFragment.copyMarkdownTemplateToClipboard()
return true
}
else -> return super.onKeyUp(keyCode, event)
}
}
return true

if (event.isAltPressed && keyCode == KeyEvent.KEYCODE_K) {
CompatHelper.compat.showKeyboardShortcutsDialog(this)
return true
}

// Show snackbar only if a modifier key is pressed and the keyCode is an unmapped alphabet key
if ((event.isCtrlPressed || event.isAltPressed || event.isShiftPressed) && keyCode in KeyEvent.KEYCODE_A..KeyEvent.KEYCODE_Z) {
showSnackbar(R.string.show_shortcuts_message, Snackbar.LENGTH_SHORT)
}

return super.onKeyUp(keyCode, event)
}

@get:VisibleForTesting
Expand Down Expand Up @@ -421,6 +458,24 @@ open class CardTemplateEditor : AnkiActivity(), DeckSelectionListener {
}
}

override val shortcuts = CompatV24.ShortcutGroup(
listOf(
Shortcut("Ctrl+P", R.string.card_editor_preview_card),
Shortcut("Ctrl+NUMPAD_1", R.string.edit_question),
Shortcut("Ctrl+NUMPAD_2", R.string.edit_answer),
Shortcut("Ctrl+NUMPAD_3", R.string.edit_styling),
Shortcut("Ctrl+S", R.string.save),
Shortcut("Ctrl+I", R.string.card_template_editor_insert_field),
Shortcut("Ctrl+A", Translations::cardTemplatesAddCardType),
Shortcut("Ctrl+R", Translations::cardTemplatesRenameCardType),
Shortcut("Ctrl+B", R.string.edit_browser_appearance),
Shortcut("Ctrl+D", Translations::cardTemplatesRemoveCardType),
Shortcut("Ctrl+O", Translations::cardTemplatesDeckOverride),
Shortcut("Ctrl+M", R.string.copy_the_template)
),
R.string.card_template_editor_group
)

class CardTemplateFragment : Fragment() {
private val refreshFragmentHandler = Handler(Looper.getMainLooper())
private var currentEditorTitle: FixedTextView? = null
Expand Down
49 changes: 49 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import android.os.Bundle
import android.os.Message
import android.util.TypedValue
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.Menu
import android.view.MenuItem
import android.view.View
Expand Down Expand Up @@ -156,6 +157,8 @@ import com.ichi2.compat.CompatHelper
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatHelper.Companion.sdkVersion
import com.ichi2.compat.CompatV24
import com.ichi2.compat.CompatV24.Shortcut
import com.ichi2.libanki.ChangeManager
import com.ichi2.libanki.Consts
import com.ichi2.libanki.DeckId
Expand Down Expand Up @@ -192,6 +195,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.ankiweb.rsdroid.RustCleanup
import net.ankiweb.rsdroid.Translations
import org.json.JSONException
import timber.log.Timber
import java.io.File
Expand Down Expand Up @@ -1264,6 +1268,16 @@ open class DeckPicker :
}
}

override fun onProvideKeyboardShortcuts(
data: MutableList<KeyboardShortcutGroup>,
menu: Menu?,
deviceId: Int
) {
val shortcutGroups = CompatHelper.compat.getShortcuts(this)
data.addAll(shortcutGroups)
super.onProvideKeyboardShortcuts(data, menu, deviceId)
}

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (toolbarSearchView?.hasFocus() == true) {
Timber.d("Skipping keypress: search action bar is focused")
Expand Down Expand Up @@ -1388,8 +1402,20 @@ open class DeckPicker :
return true
}
}
KeyEvent.KEYCODE_K -> {
if (event.isAltPressed) {
CompatHelper.compat.showKeyboardShortcutsDialog(this)
return true
}
}
else -> {}
}

// Show snackbar only if a modifier key is pressed and the keyCode is an unmapped alphabet key
if ((event.isCtrlPressed || event.isAltPressed || event.isShiftPressed) && keyCode in KeyEvent.KEYCODE_A..KeyEvent.KEYCODE_Z) {
showSnackbar(R.string.show_shortcuts_message, Snackbar.LENGTH_SHORT)
}

return super.onKeyUp(keyCode, event)
}

Expand Down Expand Up @@ -2412,6 +2438,29 @@ open class DeckPicker :
SKIP_STUDY_OPTIONS
}

override val shortcuts = CompatV24.ShortcutGroup(
listOf(
Shortcut("A", R.string.menu_add_note),
Shortcut("B", R.string.card_browser_context_menu),
Shortcut("Y", R.string.pref_cat_sync),
Shortcut("SLASH", R.string.deck_conf_cram_search),
Shortcut("S", Translations::decksStudyDeck),
Shortcut("T", R.string.open_statistics),
Shortcut("C", R.string.check_db),
Shortcut("D", R.string.new_deck),
Shortcut("F", R.string.new_dynamic_deck),
Shortcut("DEL", R.string.delete_deck_title),
Shortcut("Shift+DEL", R.string.delete_deck_without_confirmation),
Shortcut("R", R.string.rename_deck),
Shortcut("P", R.string.open_settings),
Shortcut("M", R.string.check_media),
Shortcut("Ctrl+E", R.string.export_collection),
Shortcut("Ctrl+Shift+I", R.string.menu_import),
Shortcut("Ctrl+Shift+N", R.string.model_browser_label)
),
R.string.deck_picker_group
)

companion object {
/**
* Result codes from other activities
Expand Down
Loading

0 comments on commit 52d5b0f

Please sign in to comment.