full hardware key support
This commit is contained in:
parent
ddabff76a4
commit
3626ac24ea
20 changed files with 542 additions and 494 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Integer, Ternary> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, String> 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<String> 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<String, Integer> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, PreferenceHotkey> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PreferenceHotkey> hotkeys;
|
||||
|
||||
|
||||
ItemResetKeys(Preference item, PreferencesActivity activity, SectionKeymap dropdowns) {
|
||||
ItemResetKeys(@Nullable Preference item, @NonNull PreferencesActivity activity, @NonNull Iterable<PreferenceHotkey> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DropDownPreference> items;
|
||||
private final SettingsStore settings;
|
||||
private final boolean isVoiceInputAvailable;
|
||||
|
||||
|
||||
public SectionKeymap(Collection<DropDownPreference> 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<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<String, Integer> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 *************/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -152,6 +152,10 @@
|
|||
<string name="donate_hold_to_open">Press and hold to open in the browser.</string>
|
||||
<string name="donate_url" translatable="false">https://www.buymeacoffee.com/sspanak</string>
|
||||
|
||||
<string name="function_already_assigned">Key \"%1$s\" is already assigned to function \"%2$s\". Do you want to assign it to \"%3$s\"?</string>
|
||||
<string name="function_assign_instructions">* 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.</string>
|
||||
<string name="function_reassign">Reassign</string>
|
||||
|
||||
<string name="function_add_word">Add Word</string>
|
||||
<string name="function_add_word_not_available">Adding words is not possible in this language.</string>
|
||||
<string name="function_backspace">Backspace</string>
|
||||
|
|
@ -195,6 +199,7 @@
|
|||
<string name="setup_spell_checker_on">System spell checker may interfere with adding words. Click here to disable.</string>
|
||||
<string name="setup_spell_checker_off">Disabled</string>
|
||||
|
||||
<string name="key_key">Key</string>
|
||||
<string name="key_hold_key">(hold)</string>
|
||||
<string name="key_none" translatable="false">--</string>
|
||||
<string name="key_back">Back</string>
|
||||
|
|
|
|||
|
|
@ -2,63 +2,63 @@
|
|||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:orderingFromXml="true">
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_add_word"
|
||||
app:title="@string/function_add_word" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceBackspaceHotkey
|
||||
app:key="key_backspace"
|
||||
app:title="@string/function_backspace" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_command_palette"
|
||||
app:title="@string/function_show_command_palette" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_filter_clear"
|
||||
app:title="@string/function_filter_clear" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_edit_text"
|
||||
app:title="@string/function_edit_text" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_filter_suggestions"
|
||||
app:title="@string/function_filter_suggestions" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_previous_suggestion"
|
||||
app:title="@string/function_previous_suggestion" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_next_suggestion"
|
||||
app:title="@string/function_next_suggestion" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_next_language"
|
||||
app:title="@string/function_next_language" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_next_input_mode"
|
||||
app:title="@string/function_next_mode" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_select_keyboard"
|
||||
app:title="@string/function_select_keyboard" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_shift"
|
||||
app:title="@string/virtual_key_shift" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_space_korean"
|
||||
app:title="@string/virtual_key_space_korean" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceHotkey
|
||||
app:key="key_show_settings"
|
||||
app:title="@string/function_show_settings" />
|
||||
|
||||
<DropDownPreference
|
||||
<io.github.sspanak.tt9.preferences.screens.hotkeys.PreferenceVoiceInputHotkey
|
||||
app:key="key_voice_input"
|
||||
app:title="@string/function_voice_input" />
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue