diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97311499..226c96aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -142,15 +142,6 @@ To translate Traditional T9 menus and messages in your language, add: `res/value Alternatively, if you don't have Android Studio, you could just use `res/values/strings.xml` as a reference and translate all strings in your file, skipping the ones that have the `translatable="false"` attribute. -## Adding Support for New Hardware Keys (Hotkeys) -TT9 allows assigning hotkeys for performing different functions. If your phone has a special key that does not appear on the Hotkey configuration screen, you can easily add support for it. - -- In `preferences/helpers/Hotkeys.java`, find the `generateList()` function. -- Add the new key there. The order of adding is used when displaying the dropdown options. -- Optionally, you can translate the name of the key in different languages in the `res/values-XX/strings.xml` files. - - _You can find the key codes [in the Android docs](https://developer.android.com/reference/android/view/KeyEvent)._ - ## Contribution Process ### Before you start diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java index c04f3282..acdfd48e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java @@ -7,7 +7,6 @@ import io.github.sspanak.tt9.db.words.DictionaryLoader; import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputModeKind; -import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.util.Ternary; @@ -16,7 +15,7 @@ public abstract class HotkeyHandler extends CommandHandler { protected void onInit() { super.onInit(); if (settings.areHotkeysInitialized()) { - Hotkeys.setDefault(settings); + settings.setDefaultKeys(); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java index d8bad119..74b2add1 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/Key.java @@ -1,11 +1,19 @@ package io.github.sspanak.tt9.ime.helpers; +import android.annotation.SuppressLint; +import android.content.Context; import android.view.KeyEvent; +import androidx.annotation.NonNull; + import java.util.HashMap; +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Ternary; +import io.github.sspanak.tt9.util.Text; public class Key { private static final HashMap handledKeys = new HashMap<>(); @@ -107,4 +115,57 @@ public class Key { return -1; } } + + @SuppressLint("GestureBackNavigation") // we are not handling anything here, the warning makes no sense + public static String codeToName(@NonNull Context context, int keyCode) { + return switch (keyCode) { + case KeyEvent.KEYCODE_UNKNOWN -> context.getString(R.string.key_none); + case KeyEvent.KEYCODE_POUND -> "#"; + case KeyEvent.KEYCODE_STAR -> "✱"; + case KeyEvent.KEYCODE_BACK -> context.getString(R.string.key_back); + case KeyEvent.KEYCODE_CALL -> context.getString(R.string.key_call); + case KeyEvent.KEYCODE_CHANNEL_DOWN -> context.getString(R.string.key_channel_down); + case KeyEvent.KEYCODE_CHANNEL_UP -> context.getString(R.string.key_channel_up); + case KeyEvent.KEYCODE_DPAD_UP -> context.getString(R.string.key_dpad_up); + case KeyEvent.KEYCODE_DPAD_DOWN -> context.getString(R.string.key_dpad_down); + case KeyEvent.KEYCODE_DPAD_LEFT -> context.getString(R.string.key_dpad_left); + case KeyEvent.KEYCODE_DPAD_RIGHT -> context.getString(R.string.key_dpad_right); + case KeyEvent.KEYCODE_MENU -> context.getString(R.string.key_menu); + case KeyEvent.KEYCODE_NUMPAD_ADD -> "Num +"; + case KeyEvent.KEYCODE_NUMPAD_DIVIDE -> "Num /"; + case KeyEvent.KEYCODE_NUMPAD_DOT -> "Num ."; + case KeyEvent.KEYCODE_NUMPAD_MULTIPLY -> "Num *"; + case KeyEvent.KEYCODE_NUMPAD_SUBTRACT -> "Num -"; + case KeyEvent.KEYCODE_PROG_RED -> context.getString(R.string.key_red); + case KeyEvent.KEYCODE_PROG_GREEN -> context.getString(R.string.key_green); + case KeyEvent.KEYCODE_PROG_YELLOW -> context.getString(R.string.key_yellow); + case KeyEvent.KEYCODE_PROG_BLUE -> context.getString(R.string.key_blue); + case KeyEvent.KEYCODE_SOFT_LEFT -> context.getString(R.string.key_soft_left); + case KeyEvent.KEYCODE_SOFT_RIGHT -> context.getString(R.string.key_soft_right); + case KeyEvent.KEYCODE_VOLUME_MUTE -> context.getString(R.string.key_volume_mute); + case KeyEvent.KEYCODE_VOLUME_DOWN -> context.getString(R.string.key_volume_down); + case KeyEvent.KEYCODE_VOLUME_UP -> context.getString(R.string.key_volume_up); + default -> codeToSystemName(context, keyCode); + }; + } + + private static String codeToSystemName(@NonNull Context context, int keyCode) { + String name = KeyEvent.keyCodeToString(keyCode).replace("KEYCODE_", ""); + + if (new Text(name).isNumeric()) { + return context.getString(R.string.key_key) + " #" + name; + } + + Language english = LanguageCollection.getByLanguageCode("en"); + String[] parts = name.split("_"); + StringBuilder formattedName = new StringBuilder(); + for (int i = 0; i < parts.length; i++) { + formattedName.append(new Text(english, parts[i].toLowerCase()).capitalize()); + if (i < parts.length - 1) { + formattedName.append(" "); + } + } + + return formattedName.toString(); + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java b/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java index 0e4616d9..d39c9535 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java @@ -19,7 +19,6 @@ import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.db.words.LegacyDb; import io.github.sspanak.tt9.ime.helpers.InputModeValidator; import io.github.sspanak.tt9.languages.LanguageCollection; -import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; import io.github.sspanak.tt9.preferences.screens.UsageStatsScreen; import io.github.sspanak.tt9.preferences.screens.appearance.AppearanceScreen; @@ -225,7 +224,7 @@ public class PreferencesActivity extends ActivityWithNavigation implements Prefe private void validateFunctionKeys() { if (settings.areHotkeysInitialized()) { - Hotkeys.setDefault(settings); + settings.setDefaultKeys(); } } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java b/app/src/main/java/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java deleted file mode 100644 index 041e82dd..00000000 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java +++ /dev/null @@ -1,216 +0,0 @@ -package io.github.sspanak.tt9.preferences.helpers; - -import android.content.Context; -import android.content.res.Resources; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.ViewConfiguration; - -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Set; - -import io.github.sspanak.tt9.R; -import io.github.sspanak.tt9.preferences.screens.hotkeys.SectionKeymap; -import io.github.sspanak.tt9.preferences.settings.SettingsStore; - -public class Hotkeys { - private final Context context; - private final Resources resources; - private final String holdKeyTranslation; - - private final LinkedHashMap HARDWARE_KEYS = new LinkedHashMap<>(); - - - public Hotkeys(Context context) { - this.context = context; - resources = context.getResources(); - holdKeyTranslation = resources.getString(R.string.key_hold_key); - - addNoKey(); - generateList(); - } - - - public String getHardwareKeyName(String key) { - return HARDWARE_KEYS.get(key); - } - - - public Set getHardwareKeys() { - return HARDWARE_KEYS.keySet(); - } - - - /** - * setDefault - * Applies the default hotkey scheme. - * - * When a standard "Backspace" hardware key is available, "Backspace" hotkey association is not necessary, - * so it will be left out blank, to allow the hardware key do its job. - * When the on-screen keyboard is on, "Back" is also not associated, because it will cause weird user - * experience. Instead the on-screen "Backspace" key can be used. - * - * Arrow keys for manipulating suggestions are also assigned only if available. - */ - public static void setDefault(SettingsStore settings) { - HashMap defaultKeys = new HashMap<>(); - - defaultKeys.put(SectionKeymap.ITEM_ADD_WORD, KeyEvent.KEYCODE_UNKNOWN); // unassigned - - defaultKeys.put(SectionKeymap.ITEM_BACKSPACE, KeyEvent.KEYCODE_BACK); - if ( - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR) - || KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL) - || settings.isMainLayoutNumpad() - ) { - defaultKeys.put(SectionKeymap.ITEM_BACKSPACE, 0); - } - - defaultKeys.put(SectionKeymap.ITEM_COMMAND_PALETTE, -KeyEvent.KEYCODE_STAR); // negative means "hold" - defaultKeys.put(SectionKeymap.ITEM_EDIT_TEXT, KeyEvent.KEYCODE_UNKNOWN); - - defaultKeys.put( - SectionKeymap.ITEM_FILTER_CLEAR, - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_DOWN) ? KeyEvent.KEYCODE_DPAD_DOWN : 0 - ); - - defaultKeys.put( - SectionKeymap.ITEM_FILTER_SUGGESTIONS, - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_UP) ? KeyEvent.KEYCODE_DPAD_UP : 0 - ); - - defaultKeys.put( - SectionKeymap.ITEM_PREVIOUS_SUGGESTION, - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_LEFT) ? KeyEvent.KEYCODE_DPAD_LEFT : 0 - ); - - defaultKeys.put( - SectionKeymap.ITEM_NEXT_SUGGESTION, - KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_RIGHT) ? KeyEvent.KEYCODE_DPAD_RIGHT : 0 - ); - - defaultKeys.put(SectionKeymap.ITEM_NEXT_INPUT_MODE, KeyEvent.KEYCODE_POUND); - defaultKeys.put(SectionKeymap.ITEM_NEXT_LANGUAGE, -KeyEvent.KEYCODE_POUND); // negative means "hold" - defaultKeys.put(SectionKeymap.ITEM_SELECT_KEYBOARD, KeyEvent.KEYCODE_UNKNOWN); - defaultKeys.put(SectionKeymap.ITEM_SHIFT, KeyEvent.KEYCODE_STAR); - defaultKeys.put(SectionKeymap.ITEM_SPACE_KOREAN, KeyEvent.KEYCODE_STAR); - defaultKeys.put(SectionKeymap.ITEM_SHOW_SETTINGS, KeyEvent.KEYCODE_UNKNOWN); - defaultKeys.put(SectionKeymap.ITEM_VOICE_INPUT, KeyEvent.KEYCODE_UNKNOWN); - - settings.setDefaultKeys(defaultKeys); - } - - - /** - * addIfDeviceHasKey - * Add the key only if Android says the device has such keypad button or a permanent touch key. - */ - private void addIfDeviceHasKey(int code, String name, boolean allowHold) { - if ( - (code == KeyEvent.KEYCODE_MENU && ViewConfiguration.get(context).hasPermanentMenuKey()) - || KeyCharacterMap.deviceHasKey(code) - ) { - add(code, name, allowHold); - } - } - - - /** - * addIfDeviceHasKey - * Same as addIfDeviceHasKey, but accepts a Resource String as a key name. - * - */ - @SuppressWarnings("SameParameterValue") - private void addIfDeviceHasKey(int code, int nameResource, boolean allowHold) { - addIfDeviceHasKey(code, resources.getString(nameResource), allowHold); - } - - - /** - * add - * These key will be added as a selectable option, regardless if it exists or or not. - * No validation will be performed. - */ - private void add(int code, String name, boolean allowHold) { - HARDWARE_KEYS.put(String.valueOf(code), name); - - if (allowHold) { - HARDWARE_KEYS.put(String.valueOf(-code), name + " " + holdKeyTranslation); - } - } - - /** - * add - * Same as add(), but accepts a Resource String as a key name. - */ - @SuppressWarnings("SameParameterValue") - private void add(int code, int nameResource, boolean allowHold) { - add(code, resources.getString(nameResource), allowHold); - } - - - /** - * addNoKey - * This is the "--" option. The key code matches no key on the keypad. - */ - private void addNoKey() { - add(0, R.string.key_none, false); - } - - - /** - * generateList - * Generates a list of all supported hotkeys for associating functions in the Settings. - * - * NOTE: Some TT9 functions do not support all keys. Here you just list all possible options. - * Actual validation and assigning happens in SectionKeymap.populate(). - */ - private void generateList() { - add(KeyEvent.KEYCODE_CALL, R.string.key_call, true); - - addIfDeviceHasKey(KeyEvent.KEYCODE_BACK, R.string.key_back, false); - - addIfDeviceHasKey(KeyEvent.KEYCODE_F1, "F1", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F2, "F2", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F3, "F3", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F4, "F4", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F5, "F5", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F6, "F6", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F7, "F7", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F8, "F8", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F9, "F9", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F10, "F10", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F11, "F11", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_F12, "F12", false); - - addIfDeviceHasKey(KeyEvent.KEYCODE_MENU, R.string.key_menu, true); - addIfDeviceHasKey(KeyEvent.KEYCODE_SOFT_LEFT, R.string.key_soft_left, true); - addIfDeviceHasKey(KeyEvent.KEYCODE_SOFT_RIGHT, R.string.key_soft_right, true); - - add(KeyEvent.KEYCODE_POUND, "#", true); - add(KeyEvent.KEYCODE_STAR, "✱", true); - - addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_UP, R.string.key_dpad_up, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_DOWN, R.string.key_dpad_down, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_LEFT, R.string.key_dpad_left, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_DPAD_RIGHT, R.string.key_dpad_right, false); - - addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_ADD, "Num +", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, "Num -", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, "Num *", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_DIVIDE, "Num /", false); - addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_DOT, "Num .", false); - - addIfDeviceHasKey(KeyEvent.KEYCODE_CHANNEL_DOWN, R.string.key_channel_down, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_CHANNEL_UP, R.string.key_channel_up, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_RED, R.string.key_red, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_GREEN, R.string.key_green, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_YELLOW, R.string.key_yellow, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_PROG_BLUE, R.string.key_blue, false); - - addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_MUTE, R.string.key_volume_mute, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_DOWN, R.string.key_volume_down, false); - addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_UP, R.string.key_volume_up, false); - } -} diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java index f2f9ea14..163a71d2 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/deleteWords/PreferenceDeletableWord.java @@ -55,6 +55,8 @@ public class PreferenceDeletableWord extends ScreenPreference { context.getString(R.string.delete_words_deleted_confirm_deletion_question, word), context.getString(R.string.delete_words_delete), this::onDeletionConfirmed, + true, + null, null ); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java index 5a921e37..fb38d228 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/HotkeysScreen.java @@ -1,15 +1,19 @@ package io.github.sspanak.tt9.preferences.screens.hotkeys; -import androidx.preference.DropDownPreference; +import androidx.annotation.NonNull; -import java.util.Arrays; +import java.util.HashMap; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; public class HotkeysScreen extends BaseScreenFragment { - final public static String NAME = "Hotkeys"; + public static final String NAME = "Hotkeys"; + @NonNull static HashMap hotkeys = new HashMap<>(); + + public HotkeysScreen() { init(); } public HotkeysScreen(PreferencesActivity activity) { init(activity); } @@ -19,29 +23,22 @@ public class HotkeysScreen extends BaseScreenFragment { @Override public void onCreate() { - DropDownPreference[] dropDowns = { - findPreference(SectionKeymap.ITEM_ADD_WORD), - findPreference(SectionKeymap.ITEM_BACKSPACE), - findPreference(SectionKeymap.ITEM_COMMAND_PALETTE), - findPreference(SectionKeymap.ITEM_EDIT_TEXT), - findPreference(SectionKeymap.ITEM_FILTER_CLEAR), - findPreference(SectionKeymap.ITEM_FILTER_SUGGESTIONS), - findPreference(SectionKeymap.ITEM_PREVIOUS_SUGGESTION), - findPreference(SectionKeymap.ITEM_NEXT_SUGGESTION), - findPreference(SectionKeymap.ITEM_NEXT_INPUT_MODE), - findPreference(SectionKeymap.ITEM_NEXT_LANGUAGE), - findPreference(SectionKeymap.ITEM_SELECT_KEYBOARD), - findPreference(SectionKeymap.ITEM_SHIFT), - findPreference(SectionKeymap.ITEM_SPACE_KOREAN), - findPreference(SectionKeymap.ITEM_SHOW_SETTINGS), - findPreference(SectionKeymap.ITEM_VOICE_INPUT), - }; - SectionKeymap section = new SectionKeymap(Arrays.asList(dropDowns), activity); - section.populate().activate(); - - (new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, section)) - .enableClickHandler(); - + getHotkeys(); + (new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, hotkeys.values())).enableClickHandler(); resetFontSize(false); } + + @Override + public int getPreferenceCount() { + return -1; // prevent scrolling and item selection using the number keys on this screen + } + + private void getHotkeys() { + for (String function : SettingsStore.FUNCTIONS) { + PreferenceHotkey hotkey = findPreference(function); + if (hotkey != null) { + hotkeys.put(function, hotkey); + } + } + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/ItemResetKeys.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/ItemResetKeys.java index 5bfd3318..714bd49a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/ItemResetKeys.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/ItemResetKeys.java @@ -1,10 +1,11 @@ package io.github.sspanak.tt9.preferences.screens.hotkeys; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.preference.Preference; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.preferences.PreferencesActivity; -import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.preferences.items.ItemClickable; import io.github.sspanak.tt9.ui.UI; @@ -13,19 +14,21 @@ class ItemResetKeys extends ItemClickable { public static final String NAME = "reset_keys"; private final PreferencesActivity activity; - private final SectionKeymap dropdowns; + private final Iterable hotkeys; - ItemResetKeys(Preference item, PreferencesActivity activity, SectionKeymap dropdowns) { + ItemResetKeys(@Nullable Preference item, @NonNull PreferencesActivity activity, @NonNull Iterable hotkeys) { super(item); this.activity = activity; - this.dropdowns = dropdowns; + this.hotkeys = hotkeys; } @Override protected boolean onClick(Preference p) { - Hotkeys.setDefault(activity.getSettings()); - dropdowns.reloadSettings(); + activity.getSettings().setDefaultKeys(); + for (PreferenceHotkey hotkey : hotkeys) { + hotkey.populate(); + } UI.toast(activity, R.string.function_reset_keys_done); return true; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceBackspaceHotkey.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceBackspaceHotkey.java new file mode 100644 index 00000000..d024f192 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceBackspaceHotkey.java @@ -0,0 +1,22 @@ +package io.github.sspanak.tt9.preferences.screens.hotkeys; + +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class PreferenceBackspaceHotkey extends PreferenceHotkey { + public PreferenceBackspaceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } + public PreferenceBackspaceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } + public PreferenceBackspaceHotkey(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } + public PreferenceBackspaceHotkey(@NonNull Context context) { super(context); } + + @Override + protected boolean onAssign(DialogInterface dialog, int keyCode) { + // backspace works both when pressed short and long, + // so separate "hold" and "not hold" options for it make no sense + return super.onAssign(dialog, Math.abs(keyCode)); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceHotkey.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceHotkey.java new file mode 100644 index 00000000..ecf8f418 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceHotkey.java @@ -0,0 +1,203 @@ +package io.github.sspanak.tt9.preferences.screens.hotkeys; + +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.hacks.DeviceInfo; +import io.github.sspanak.tt9.ime.helpers.Key; +import io.github.sspanak.tt9.preferences.custom.ScreenPreference; +import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.UI; + +public class PreferenceHotkey extends ScreenPreference implements DialogInterface.OnKeyListener{ + private static final int CANCEL_KEY = 0; + private static final int UNASSIGN_KEY = 2; + + private static SettingsStore settings; + + private int lastKeyDownCode = KeyEvent.KEYCODE_UNKNOWN; + private int lastLongPressCode = KeyEvent.KEYCODE_UNKNOWN; + + + public PreferenceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } + public PreferenceHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } + public PreferenceHotkey(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context); } + public PreferenceHotkey(@NonNull Context context) { super(context); init(context); } + + + private void init(Context context) { + if (settings == null) { + settings = new SettingsStore(context); + } + } + + + @Override + public void onAttached() { + super.onAttached(); + populate(); + } + + + @Override protected int getLargeLayout() { + return DeviceInfo.AT_LEAST_ANDROID_12 ? getDefaultLayout() : R.layout.pref_default_large; + } + + + public void populate() { + populate(settings.getFunctionKey(getKey())); + } + + + private void populate(int keyCode) { + String holdSuffix = ""; + if (keyCode < 0) { + holdSuffix = " " + getContext().getString(R.string.key_hold_key); + keyCode = -keyCode; + } + + setSummary(Key.codeToName(getContext(), keyCode) + holdSuffix); + } + + + @Override + protected void onClick() { + super.onClick(); + UI.confirm( + getContext(), + getKey(), + getContext().getString(R.string.function_assign_instructions, getTitle()), + null, + null, + false, + null, + this + ); + } + + + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + onKeyDown(dialog, keyCode); + } + + if (event.getAction() == KeyEvent.ACTION_UP) { + onKeyUp(dialog, keyCode); + } + + // prevent passing the key event to the activity + return false; + } + + + private void onKeyDown(DialogInterface dialog, int keyCode) { + if (keyCode == lastKeyDownCode && keyCode != lastLongPressCode && onAssign(dialog, -keyCode)) { + lastLongPressCode = keyCode; + dialog.dismiss(); + } + + lastKeyDownCode = keyCode; + } + + + private void onKeyUp(DialogInterface dialog, int keyCode) { + lastKeyDownCode = KeyEvent.KEYCODE_UNKNOWN; + lastLongPressCode = KeyEvent.KEYCODE_UNKNOWN; + if (onIgnore(keyCode)) { + return; + } + + if (onCancel(keyCode) || onUnassign(keyCode) || onAssign(dialog, keyCode)) { + dialog.dismiss(); + } + } + + + protected boolean onAssign(DialogInterface dialog, int keyCode) { + if (Key.isNumber(keyCode)) { + return false; + } + + if (onReassign(dialog, keyCode)) { + return true; + } + + settings.setFunctionKey(getKey(), keyCode); + populate(keyCode); + return true; + } + + + private boolean onReassign(DialogInterface dialog, int keyCode) { + String otherFunction = settings.getFunction(keyCode); + if (otherFunction == null || otherFunction.equals(getKey())) { + return false; + } + + // "Shift" and "Korean Space" can be the same key. It is properly handled in HotkeyHandler. + if ( + (getKey().equals(SettingsStore.FUNC_SPACE_KOREAN) && otherFunction.equals(SettingsStore.FUNC_SHIFT)) + || (getKey().equals(SettingsStore.FUNC_SHIFT) && otherFunction.equals(SettingsStore.FUNC_SPACE_KOREAN)) + ) { + return false; + } + + + dialog.dismiss(); + + PreferenceHotkey otherHotkey = HotkeysScreen.hotkeys.get(otherFunction); + CharSequence prettyOtherFunction = otherHotkey != null ? otherHotkey.getTitle() : otherFunction; + String question = getContext().getString( + R.string.function_already_assigned, + Key.codeToName(getContext(), keyCode), + prettyOtherFunction, + getTitle() + ); + + UI.confirm( + getContext(), + getKey(), + question, + getContext().getString(R.string.function_reassign), + () -> { + settings.setFunctionKey(otherFunction, KeyEvent.KEYCODE_UNKNOWN); + onAssign(dialog, keyCode); + }, + true, + null, + null + ); + + return true; + } + + + private boolean onUnassign(int keyCode) { + if (Key.codeToNumber(settings, keyCode) != UNASSIGN_KEY) { + return false; + } + + settings.setFunctionKey(getKey(), KeyEvent.KEYCODE_UNKNOWN); + populate(KeyEvent.KEYCODE_UNKNOWN); + return true; + } + + + private boolean onCancel(int keyCode) { + return + (DeviceInfo.noKeyboard(getContext()) && Key.isBack(keyCode)) + || Key.codeToNumber(settings, keyCode) == CANCEL_KEY; + } + + + private boolean onIgnore(int keyCode) { + return Key.isOK(keyCode); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceVoiceInputHotkey.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceVoiceInputHotkey.java new file mode 100644 index 00000000..5b03b02d --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/PreferenceVoiceInputHotkey.java @@ -0,0 +1,36 @@ +package io.github.sspanak.tt9.preferences.screens.hotkeys; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.github.sspanak.tt9.ime.voice.VoiceInputOps; + +public class PreferenceVoiceInputHotkey extends PreferenceHotkey { + public PreferenceVoiceInputHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public PreferenceVoiceInputHotkey(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public PreferenceVoiceInputHotkey(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public PreferenceVoiceInputHotkey(@NonNull Context context) { + super(context); + } + + @Override + public void populate() { + boolean isAvailable = new VoiceInputOps(getContext(), null, null, null).isAvailable(); + setVisible(isAvailable); + if (isAvailable) { + super.populate(); + } + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java deleted file mode 100644 index e10f57e6..00000000 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/hotkeys/SectionKeymap.java +++ /dev/null @@ -1,182 +0,0 @@ -package io.github.sspanak.tt9.preferences.screens.hotkeys; - -import androidx.preference.DropDownPreference; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Objects; - -import io.github.sspanak.tt9.ime.voice.VoiceInputOps; -import io.github.sspanak.tt9.preferences.PreferencesActivity; -import io.github.sspanak.tt9.preferences.helpers.Hotkeys; -import io.github.sspanak.tt9.preferences.settings.SettingsStore; -import io.github.sspanak.tt9.util.Logger; - -public class SectionKeymap { - public static final String ITEM_ADD_WORD = "key_add_word"; - public static final String ITEM_BACKSPACE = "key_backspace"; - public static final String ITEM_COMMAND_PALETTE = "key_command_palette"; - public static final String ITEM_EDIT_TEXT = "key_edit_text"; - public static final String ITEM_FILTER_CLEAR = "key_filter_clear"; - public static final String ITEM_FILTER_SUGGESTIONS = "key_filter_suggestions"; - public static final String ITEM_PREVIOUS_SUGGESTION = "key_previous_suggestion"; - public static final String ITEM_NEXT_SUGGESTION = "key_next_suggestion"; - public static final String ITEM_NEXT_INPUT_MODE = "key_next_input_mode"; - public static final String ITEM_NEXT_LANGUAGE = "key_next_language"; - public static final String ITEM_SELECT_KEYBOARD = "key_select_keyboard"; - public static final String ITEM_SHIFT = "key_shift"; - public static final String ITEM_SPACE_KOREAN = "key_space_korean"; - public static final String ITEM_SHOW_SETTINGS = "key_show_settings"; - public static final String ITEM_VOICE_INPUT = "key_voice_input"; - - private final Hotkeys hotkeys; - private final Collection items; - private final SettingsStore settings; - private final boolean isVoiceInputAvailable; - - - public SectionKeymap(Collection dropDowns, PreferencesActivity activity) { - items = dropDowns; - hotkeys = new Hotkeys(activity); - this.settings = activity.getSettings(); - isVoiceInputAvailable = new VoiceInputOps(activity, null, null, null).isAvailable(); - } - - - public void reloadSettings() { - for (DropDownPreference dropDown : items) { - int keypadKey = settings.getFunctionKey(dropDown.getKey()); - dropDown.setValue(String.valueOf(keypadKey)); - } - populate(); - } - - - public SectionKeymap populate() { - populateOtherItems(null); - return this; - } - - - public void activate() { - for (DropDownPreference item : items) { - onItemClick(item); - } - } - - - private void populateOtherItems(DropDownPreference itemToSkip) { - for (DropDownPreference item : items) { - if (itemToSkip != null && item != null && Objects.equals(itemToSkip.getKey(), item.getKey())) { - continue; - } - populateItem(item); - previewCurrentKey(item); - } - } - - - private void populateItem(DropDownPreference dropDown) { - if (dropDown == null) { - Logger.w("SectionKeymap.populateItem", "Cannot populate a NULL item. Ignoring."); - return; - } - - ArrayList keys = new ArrayList<>(); - for (String key : hotkeys.getHardwareKeys()) { - if ( - validateKey(dropDown, String.valueOf(key)) - // backspace works both when pressed short and long, - // so separate "hold" and "not hold" options for it make no sense - && !(dropDown.getKey().equals(ITEM_BACKSPACE) && Integer.parseInt(key) < 0) - ) { - keys.add(String.valueOf(key)); - } - } - - ArrayList values = new ArrayList<>(); - for (String key : keys) { - values.add(hotkeys.getHardwareKeyName(key)); - } - - dropDown.setEntries(values.toArray(new CharSequence[0])); - dropDown.setEntryValues(keys.toArray(new CharSequence[0])); - } - - - private void onItemClick(DropDownPreference item) { - if (item == null) { - Logger.w("SectionKeymap.populateItem", "Cannot set a click listener a NULL item. Ignoring."); - return; - } - - item.setOnPreferenceChangeListener((preference, newKey) -> { - if (!validateKey((DropDownPreference) preference, newKey.toString())) { - return false; - } - - try { - ((DropDownPreference) preference).setValue(newKey.toString()); - previewCurrentKey((DropDownPreference) preference, newKey.toString()); - populateOtherItems((DropDownPreference) preference); - return true; - } catch (Exception e) { - Logger.e("SectionKeymap.onItemClick", "Failed setting new hotkey. " + e.getMessage()); - return false; - } - }); - } - - - private void previewCurrentKey(DropDownPreference dropDown) { - previewCurrentKey(dropDown, dropDown != null ? dropDown.getValue() : null); - } - - - private void previewCurrentKey(DropDownPreference dropDown, String key) { - if (dropDown == null) { - return; - } - - dropDown.setSummary(hotkeys.getHardwareKeyName(key)); - - if (dropDown.getKey().equals(ITEM_VOICE_INPUT)) { - dropDown.setVisible(isVoiceInputAvailable); - } - } - - - private boolean validateKey(DropDownPreference dropDown, String key) { - if (dropDown == null || key == null) { - return false; - } - - if (key.equals("0")) { - return true; - } - - for (DropDownPreference item : items) { - if (item == null) { - continue; - } - - // "Shift" and "Korean Space" can be the same key. It is properly handled in HotkeyHandler. - if ( - ( - (dropDown.getKey().equals(ITEM_SHIFT) && item.getKey().equals(ITEM_SPACE_KOREAN)) - || (dropDown.getKey().equals(ITEM_SPACE_KOREAN) && item.getKey().equals(ITEM_SHIFT)) - ) - && key.equals(item.getValue()) - ) { - continue; - } - - if (!dropDown.getKey().equals(item.getKey()) && key.equals(item.getValue())) { - Logger.i("SectionKeymap.validateKey", "Key: '" + key + "' is already in use for function: " + item.getKey()); - return false; - } - } - - return true; - } -} diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/BaseSettings.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/BaseSettings.java index 4c02a2d9..4ffbd8a6 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/BaseSettings.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/BaseSettings.java @@ -6,6 +6,8 @@ import android.content.SharedPreferences; import androidx.preference.PreferenceManager; class BaseSettings { + protected final String LOG_TAG = SettingsInput.class.getSimpleName(); + protected final Context context; protected final SharedPreferences prefs; protected final SharedPreferences.Editor prefsEditor; diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java index d30d01e1..64b9f06c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsHotkeys.java @@ -1,26 +1,113 @@ package io.github.sspanak.tt9.preferences.settings; import android.content.Context; +import android.view.KeyCharacterMap; import android.view.KeyEvent; -import java.util.HashMap; +import io.github.sspanak.tt9.util.Logger; -import io.github.sspanak.tt9.preferences.screens.hotkeys.SectionKeymap; - -class SettingsHotkeys extends SettingsHacks { +public class SettingsHotkeys extends SettingsUI { private static final String HOTKEY_VERSION = "hotkeys_v5"; + public static final String FUNC_ADD_WORD = "key_add_word"; + public static final String FUNC_BACKSPACE = "key_backspace"; + public static final String FUNC_COMMAND_PALETTE = "key_command_palette"; + public static final String FUNC_EDIT_TEXT = "key_edit_text"; + public static final String FUNC_FILTER_CLEAR = "key_filter_clear"; + public static final String FUNC_FILTER_SUGGESTIONS = "key_filter_suggestions"; + public static final String FUNC_PREVIOUS_SUGGESTION = "key_previous_suggestion"; + public static final String FUNC_NEXT_SUGGESTION = "key_next_suggestion"; + public static final String FUNC_NEXT_INPUT_MODE = "key_next_input_mode"; + public static final String FUNC_NEXT_LANGUAGE = "key_next_language"; + public static final String FUNC_SELECT_KEYBOARD = "key_select_keyboard"; + public static final String FUNC_SHIFT = "key_shift"; + public static final String FUNC_SPACE_KOREAN = "key_space_korean"; + public static final String FUNC_SHOW_SETTINGS = "key_show_settings"; + public static final String FUNC_VOICE_INPUT = "key_voice_input"; + + public static final String[] FUNCTIONS = { + FUNC_ADD_WORD, + FUNC_BACKSPACE, + FUNC_COMMAND_PALETTE, + FUNC_EDIT_TEXT, + FUNC_FILTER_CLEAR, + FUNC_FILTER_SUGGESTIONS, + FUNC_PREVIOUS_SUGGESTION, + FUNC_NEXT_SUGGESTION, + FUNC_NEXT_INPUT_MODE, + FUNC_NEXT_LANGUAGE, + FUNC_SELECT_KEYBOARD, + FUNC_SHIFT, + FUNC_SPACE_KOREAN, + FUNC_SHOW_SETTINGS, + FUNC_VOICE_INPUT, + }; + + SettingsHotkeys(Context context) { super(context); } + public boolean areHotkeysInitialized() { return !prefs.getBoolean(HOTKEY_VERSION, false); } - public void setDefaultKeys(HashMap defaultKeys) { - for (String key : defaultKeys.keySet()) { - prefsEditor.putString(key, String.valueOf(defaultKeys.get(key))); + + /** + * Applies the default hotkey scheme. + * When a standard "Backspace" hardware key is available, "Backspace" hotkey association is not necessary, + * so it will be left out blank, to allow the hardware key do its job. + * When the on-screen keyboard is on, "Back" is also not associated, because it will cause weird user + * experience. Instead the on-screen "Backspace" key can be used. + * Arrow keys for manipulating suggestions are also assigned only if available. + */ + public void setDefaultKeys() { + // no default keys + String[] unassigned = { FUNC_ADD_WORD, FUNC_EDIT_TEXT, FUNC_SELECT_KEYBOARD, FUNC_SHOW_SETTINGS, FUNC_VOICE_INPUT }; + for (String key : unassigned) { + prefsEditor.putString(key, String.valueOf(KeyEvent.KEYCODE_UNKNOWN)); } + // backspace + if ( + KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_CLEAR) + || KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL) + || isMainLayoutNumpad() + ) { + prefsEditor.putString(FUNC_BACKSPACE, String.valueOf(KeyEvent.KEYCODE_UNKNOWN)); + } else { + prefsEditor.putString(FUNC_BACKSPACE, String.valueOf(KeyEvent.KEYCODE_BACK)); + } + + // filter clear + prefsEditor.putString( + FUNC_FILTER_CLEAR, + String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_DOWN) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_UNKNOWN) + ); + + // filter + prefsEditor.putString( + FUNC_FILTER_SUGGESTIONS, + String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_UP) ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_UNKNOWN) + ); + + // previous suggestion + prefsEditor.putString( + FUNC_PREVIOUS_SUGGESTION, + String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_LEFT) ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_UNKNOWN) + ); + + // next suggestion + prefsEditor.putString( + FUNC_NEXT_SUGGESTION, + String.valueOf(KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DPAD_RIGHT) ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_UNKNOWN) + ); + + prefsEditor.putString(FUNC_COMMAND_PALETTE, String.valueOf(-KeyEvent.KEYCODE_STAR)); // negative means "hold" + prefsEditor.putString(FUNC_NEXT_INPUT_MODE, String.valueOf(KeyEvent.KEYCODE_POUND)); + prefsEditor.putString(FUNC_NEXT_LANGUAGE, String.valueOf(-KeyEvent.KEYCODE_POUND)); // negative means "hold" + prefsEditor.putString(FUNC_SHIFT, String.valueOf(KeyEvent.KEYCODE_STAR)); + prefsEditor.putString(FUNC_SPACE_KOREAN, String.valueOf(KeyEvent.KEYCODE_STAR)); + prefsEditor.putBoolean(HOTKEY_VERSION, true).apply(); } @@ -30,49 +117,84 @@ class SettingsHotkeys extends SettingsHacks { } + public void setFunctionKey(String functionName, int keyCode) { + if (isValidFunction(functionName)) { + Logger.d(LOG_TAG, "Setting hotkey for function: '" + functionName + "' to " + keyCode); + prefsEditor.putString(functionName, String.valueOf(keyCode)).apply(); + } else { + Logger.w(LOG_TAG,"Not setting a hotkey for invalid function: '" + functionName + "'"); + } + } + + public int getKeyAddWord() { - return getFunctionKey(SectionKeymap.ITEM_ADD_WORD); + return getFunctionKey(FUNC_ADD_WORD); } public int getKeyBackspace() { - return getFunctionKey(SectionKeymap.ITEM_BACKSPACE); + return getFunctionKey(FUNC_BACKSPACE); } public int getKeyCommandPalette() { - return getFunctionKey(SectionKeymap.ITEM_COMMAND_PALETTE); + return getFunctionKey(FUNC_COMMAND_PALETTE); } public int getKeyEditText() { - return getFunctionKey(SectionKeymap.ITEM_EDIT_TEXT); + return getFunctionKey(FUNC_EDIT_TEXT); } public int getKeyFilterClear() { - return getFunctionKey(SectionKeymap.ITEM_FILTER_CLEAR); + return getFunctionKey(FUNC_FILTER_CLEAR); } public int getKeyFilterSuggestions() { - return getFunctionKey(SectionKeymap.ITEM_FILTER_SUGGESTIONS); + return getFunctionKey(FUNC_FILTER_SUGGESTIONS); } public int getKeyPreviousSuggestion() { - return getFunctionKey(SectionKeymap.ITEM_PREVIOUS_SUGGESTION); + return getFunctionKey(FUNC_PREVIOUS_SUGGESTION); } public int getKeyNextSuggestion() { - return getFunctionKey(SectionKeymap.ITEM_NEXT_SUGGESTION); + return getFunctionKey(FUNC_NEXT_SUGGESTION); } public int getKeyNextInputMode() { - return getFunctionKey(SectionKeymap.ITEM_NEXT_INPUT_MODE); + return getFunctionKey(FUNC_NEXT_INPUT_MODE); } public int getKeyNextLanguage() { - return getFunctionKey(SectionKeymap.ITEM_NEXT_LANGUAGE); + return getFunctionKey(FUNC_NEXT_LANGUAGE); } public int getKeySelectKeyboard() { - return getFunctionKey(SectionKeymap.ITEM_SELECT_KEYBOARD); + return getFunctionKey(FUNC_SELECT_KEYBOARD); } public int getKeyShift() { - return getFunctionKey(SectionKeymap.ITEM_SHIFT); + return getFunctionKey(FUNC_SHIFT); } public int getKeySpaceKorean() { - return getFunctionKey(SectionKeymap.ITEM_SPACE_KOREAN); + return getFunctionKey(FUNC_SPACE_KOREAN); } public int getKeyShowSettings() { - return getFunctionKey(SectionKeymap.ITEM_SHOW_SETTINGS); + return getFunctionKey(FUNC_SHOW_SETTINGS); } public int getKeyVoiceInput() { - return getFunctionKey(SectionKeymap.ITEM_VOICE_INPUT); + return getFunctionKey(FUNC_VOICE_INPUT); + } + + + public String getFunction(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + return null; + } + + for (String key : FUNCTIONS) { + if (keyCode == getFunctionKey(key)) { + return key; + } + } + + return null; + } + + + private boolean isValidFunction(String functionName) { + for (String validName : FUNCTIONS) { + if (validName.equals(functionName)) { + return true; + } + } + return false; } } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java index 7cb841df..e0fdc666 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsInput.java @@ -10,9 +10,7 @@ import java.util.Set; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.util.Logger; -class SettingsInput extends SettingsHotkeys { - private final String LOG_TAG = SettingsInput.class.getSimpleName(); - +class SettingsInput extends SettingsHacks { SettingsInput(Context context) { super(context); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java index 51c9b9d9..3391f166 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsStore.java @@ -3,7 +3,7 @@ package io.github.sspanak.tt9.preferences.settings; import android.content.Context; -public class SettingsStore extends SettingsUI { +public class SettingsStore extends SettingsHotkeys { public SettingsStore(Context context) { super(context); } /************* internal settings *************/ diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/UI.java b/app/src/main/java/io/github/sspanak/tt9/ui/UI.java index 01afbefc..4459ebf3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/UI.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/UI.java @@ -1,8 +1,10 @@ package io.github.sspanak.tt9.ui; import android.app.AlertDialog; +import android.app.Dialog; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.inputmethodservice.InputMethodService; import android.os.Looper; @@ -55,24 +57,28 @@ public class UI { } - public static void confirm(Context context, String title, String message, String OKLabel, Runnable onOk, Runnable onCancel) { + public static void confirm(@NonNull Context context, String title, String message, String OKLabel, Runnable onOk, boolean cancelLabel, Runnable onCancel, DialogInterface.OnKeyListener onKey) { + Dialog dialogue; + if (DeviceInfo.AT_LEAST_ANDROID_12) { - new MaterialAlertDialogBuilder(context) - .setTitle(title) + dialogue = new MaterialAlertDialogBuilder(context) .setMessage(message) .setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); }) - .setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); }) - .setCancelable(false) + .setNegativeButton(cancelLabel ? context.getString(android.R.string.cancel) : null, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); }) .show(); } else { - new AlertDialog.Builder(context) - .setTitle(title) + dialogue = new AlertDialog.Builder(context) .setMessage(message) .setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); }) - .setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); }) - .setCancelable(false) + .setNegativeButton(cancelLabel ? context.getString(android.R.string.cancel) : null, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); }) .show(); } + + dialogue.setTitle(title); + dialogue.setCancelable(false); + if (onKey != null) { + dialogue.setOnKeyListener(onKey); + } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/PopupDialog.java b/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/PopupDialog.java index 66a1c3ef..c8eb144e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/PopupDialog.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/dialogs/PopupDialog.java @@ -30,7 +30,7 @@ abstract public class PopupDialog { } protected void render(Runnable OKAction) { - UI.confirm(context, title, message, OKLabel, OKAction, () -> activityFinisher.accept("")); + UI.confirm(context, title, message, OKLabel, OKAction, true, () -> activityFinisher.accept(""), null); } abstract void render(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index daa2039f..a96c6333 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -152,6 +152,10 @@ Press and hold to open in the browser. https://www.buymeacoffee.com/sspanak + Key \"%1$s\" is already assigned to function \"%2$s\". Do you want to assign it to \"%3$s\"? + * Press or hold a key to assign it to the \"%1$s\" function.\n\n* Press 0 to cancel.\n\n* Press 2 to disable the function. + Reassign + Add Word Adding words is not possible in this language. Backspace @@ -195,6 +199,7 @@ System spell checker may interfere with adding words. Click here to disable. Disabled + Key (hold) -- Back diff --git a/app/src/main/res/xml/prefs_screen_hotkeys.xml b/app/src/main/res/xml/prefs_screen_hotkeys.xml index 9097ec77..480ddd16 100644 --- a/app/src/main/res/xml/prefs_screen_hotkeys.xml +++ b/app/src/main/res/xml/prefs_screen_hotkeys.xml @@ -2,63 +2,63 @@ - - - - - - - - - - - - - - -