diff --git a/README.md b/README.md
index 3f25246a3..f40742fc6 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@ This is an updated version of the [original project](https://github.com/Clam-/Tr
or get the APK from the [Releases Section](https://github.com/sspanak/tt9/releases/latest).
## System Requirements
-- Android 4.4 or higher. _(Tested and confirmed on Android 4.4.2, 5.1.1 and 11)_
+- Android 4.4 or higher. _(Tested and confirmed on Android 4.4.2, 10 and 11)_
- Free space:
- Minimum 25 Mb when not using Predictive mode and no dictionaries are loaded.
- Plenty of space per each enabled language in Predictive mode (25-100 Mb, depending on the word count).
-- A hardware keypad or a keyboard. The application is not usable on touchscreen-only devices.
+- A hardware keypad or a keyboard. For touchscreen-only devices, an on-screen keypad can be enabled in the Settings.
_If you own a phone with Android 2.2 up to 4.4, please refer to the original version of Traditional T9 from 2016._
@@ -24,9 +24,9 @@ Before using Traditional T9 for the first time you would need to load a dictiona
So make sure to read the initial setup and the hotkey tips in the [user manual](docs/user-manual.md).
## Contributing to the Project
-As with many other open-source projects, this one is also maintained by its author in his free time. Any help in making Traditional T9 better will be highly appreciated. Here is what you could do:
-- [Report bugs](https://github.com/sspanak/tt9/issues) or other unusual behavior on different phones. Currently, the only testing and development device is: Qin F21 Pro+ / Android 11.
-- Add [a new language](CONTRIBUTING.md#adding-a-new-language), [new UI translations](CONTRIBUTING.md#translating-the-ui) or simply fix a spelling mistake. The process is very simple and even with minimum techincal knowledge, your skills as a native speaker will be of great use. Or, if you are not tech-savvy, just [open a new issue](https://github.com/sspanak/tt9/issues) and put the correct translations there.
+As with many other open-source projects, this one is also maintained by its author in his free time. Any help in making Traditional T9 better will be highly appreciated. Here is how:
+- [Report bugs](https://github.com/sspanak/tt9/issues) or other unusual behavior on different phones. Currently, the only testing and development devices are: Qin F21 Pro+ / Android 11; Energizer H620SEU / Android 10. But Android behaviour and appearance varies a lot across the millions of devices available out there.
+- Add [a new language](CONTRIBUTING.md#adding-a-new-language), [new UI translations](CONTRIBUTING.md#translating-the-ui) or simply fix a spelling mistake. The process is very simple and even with minimum technical knowledge, your skills as a native speaker will be of great use. Or, if you are not tech-savvy, just [open a new issue](https://github.com/sspanak/tt9/issues) and put the correct translations there.
- Experienced developers who are willing fix a bug, or maybe create a brand new feature, see the [Contribution Guide](CONTRIBUTING.md).
Your PRs are welcome!
diff --git a/docs/user-manual.md b/docs/user-manual.md
index 1a579d23d..18ca8a9b5 100644
--- a/docs/user-manual.md
+++ b/docs/user-manual.md
@@ -1,15 +1,16 @@
# Traditional T9
-TT9 is an IME (Input Method Editor) for Android devices with hardware keypad. It supports multiple languages and predictive text typing. _NOTE: TT9 is not usable on touchscreen-only devices._
+TT9 is an IME (Input Method Editor) for Android devices with a hardware keypad. It supports multiple languages and predictive text typing, and an on-screen numpad for touchscreen devices.
All source code, documentation and the privacy policy are available on Github: [https://github.com/sspanak/tt9](https://github.com/sspanak/tt9).
## Initial Setup
-After installing, in order to use Traditional T9, you need to enable it as an Android keyboard. To do so, click on the launcher icon. If you
-need to take any action, you will see the Initial Setup screen, where you will be prompted to enable TT9 and set it as default system
-keyboard.
+After installing, in order to use Traditional T9, you need to enable it as an Android keyboard. To do so, click on the launcher icon. If you need to take any action, all options besides Initial Setup would be disabled and there would be a label saying TT9 is disabled. Go to Initial Setup and enable it.
_If you don't see the icon right after installing, restart your phone and it should appear. Android is trying to save some battery life by not refreshing the newly installed apps list in some cases._
+### Using on a touchscreen-only phone
+If your phone does not have a hardware keypad, check out the [On-screen Keypad section](#on-screen-keypad).
+
### Enabling Predictive Mode
With the default settings, it is only possible to type in 123 and ABC modes. In order to enable the Predictive mode, there are additional steps:
@@ -47,8 +48,8 @@ Select next word/letter suggestion.
#### D-pad Right (→):
_Predictive mode only._
-- **Single press**: Filter the suggestion list, leaving out only the ones that start with the current word. It doesn't matter if it is a complete word or not. For example, type "rewin" and press Right. It will leave out all words starting with "rewin": "rewin" itself, "rewind", "rewinds", "rewinded", "rewinding", and so on.
-- **Double press**: Expand the filter to the full suggestion. For example, type "rewin" and press Right twice. It will first filter by "rewin", then expand the filter to "rewind". You can keep expanding the filter with Right, until you get to the longest suggestion in the list.
+- **Single press**: Filter the suggestion list, leaving out only the ones that start with the current word. It doesn't matter if it is a complete word or not. For example, type "remin" and press Right. It will leave out all words starting with "remin": "remin" itself, "remind", "reminds", "reminded", "reminding", and so on.
+- **Double press**: Expand the filter to the full suggestion. For example, type "remin" and press Right twice. It will first filter by "remin", then expand the filter to "remind". You can keep expanding the filter with Right, until you get to the longest suggestion in the list.
Filtering can also be used to type unknown words. Let's say you want to type "Anakin", which is not in the dictionary. Start with "A", then press Right to hide "B" and "C". Now press 6-key. Since the filter is on, in addition to the real dictionary words, it will provide all possible combinations for 6: "Am", "An", "Ao". Select "An" and press Right to confirm your selection. Now pressing 2-key, will provide "Ana", "Anb", "Anc". You can keep going, until you complete "Anakin".
@@ -103,19 +104,17 @@ _In these cases, you could assign another key (all other keys are fully usable),
- **Number-only fields:** No special action. Type a "#" with the default key. Changing the mode is not possible in such fields.
#### Next Language Key (Default: Hold #):
-Select the next language, when mulitple languages have been enabled from the Settings.
+Select the next language, when multiple languages have been enabled from the Settings.
#### Settings Key (Default: Hold ✱):
-Open the Configration screen.
+Open the Settings configuration screen.
-## On-screen Soft keys
-All functionality is available using the keypad, but for convenience, on touchscreen phones, you could also use the on-screen keys. If you instead prefer to have more screen space, disable them from the Settings.
+## On-screen Keypad
+On touchscreen-only phones, a fully functional on-screen keypad is available. Enable it from Settings -> Appearance -> Show On-Screen Numpad.
-#### Left Soft Key:
-Open the [Settings screen](#settings-screen).
+It is also recommended to disable the special behavior of "Back" key working as "Backspace". It is useful only for a hardware keypad. To do so, go to: Settings -> Keyboard -> Select Hotkeys -> Backspace key, then select the "--" option.
-#### Right Soft Key:
-Backspace.
+If you do have a hardware keypad and prefer having more screen space, disable the software keys from the Settings -> Appearance.
## Settings Screen
On the Settings screen, you can choose languages for typing, configure the keypad hotkeys or change the application appearance.
diff --git a/res/layout/main_numpad.xml b/res/layout/main_numpad.xml
new file mode 100644
index 000000000..c09a636c2
--- /dev/null
+++ b/res/layout/main_numpad.xml
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/mainview.xml b/res/layout/main_small.xml
similarity index 73%
rename from res/layout/mainview.xml
rename to res/layout/main_small.xml
index 6d1c4e028..3d49cf0ab 100644
--- a/res/layout/mainview.xml
+++ b/res/layout/main_small.xml
@@ -1,13 +1,14 @@
+ android:layout_height="@dimen/candidate_height">
+ android:orientation="horizontal"
+ tools:ignore="HardcodedText,KeyboardInaccessibleWidget">
-
+ android:textSize="@dimen/soft_key_icon_size" />
-
+ android:text="@android:string/ok" />
-
+ android:textSize="@dimen/soft_key_icon_size" />
diff --git a/res/layout/suggestion_list_view.xml b/res/layout/suggestion_list.xml
similarity index 74%
rename from res/layout/suggestion_list_view.xml
rename to res/layout/suggestion_list.xml
index e86f27448..5233e5884 100644
--- a/res/layout/suggestion_list_view.xml
+++ b/res/layout/suggestion_list.xml
@@ -2,15 +2,14 @@
diff --git a/res/layout/suggestion_list_numpad.xml b/res/layout/suggestion_list_numpad.xml
new file mode 100644
index 000000000..182049578
--- /dev/null
+++ b/res/layout/suggestion_list_numpad.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index b372543d6..e1bd54f5e 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -59,4 +59,7 @@
Включете настройката, ако на първият ред са 7–8–9, вместо 1–2–3.
Изтрий неизбраните
Начална настройка
+ Цифрова клавиатура на екрана
+ Намаляне на звук
+ Усилване на звук
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 61f0d9bb3..f2193c9eb 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -35,4 +35,5 @@
Orden de teclas inverso
Habilite la configuración si hay 7–8–9 en la primera fila, en lugar de 1–2–3.
Configuración inicial
+ Teclado numérico en pantalla
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 95c8aea94..79fa297b5 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -50,4 +50,5 @@
Activez le paramètre s\'il y a 7–8–9 sur le premier rang, au lieu de 1–2–3.
Vider les non sélectionnés
Configuration initiale
+ Pavé numérique à l\'écran
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 6291faf7d..b172c5fe8 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -59,4 +59,7 @@
Используйте настройку, если в первом ряду 7–8–9 вместо 1–2–3.
Очистить невыбранные
Начальная настройка
+ Экранная цифровая клавиатура
+ Уменьшить громкости
+ Увеличить громкости
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 15b1f07c0..2c8b4bc49 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -59,4 +59,7 @@
Використовуйте налаштування, якщо 7–8–9 у першому рядку замість 1–2–3.
Очистіть невибрані
Початкове налаштування
+ Екранна цифрова клавіатура
+ Збільшення гучності
+ Зменшення гучності
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6314cd6f8..fb6a1de4e 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -8,10 +8,16 @@
#AAAAAA
#888888
+ #E7F0E7
+ #CCC
+
#C0C0C0
- #333333
+ #2C2C2C
#CCCCCC
#555555
+
+ #353835
+ #555
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 782c8b875..1263f9849 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -1,9 +1,8 @@
- 26sp
18sp
+ 26sp
6sp
- 1sp
44dp
24sp
@@ -14,4 +13,13 @@
18dp
22sp
19sp
+
+
+ 5dp
+ 15dp
+ 56dp
+
+ 17sp
+ 32dp
+ 36dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4edf7fc6e..a46c5d0d3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -29,6 +29,8 @@
Dark Theme
Character for Double 0-key Press
Show On-Screen Keys
+ Show On-Screen Numpad
+ (BETA)
Help
Reverse Key Order
Use this if you have 7–8–9 on the first row, instead of 1–2–3.
@@ -72,6 +74,8 @@
Menu
Left Func
Right Func
+ Volume Down
+ Volume Up
.
New Line
diff --git a/res/values/styles.xml b/res/values/styles.xml
index bef886037..f2c88d9f3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -6,4 +6,9 @@
- 1dp
- 1dp
+
+
diff --git a/res/xml/prefs_screen_appearance.xml b/res/xml/prefs_screen_appearance.xml
index a0ed12829..70f6df444 100644
--- a/res/xml/prefs_screen_appearance.xml
+++ b/res/xml/prefs_screen_appearance.xml
@@ -13,4 +13,12 @@
app:layout="@layout/pref_switch"
app:title="@string/pref_show_soft_function_keys" />
+
+
diff --git a/src/io/github/sspanak/tt9/Logger.java b/src/io/github/sspanak/tt9/Logger.java
index 83264a2a8..20fc723c1 100644
--- a/src/io/github/sspanak/tt9/Logger.java
+++ b/src/io/github/sspanak/tt9/Logger.java
@@ -3,7 +3,7 @@
import android.util.Log;
public class Logger {
- public static int LEVEL = BuildConfig.DEBUG ? Log.DEBUG : Log.ERROR;
+ public static final int LEVEL = BuildConfig.DEBUG ? Log.DEBUG : Log.ERROR;
static public void v(String tag, String msg) {
if (LEVEL <= Log.VERBOSE) {
diff --git a/src/io/github/sspanak/tt9/db/migrations/DB7.java b/src/io/github/sspanak/tt9/db/migrations/DB7.java
index 4e8b662ce..36b1a5e4a 100644
--- a/src/io/github/sspanak/tt9/db/migrations/DB7.java
+++ b/src/io/github/sspanak/tt9/db/migrations/DB7.java
@@ -2,6 +2,7 @@
import android.content.Context;
+import androidx.annotation.NonNull;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
@@ -74,7 +75,7 @@ private void migrateSettings() {
}
@Override
- public void migrate(SupportSQLiteDatabase database) {
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
migrateSQL(database);
migrateSettings();
}
diff --git a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
index 8e83ed2cf..62c4049e0 100644
--- a/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
+++ b/src/io/github/sspanak/tt9/ime/KeyPadHandler.java
@@ -114,12 +114,6 @@ public void onFinishInput() {
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- }
-
-
/**
* Use this to monitor key events being delivered to the application. We get
* first crack at them, and can either resume them or let them continue to
@@ -128,7 +122,7 @@ public void onDestroy() {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (isOff()) {
- return false;
+ return super.onKeyDown(keyCode, event);
}
// Logger.d("onKeyDown", "Key: " + event + " repeat?: " + event.getRepeatCount() + " long-time: " + event.isLongPress());
@@ -143,45 +137,31 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
isBackspaceHandled = false;
}
- if (Key.isOK(keyCode)) {
- return true;
- }
-
- // In numeric fields, we do not want to handle anything, but "backspace"
- if (mEditing == EDITING_STRICT_NUMERIC) {
- return false;
- }
-
- // holding "0" is important in all cases
- if (keyCode == KeyEvent.KEYCODE_0) {
- event.startTracking();
- return true;
- }
-
- // In dialer fields we just want passthrough, but we do handle holding "0",
- // to convert it to "+".
- if (mEditing == EDITING_DIALER) {
- return false;
- }
-
// start tracking key hold
- if (shouldTrackNumPress() || Key.isHotkey(settings, -keyCode)) {
+ if (Key.isNumber(keyCode) || Key.isHotkey(settings, -keyCode)) {
event.startTracking();
}
- return Key.isHotkey(settings, keyCode) || Key.isHotkey(settings, -keyCode)
+ if (
+ Key.isNumber(keyCode)
+ || Key.isOK(keyCode)
+ || Key.isHotkey(settings, keyCode) || Key.isHotkey(settings, -keyCode)
|| keyCode == KeyEvent.KEYCODE_STAR
|| keyCode == KeyEvent.KEYCODE_POUND
- || (Key.isNumber(keyCode) && shouldTrackNumPress())
|| ((keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && shouldTrackUpDown())
- || ((keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) && shouldTrackLeftRight());
+ || ((keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) && shouldTrackLeftRight())
+ ) {
+ return true;
+ }
+
+ return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (isOff()) {
- return false;
+ return super.onKeyLongPress(keyCode, event);
}
// Logger.d("onLongPress", "LONG PRESS: " + keyCode);
@@ -202,7 +182,7 @@ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
}
ignoreNextKeyUp = 0;
- return false;
+ return super.onKeyLongPress(keyCode, event);
}
@@ -214,15 +194,18 @@ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (isOff()) {
- return false;
+ return super.onKeyUp(keyCode, event);
}
+ // Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount());
+
if (keyCode == ignoreNextKeyUp) {
// Logger.d("onKeyUp", "Ignored: " + keyCode);
ignoreNextKeyUp = 0;
return true;
}
+ // repeat handling
keyRepeatCounter = (lastKeyCode == keyCode) ? keyRepeatCounter + 1 : 0;
lastKeyCode = keyCode;
@@ -231,49 +214,34 @@ public boolean onKeyUp(int keyCode, KeyEvent event) {
lastNumKeyCode = keyCode;
}
-// Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount());
-
+ // backspace is handled in onKeyDown only, so we ignore it here
if (isBackspaceHandled) {
return true;
}
- if (Key.isOK(keyCode)) {
- return onOK();
- }
-
- // in numeric fields, we just handle backspace and let the rest go as-is.
- if (mEditing == EDITING_STRICT_NUMERIC) {
- return false;
- }
-
- if (keyCode == KeyEvent.KEYCODE_0) {
+ if (Key.isNumber(keyCode)) {
return onNumber(Key.codeToNumber(settings, keyCode), false, numKeyRepeatCounter);
}
- // dialer fields are similar to pure numeric fields, but for user convenience, holding "0"
- // is converted to "+"
- if (mEditing == EDITING_DIALER) {
- return false;
+ if (Key.isOK(keyCode)) {
+ return onOK();
}
if (handleHotkey(keyCode, false)) {
return true;
}
- if (Key.isNumber(keyCode)) {
- return onNumber(Key.codeToNumber(settings, keyCode), false, numKeyRepeatCounter);
- }
-
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP: return onUp();
case KeyEvent.KEYCODE_DPAD_DOWN: return onDown();
case KeyEvent.KEYCODE_DPAD_LEFT: return onLeft();
case KeyEvent.KEYCODE_DPAD_RIGHT: return onRight(keyRepeatCounter > 0);
- case KeyEvent.KEYCODE_STAR: return onStar();
- case KeyEvent.KEYCODE_POUND: return onPound();
+ case KeyEvent.KEYCODE_STAR:
+ case KeyEvent.KEYCODE_POUND:
+ return onOtherKey(keyCode);
}
- return false;
+ return super.onKeyUp(keyCode, event);
}
@@ -314,7 +282,6 @@ protected void resetKeyRepeat() {
// toggle handlers
abstract protected boolean shouldTrackUpDown();
abstract protected boolean shouldTrackLeftRight();
- abstract protected boolean shouldTrackNumPress();
// default hardware key handlers
abstract public boolean onBackspace();
@@ -324,8 +291,7 @@ protected void resetKeyRepeat() {
abstract protected boolean onLeft();
abstract protected boolean onRight(boolean repeat);
abstract protected boolean onNumber(int key, boolean hold, int repeat);
- abstract protected boolean onStar();
- abstract protected boolean onPound();
+ abstract protected boolean onOtherKey(int keyCode);
// customized key handlers
abstract protected boolean onKeyAddWord();
diff --git a/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java b/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
deleted file mode 100644
index 1705739d7..000000000
--- a/src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package io.github.sspanak.tt9.ime;
-
-import android.graphics.drawable.Drawable;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.Button;
-import android.widget.LinearLayout;
-
-import androidx.core.content.ContextCompat;
-
-import io.github.sspanak.tt9.R;
-import io.github.sspanak.tt9.ui.UI;
-
-class SoftKeyHandler implements View.OnTouchListener {
- private static final int[] buttons = { R.id.main_left, R.id.main_mid, R.id.main_right };
- private final TraditionalT9 tt9;
- private View view = null;
-
- private long lastBackspaceCall = 0;
-
- public SoftKeyHandler(TraditionalT9 tt9) {
- this.tt9 = tt9;
-
- getView();
- }
-
-
- View getView() {
- if (view == null) {
- view = View.inflate(tt9.getApplicationContext(), R.layout.mainview, null);
-
- for (int buttonId : buttons) {
- view.findViewById(buttonId).setOnTouchListener(this);
- }
- }
-
- return view;
- }
-
-
- void show() {
- if (view != null) {
- view.setVisibility(View.VISIBLE);
- }
- }
-
-
- void hide() {
- if (view != null) {
- view.setVisibility(View.GONE);
- }
- }
-
-
- void setSoftKeysVisibility(boolean visible) {
- if (view != null) {
- view.findViewById(R.id.main_soft_keys).setVisibility(visible ? LinearLayout.VISIBLE : LinearLayout.GONE);
- }
- }
-
-
- /** setDarkTheme
- * Changes the main view colors according to the theme.
- *
- * We need to do this manually, instead of relying on the Context to resolve the appropriate colors,
- * because this View is part of the main service View. And service Views are always locked to the
- * system context and theme.
- *
- * More info:
- * https://stackoverflow.com/questions/72382886/system-applies-night-mode-to-views-added-in-service-type-application-overlay
- */
- void setDarkTheme(boolean darkEnabled) {
- if (view == null) {
- return;
- }
-
- // background
- view.findViewById(R.id.main_soft_keys).setBackground(ContextCompat.getDrawable(
- view.getContext(),
- darkEnabled ? R.drawable.button_background_dark : R.drawable.button_background
- ));
-
- // text
- int textColor = ContextCompat.getColor(
- view.getContext(),
- darkEnabled ? R.color.dark_button_text : R.color.button_text
- );
-
- for (int buttonId : buttons) {
- Button button = view.findViewById(buttonId);
- button.setTextColor(textColor);
- }
-
- // separators
- Drawable separatorColor = ContextCompat.getDrawable(
- view.getContext(),
- darkEnabled ? R.drawable.button_separator_dark : R.drawable.button_separator
- );
-
- view.findViewById(R.id.main_separator_left).setBackground(separatorColor);
- view.findViewById(R.id.main_separator_right).setBackground(separatorColor);
- }
-
-
- private boolean handleBackspaceHold() {
- if (System.currentTimeMillis() - lastBackspaceCall < tt9.settings.getSoftKeyRepeatDelay()) {
- return true;
- }
-
- boolean handled = tt9.onBackspace();
-
- long now = System.currentTimeMillis();
- lastBackspaceCall = lastBackspaceCall == 0 ? tt9.settings.getSoftKeyInitialDelay() + now : now;
-
- return handled;
- }
-
-
- private boolean handleBackspaceUp() {
- lastBackspaceCall = 0;
- return true;
- }
-
-
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- int action = event.getAction();
- int buttonId = view.getId();
-
- if (buttonId == R.id.main_left && action == MotionEvent.ACTION_UP) {
- UI.showSettingsScreen(tt9);
- return view.performClick();
- }
-
- if (buttonId == R.id.main_mid && action == MotionEvent.ACTION_UP) {
- tt9.onOK();
- return view.performClick();
- }
-
- if (buttonId == R.id.main_right) {
- if (action == MotionEvent.AXIS_PRESSURE) {
- return handleBackspaceHold();
- } else if (action == MotionEvent.ACTION_UP) {
- return handleBackspaceUp();
- }
- }
-
- return false;
- }
-}
diff --git a/src/io/github/sspanak/tt9/ime/TraditionalT9.java b/src/io/github/sspanak/tt9/ime/TraditionalT9.java
index 25a63cd57..d3cb71225 100644
--- a/src/io/github/sspanak/tt9/ime/TraditionalT9.java
+++ b/src/io/github/sspanak/tt9/ime/TraditionalT9.java
@@ -17,13 +17,16 @@
import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
import io.github.sspanak.tt9.ime.helpers.InputType;
+import io.github.sspanak.tt9.ime.helpers.Key;
import io.github.sspanak.tt9.ime.helpers.TextField;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
-import io.github.sspanak.tt9.ui.bottom.StatusBar;
-import io.github.sspanak.tt9.ui.bottom.SuggestionsBar;
+import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.ui.UI;
+import io.github.sspanak.tt9.ui.main.MainView;
+import io.github.sspanak.tt9.ui.tray.StatusBar;
+import io.github.sspanak.tt9.ui.tray.SuggestionsBar;
public class TraditionalT9 extends KeyPadHandler {
// internal settings/data
@@ -40,7 +43,7 @@ public class TraditionalT9 extends KeyPadHandler {
protected Language mLanguage;
// soft key view
- private SoftKeyHandler softKeyHandler = null;
+ private MainView mainView = null;
private StatusBar statusBar = null;
private SuggestionsBar suggestionBar = null;
@@ -50,6 +53,10 @@ public static Context getMainContext() {
return self.getApplicationContext();
}
+ public SettingsStore getSettings() {
+ return settings;
+ }
+
private void loadSettings() {
mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage());
@@ -80,16 +87,9 @@ protected void onInit() {
DictionaryDb.init(this);
DictionaryDb.normalizeWordFrequencies(settings);
- if (softKeyHandler == null) {
- softKeyHandler = new SoftKeyHandler(this);
- }
-
- if (statusBar == null) {
- statusBar = new StatusBar(softKeyHandler.getView());
- }
-
- if (suggestionBar == null) {
- suggestionBar = new SuggestionsBar(settings, softKeyHandler.getView());
+ if (mainView == null) {
+ mainView = new MainView(this);
+ initTray();
}
loadSettings();
@@ -115,17 +115,28 @@ private void initTyping() {
}
- private void initUi() {
- statusBar
- .setText(mInputMode != null ? mInputMode.toString() : "")
- .setDarkTheme(settings.getDarkTheme());
+ private void initTray() {
+ setInputView(mainView.getView());
+ statusBar = new StatusBar(mainView.getView());
+ suggestionBar = new SuggestionsBar(this, mainView.getView());
+ }
- clearSuggestions();
+
+ private void setDarkTheme() {
+ mainView.setDarkTheme(settings.getDarkTheme());
+ statusBar.setDarkTheme(settings.getDarkTheme());
suggestionBar.setDarkTheme(settings.getDarkTheme());
+ }
- softKeyHandler.setDarkTheme(settings.getDarkTheme());
- softKeyHandler.setSoftKeysVisibility(settings.getShowSoftKeys());
- softKeyHandler.show();
+
+ private void initUi() {
+ if (mainView.createView()) {
+ initTray();
+ }
+ clearSuggestions();
+ statusBar.setText(mInputMode != null ? mInputMode.toString() : "");
+ setDarkTheme();
+ mainView.render();
}
@@ -163,8 +174,6 @@ protected void onFinishTyping() {
protected void onStop() {
onFinishTyping();
clearSuggestions();
-
- softKeyHandler.hide();
}
@@ -193,10 +202,10 @@ public boolean onBackspace() {
public boolean onOK() {
- if (!textField.isThereText()) {
+ if (!isInputViewShown() && !textField.isThereText()) {
forceShowWindowIfHidden();
return true;
- } else if (isSuggestionViewHidden() && currentInputConnection != null) {
+ } else if (isSuggestionViewHidden()) {
return performOKAction();
}
@@ -275,12 +284,10 @@ protected boolean onNumber(int key, boolean hold, int repeat) {
String currentWord = getComposingText();
- // Automatically accept the current word, when the next one is a space or whatnot,
+ // Automatically accept the current word, when the next one is a space or punctuation,
// instead of requiring "OK" before that.
if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat > 0)) {
- mInputMode.onAcceptSuggestion(currentWord);
- commitCurrentSuggestion(false);
- autoCorrectSpace(currentWord, false, key, hold, repeat > 0);
+ autoCorrectSpace(acceptIncompleteSuggestion(), false, key, hold, repeat > 0);
currentWord = "";
}
@@ -296,17 +303,6 @@ protected boolean onNumber(int key, boolean hold, int repeat) {
if (mInputMode.shouldSelectNextSuggestion() && !isSuggestionViewHidden()) {
nextSuggestion();
- return true;
- }
-
- if (mInputMode.getWord() != null) {
- currentWord = mInputMode.getWord();
-
- mInputMode.onAcceptSuggestion(currentWord);
- textField.setText(currentWord);
- clearSuggestions();
- autoCorrectSpace(currentWord, true, key, hold, repeat > 0);
- resetKeyRepeat();
} else {
getSuggestions();
}
@@ -315,19 +311,33 @@ protected boolean onNumber(int key, boolean hold, int repeat) {
}
- protected boolean onPound() {
- textField.setText("#");
+ public boolean onOtherKey(int keyCode) {
+ if (
+ keyCode <= 0 ||
+ (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) && !Key.isNumber(keyCode)
+ ) {
+ return false;
+ }
+
+ autoCorrectSpace(acceptIncompleteSuggestion(), false, -1, false, false);
+ sendDownUpKeyEvents(keyCode);
return true;
}
- protected boolean onStar() {
- textField.setText("*");
+ public boolean onText(String text) {
+ if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER || text.length() == 0) {
+ return false;
+ }
+
+ autoCorrectSpace(acceptIncompleteSuggestion(), false, -1, false, false);
+ textField.setText(text);
+ autoCorrectSpace(text, false, -1, false, false);
return true;
}
- protected boolean onKeyAddWord() {
+ public boolean onKeyAddWord() {
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
return false;
}
@@ -337,7 +347,7 @@ protected boolean onKeyAddWord() {
}
- protected boolean onKeyNextLanguage() {
+ public boolean onKeyNextLanguage() {
if (nextLang()) {
commitCurrentSuggestion(false);
mInputMode.changeLanguage(mLanguage);
@@ -345,6 +355,7 @@ protected boolean onKeyNextLanguage() {
resetKeyRepeat();
clearSuggestions();
statusBar.setText(mInputMode.toString());
+ mainView.render();
forceShowWindowIfHidden();
return true;
@@ -354,14 +365,15 @@ protected boolean onKeyNextLanguage() {
}
- protected boolean onKeyNextInputMode() {
+ public boolean onKeyNextInputMode() {
nextInputMode();
+ mainView.render();
forceShowWindowIfHidden();
return (mEditing != EDITING_STRICT_NUMERIC && mEditing != EDITING_DIALER);
}
- protected boolean onKeyShowSettings() {
+ public boolean onKeyShowSettings() {
if (mEditing == EDITING_DIALER) {
return false;
}
@@ -371,11 +383,6 @@ protected boolean onKeyShowSettings() {
}
- protected boolean shouldTrackNumPress() {
- return mInputMode.shouldTrackNumPress();
- }
-
-
protected boolean shouldTrackUpDown() {
return mEditing != EDITING_STRICT_NUMERIC && !isSuggestionViewHidden() && mInputMode.shouldTrackUpDown();
}
@@ -414,6 +421,15 @@ private boolean nextSuggestion() {
}
+ private String acceptIncompleteSuggestion() {
+ String currentWord = getComposingText();
+ mInputMode.onAcceptSuggestion(currentWord);
+ commitCurrentSuggestion(false);
+
+ return currentWord;
+ }
+
+
private void commitCurrentSuggestion() {
commitCurrentSuggestion(true);
}
@@ -448,9 +464,22 @@ private void getSuggestions() {
private void handleSuggestions() {
+ // key code "suggestions" take priority over words
+ if (mInputMode.getKeyCode() > 0) {
+ sendDownUpKeyEvents(mInputMode.getKeyCode());
+ mInputMode.onAcceptSuggestion(null);
+ }
+
+ // display the list of suggestions
setSuggestions(mInputMode.getSuggestions());
- // Put the first suggestion in the text field,
+ // flush the first suggestion immediately, if the InputMode has requested it
+ if (mInputMode.getAutoAcceptTimeout() == 0) {
+ onOK();
+ return;
+ }
+
+ // Otherwise, put the first suggestion in the text field,
// but cut it off to the length of the sequence (how many keys were pressed),
// for a more intuitive experience.
String word = suggestionBar.getCurrentSuggestion();
@@ -607,7 +636,6 @@ private void autoCorrectSpace(String currentWord, boolean isWordAcceptedManually
private void determineNextTextCase() {
mInputMode.determineNextWordTextCase(
- settings,
textField.isThereText(),
textField.getTextBeforeCursor()
);
@@ -615,6 +643,10 @@ private void determineNextTextCase() {
private boolean performOKAction() {
+ if (currentInputConnection == null) {
+ return false;
+ }
+
int action = textField.getAction();
switch (action) {
case EditorInfo.IME_ACTION_NONE:
@@ -684,7 +716,10 @@ private void restoreAddedWordIfAny() {
* Generates the actual UI of TT9.
*/
protected View createSoftKeyView() {
- return softKeyHandler.getView();
+ mainView.forceCreateView();
+ initTray();
+ setDarkTheme();
+ return mainView.getView();
}
diff --git a/src/io/github/sspanak/tt9/ime/helpers/InputType.java b/src/io/github/sspanak/tt9/ime/helpers/InputType.java
index f8ebdedcd..a524fc236 100644
--- a/src/io/github/sspanak/tt9/ime/helpers/InputType.java
+++ b/src/io/github/sspanak/tt9/ime/helpers/InputType.java
@@ -25,7 +25,7 @@ public boolean isValid() {
* Special or limited input type means the input connection is not rich,
* or it can not process or show things like candidate text, nor retrieve the current text.
*
- * https://developer.android.com/reference/android/text/InputType#TYPE_NULL
+ * ...
*/
public boolean isLimited() {
return field != null && field.inputType == android.text.InputType.TYPE_NULL;
diff --git a/src/io/github/sspanak/tt9/ime/helpers/Key.java b/src/io/github/sspanak/tt9/ime/helpers/Key.java
index ac5963de2..bee0b744e 100644
--- a/src/io/github/sspanak/tt9/ime/helpers/Key.java
+++ b/src/io/github/sspanak/tt9/ime/helpers/Key.java
@@ -65,4 +65,12 @@ public static int codeToNumber(SettingsStore settings, int keyCode) {
return -1;
}
}
+
+ public static int numberToCode(int number) {
+ if (number >= 0 && number <= 9) {
+ return KeyEvent.KEYCODE_0 + number;
+ } else {
+ return -1;
+ }
+ }
}
diff --git a/src/io/github/sspanak/tt9/ime/modes/InputMode.java b/src/io/github/sspanak/tt9/ime/modes/InputMode.java
index 3953ba153..bf9ea3301 100644
--- a/src/io/github/sspanak/tt9/ime/modes/InputMode.java
+++ b/src/io/github/sspanak/tt9/ime/modes/InputMode.java
@@ -22,14 +22,15 @@ abstract public class InputMode {
public static final int CASE_CAPITALIZE = 1;
public static final int CASE_LOWER = 2;
public static final int CASE_DICTIONARY = 3; // do not force it, but use the dictionary word as-is
- protected ArrayList allowedTextCases = new ArrayList<>();
+ protected final ArrayList allowedTextCases = new ArrayList<>();
protected int textCase = CASE_LOWER;
protected int textFieldTextCase = CASE_UNDEFINED;
// data
+ protected int autoAcceptTimeout = -1;
protected Language language;
- protected ArrayList suggestions = new ArrayList<>();
- protected String word = null;
+ protected final ArrayList suggestions = new ArrayList<>();
+ protected int keyCode = 0;
public static InputMode getInstance(SettingsStore settings, Language language, int mode) {
@@ -47,7 +48,7 @@ public static InputMode getInstance(SettingsStore settings, Language language, i
// Key handlers. Return "true" when handling the key or "false", when is nothing to do.
public boolean onBackspace() { return false; }
- abstract public boolean onNumber(int key, boolean hold, int repeat);
+ abstract public boolean onNumber(int number, boolean hold, int repeat);
// Predictions
public void onAcceptSuggestion(String suggestion) {}
@@ -63,9 +64,6 @@ public ArrayList getSuggestions() {
return newSuggestions;
}
- // Word
- public String getWord() { return word; }
-
// Mode identifiers
public boolean isPredictive() { return false; }
public boolean isABC() { return false; }
@@ -74,6 +72,10 @@ public ArrayList getSuggestions() {
// Utility
abstract public int getId();
abstract public int getSequenceLength(); // The number of key presses for the current word.
+ public int getAutoAcceptTimeout() {
+ return autoAcceptTimeout;
+ }
+ public int getKeyCode() { return keyCode; }
public void changeLanguage(Language newLanguage) {
if (newLanguage != null) {
language = newLanguage;
@@ -85,13 +87,14 @@ public void changeLanguage(Language newLanguage) {
public boolean shouldAddAutoSpace(InputType inputType, TextField textField, boolean isWordAcceptedManually, int incomingKey, boolean hold, boolean repeat) { return false; }
public boolean shouldDeletePrecedingSpace(InputType inputType) { return false; }
public boolean shouldSelectNextSuggestion() { return false; }
- public boolean shouldTrackNumPress() { return true; }
+
public boolean shouldTrackUpDown() { return false; }
public boolean shouldTrackLeftRight() { return false; }
public void reset() {
- suggestions = new ArrayList<>();
- word = null;
+ autoAcceptTimeout = -1;
+ keyCode = 0;
+ suggestions.clear();
}
// Text case
@@ -119,7 +122,7 @@ public void nextTextCase() {
textCase = allowedTextCases.get(nextIndex);
}
- public void determineNextWordTextCase(SettingsStore settings, boolean isThereText, String textBeforeCursor) {}
+ public void determineNextWordTextCase(boolean isThereText, String textBeforeCursor) {}
// Based on the internal logic of the mode (punctuation or grammar rules), re-adjust the text case for when getSuggestions() is called.
protected String adjustSuggestionTextCase(String word, int newTextCase) { return word; }
diff --git a/src/io/github/sspanak/tt9/ime/modes/Mode123.java b/src/io/github/sspanak/tt9/ime/modes/Mode123.java
index eeede02f2..9b9db887e 100644
--- a/src/io/github/sspanak/tt9/ime/modes/Mode123.java
+++ b/src/io/github/sspanak/tt9/ime/modes/Mode123.java
@@ -2,7 +2,7 @@
import androidx.annotation.NonNull;
-import java.util.ArrayList;
+import io.github.sspanak.tt9.ime.helpers.Key;
public class Mode123 extends InputMode {
public int getId() { return MODE_123; }
@@ -11,21 +11,23 @@ public class Mode123 extends InputMode {
allowedTextCases.add(CASE_LOWER);
}
-
- public boolean onNumber(int key, boolean hold, int repeat) {
- if (key != 0) {
- return false;
+ @Override
+ public boolean onNumber(int number, boolean hold, int repeat) {
+ reset();
+
+ if (number == 0 && hold) {
+ autoAcceptTimeout = 0;
+ suggestions.add("+");
+ } else {
+ keyCode = Key.numberToCode(number);
}
- suggestions = new ArrayList<>();
- word = hold ? "+" : "0";
return true;
}
- final public boolean is123() { return true; }
- public int getSequenceLength() { return 0; }
- public boolean shouldTrackNumPress() { return false; }
+ @Override final public boolean is123() { return true; }
+ @Override public int getSequenceLength() { return 0; }
@NonNull
@Override
diff --git a/src/io/github/sspanak/tt9/ime/modes/ModeABC.java b/src/io/github/sspanak/tt9/ime/modes/ModeABC.java
index f9404a643..a20bb4fd3 100644
--- a/src/io/github/sspanak/tt9/ime/modes/ModeABC.java
+++ b/src/io/github/sspanak/tt9/ime/modes/ModeABC.java
@@ -2,8 +2,6 @@
import androidx.annotation.NonNull;
-import java.util.ArrayList;
-
import io.github.sspanak.tt9.languages.Language;
public class ModeABC extends InputMode {
@@ -17,16 +15,16 @@ public class ModeABC extends InputMode {
@Override
- public boolean onNumber(int key, boolean hold, int repeat) {
- shouldSelectNextLetter = false;
- suggestions = language.getKeyCharacters(key);
- word = null;
-
+ public boolean onNumber(int number, boolean hold, int repeat) {
if (hold) {
- suggestions = new ArrayList<>();
- word = String.valueOf(key);
+ reset();
+ suggestions.add(String.valueOf(number));
+ autoAcceptTimeout = 0;
} else if (repeat > 0) {
shouldSelectNextLetter = true;
+ } else {
+ reset();
+ suggestions.addAll(language.getKeyCharacters(number));
}
return true;
@@ -59,6 +57,12 @@ public void changeLanguage(Language language) {
return shouldSelectNextLetter;
}
+ @Override
+ public void reset() {
+ super.reset();
+ shouldSelectNextLetter = false;
+ }
+
@NonNull
@Override
public String toString() {
diff --git a/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java b/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java
index 15309f842..fbc2e8541 100644
--- a/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java
+++ b/src/io/github/sspanak/tt9/ime/modes/ModePredictive.java
@@ -13,7 +13,6 @@
import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace;
import io.github.sspanak.tt9.ime.modes.helpers.AutoTextCase;
import io.github.sspanak.tt9.ime.modes.helpers.Predictions;
-import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.SettingsStore;
@@ -69,40 +68,25 @@ public boolean onBackspace() {
@Override
- public boolean onNumber(int key, boolean hold, int repeat) {
+ public boolean onNumber(int number, boolean hold, int repeat) {
if (hold) {
// hold to type any digit
reset();
- word = String.valueOf(key);
- } else if (key == 0 && repeat > 0) {
- onDouble0();
+ autoAcceptTimeout = 0;
+ suggestions.add(String.valueOf(number));
} else {
// words
super.reset();
- digitSequence += key;
+ digitSequence += number;
+ if (number == 0 && repeat > 0) {
+ autoAcceptTimeout = 0;
+ }
}
return true;
}
- /**
- * onDouble0
- * Double "0" is a shortcut for the preferred character.
- */
- private void onDouble0() {
- try {
- reset();
- word = settings.getDoubleZeroChar();
- digitSequence = language.getDigitSequenceForWord(word);
- } catch (InvalidLanguageCharactersException e) {
- Logger.w("tt9/onDouble0", "Failed getting the sequence for word: '" + word + "'. Performing standard 0-key action.");
- reset();
- digitSequence = "0";
- }
- }
-
-
@Override
public void changeLanguage(Language language) {
super.changeLanguage(language);
@@ -217,7 +201,6 @@ public boolean loadSuggestions(Handler handler, String currentWord) {
.setWordsChangedHandler(handleSuggestions);
handleSuggestionsExternal = handler;
- super.reset();
return predictions.load();
}
@@ -274,7 +257,7 @@ protected String adjustSuggestionTextCase(String word, int newTextCase) {
}
@Override
- public void determineNextWordTextCase(SettingsStore settings, boolean isThereText, String textBeforeCursor) {
+ public void determineNextWordTextCase(boolean isThereText, String textBeforeCursor) {
textCase = autoTextCase.determineNextWordTextCase(isThereText, textCase, textFieldTextCase, textBeforeCursor);
}
diff --git a/src/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java b/src/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java
index d5af995c6..1f23a96a9 100644
--- a/src/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java
+++ b/src/io/github/sspanak/tt9/ime/modes/helpers/Predictions.java
@@ -27,7 +27,7 @@ public class Predictions {
private Handler wordsChangedHandler;
// data
- private ArrayList words = new ArrayList<>();
+ private final ArrayList words = new ArrayList<>();
// punctuation/emoji
private final Pattern containsOnly1Regex = Pattern.compile("^1+$");
@@ -73,9 +73,8 @@ public Predictions setInputWord(String inputWord) {
return this;
}
- public Predictions setWordsChangedHandler(Handler handler) {
+ public void setWordsChangedHandler(Handler handler) {
wordsChangedHandler = handler;
- return this;
}
public ArrayList getList() {
@@ -127,7 +126,7 @@ private void onWordsChanged() {
*/
public boolean load() {
if (digitSequence == null || digitSequence.length() == 0) {
- words = new ArrayList<>();
+ words.clear();
onWordsChanged();
return false;
}
@@ -155,16 +154,27 @@ public boolean load() {
* Returns "false", when there are no static options for the current digitSequence.
*/
private boolean loadStatic() {
+ // whitespace/special/math characters
if (digitSequence.equals("0")) {
+ words.clear();
+ stem = "";
+ words.addAll(language.getKeyCharacters(0, false));
+ }
+ // "00" is a shortcut for the preferred character
+ else if (digitSequence.equals("00")) {
+ words.clear();
stem = "";
- words = language.getKeyCharacters(0, false);
- } else if (containsOnly1Regex.matcher(digitSequence).matches()) {
+ words.add(settings.getDoubleZeroChar());
+ }
+ // emoji
+ else if (containsOnly1Regex.matcher(digitSequence).matches()) {
+ words.clear();
stem = "";
if (digitSequence.length() == 1) {
- words = language.getKeyCharacters(1, false);
+ words.addAll(language.getKeyCharacters(1, false));
} else {
digitSequence = digitSequence.length() <= maxEmojiSequence.length() ? digitSequence : maxEmojiSequence;
- words = Characters.getEmoji(digitSequence.length() - 2);
+ words.addAll(Characters.getEmoji(digitSequence.length() - 2));
}
} else {
return false;
diff --git a/src/io/github/sspanak/tt9/languages/InvalidLanguageCharactersException.java b/src/io/github/sspanak/tt9/languages/InvalidLanguageCharactersException.java
index 67ba09c05..2da2fe623 100644
--- a/src/io/github/sspanak/tt9/languages/InvalidLanguageCharactersException.java
+++ b/src/io/github/sspanak/tt9/languages/InvalidLanguageCharactersException.java
@@ -8,8 +8,5 @@ public InvalidLanguageCharactersException(Language language, String extraMessage
this.language = language;
}
- public Language getLanguage() {
- return language;
- }
}
diff --git a/src/io/github/sspanak/tt9/languages/definitions/Spanish.java b/src/io/github/sspanak/tt9/languages/definitions/Spanish.java
index 309cdd760..120c73c14 100644
--- a/src/io/github/sspanak/tt9/languages/definitions/Spanish.java
+++ b/src/io/github/sspanak/tt9/languages/definitions/Spanish.java
@@ -19,9 +19,9 @@ public Spanish() {
characterMap.set(1, new ArrayList<>(Characters.Sentence));
characterMap.get(1).addAll(Arrays.asList("¡", "¿"));
- characterMap.get(2).addAll(Collections.singletonList("á"));
- characterMap.get(3).addAll(Collections.singletonList("é"));
- characterMap.get(4).addAll(Collections.singletonList("í"));
+ characterMap.get(2).add("á");
+ characterMap.get(3).add("é");
+ characterMap.get(4).add("í");
characterMap.set(6, new ArrayList<>(Arrays.asList("m", "n", "ñ", "o", "ó")));
characterMap.get(8).addAll(Arrays.asList("ú", "ü"));
}
diff --git a/src/io/github/sspanak/tt9/preferences/SettingsStore.java b/src/io/github/sspanak/tt9/preferences/SettingsStore.java
index 504092499..8b7a1a55f 100644
--- a/src/io/github/sspanak/tt9/preferences/SettingsStore.java
+++ b/src/io/github/sspanak/tt9/preferences/SettingsStore.java
@@ -198,17 +198,13 @@ public int getKeyShowSettings() {
return getFunctionKey(SectionKeymap.ITEM_SHOW_SETTINGS);
}
-
/************* UI settings *************/
- public boolean getNotifyNextLanguageInModeAbc() { return prefs.getBoolean("notify_next_language_in_mode_abc", true); }
-
public boolean getDarkTheme() { return prefs.getBoolean("pref_dark_theme", true); }
-
public boolean getShowSoftKeys() { return prefs.getBoolean("pref_show_soft_keys", true); }
-
+ public boolean getShowSoftNumpad() { return getShowSoftKeys() && prefs.getBoolean("pref_show_soft_numpad", false); }
/************* typing settings *************/
diff --git a/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java b/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java
index 6c5205c69..6bd716be4 100644
--- a/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java
+++ b/src/io/github/sspanak/tt9/preferences/helpers/Hotkeys.java
@@ -126,6 +126,9 @@ private void generateList() {
addIfDeviceHasKey(KeyEvent.KEYCODE_SOFT_LEFT, R.string.key_soft_left, false);
addIfDeviceHasKey(KeyEvent.KEYCODE_SOFT_RIGHT, R.string.key_soft_right, false);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_DOWN, R.string.key_volume_down, false);
+ addIfDeviceHasKey(KeyEvent.KEYCODE_VOLUME_UP, R.string.key_volume_up, false);
+
add(KeyEvent.KEYCODE_POUND, "#", true);
add(KeyEvent.KEYCODE_STAR, "✱", true);
diff --git a/src/io/github/sspanak/tt9/preferences/items/ItemClickable.java b/src/io/github/sspanak/tt9/preferences/items/ItemClickable.java
index 7d6e4c244..9a2fec07a 100644
--- a/src/io/github/sspanak/tt9/preferences/items/ItemClickable.java
+++ b/src/io/github/sspanak/tt9/preferences/items/ItemClickable.java
@@ -28,7 +28,7 @@ public void enableClickHandler() {
*
* My smashed Qin F21 Pro+ occasionally does this, if I press the keys hard.
* There were reports the same happens on Kyocera KYF31, causing absolutely undesirable side effects.
- * @see: https://github.com/sspanak/tt9/issues/117
+ * @see: ...
*/
protected boolean debounceClick(Preference p) {
long now = System.currentTimeMillis();
diff --git a/src/io/github/sspanak/tt9/preferences/items/ItemSelectLanguage.java b/src/io/github/sspanak/tt9/preferences/items/ItemSelectLanguage.java
index b62db3c90..7d2e3f5fd 100644
--- a/src/io/github/sspanak/tt9/preferences/items/ItemSelectLanguage.java
+++ b/src/io/github/sspanak/tt9/preferences/items/ItemSelectLanguage.java
@@ -46,9 +46,9 @@ public ItemSelectLanguage populate() {
}
- public ItemSelectLanguage enableValidation() {
+ public void enableValidation() {
if (item == null) {
- return this;
+ return;
}
item.setOnPreferenceChangeListener((preference, newValue) -> {
@@ -65,7 +65,6 @@ public ItemSelectLanguage enableValidation() {
return false;
});
- return this;
}
diff --git a/src/io/github/sspanak/tt9/preferences/items/ItemSelectZeroKeyCharacter.java b/src/io/github/sspanak/tt9/preferences/items/ItemSelectZeroKeyCharacter.java
index a8a6dce31..003a745d8 100644
--- a/src/io/github/sspanak/tt9/preferences/items/ItemSelectZeroKeyCharacter.java
+++ b/src/io/github/sspanak/tt9/preferences/items/ItemSelectZeroKeyCharacter.java
@@ -41,10 +41,10 @@ public ItemSelectZeroKeyCharacter populate() {
}
- public ItemSelectZeroKeyCharacter activate() {
+ public void activate() {
if (item == null) {
Logger.w("tt9/ItemSelectZeroKeyChar.activate", "Cannot set a click listener a NULL item. Ignoring.");
- return this;
+ return;
}
item.setOnPreferenceChangeListener((preference, newChar) -> {
@@ -53,7 +53,6 @@ public ItemSelectZeroKeyCharacter activate() {
return true;
});
- return this;
}
diff --git a/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java b/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java
index c09481c6b..7592f53c2 100644
--- a/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java
+++ b/src/io/github/sspanak/tt9/preferences/items/SectionKeymap.java
@@ -46,12 +46,11 @@ public SectionKeymap populate() {
}
- public SectionKeymap activate() {
+ public void activate() {
for (DropDownPreference item : items) {
onItemClick(item);
}
- return this;
}
diff --git a/src/io/github/sspanak/tt9/ui/AddWordAct.java b/src/io/github/sspanak/tt9/ui/AddWordAct.java
index 72611efcf..005208c77 100644
--- a/src/io/github/sspanak/tt9/ui/AddWordAct.java
+++ b/src/io/github/sspanak/tt9/ui/AddWordAct.java
@@ -40,14 +40,13 @@ protected void onCreate(Bundle savedData) {
word = i.getStringExtra("io.github.sspanak.tt9.word");
lang = i.getIntExtra("io.github.sspanak.tt9.lang", -1);
- View v = View.inflate(this, R.layout.addwordview, null);
+ main = View.inflate(this, R.layout.addwordview, null);
- EditText et = v.findViewById(R.id.add_word_text);
+ EditText et = main.findViewById(R.id.add_word_text);
et.setOnClickListener(this::addWord);
et.setText(word);
et.setSelection(word.length());
- setContentView(v);
- main = v;
+ setContentView(main);
}
diff --git a/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java b/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java
index 63b352a29..7e9ce1530 100644
--- a/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java
+++ b/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java
@@ -170,14 +170,11 @@ private void showError(String errorType, int langId, long line, String word) {
if (lang == null || errorType.equals(InvalidLanguageException.class.getSimpleName())) {
message = resources.getString(R.string.add_word_invalid_language);
} else if (errorType.equals(DictionaryImportException.class.getSimpleName()) || errorType.equals(InvalidLanguageCharactersException.class.getSimpleName())) {
- String languageName = lang.getName();
- message = resources.getString(R.string.dictionary_load_bad_char, word, line, languageName);
+ message = resources.getString(R.string.dictionary_load_bad_char, word, line, lang.getName());
} else if (errorType.equals(IOException.class.getSimpleName()) || errorType.equals(FileNotFoundException.class.getSimpleName())) {
- String languageName = lang.getName();
- message = resources.getString(R.string.dictionary_not_found, languageName);
+ message = resources.getString(R.string.dictionary_not_found, lang.getName());
} else {
- String languageName = lang.getName();
- message = resources.getString(R.string.dictionary_load_error, languageName, errorType);
+ message = resources.getString(R.string.dictionary_load_error, lang.getName(), errorType);
}
title = generateTitle(-1);
diff --git a/src/io/github/sspanak/tt9/ui/UI.java b/src/io/github/sspanak/tt9/ui/UI.java
index e1ddeeb30..c679595a5 100644
--- a/src/io/github/sspanak/tt9/ui/UI.java
+++ b/src/io/github/sspanak/tt9/ui/UI.java
@@ -4,13 +4,8 @@
import android.content.Intent;
import android.widget.Toast;
-import io.github.sspanak.tt9.Logger;
-import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.TraditionalT9;
-import io.github.sspanak.tt9.ime.modes.InputMode;
-import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
-import io.github.sspanak.tt9.preferences.SettingsStore;
public class UI {
public static void showAddWordDialog(TraditionalT9 tt9, int language, String currentWord) {
diff --git a/src/io/github/sspanak/tt9/ui/main/BaseMainLayout.java b/src/io/github/sspanak/tt9/ui/main/BaseMainLayout.java
new file mode 100644
index 000000000..e50a9fe39
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/BaseMainLayout.java
@@ -0,0 +1,80 @@
+package io.github.sspanak.tt9.ui.main;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+import io.github.sspanak.tt9.ime.TraditionalT9;
+import io.github.sspanak.tt9.ui.main.keys.SoftKey;
+
+abstract class BaseMainLayout {
+ protected final TraditionalT9 tt9;
+ private final int xml;
+
+ protected View view = null;
+ protected ArrayList keys = new ArrayList<>();
+
+ public BaseMainLayout(TraditionalT9 tt9, int xml) {
+ this.tt9 = tt9;
+ this.xml = xml;
+ }
+
+
+ /** setDarkTheme
+ * Changes the main view colors according to the theme.
+ *
+ * We need to do this manually, instead of relying on the Context to resolve the appropriate colors,
+ * because this View is part of the main service View. And service Views are always locked to the
+ * system context and theme.
+ *
+ * More info:
+ * ...
+ */
+ abstract public void setDarkTheme(boolean yes);
+
+
+ /**
+ * render
+ * Do all the necessary stuff to display the View.
+ */
+ abstract public void render();
+
+
+ /**
+ * getKeys
+ * Returns a list of all the usable Soft Keys.
+ */
+ abstract protected ArrayList getKeys();
+
+
+ public View getView() {
+ if (view == null) {
+ view = View.inflate(tt9.getApplicationContext(), xml, null);
+ }
+
+ return view;
+ }
+
+ public void enableClickHandlers() {
+ for (SoftKey key : getKeys()) {
+ key.setTT9(tt9);
+ }
+ }
+
+
+
+ protected ArrayList getKeysFromContainer(ViewGroup container) {
+ ArrayList keyList = new ArrayList<>();
+ final int childrenCount = container != null ? container.getChildCount() : 0;
+
+ for (int i = 0; i < childrenCount; i++) {
+ View child = container.getChildAt(i);
+ if (child instanceof SoftKey) {
+ keyList.add((SoftKey) child);
+ }
+ }
+
+ return keyList;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java b/src/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java
new file mode 100644
index 000000000..34c6c8e83
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java
@@ -0,0 +1,91 @@
+package io.github.sspanak.tt9.ui.main;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.ime.TraditionalT9;
+import io.github.sspanak.tt9.ui.main.keys.SoftKey;
+
+class MainLayoutNumpad extends BaseMainLayout {
+ public MainLayoutNumpad(TraditionalT9 tt9) {
+ super(tt9, R.layout.main_numpad);
+ }
+
+ @Override
+ public void setDarkTheme(boolean darkEnabled) {
+ if (view == null) {
+ return;
+ }
+
+ // background
+ view.setBackground(ContextCompat.getDrawable(
+ view.getContext(),
+ darkEnabled ? R.color.dark_numpad_background : R.color.numpad_background
+ ));
+
+ // text
+ for (SoftKey key : getKeys()) {
+ key.setDarkTheme(darkEnabled);
+ }
+
+ // separators
+ int separatorColor = ContextCompat.getColor(
+ view.getContext(),
+ darkEnabled ? R.color.dark_numpad_separator : R.color.numpad_separator
+ );
+
+ for (View separator : getSeparators()) {
+ if (separator != null) {
+ separator.setBackgroundColor(separatorColor);
+ }
+ }
+ }
+
+ @Override
+ public void render() {
+ getView();
+ enableClickHandlers();
+ for (SoftKey key : getKeys()) {
+ key.render();
+ }
+ }
+
+ @Override
+ protected ArrayList getKeys() {
+ if (keys != null && keys.size() > 0) {
+ return keys;
+ }
+
+ ViewGroup table = view.findViewById(R.id.main_soft_keys);
+ int tableRowsCount = table.getChildCount();
+
+ for (int rowId = 0; rowId < tableRowsCount; rowId++) {
+ View row = table.getChildAt(rowId);
+ if (row instanceof ViewGroup) {
+ keys.addAll(getKeysFromContainer((ViewGroup) row));
+ }
+ }
+
+ return keys;
+ }
+
+ protected ArrayList getSeparators() {
+ // it's fine... it's shorter, faster and easier to read than searching with 3 nested loops
+ return new ArrayList<>(Arrays.asList(
+ view.findViewById(R.id.separator_1_1),
+ view.findViewById(R.id.separator_1_2),
+ view.findViewById(R.id.separator_2_1),
+ view.findViewById(R.id.separator_2_2),
+ view.findViewById(R.id.separator_3_1),
+ view.findViewById(R.id.separator_3_2),
+ view.findViewById(R.id.separator_4_1),
+ view.findViewById(R.id.separator_4_2)
+ ));
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java b/src/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java
new file mode 100644
index 000000000..ec0d201ac
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/MainLayoutSmall.java
@@ -0,0 +1,67 @@
+package io.github.sspanak.tt9.ui.main;
+
+import android.graphics.drawable.Drawable;
+import android.widget.LinearLayout;
+
+import androidx.core.content.ContextCompat;
+
+import java.util.ArrayList;
+
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.ime.TraditionalT9;
+import io.github.sspanak.tt9.ui.main.keys.SoftKey;
+
+class MainLayoutSmall extends BaseMainLayout {
+ public MainLayoutSmall(TraditionalT9 tt9) {
+ super(tt9, R.layout.main_small);
+ }
+
+ private void setSoftKeysVisibility() {
+ if (view != null) {
+ view.findViewById(R.id.main_soft_keys).setVisibility(tt9.getSettings().getShowSoftKeys() ? LinearLayout.VISIBLE : LinearLayout.GONE);
+ }
+ }
+
+ @Override
+ public void render() {
+ getView();
+ enableClickHandlers();
+ setSoftKeysVisibility();
+ }
+
+ @Override
+ final public void setDarkTheme(boolean darkEnabled) {
+ if (view == null) {
+ return;
+ }
+
+ // background
+ view.findViewById(R.id.main_soft_keys).setBackground(ContextCompat.getDrawable(
+ view.getContext(),
+ darkEnabled ? R.drawable.button_background_dark : R.drawable.button_background
+ ));
+
+ // text
+ for (SoftKey key : getKeys()) {
+ key.setDarkTheme(darkEnabled);
+ }
+
+ // separators
+ Drawable separatorColor = ContextCompat.getDrawable(
+ view.getContext(),
+ darkEnabled ? R.drawable.button_separator_dark : R.drawable.button_separator
+ );
+
+ view.findViewById(R.id.main_separator_left).setBackground(separatorColor);
+ view.findViewById(R.id.main_separator_right).setBackground(separatorColor);
+ }
+
+
+ @Override
+ protected ArrayList getKeys() {
+ if (view != null && (keys == null || keys.size() == 0)) {
+ keys = getKeysFromContainer(view.findViewById(R.id.main_soft_keys));
+ }
+ return keys;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/MainView.java b/src/io/github/sspanak/tt9/ui/main/MainView.java
new file mode 100644
index 000000000..ef9fc3489
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/MainView.java
@@ -0,0 +1,47 @@
+package io.github.sspanak.tt9.ui.main;
+
+import android.view.View;
+
+import io.github.sspanak.tt9.ime.TraditionalT9;
+
+public class MainView {
+ private final TraditionalT9 tt9;
+ private BaseMainLayout main;
+
+ public MainView(TraditionalT9 tt9) {
+ this.tt9 = tt9;
+
+ forceCreateView();
+ }
+
+ public boolean createView() {
+ if (tt9.getSettings().getShowSoftNumpad() && !(main instanceof MainLayoutNumpad)) {
+ main = new MainLayoutNumpad(tt9);
+ main.render();
+ return true;
+ } else if (!tt9.getSettings().getShowSoftNumpad() && !(main instanceof MainLayoutSmall)) {
+ main = new MainLayoutSmall(tt9);
+ main.render();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void forceCreateView() {
+ main = null;
+ createView();
+ }
+
+ public View getView() {
+ return main.getView();
+ }
+
+ public void render() {
+ main.render();
+ }
+
+ public void setDarkTheme(boolean darkEnabled) {
+ main.setDarkTheme(darkEnabled);
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java
new file mode 100644
index 000000000..4a3196e28
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftBackspaceKey.java
@@ -0,0 +1,41 @@
+package io.github.sspanak.tt9.ui.main.keys;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import io.github.sspanak.tt9.Logger;
+
+public class SoftBackspaceKey extends SoftKey {
+
+ public SoftBackspaceKey(Context context) {
+ super(context);
+ }
+
+ public SoftBackspaceKey(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SoftBackspaceKey(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ final protected boolean handlePress() {
+ return handleHold();
+ }
+
+ @Override
+ final protected boolean handleHold() {
+ if (tt9 == null) {
+ Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
+ return false;
+ }
+
+ return tt9.onBackspace();
+ }
+
+ @Override
+ final protected boolean handleRelease() {
+ return false;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java
new file mode 100644
index 000000000..3ed3d14f9
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftKey.java
@@ -0,0 +1,196 @@
+package io.github.sspanak.tt9.ui.main.keys;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StyleSpan;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.core.content.ContextCompat;
+
+import io.github.sspanak.tt9.Logger;
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.ime.TraditionalT9;
+
+public class SoftKey extends androidx.appcompat.widget.AppCompatButton implements View.OnTouchListener, View.OnLongClickListener {
+ protected TraditionalT9 tt9;
+
+ protected float COMPLEX_LABEL_TITLE_SIZE = 0.55f;
+ protected float COMPLEX_LABEL_SUB_TITLE_SIZE = 0.8f;
+
+ private boolean hold = false;
+ private boolean repeat = false;
+ private final Handler repeatHandler = new Handler(Looper.getMainLooper());
+
+
+ public SoftKey(Context context) {
+ super(context);
+ }
+
+ public SoftKey(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SoftKey(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+
+ public void setTT9(TraditionalT9 tt9) {
+ this.tt9 = tt9;
+ }
+
+ public void setDarkTheme(boolean darkEnabled) {
+ int textColor = ContextCompat.getColor(
+ getContext(),
+ darkEnabled ? R.color.dark_button_text : R.color.button_text
+ );
+ setTextColor(textColor);
+ }
+
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ getRootView().setOnTouchListener(this);
+ getRootView().setOnLongClickListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ super.onTouchEvent(event);
+
+ int action = (event.getAction() & MotionEvent.ACTION_MASK);
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ return handlePress();
+ } else if (action == MotionEvent.ACTION_UP) {
+ preventRepeat();
+ if (!repeat) {
+ return handleRelease();
+ }
+ repeat = false;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ hold = true;
+
+ // sometimes this gets called twice, so we debounce the call to the repeating function
+ repeatHandler.removeCallbacks(this::repeatOnLongPress);
+ repeatHandler.postDelayed(this::repeatOnLongPress, 1);
+ return true;
+ }
+
+ /**
+ * repeatOnLongPress
+ * Repeatedly calls "handleHold()" upon holding the respective SoftKey, to simulate physical keyboard behavior.
+ */
+ private void repeatOnLongPress() {
+ if (tt9 == null) {
+ Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
+ hold = false;
+ return;
+ }
+
+ if (hold) {
+ repeat = true;
+ handleHold();
+ repeatHandler.removeCallbacks(this::repeatOnLongPress);
+ repeatHandler.postDelayed(this::repeatOnLongPress, tt9.getSettings().getSoftKeyRepeatDelay());
+ }
+ }
+
+ /**
+ * preventRepeat
+ * Prevents "handleHold()" from being called repeatedly when the SoftKey is being held.
+ */
+ protected void preventRepeat() {
+ hold = false;
+ repeatHandler.removeCallbacks(this::repeatOnLongPress);
+ }
+
+ protected boolean handlePress() {
+ return false;
+ }
+
+ protected boolean handleHold() {
+ return false;
+ }
+
+ protected boolean handleRelease() {
+ if (tt9 == null) {
+ Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
+ return false;
+ }
+
+ int keyId = getId();
+
+ if (keyId == R.id.soft_key_add_word) return tt9.onKeyAddWord();
+ if (keyId == R.id.soft_key_input_mode) return tt9.onKeyNextInputMode();
+ if (keyId == R.id.soft_key_language) return tt9.onKeyNextLanguage();
+ if (keyId == R.id.soft_key_ok) return tt9.onOK();
+ if (keyId == R.id.soft_key_settings) return tt9.onKeyShowSettings();
+
+ return false;
+ }
+
+ /**
+ * getTitle
+ * Generates the name of the key, for example: "OK", "Backspace", "1", etc...
+ */
+ protected String getTitle() {
+ return null;
+ }
+
+ /**
+ * getSubTitle
+ * Generates a String describing what the key does.
+ * For example: "ABC" for 2-key; "⌫" for Backspace key, "⚙" for Settings key, and so on.
+ *
+ * The sub title label is optional.
+ */
+ protected String getSubTitle() {
+ return null;
+ }
+
+ /**
+ * render
+ * Sets the key label using "getTitle()" and "getSubtitle()" or if they both
+ * return NULL, the XML "text" attribute will be preserved.
+ *
+ * If there is only name label, it will be centered and at normal font size.
+ * If there is also a function label, it will be displayed below the name label and both will
+ * have their font size adjusted to fit inside the key.
+ */
+ public void render() {
+ String title = getTitle();
+ String subtitle = getSubTitle();
+
+ if (title == null) {
+ return;
+ } else if (subtitle == null) {
+ setText(title);
+ return;
+ }
+
+ SpannableStringBuilder sb = new SpannableStringBuilder(title);
+ sb.append('\n');
+ sb.append(subtitle);
+
+ sb.setSpan(new RelativeSizeSpan(COMPLEX_LABEL_TITLE_SIZE), 0, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ sb.setSpan(new StyleSpan(Typeface.ITALIC), 0, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ sb.setSpan(new RelativeSizeSpan(COMPLEX_LABEL_SUB_TITLE_SIZE), 1, sb.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+ setText(sb);
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java
new file mode 100644
index 000000000..e67c937e3
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftNumberKey.java
@@ -0,0 +1,125 @@
+package io.github.sspanak.tt9.ui.main.keys;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+
+import io.github.sspanak.tt9.Logger;
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.ime.helpers.Key;
+import io.github.sspanak.tt9.ime.modes.InputMode;
+import io.github.sspanak.tt9.languages.Language;
+import io.github.sspanak.tt9.languages.LanguageCollection;
+
+public class SoftNumberKey extends SoftKey {
+ public SoftNumberKey(Context context) {
+ super(context);
+ }
+
+ public SoftNumberKey(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SoftNumberKey(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected boolean handleHold() {
+ if (tt9 == null || tt9.getSettings().getInputMode() != InputMode.MODE_123 || getId() != R.id.soft_key_0) {
+ return super.handleHold();
+ }
+
+ preventRepeat();
+ int zeroCode = Key.numberToCode(0);
+ tt9.onKeyLongPress(zeroCode, new KeyEvent(KeyEvent.ACTION_DOWN, zeroCode));
+ return true;
+ }
+
+ @Override
+ protected boolean handleRelease() {
+ if (tt9 == null) {
+ Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
+ return false;
+ }
+
+ int keyCode = Key.numberToCode(getNumber(getId()));
+ if (keyCode < 0) {
+ return false;
+ }
+
+ tt9.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+ tt9.onKeyUp(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+
+ return true;
+ }
+
+ @Override
+ protected String getTitle() {
+ return String.valueOf(getNumber(getId()));
+ }
+
+ @Override
+ protected String getSubTitle() {
+ if (tt9 == null) {
+ return null;
+ }
+
+ int number = getNumber(getId());
+
+ if (number == 0) {
+ if (tt9.getSettings().getInputMode() == InputMode.MODE_123) {
+ return "+";
+ } else {
+ COMPLEX_LABEL_SUB_TITLE_SIZE = 1;
+ return "␣";
+ }
+ }
+
+ // no special labels in 123 mode
+ if (tt9.getSettings().getInputMode() == InputMode.MODE_123) {
+ return null;
+ }
+
+ // 1
+ if (number == 1) {
+ return ",:-)";
+ }
+
+ // 2-9
+ int textCase = tt9.getSettings().getTextCase();
+ Language language = LanguageCollection.getLanguage(tt9.getSettings().getInputLanguage());
+
+ if (language == null) {
+ Logger.d("SoftNumberKey.getLabel", "Cannot generate a label when the language is NULL.");
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ ArrayList chars = language.getKeyCharacters(number, false);
+ for (int i = 0; i < 5 && i < chars.size(); i++) {
+ sb.append(
+ textCase == InputMode.CASE_UPPER ? chars.get(i).toUpperCase(language.getLocale()) : chars.get(i)
+ );
+ }
+
+ return sb.toString();
+ }
+
+ private int getNumber(int keyId) {
+ if (keyId == R.id.soft_key_0) return 0;
+ if (keyId == R.id.soft_key_1) return 1;
+ if (keyId == R.id.soft_key_2) return 2;
+ if (keyId == R.id.soft_key_3) return 3;
+ if (keyId == R.id.soft_key_4) return 4;
+ if (keyId == R.id.soft_key_5) return 5;
+ if (keyId == R.id.soft_key_6) return 6;
+ if (keyId == R.id.soft_key_7) return 7;
+ if (keyId == R.id.soft_key_8) return 8;
+ if (keyId == R.id.soft_key_9) return 9;
+
+ return -1;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java b/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java
new file mode 100644
index 000000000..aab414c0d
--- /dev/null
+++ b/src/io/github/sspanak/tt9/ui/main/keys/SoftPunctuationKey.java
@@ -0,0 +1,85 @@
+package io.github.sspanak.tt9.ui.main.keys;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+
+import io.github.sspanak.tt9.Logger;
+import io.github.sspanak.tt9.R;
+import io.github.sspanak.tt9.ime.modes.InputMode;
+
+public class SoftPunctuationKey extends SoftKey {
+ public SoftPunctuationKey(Context context) {
+ super(context);
+ }
+
+ public SoftPunctuationKey(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SoftPunctuationKey(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected boolean handleHold() {
+ if (tt9 == null || tt9.getSettings().getInputMode() != InputMode.MODE_123) {
+ return super.handleHold();
+ }
+
+ preventRepeat();
+ int keyId = getId();
+ if (keyId == R.id.soft_key_punctuation_1) return tt9.onText(",");
+ if (keyId == R.id.soft_key_punctuation_2) return tt9.onText(".");
+
+ return false;
+ }
+
+ @Override
+ protected boolean handleRelease() {
+ if (tt9 == null) {
+ Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
+ return false;
+ }
+
+ int keyId = getId();
+ if (tt9.getSettings().getInputMode() == InputMode.MODE_123) {
+ if (keyId == R.id.soft_key_punctuation_1) return tt9.onOtherKey(KeyEvent.KEYCODE_STAR);
+ if (keyId == R.id.soft_key_punctuation_2) return tt9.onOtherKey(KeyEvent.KEYCODE_POUND);
+ } else {
+ if (keyId == R.id.soft_key_punctuation_1) return tt9.onText("!");
+ if (keyId == R.id.soft_key_punctuation_2) return tt9.onText("?");
+ }
+
+ return true;
+ }
+
+ @Override
+ protected String getTitle() {
+ if (tt9 == null) {
+ return "PUNC";
+ }
+
+ int keyId = getId();
+ if (tt9.getSettings().getInputMode() == InputMode.MODE_123) {
+ if (keyId == R.id.soft_key_punctuation_1) return "✱";
+ if (keyId == R.id.soft_key_punctuation_2) return "#";
+ } else {
+ if (keyId == R.id.soft_key_punctuation_1) return "!";
+ if (keyId == R.id.soft_key_punctuation_2) return "?";
+ }
+
+ return "PUNC";
+ }
+
+ @Override
+ protected String getSubTitle() {
+ int keyId = getId();
+ if (tt9 != null && tt9.getSettings().getInputMode() == InputMode.MODE_123) {
+ if (keyId == R.id.soft_key_punctuation_1) return ",";
+ if (keyId == R.id.soft_key_punctuation_2) return ".";
+ }
+
+ return null;
+ }
+}
diff --git a/src/io/github/sspanak/tt9/ui/bottom/StatusBar.java b/src/io/github/sspanak/tt9/ui/tray/StatusBar.java
similarity index 86%
rename from src/io/github/sspanak/tt9/ui/bottom/StatusBar.java
rename to src/io/github/sspanak/tt9/ui/tray/StatusBar.java
index cab2ad9a7..efbe3fa42 100644
--- a/src/io/github/sspanak/tt9/ui/bottom/StatusBar.java
+++ b/src/io/github/sspanak/tt9/ui/tray/StatusBar.java
@@ -1,4 +1,4 @@
-package io.github.sspanak.tt9.ui.bottom;
+package io.github.sspanak.tt9.ui.tray;
import android.content.Context;
import android.view.View;
@@ -19,17 +19,16 @@ public StatusBar(View mainView) {
}
- public StatusBar setText(String text) {
+ public void setText(String text) {
statusText = "[ " + text + " ]";
this.render();
- return this;
}
- public StatusBar setDarkTheme(boolean darkTheme) {
+ public void setDarkTheme(boolean darkTheme) {
if (statusView == null) {
Logger.w("StatusBar.setDarkTheme", "Not changing the theme of a NULL View.");
- return this;
+ return;
}
Context context = statusView.getContext();
@@ -47,7 +46,6 @@ public StatusBar setDarkTheme(boolean darkTheme) {
statusView.setTextColor(color);
this.render();
- return this;
}
private void render() {
diff --git a/src/io/github/sspanak/tt9/ui/bottom/SuggestionsAdapter.java b/src/io/github/sspanak/tt9/ui/tray/SuggestionsAdapter.java
similarity index 81%
rename from src/io/github/sspanak/tt9/ui/bottom/SuggestionsAdapter.java
rename to src/io/github/sspanak/tt9/ui/tray/SuggestionsAdapter.java
index 060951270..653cb6de6 100644
--- a/src/io/github/sspanak/tt9/ui/bottom/SuggestionsAdapter.java
+++ b/src/io/github/sspanak/tt9/ui/tray/SuggestionsAdapter.java
@@ -1,4 +1,4 @@
-package io.github.sspanak.tt9.ui.bottom;
+package io.github.sspanak.tt9.ui.tray;
import android.content.Context;
import android.graphics.Color;
@@ -13,6 +13,7 @@
import java.util.List;
public class SuggestionsAdapter extends RecyclerView.Adapter {
+ private final SuggestionsBar suggestionsBar;
private final int layout;
private final int textViewResourceId;
private final LayoutInflater mInflater;
@@ -23,7 +24,8 @@ public class SuggestionsAdapter extends RecyclerView.Adapter suggestions) {
+ public SuggestionsAdapter(Context context, SuggestionsBar suggestionBar, int layout, int textViewResourceId, List suggestions) {
+ this.suggestionsBar = suggestionBar;
this.layout = layout;
this.textViewResourceId = textViewResourceId;
this.mInflater = LayoutInflater.from(context);
@@ -43,6 +45,7 @@ public void onBindViewHolder(ViewHolder holder, int position) {
holder.suggestionItem.setText(mSuggestions.get(position));
holder.suggestionItem.setTextColor(colorDefault);
holder.suggestionItem.setBackgroundColor(selectedIndex == position ? colorHighlight : Color.TRANSPARENT);
+ holder.suggestionItem.setOnClickListener(v -> suggestionsBar.onItemClick(holder.getAdapterPosition()));
}
@@ -68,7 +71,7 @@ public void setColorHighlight(int colorHighlight) {
public class ViewHolder extends RecyclerView.ViewHolder {
- TextView suggestionItem;
+ final TextView suggestionItem;
ViewHolder(View itemView) {
super(itemView);
diff --git a/src/io/github/sspanak/tt9/ui/bottom/SuggestionsBar.java b/src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java
similarity index 86%
rename from src/io/github/sspanak/tt9/ui/bottom/SuggestionsBar.java
rename to src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java
index 011fd1cf2..ba5705eaf 100644
--- a/src/io/github/sspanak/tt9/ui/bottom/SuggestionsBar.java
+++ b/src/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java
@@ -1,4 +1,4 @@
-package io.github.sspanak.tt9.ui.bottom;
+package io.github.sspanak.tt9.ui.tray;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -16,7 +16,7 @@
import java.util.List;
import io.github.sspanak.tt9.R;
-import io.github.sspanak.tt9.preferences.SettingsStore;
+import io.github.sspanak.tt9.ime.TraditionalT9;
public class SuggestionsBar {
private final List suggestions = new ArrayList<>();
@@ -24,14 +24,14 @@ public class SuggestionsBar {
private boolean isDarkThemeEnabled = false;
private final RecyclerView mView;
- private final SettingsStore settings;
+ private final TraditionalT9 tt9;
private SuggestionsAdapter mSuggestionsAdapter;
- public SuggestionsBar(SettingsStore settings, View mainView) {
+ public SuggestionsBar(TraditionalT9 tt9, View mainView) {
super();
- this.settings = settings;
+ this.tt9 = tt9;
mView = mainView.findViewById(R.id.suggestions_bar);
mView.setLayoutManager(new LinearLayoutManager(mainView.getContext(), RecyclerView.HORIZONTAL,false));
@@ -45,8 +45,8 @@ public SuggestionsBar(SettingsStore settings, View mainView) {
private void configureAnimation() {
DefaultItemAnimator animator = new DefaultItemAnimator();
- int translateDuration = settings.getSuggestionTranslateAnimationDuration();
- int selectDuration = settings.getSuggestionSelectAnimationDuration();
+ int translateDuration = tt9.getSettings().getSuggestionTranslateAnimationDuration();
+ int selectDuration = tt9.getSettings().getSuggestionSelectAnimationDuration();
animator.setMoveDuration(selectDuration);
animator.setChangeDuration(translateDuration);
@@ -60,13 +60,14 @@ private void configureAnimation() {
private void initDataAdapter(Context context) {
mSuggestionsAdapter = new SuggestionsAdapter(
context,
- R.layout.suggestion_list_view,
+ this,
+ tt9.getSettings().getShowSoftNumpad() ? R.layout.suggestion_list_numpad : R.layout.suggestion_list,
R.id.suggestion_list_item,
suggestions
);
mView.setAdapter(mSuggestionsAdapter);
- setDarkTheme(settings.getDarkTheme());
+ setDarkTheme(tt9.getSettings().getDarkTheme());
}
@@ -160,7 +161,7 @@ public void scrollToSuggestion(int increment) {
* system context and theme.
*
* More info:
- * https://stackoverflow.com/questions/72382886/system-applies-night-mode-to-views-added-in-service-type-application-overlay
+ * ...
*/
public void setDarkTheme(boolean darkEnabled) {
isDarkThemeEnabled = darkEnabled;
@@ -213,4 +214,14 @@ private void ecoSetBackground(List newSuggestions) {
setBackground(newSuggestions);
}
+
+
+ /**
+ * onItemClick
+ * Passes through suggestion selected using the touchscreen.
+ */
+ public void onItemClick(int position) {
+ selectedIndex = position;
+ tt9.onOK();
+ }
}