diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 990073c2..fed9250e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -105,6 +105,16 @@ 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 Keys
+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.
+
+- Find [preferences/helpers/Hotkeys.java](io/github/sspanak/tt9/preferences/helpers/Hotkeys.java).
+- In the file, 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/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 21152d10..9c9aecfc 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -50,4 +50,6 @@
Нов ред
Интервал
Напишете дума…
+ Клавиши в обратен ред
+ Включете настройката, ако на първият ред са 7–8–9, вместо 1–2–3.
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 71811193..a4d0a3a0 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -31,4 +31,6 @@
Carácter cuando se presiona \"0\" varias veces
Teclado en pantalla
Diccionario cargado con éxito.
+ Orden de teclas inverso
+ Habilite la configuración si hay 7–8–9 en la primera fila, en lugar de 1–2–3.
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 1ee02445..9ac2f3c7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -41,4 +41,6 @@
Nouvelle ligne
Caractère lorsque «0» est appuyé plusieurs fois
Tapez un mot…
+ Inverser l\'ordre des clés
+ Activez le paramètre s\'il y a 7–8–9 sur le premier rang, au lieu de 1–2–3.
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e259f3be..db8b574d 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,5 +27,7 @@
Tastiera
Spazio
Nuova riga
+ Invertire l\'ordine delle chiavi
+ Abilita l\'impostazione se ci sono 7–8–9 sulla prima riga, invece di 1–2–3.
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 144967b2..c2fb58fb 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -50,4 +50,6 @@
Назад
Позвонить
Введите слово…
+ Перевернутая клавиатура
+ Используйте настройку, если в первом ряду 7–8–9 вместо 1–2–3.
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 6c57552b..3b754668 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -50,4 +50,6 @@
Немає словника для мови «%1$s». Перейдіть до Налаштувань, щоб завантажити його.
Помилка завантаження. Недійсне слово «%1$s» в рядку %2$d мови «%3$s».
Введіть слово…
+ Зворотна клавіатура
+ Використовуйте налаштування, якщо 7–8–9 у першому рядку замість 1–2–3.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bb0fcb02..c0e71431 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -29,6 +29,8 @@
Character for Multiple 0-key Press
Show On-Screen Keys
Help
+ Reverse Key Order
+ Use this if you have 7–8–9 on the first row, instead of 1–2–3.
Cancel loading
Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".
@@ -56,13 +58,7 @@
Back
Call
Delete
- F1
- F2
- F3
- F4
Menu
- #
- ✱
.
New Line
diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml
index 9ddaa431..3ceb77c0 100644
--- a/res/xml/prefs.xml
+++ b/res/xml/prefs.xml
@@ -88,11 +88,20 @@
+
+
+
+
0);
- case KeyEvent.KEYCODE_1:
- case KeyEvent.KEYCODE_2:
- case KeyEvent.KEYCODE_3:
- case KeyEvent.KEYCODE_4:
- case KeyEvent.KEYCODE_5:
- case KeyEvent.KEYCODE_6:
- case KeyEvent.KEYCODE_7:
- case KeyEvent.KEYCODE_8:
- case KeyEvent.KEYCODE_9:
- return onNumber(Key.codeToNumber(keyCode), false, numKeyRepeatCounter);
case KeyEvent.KEYCODE_STAR: return onStar();
case KeyEvent.KEYCODE_POUND: return onPound();
}
diff --git a/src/io/github/sspanak/tt9/ime/helpers/Key.java b/src/io/github/sspanak/tt9/ime/helpers/Key.java
index cfe515ad..ac5963de 100644
--- a/src/io/github/sspanak/tt9/ime/helpers/Key.java
+++ b/src/io/github/sspanak/tt9/ime/helpers/Key.java
@@ -6,7 +6,9 @@ import io.github.sspanak.tt9.preferences.SettingsStore;
public class Key {
public static boolean isNumber(int keyCode) {
- return keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9;
+ return
+ (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9)
+ || (keyCode >= KeyEvent.KEYCODE_NUMPAD_0 && keyCode <= KeyEvent.KEYCODE_NUMPAD_9);
}
@@ -27,28 +29,38 @@ public class Key {
}
- public static int codeToNumber(int keyCode) {
+ public static int codeToNumber(SettingsStore settings, int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_0:
+ case KeyEvent.KEYCODE_NUMPAD_0:
return 0;
case KeyEvent.KEYCODE_1:
- return 1;
+ case KeyEvent.KEYCODE_NUMPAD_1:
+ return settings.getUpsideDownKeys() ? 7 : 1;
case KeyEvent.KEYCODE_2:
- return 2;
+ case KeyEvent.KEYCODE_NUMPAD_2:
+ return settings.getUpsideDownKeys() ? 8 : 2;
case KeyEvent.KEYCODE_3:
- return 3;
+ case KeyEvent.KEYCODE_NUMPAD_3:
+ return settings.getUpsideDownKeys() ? 9 : 3;
case KeyEvent.KEYCODE_4:
+ case KeyEvent.KEYCODE_NUMPAD_4:
return 4;
case KeyEvent.KEYCODE_5:
+ case KeyEvent.KEYCODE_NUMPAD_5:
return 5;
case KeyEvent.KEYCODE_6:
+ case KeyEvent.KEYCODE_NUMPAD_6:
return 6;
case KeyEvent.KEYCODE_7:
- return 7;
+ case KeyEvent.KEYCODE_NUMPAD_7:
+ return settings.getUpsideDownKeys() ? 1 : 7;
case KeyEvent.KEYCODE_8:
- return 8;
+ case KeyEvent.KEYCODE_NUMPAD_8:
+ return settings.getUpsideDownKeys() ? 2 : 8;
case KeyEvent.KEYCODE_9:
- return 9;
+ case KeyEvent.KEYCODE_NUMPAD_9:
+ return settings.getUpsideDownKeys() ? 3 : 9;
default:
return -1;
}
diff --git a/src/io/github/sspanak/tt9/preferences/SettingsStore.java b/src/io/github/sspanak/tt9/preferences/SettingsStore.java
index 246bda4c..c878eb84 100644
--- a/src/io/github/sspanak/tt9/preferences/SettingsStore.java
+++ b/src/io/github/sspanak/tt9/preferences/SettingsStore.java
@@ -45,6 +45,7 @@ public class SettingsStore {
return true;
}
+ @SuppressWarnings("SameParameterValue")
private boolean isIntInList(int number, ArrayList list, String logTag, String logMsg) {
if (!list.contains(number)) {
Logger.w(logTag, logMsg);
@@ -209,6 +210,7 @@ public class SettingsStore {
public boolean getAutoSpace() { return prefs.getBoolean("auto_space", false); }
public boolean getAutoTextCase() { return prefs.getBoolean("auto_text_case", true); }
public String getDoubleZeroChar() { return prefs.getString("pref_double_zero_char", " "); }
+ public boolean getUpsideDownKeys() { return prefs.getBoolean("pref_upside_down_keys", false); }
/************* internal settings *************/
diff --git a/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java b/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java
new file mode 100644
index 00000000..31322be3
--- /dev/null
+++ b/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java
@@ -0,0 +1,135 @@
+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.LinkedHashMap;
+import java.util.Set;
+
+import io.github.sspanak.tt9.R;
+
+public class Hotkeys {
+ private final Context context;
+ private final Resources resources;
+ private final String holdKeyTranslation;
+
+ private final LinkedHashMap 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 get(String key) {
+ return KEYS.get(key);
+ }
+
+
+ public Set toSet() {
+ return KEYS.keySet();
+ }
+
+
+ /**
+ * 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) {
+ KEYS.put(String.valueOf(code), name);
+
+ if (allowHold) {
+ 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
+ * These keys will appears as options only if Android says the device has them.
+ *
+ * NOTE: Some TT9 functions do not support all keys. Here you just list all possible options.
+ * Actual validation and assigning happens in SectionKeymap.populate().
+ *
+ * NOTE 2: Holding is deliberately skipped for most of the keys.
+ * It's because handling holding requires short press event to be consumed in
+ * KeyPadHandler, as well.
+ *
+ * From user perspective, when holding is assigned to a function,
+ * short press will also stop performing its default system action, which may be confusing.
+ * And in order to avoid lengthy explanations in the documentation (that no one reads),
+ * the problem is avoided by simply not causing it.
+ */
+ private void generateList() {
+ add(KeyEvent.KEYCODE_CALL, R.string.key_call, false);
+
+ addIfDeviceHasKey(KeyEvent.KEYCODE_BACK, R.string.key_back, false);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_DEL, R.string.key_delete, false);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_F1, "F1", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_F2, "F2", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_F3, "F3", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_F4, "F4", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_MENU, R.string.key_menu, false);
+
+ add(KeyEvent.KEYCODE_POUND, "#", true);
+ add(KeyEvent.KEYCODE_STAR, "✱", true);
+
+ addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_ADD, "Num +", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, "Num -", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, "Num *", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_DIVIDE, "Num /", true);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_NUMPAD_DOT, "Num .", true);
+ }
+}
diff --git a/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java b/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java
index d97091e2..52790069 100644
--- a/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java
+++ b/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java
@@ -1,21 +1,16 @@
package io.github.sspanak.tt9.preferences.items;
import android.content.Context;
-import android.content.res.Resources;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.ViewConfiguration;
import androidx.preference.DropDownPreference;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.LinkedHashMap;
import java.util.Objects;
import io.github.sspanak.tt9.Logger;
-import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.SettingsStore;
+import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
public class SectionKeymap {
public static final String ITEM_ADD_WORD = "key_add_word";
@@ -24,15 +19,15 @@ public class SectionKeymap {
public static final String ITEM_NEXT_LANGUAGE = "key_next_language";
public static final String ITEM_SHOW_SETTINGS = "key_show_settings";
- private final LinkedHashMap KEYS = new LinkedHashMap<>();
+ private final Hotkeys hotkeys;
private final Collection items;
private final SettingsStore settings;
public SectionKeymap(Collection dropDowns, Context context, SettingsStore settings) {
items = dropDowns;
+ hotkeys = new Hotkeys(context);
this.settings = settings;
- generateKeyList(context);
}
@@ -60,95 +55,7 @@ public class SectionKeymap {
}
- /**
- * generateKeyList
- * Generates the key list to be used in each dropdown.
- *
- * NOTE: Some dropdowns do not support some keys,but filtering is performed
- * in populate(), not here.
- *
- * NOTE 2: Holding is deliberately skipped for most of the keys. It's because
- * when a function is assigned only to the "hold" state, the "short press" state
- * also gets consumed in KeyPadHandler, effectively disabling the default function of the key.
- * However, this may be confusing from user perspective, so in order to avoid lengthy
- * explanations in the documentation (that no one reads), the problem is avoided by
- * simply not causing it.
- *
- * So, if adding support for new keys, think twice whether to add the "hold" variant as well.
- */
- private void generateKeyList(Context context) {
- Resources resources = context.getResources();
- KEYS.put(String.valueOf(0), resources.getString(R.string.key_none));
-
- // BACK
- if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK)) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_BACK), resources.getString(R.string.key_back));
- }
-
- // CALL
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_CALL), resources.getString(R.string.key_call));
-
- // DELETE / BACKSPACE
- if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_DEL)) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_DEL), resources.getString(R.string.key_delete));
- }
-
- // F1
- if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F1)) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_F1), resources.getString(R.string.key_f1));
- KEYS.put(
- String.valueOf(-KeyEvent.KEYCODE_F1),
- resources.getString(R.string.key_f1) + " " + resources.getString(R.string.key_hold_key)
- );
- }
-
- // F2
- if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F2)) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_F2), resources.getString(R.string.key_f2));
- KEYS.put(
- String.valueOf(-KeyEvent.KEYCODE_F2),
- resources.getString(R.string.key_f2) + " " + resources.getString(R.string.key_hold_key)
- );
- }
-
- // F3
- if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F3)) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_F3), resources.getString(R.string.key_f3));
- KEYS.put(
- String.valueOf(-KeyEvent.KEYCODE_F3),
- resources.getString(R.string.key_f3) + " " + resources.getString(R.string.key_hold_key)
- );
- }
-
- // F4
- if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_F4)) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_F4), resources.getString(R.string.key_f4));
- KEYS.put(
- String.valueOf(-KeyEvent.KEYCODE_F4),
- resources.getString(R.string.key_f4) + " " + resources.getString(R.string.key_hold_key)
- );
- }
-
- // MENU
- if (ViewConfiguration.get(context).hasPermanentMenuKey()) {
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_MENU), resources.getString(R.string.key_menu));
- }
-
- // #
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_POUND), resources.getString(R.string.key_pound));
- KEYS.put(
- String.valueOf(-KeyEvent.KEYCODE_POUND),
- resources.getString(R.string.key_pound) + " " + resources.getString(R.string.key_hold_key)
- );
-
- // *
- KEYS.put(String.valueOf(KeyEvent.KEYCODE_STAR), resources.getString(R.string.key_star));
- KEYS.put(
- String.valueOf(-KeyEvent.KEYCODE_STAR),
- resources.getString(R.string.key_star) + " " + resources.getString(R.string.key_hold_key)
- );
- }
private void populateOtherItems(DropDownPreference itemToSkip) {
@@ -169,7 +76,7 @@ public class SectionKeymap {
}
ArrayList keys = new ArrayList<>();
- for (String key : KEYS.keySet()) {
+ for (String key : hotkeys.toSet()) {
if (
validateKey(dropDown, String.valueOf(key))
// backspace works both when pressed short and long,
@@ -185,7 +92,7 @@ public class SectionKeymap {
ArrayList values = new ArrayList<>();
for (String key : keys) {
- values.add(KEYS.get(key));
+ values.add(hotkeys.get(key));
}
dropDown.setEntries(values.toArray(new CharSequence[0]));
@@ -222,7 +129,7 @@ public class SectionKeymap {
return;
}
- dropDown.setSummary(KEYS.get(key));
+ dropDown.setSummary(hotkeys.get(key));
}