1
0
Fork 0

Touchscreen support + small improvements

* Soft-Keyboard numpad

* no more SoftKeyHandler, the main view is in its own package

* settings are no longer passed unnecessarilly here and there

* fixed numeric mode not working in some cases

* simplified suggestion handling

* fixed crashing when changing the phone orientation
This commit is contained in:
nebkrid 2023-02-17 22:09:31 +01:00 committed by Dimo Karaivanov
parent 7f6cd6110d
commit 4e5416f6b4
36 changed files with 1142 additions and 368 deletions

View file

@ -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). or get the APK from the [Releases Section](https://github.com/sspanak/tt9/releases/latest).
## System Requirements ## 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: - Free space:
- Minimum 25 Mb when not using Predictive mode and no dictionaries are loaded. - 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). - 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._ _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). So make sure to read the initial setup and the hotkey tips in the [user manual](docs/user-manual.md).
## Contributing to the Project ## 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: 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 device is: Qin F21 Pro+ / Android 11. - [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 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. - 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). - 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! Your PRs are welcome!

View file

@ -1,15 +1,16 @@
# Traditional T9 # 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). All source code, documentation and the privacy policy are available on Github: [https://github.com/sspanak/tt9](https://github.com/sspanak/tt9).
## Initial Setup ## 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 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.
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.
_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._ _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 ### 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: 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 (→): #### D-pad Right (→):
_Predictive mode only._ _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. - **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 "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. - **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". 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. - **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 #): #### 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 ✱): #### Settings Key (Default: Hold ✱):
Open the Configration screen. Open the Settings configuration screen.
## On-screen Soft keys ## On-screen Keypad
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 touchscreen-only phones, a fully functional on-screen keypad is available. Enable it from Settings -> Appearance -> Show On-Screen Numpad.
#### Left Soft Key: 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.
Open the [Settings screen](#settings-screen).
#### Right Soft Key: If you do have a hardware keypad and prefer having more screen space, disable the software keys from the Settings -> Appearance.
Backspace.
## Settings Screen ## Settings Screen
On the Settings screen, you can choose languages for typing, configure the keypad hotkeys or change the application appearance. On the Settings screen, you can choose languages for typing, configure the keypad hotkeys or change the application appearance.

231
res/layout/main_numpad.xml Normal file
View file

@ -0,0 +1,231 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_numpad"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="@dimen/numpad_candidate_height">
<TextView
android:id="@+id/status_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="italic"
tools:text="@tools:sample/lorem" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/suggestions_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="horizontal"
android:gravity="center"
android:orientation="horizontal"
android:scrollbars="none" />
</FrameLayout>
<LinearLayout
android:id="@+id/main_soft_keys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="@dimen/numpad_padding_top"
android:paddingBottom="@dimen/numpad_padding_bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/numpad_key_height"
tools:ignore="HardcodedText,KeyboardInaccessibleWidget">
<io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/soft_key_settings"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="⚙"
android:textSize="@dimen/soft_key_icon_size" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_1"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_2"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_3"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftBackspaceKey
android:id="@+id/soft_key_backspace"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="⌫"
android:textSize="@dimen/soft_key_icon_size"
tools:ignore="HardcodedText" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/numpad_key_height"
tools:ignore="HardcodedText">
<io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/soft_key_add_word"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="+WORD" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_4"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_5"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_6"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/numpad_key_height"
tools:ignore="HardcodedText">
<io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/soft_key_input_mode"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="MODE" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_7"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_8"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_9"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/numpad_key_height"
tools:ignore="HardcodedText">
<io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/soft_key_language"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="🌐"
android:textSize="@dimen/soft_key_icon_size"
tools:ignore="HardcodedText" />
<io.github.sspanak.tt9.ui.main.keys.SoftPunctuationKey
android:id="@+id/soft_key_punctuation_1"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftNumberKey
android:id="@+id/soft_key_0"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftPunctuationKey
android:id="@+id/soft_key_punctuation_2"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
<io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/soft_key_ok"
style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@android:string/ok"
tools:ignore="ButtonOrder" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_small"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<FrameLayout <FrameLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="@dimen/candidate_list_height"> android:layout_height="@dimen/candidate_height">
<TextView <TextView
android:id="@+id/status_bar" android:id="@+id/status_bar"
@ -34,55 +35,47 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/soft_key_height" android:layout_height="@dimen/soft_key_height"
android:baselineAligned="true" android:baselineAligned="true"
android:orientation="horizontal"> android:orientation="horizontal"
tools:ignore="HardcodedText,KeyboardInaccessibleWidget">
<Button <io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/main_left" android:id="@+id/soft_key_settings"
style="@android:style/Widget.Holo.Button.Borderless" style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="3" android:layout_weight="3"
android:clickable="true"
android:focusable="false" android:focusable="false"
android:longClickable="true"
android:text="⚙" android:text="⚙"
android:textSize="@dimen/soft_key_icon_size" android:textSize="@dimen/soft_key_icon_size" />
tools:ignore="HardcodedText,KeyboardInaccessibleWidget" />
<View <View
android:id="@+id/main_separator_left" android:id="@+id/main_separator_left"
style="@style/hSeparator" style="@style/hSeparator"
android:background="@drawable/button_separator_dark" /> android:background="@drawable/button_separator_dark" />
<Button <io.github.sspanak.tt9.ui.main.keys.SoftKey
android:id="@+id/main_mid" android:id="@+id/soft_key_ok"
style="@android:style/Widget.Holo.Button.Borderless" style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="5" android:layout_weight="5"
android:clickable="true"
android:focusable="false" android:focusable="false"
android:longClickable="true" android:text="@android:string/ok" />
android:text="@android:string/ok"
tools:ignore="ButtonOrder,KeyboardInaccessibleWidget" />
<View <View
android:id="@+id/main_separator_right" android:id="@+id/main_separator_right"
android:background="@drawable/button_separator_dark" android:background="@drawable/button_separator_dark"
style="@style/hSeparator" /> style="@style/hSeparator" />
<Button <io.github.sspanak.tt9.ui.main.keys.SoftBackspaceKey
android:id="@+id/main_right" android:id="@+id/soft_key_backspace"
style="@android:style/Widget.Holo.Button.Borderless" style="@android:style/Widget.Holo.Button.Borderless"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="3" android:layout_weight="3"
android:clickable="true"
android:focusable="false" android:focusable="false"
android:longClickable="true"
android:text="⌫" android:text="⌫"
android:textSize="@dimen/soft_key_icon_size" android:textSize="@dimen/soft_key_icon_size" />
tools:ignore="HardcodedText,KeyboardInaccessibleWidget" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -2,15 +2,14 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suggestion_list_view" android:id="@+id/suggestion_list_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:id="@+id/suggestion_list_item" android:id="@+id/suggestion_list_item"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="@dimen/candidate_padding_horizontal" android:paddingHorizontal="@dimen/candidate_padding_horizontal"
android:paddingVertical="@dimen/candidate_padding_vertical"
android:text=""
android:textSize="@dimen/candidate_font_size" /> android:textSize="@dimen/candidate_font_size" />
</LinearLayout> </LinearLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/suggestion_list_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/suggestion_list_item"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:minWidth="@dimen/numpad_candidate_min_width"
android:paddingHorizontal="@dimen/candidate_padding_horizontal"
android:textSize="@dimen/numpad_candidate_font_size" />
</LinearLayout>

View file

@ -59,4 +59,5 @@
<string name="pref_upside_down_keys_summary">Включете настройката, ако на първият ред са 789, вместо 123.</string> <string name="pref_upside_down_keys_summary">Включете настройката, ако на първият ред са 789, вместо 123.</string>
<string name="dictionary_truncate_unselected">Изтрий неизбраните</string> <string name="dictionary_truncate_unselected">Изтрий неизбраните</string>
<string name="pref_category_setup">Начална настройка</string> <string name="pref_category_setup">Начална настройка</string>
<string name="pref_show_soft_numpad">Цифрова клавиатура на екрана</string>
</resources> </resources>

View file

@ -35,4 +35,5 @@
<string name="pref_upside_down_keys">Orden de teclas inverso</string> <string name="pref_upside_down_keys">Orden de teclas inverso</string>
<string name="pref_upside_down_keys_summary">Habilite la configuración si hay 789 en la primera fila, en lugar de 123.</string> <string name="pref_upside_down_keys_summary">Habilite la configuración si hay 789 en la primera fila, en lugar de 123.</string>
<string name="pref_category_setup">Configuración inicial</string> <string name="pref_category_setup">Configuración inicial</string>
<string name="pref_show_soft_numpad">Teclado numérico en pantalla</string>
</resources> </resources>

View file

@ -50,4 +50,5 @@
<string name="pref_upside_down_keys_summary">Activez le paramètre s\'il y a 789 sur le premier rang, au lieu de 123.</string> <string name="pref_upside_down_keys_summary">Activez le paramètre s\'il y a 789 sur le premier rang, au lieu de 123.</string>
<string name="dictionary_truncate_unselected">Vider les non sélectionnés</string> <string name="dictionary_truncate_unselected">Vider les non sélectionnés</string>
<string name="pref_category_setup">Configuration initiale</string> <string name="pref_category_setup">Configuration initiale</string>
<string name="pref_show_soft_numpad">Pavé numérique à l\'écran</string>
</resources> </resources>

View file

@ -59,4 +59,5 @@
<string name="pref_upside_down_keys_summary">Используйте настройку, если в первом ряду 789 вместо 123.</string> <string name="pref_upside_down_keys_summary">Используйте настройку, если в первом ряду 789 вместо 123.</string>
<string name="dictionary_truncate_unselected">Очистить невыбранные</string> <string name="dictionary_truncate_unselected">Очистить невыбранные</string>
<string name="pref_category_setup">Начальная настройка</string> <string name="pref_category_setup">Начальная настройка</string>
<string name="pref_show_soft_numpad">Экранная цифровая клавиатура</string>
</resources> </resources>

View file

@ -59,4 +59,5 @@
<string name="pref_upside_down_keys_summary">Використовуйте налаштування, якщо 789 у першому рядку замість 123.</string> <string name="pref_upside_down_keys_summary">Використовуйте налаштування, якщо 789 у першому рядку замість 123.</string>
<string name="dictionary_truncate_unselected">Очистіть невибрані</string> <string name="dictionary_truncate_unselected">Очистіть невибрані</string>
<string name="pref_category_setup">Початкове налаштування</string> <string name="pref_category_setup">Початкове налаштування</string>
<string name="pref_show_soft_numpad">Екранна цифрова клавіатура</string>
</resources> </resources>

View file

@ -8,10 +8,14 @@
<color name="candidate_selected">#AAAAAA</color> <color name="candidate_selected">#AAAAAA</color>
<color name="candidate_separator">#888888</color> <color name="candidate_separator">#888888</color>
<color name="numpad_background">#E7F0E7</color>
<!-- Dark theme --> <!-- Dark theme -->
<color name="dark_button_text">#C0C0C0</color> <color name="dark_button_text">#C0C0C0</color>
<color name="dark_candidate_background">#333333</color> <color name="dark_candidate_background">#2C2C2C</color>
<color name="dark_candidate_color">#CCCCCC</color> <color name="dark_candidate_color">#CCCCCC</color>
<color name="dark_candidate_selected">#555555</color> <color name="dark_candidate_selected">#555555</color>
<color name="dark_numpad_background">#353835</color>
</resources> </resources>

View file

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="candidate_list_height">26sp</dimen>
<dimen name="candidate_font_size">18sp</dimen> <dimen name="candidate_font_size">18sp</dimen>
<dimen name="candidate_height">26sp</dimen>
<dimen name="candidate_padding_horizontal">6sp</dimen> <dimen name="candidate_padding_horizontal">6sp</dimen>
<dimen name="candidate_padding_vertical">1sp</dimen>
<dimen name="soft_key_height">44dp</dimen> <dimen name="soft_key_height">44dp</dimen>
<dimen name="soft_key_icon_size">24sp</dimen> <dimen name="soft_key_icon_size">24sp</dimen>
@ -14,4 +13,13 @@
<dimen name="pref_padding_vertical">18dp</dimen> <dimen name="pref_padding_vertical">18dp</dimen>
<dimen name="pref_text_size">22sp</dimen> <dimen name="pref_text_size">22sp</dimen>
<dimen name="pref_summary_size">19sp</dimen> <dimen name="pref_summary_size">19sp</dimen>
<!-- Numpad -->
<dimen name="numpad_padding_top">5dp</dimen>
<dimen name="numpad_padding_bottom">15dp</dimen>
<dimen name="numpad_key_height">56dp</dimen>
<dimen name="numpad_candidate_font_size">17sp</dimen>
<dimen name="numpad_candidate_height">32dp</dimen>
<dimen name="numpad_candidate_min_width">36dp</dimen>
</resources> </resources>

View file

@ -29,6 +29,8 @@
<string name="pref_dark_theme">Dark Theme</string> <string name="pref_dark_theme">Dark Theme</string>
<string name="pref_double_zero_char">Character for Double 0-key Press</string> <string name="pref_double_zero_char">Character for Double 0-key Press</string>
<string name="pref_show_soft_function_keys">Show On-Screen Keys</string> <string name="pref_show_soft_function_keys">Show On-Screen Keys</string>
<string name="pref_show_soft_numpad">Show On-Screen Numpad</string>
<string name="pref_show_soft_numpad_summary" translatable="false">(BETA)</string>
<string name="pref_help">Help</string> <string name="pref_help">Help</string>
<string name="pref_upside_down_keys">Reverse Key Order</string> <string name="pref_upside_down_keys">Reverse Key Order</string>
<string name="pref_upside_down_keys_summary">Use this if you have 789 on the first row, instead of 123.</string> <string name="pref_upside_down_keys_summary">Use this if you have 789 on the first row, instead of 123.</string>

View file

@ -13,4 +13,12 @@
app:layout="@layout/pref_switch" app:layout="@layout/pref_switch"
app:title="@string/pref_show_soft_function_keys" /> app:title="@string/pref_show_soft_function_keys" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="pref_show_soft_numpad"
app:layout="@layout/pref_switch"
app:title="@string/pref_show_soft_numpad"
app:summary="@string/pref_show_soft_numpad_summary"
app:dependency="pref_show_soft_keys" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -143,36 +143,17 @@ abstract class KeyPadHandler extends InputMethodService {
isBackspaceHandled = false; 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 // start tracking key hold
if (shouldTrackNumPress() || Key.isHotkey(settings, -keyCode)) { if (Key.isNumber(keyCode) || Key.isHotkey(settings, -keyCode)) {
event.startTracking(); event.startTracking();
} }
return Key.isHotkey(settings, keyCode) || Key.isHotkey(settings, -keyCode) return
Key.isNumber(keyCode)
|| Key.isOK(keyCode)
|| Key.isHotkey(settings, keyCode) || Key.isHotkey(settings, -keyCode)
|| keyCode == KeyEvent.KEYCODE_STAR || keyCode == KeyEvent.KEYCODE_STAR
|| keyCode == KeyEvent.KEYCODE_POUND || keyCode == KeyEvent.KEYCODE_POUND
|| (Key.isNumber(keyCode) && shouldTrackNumPress())
|| ((keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && shouldTrackUpDown()) || ((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());
} }
@ -217,12 +198,15 @@ abstract class KeyPadHandler extends InputMethodService {
return false; return false;
} }
// Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount());
if (keyCode == ignoreNextKeyUp) { if (keyCode == ignoreNextKeyUp) {
// Logger.d("onKeyUp", "Ignored: " + keyCode); // Logger.d("onKeyUp", "Ignored: " + keyCode);
ignoreNextKeyUp = 0; ignoreNextKeyUp = 0;
return true; return true;
} }
// repeat handling
keyRepeatCounter = (lastKeyCode == keyCode) ? keyRepeatCounter + 1 : 0; keyRepeatCounter = (lastKeyCode == keyCode) ? keyRepeatCounter + 1 : 0;
lastKeyCode = keyCode; lastKeyCode = keyCode;
@ -231,46 +215,31 @@ abstract class KeyPadHandler extends InputMethodService {
lastNumKeyCode = keyCode; lastNumKeyCode = keyCode;
} }
// Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount()); // backspace is handled in onKeyDown only, so we ignore it here
if (isBackspaceHandled) { if (isBackspaceHandled) {
return true; 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) {
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 (handleHotkey(keyCode, false)) {
return true;
}
if (Key.isNumber(keyCode)) { if (Key.isNumber(keyCode)) {
return onNumber(Key.codeToNumber(settings, keyCode), false, numKeyRepeatCounter); return onNumber(Key.codeToNumber(settings, keyCode), false, numKeyRepeatCounter);
} }
if (Key.isOK(keyCode)) {
return onOK();
}
if (handleHotkey(keyCode, false)) {
return true;
}
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP: return onUp(); case KeyEvent.KEYCODE_DPAD_UP: return onUp();
case KeyEvent.KEYCODE_DPAD_DOWN: return onDown(); case KeyEvent.KEYCODE_DPAD_DOWN: return onDown();
case KeyEvent.KEYCODE_DPAD_LEFT: return onLeft(); case KeyEvent.KEYCODE_DPAD_LEFT: return onLeft();
case KeyEvent.KEYCODE_DPAD_RIGHT: return onRight(keyRepeatCounter > 0); case KeyEvent.KEYCODE_DPAD_RIGHT: return onRight(keyRepeatCounter > 0);
case KeyEvent.KEYCODE_STAR: return onStar(); case KeyEvent.KEYCODE_STAR:
case KeyEvent.KEYCODE_POUND: return onPound(); case KeyEvent.KEYCODE_POUND:
return onOtherKey(keyCode);
} }
return false; return false;
@ -324,8 +293,7 @@ abstract class KeyPadHandler extends InputMethodService {
abstract protected boolean onLeft(); abstract protected boolean onLeft();
abstract protected boolean onRight(boolean repeat); abstract protected boolean onRight(boolean repeat);
abstract protected boolean onNumber(int key, boolean hold, int repeat); abstract protected boolean onNumber(int key, boolean hold, int repeat);
abstract protected boolean onStar(); abstract protected boolean onOtherKey(int keyCode);
abstract protected boolean onPound();
// customized key handlers // customized key handlers
abstract protected boolean onKeyAddWord(); abstract protected boolean onKeyAddWord();

View file

@ -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;
}
}

View file

@ -17,13 +17,16 @@ import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.db.DictionaryDb; import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.ime.helpers.InputModeValidator; import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
import io.github.sspanak.tt9.ime.helpers.InputType; 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.helpers.TextField;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.ui.bottom.StatusBar; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.ui.bottom.SuggestionsBar;
import io.github.sspanak.tt9.ui.UI; 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 { public class TraditionalT9 extends KeyPadHandler {
// internal settings/data // internal settings/data
@ -40,7 +43,7 @@ public class TraditionalT9 extends KeyPadHandler {
protected Language mLanguage; protected Language mLanguage;
// soft key view // soft key view
private SoftKeyHandler softKeyHandler = null; private MainView mainView = null;
private StatusBar statusBar = null; private StatusBar statusBar = null;
private SuggestionsBar suggestionBar = null; private SuggestionsBar suggestionBar = null;
@ -50,6 +53,10 @@ public class TraditionalT9 extends KeyPadHandler {
return self.getApplicationContext(); return self.getApplicationContext();
} }
public SettingsStore getSettings() {
return settings;
}
private void loadSettings() { private void loadSettings() {
mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage()); mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage());
@ -80,16 +87,9 @@ public class TraditionalT9 extends KeyPadHandler {
DictionaryDb.init(this); DictionaryDb.init(this);
DictionaryDb.normalizeWordFrequencies(settings); DictionaryDb.normalizeWordFrequencies(settings);
if (softKeyHandler == null) { if (mainView == null) {
softKeyHandler = new SoftKeyHandler(this); mainView = new MainView(this);
} initTray();
if (statusBar == null) {
statusBar = new StatusBar(softKeyHandler.getView());
}
if (suggestionBar == null) {
suggestionBar = new SuggestionsBar(settings, softKeyHandler.getView());
} }
loadSettings(); loadSettings();
@ -115,17 +115,28 @@ public class TraditionalT9 extends KeyPadHandler {
} }
private void initUi() { private void initTray() {
statusBar setInputView(mainView.getView());
.setText(mInputMode != null ? mInputMode.toString() : "") statusBar = new StatusBar(mainView.getView());
.setDarkTheme(settings.getDarkTheme()); suggestionBar = new SuggestionsBar(this, mainView.getView());
}
clearSuggestions();
private void setDarkTheme() {
mainView.setDarkTheme(settings.getDarkTheme());
statusBar.setDarkTheme(settings.getDarkTheme());
suggestionBar.setDarkTheme(settings.getDarkTheme()); suggestionBar.setDarkTheme(settings.getDarkTheme());
}
softKeyHandler.setDarkTheme(settings.getDarkTheme());
softKeyHandler.setSoftKeysVisibility(settings.getShowSoftKeys()); private void initUi() {
softKeyHandler.show(); if (mainView.createView()) {
initTray();
}
clearSuggestions();
statusBar.setText(mInputMode != null ? mInputMode.toString() : "");
setDarkTheme();
mainView.render();
} }
@ -163,8 +174,6 @@ public class TraditionalT9 extends KeyPadHandler {
protected void onStop() { protected void onStop() {
onFinishTyping(); onFinishTyping();
clearSuggestions(); clearSuggestions();
softKeyHandler.hide();
} }
@ -193,10 +202,10 @@ public class TraditionalT9 extends KeyPadHandler {
public boolean onOK() { public boolean onOK() {
if (!textField.isThereText()) { if (!isInputViewShown() && !textField.isThereText()) {
forceShowWindowIfHidden(); forceShowWindowIfHidden();
return true; return true;
} else if (isSuggestionViewHidden() && currentInputConnection != null) { } else if (isSuggestionViewHidden()) {
return performOKAction(); return performOKAction();
} }
@ -275,12 +284,10 @@ public class TraditionalT9 extends KeyPadHandler {
String currentWord = getComposingText(); 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. // instead of requiring "OK" before that.
if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat > 0)) { if (mInputMode.shouldAcceptCurrentSuggestion(key, hold, repeat > 0)) {
mInputMode.onAcceptSuggestion(currentWord); autoCorrectSpace(acceptIncompleteSuggestion(), false, key, hold, repeat > 0);
commitCurrentSuggestion(false);
autoCorrectSpace(currentWord, false, key, hold, repeat > 0);
currentWord = ""; currentWord = "";
} }
@ -296,17 +303,6 @@ public class TraditionalT9 extends KeyPadHandler {
if (mInputMode.shouldSelectNextSuggestion() && !isSuggestionViewHidden()) { if (mInputMode.shouldSelectNextSuggestion() && !isSuggestionViewHidden()) {
nextSuggestion(); 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 { } else {
getSuggestions(); getSuggestions();
} }
@ -315,19 +311,33 @@ public class TraditionalT9 extends KeyPadHandler {
} }
protected boolean onPound() { public boolean onOtherKey(int keyCode) {
textField.setText("#"); 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; return true;
} }
protected boolean onStar() { public boolean onText(String text) {
textField.setText("*"); 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; return true;
} }
protected boolean onKeyAddWord() { public boolean onKeyAddWord() {
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) { if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
return false; return false;
} }
@ -337,7 +347,7 @@ public class TraditionalT9 extends KeyPadHandler {
} }
protected boolean onKeyNextLanguage() { public boolean onKeyNextLanguage() {
if (nextLang()) { if (nextLang()) {
commitCurrentSuggestion(false); commitCurrentSuggestion(false);
mInputMode.changeLanguage(mLanguage); mInputMode.changeLanguage(mLanguage);
@ -345,6 +355,7 @@ public class TraditionalT9 extends KeyPadHandler {
resetKeyRepeat(); resetKeyRepeat();
clearSuggestions(); clearSuggestions();
statusBar.setText(mInputMode.toString()); statusBar.setText(mInputMode.toString());
mainView.render();
forceShowWindowIfHidden(); forceShowWindowIfHidden();
return true; return true;
@ -354,14 +365,15 @@ public class TraditionalT9 extends KeyPadHandler {
} }
protected boolean onKeyNextInputMode() { public boolean onKeyNextInputMode() {
nextInputMode(); nextInputMode();
mainView.render();
forceShowWindowIfHidden(); forceShowWindowIfHidden();
return (mEditing != EDITING_STRICT_NUMERIC && mEditing != EDITING_DIALER); return (mEditing != EDITING_STRICT_NUMERIC && mEditing != EDITING_DIALER);
} }
protected boolean onKeyShowSettings() { public boolean onKeyShowSettings() {
if (mEditing == EDITING_DIALER) { if (mEditing == EDITING_DIALER) {
return false; return false;
} }
@ -414,6 +426,15 @@ public class TraditionalT9 extends KeyPadHandler {
} }
private String acceptIncompleteSuggestion() {
String currentWord = getComposingText();
mInputMode.onAcceptSuggestion(currentWord);
commitCurrentSuggestion(false);
return currentWord;
}
private void commitCurrentSuggestion() { private void commitCurrentSuggestion() {
commitCurrentSuggestion(true); commitCurrentSuggestion(true);
} }
@ -448,9 +469,22 @@ public class TraditionalT9 extends KeyPadHandler {
private void handleSuggestions() { 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()); 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), // but cut it off to the length of the sequence (how many keys were pressed),
// for a more intuitive experience. // for a more intuitive experience.
String word = suggestionBar.getCurrentSuggestion(); String word = suggestionBar.getCurrentSuggestion();
@ -615,6 +649,10 @@ public class TraditionalT9 extends KeyPadHandler {
private boolean performOKAction() { private boolean performOKAction() {
if (currentInputConnection == null) {
return false;
}
int action = textField.getAction(); int action = textField.getAction();
switch (action) { switch (action) {
case EditorInfo.IME_ACTION_NONE: case EditorInfo.IME_ACTION_NONE:
@ -684,7 +722,10 @@ public class TraditionalT9 extends KeyPadHandler {
* Generates the actual UI of TT9. * Generates the actual UI of TT9.
*/ */
protected View createSoftKeyView() { protected View createSoftKeyView() {
return softKeyHandler.getView(); mainView.forceCreateView();
initTray();
setDarkTheme();
return mainView.getView();
} }

View file

@ -65,4 +65,12 @@ public class Key {
return -1; return -1;
} }
} }
public static int numberToCode(int number) {
if (number >= 0 && number <= 9) {
return KeyEvent.KEYCODE_0 + number;
} else {
return -1;
}
}
} }

View file

@ -27,9 +27,10 @@ abstract public class InputMode {
protected int textFieldTextCase = CASE_UNDEFINED; protected int textFieldTextCase = CASE_UNDEFINED;
// data // data
protected int autoAcceptTimeout = -1;
protected Language language; protected Language language;
protected ArrayList<String> suggestions = new ArrayList<>(); protected ArrayList<String> suggestions = new ArrayList<>();
protected String word = null; protected int keyCode = 0;
public static InputMode getInstance(SettingsStore settings, Language language, int mode) { public static InputMode getInstance(SettingsStore settings, Language language, int mode) {
@ -47,7 +48,7 @@ abstract public class InputMode {
// Key handlers. Return "true" when handling the key or "false", when is nothing to do. // Key handlers. Return "true" when handling the key or "false", when is nothing to do.
public boolean onBackspace() { return false; } 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 // Predictions
public void onAcceptSuggestion(String suggestion) {} public void onAcceptSuggestion(String suggestion) {}
@ -63,9 +64,6 @@ abstract public class InputMode {
return newSuggestions; return newSuggestions;
} }
// Word
public String getWord() { return word; }
// Mode identifiers // Mode identifiers
public boolean isPredictive() { return false; } public boolean isPredictive() { return false; }
public boolean isABC() { return false; } public boolean isABC() { return false; }
@ -74,6 +72,10 @@ abstract public class InputMode {
// Utility // Utility
abstract public int getId(); abstract public int getId();
abstract public int getSequenceLength(); // The number of key presses for the current word. 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) { public void changeLanguage(Language newLanguage) {
if (newLanguage != null) { if (newLanguage != null) {
language = newLanguage; language = newLanguage;
@ -90,8 +92,9 @@ abstract public class InputMode {
public boolean shouldTrackLeftRight() { return false; } public boolean shouldTrackLeftRight() { return false; }
public void reset() { public void reset() {
suggestions = new ArrayList<>(); autoAcceptTimeout = -1;
word = null; keyCode = 0;
suggestions.clear();
} }
// Text case // Text case

View file

@ -2,7 +2,7 @@ package io.github.sspanak.tt9.ime.modes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.ArrayList; import io.github.sspanak.tt9.ime.helpers.Key;
public class Mode123 extends InputMode { public class Mode123 extends InputMode {
public int getId() { return MODE_123; } public int getId() { return MODE_123; }
@ -11,21 +11,24 @@ public class Mode123 extends InputMode {
allowedTextCases.add(CASE_LOWER); allowedTextCases.add(CASE_LOWER);
} }
@Override
public boolean onNumber(int number, boolean hold, int repeat) {
reset();
public boolean onNumber(int key, boolean hold, int repeat) { if (number == 0 && hold) {
if (key != 0) { autoAcceptTimeout = 0;
return false; suggestions.add("+");
} else {
keyCode = Key.numberToCode(number);
} }
suggestions = new ArrayList<>();
word = hold ? "+" : "0";
return true; return true;
} }
final public boolean is123() { return true; } @Override final public boolean is123() { return true; }
public int getSequenceLength() { return 0; } @Override public int getSequenceLength() { return 0; }
public boolean shouldTrackNumPress() { return false; } @Override public boolean shouldTrackNumPress() { return false; }
@NonNull @NonNull
@Override @Override

View file

@ -2,8 +2,6 @@ package io.github.sspanak.tt9.ime.modes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.ArrayList;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
public class ModeABC extends InputMode { public class ModeABC extends InputMode {
@ -17,16 +15,16 @@ public class ModeABC extends InputMode {
@Override @Override
public boolean onNumber(int key, boolean hold, int repeat) { public boolean onNumber(int number, boolean hold, int repeat) {
shouldSelectNextLetter = false;
suggestions = language.getKeyCharacters(key);
word = null;
if (hold) { if (hold) {
suggestions = new ArrayList<>(); reset();
word = String.valueOf(key); suggestions.add(String.valueOf(number));
autoAcceptTimeout = 0;
} else if (repeat > 0) { } else if (repeat > 0) {
shouldSelectNextLetter = true; shouldSelectNextLetter = true;
} else {
reset();
suggestions.addAll(language.getKeyCharacters(number));
} }
return true; return true;
@ -59,6 +57,12 @@ public class ModeABC extends InputMode {
return shouldSelectNextLetter; return shouldSelectNextLetter;
} }
@Override
public void reset() {
super.reset();
shouldSelectNextLetter = false;
}
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {

View file

@ -13,7 +13,6 @@ import io.github.sspanak.tt9.ime.helpers.TextField;
import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace; 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.AutoTextCase;
import io.github.sspanak.tt9.ime.modes.helpers.Predictions; 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.languages.Language;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
@ -69,40 +68,25 @@ public class ModePredictive extends InputMode {
@Override @Override
public boolean onNumber(int key, boolean hold, int repeat) { public boolean onNumber(int number, boolean hold, int repeat) {
if (hold) { if (hold) {
// hold to type any digit // hold to type any digit
reset(); reset();
word = String.valueOf(key); autoAcceptTimeout = 0;
} else if (key == 0 && repeat > 0) { suggestions.add(String.valueOf(number));
onDouble0();
} else { } else {
// words // words
super.reset(); super.reset();
digitSequence += key; digitSequence += number;
if (number == 0 && repeat > 0) {
autoAcceptTimeout = 0;
}
} }
return true; 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 @Override
public void changeLanguage(Language language) { public void changeLanguage(Language language) {
super.changeLanguage(language); super.changeLanguage(language);
@ -217,7 +201,6 @@ public class ModePredictive extends InputMode {
.setWordsChangedHandler(handleSuggestions); .setWordsChangedHandler(handleSuggestions);
handleSuggestionsExternal = handler; handleSuggestionsExternal = handler;
super.reset();
return predictions.load(); return predictions.load();
} }

View file

@ -27,7 +27,7 @@ public class Predictions {
private Handler wordsChangedHandler; private Handler wordsChangedHandler;
// data // data
private ArrayList<String> words = new ArrayList<>(); private final ArrayList<String> words = new ArrayList<>();
// punctuation/emoji // punctuation/emoji
private final Pattern containsOnly1Regex = Pattern.compile("^1+$"); private final Pattern containsOnly1Regex = Pattern.compile("^1+$");
@ -127,7 +127,7 @@ public class Predictions {
*/ */
public boolean load() { public boolean load() {
if (digitSequence == null || digitSequence.length() == 0) { if (digitSequence == null || digitSequence.length() == 0) {
words = new ArrayList<>(); words.clear();
onWordsChanged(); onWordsChanged();
return false; return false;
} }
@ -155,16 +155,27 @@ public class Predictions {
* Returns "false", when there are no static options for the current digitSequence. * Returns "false", when there are no static options for the current digitSequence.
*/ */
private boolean loadStatic() { private boolean loadStatic() {
// whitespace/special/math characters
if (digitSequence.equals("0")) { if (digitSequence.equals("0")) {
words.clear();
stem = ""; stem = "";
words = language.getKeyCharacters(0, false); words.addAll(language.getKeyCharacters(0, false));
} else if (containsOnly1Regex.matcher(digitSequence).matches()) { }
// "00" is a shortcut for the preferred character
else if (digitSequence.equals("00")) {
words.clear();
stem = "";
words.add(settings.getDoubleZeroChar());
}
// emoji
else if (containsOnly1Regex.matcher(digitSequence).matches()) {
words.clear();
stem = ""; stem = "";
if (digitSequence.length() == 1) { if (digitSequence.length() == 1) {
words = language.getKeyCharacters(1, false); words.addAll(language.getKeyCharacters(1, false));
} else { } else {
digitSequence = digitSequence.length() <= maxEmojiSequence.length() ? digitSequence : maxEmojiSequence; digitSequence = digitSequence.length() <= maxEmojiSequence.length() ? digitSequence : maxEmojiSequence;
words = Characters.getEmoji(digitSequence.length() - 2); words.addAll(Characters.getEmoji(digitSequence.length() - 2));
} }
} else { } else {
return false; return false;

View file

@ -205,10 +205,9 @@ public class SettingsStore {
public boolean getDarkTheme() { return prefs.getBoolean("pref_dark_theme", true); } public boolean getDarkTheme() { return prefs.getBoolean("pref_dark_theme", true); }
public boolean getShowSoftKeys() { return prefs.getBoolean("pref_show_soft_keys", 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 *************/ /************* typing settings *************/

View file

@ -0,0 +1,92 @@
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 TraditionalT9 tt9;
private final int xml;
protected View view = null;
protected ArrayList<SoftKey> 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:
* <a href="https://stackoverflow.com/questions/72382886/system-applies-night-mode-to-views-added-in-service-type-application-overlay">...</a>
*/
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<SoftKey> getKeys();
public View getView() {
if (view == null) {
view = View.inflate(tt9.getApplicationContext(), xml, null);
}
return view;
}
public void show() {
if (view != null) {
view.setVisibility(View.VISIBLE);
}
}
public void hide() {
if (view != null) {
view.setVisibility(View.GONE);
}
}
public void enableClickHandlers() {
for (SoftKey key : getKeys()) {
key.setTT9(tt9);
}
}
protected ArrayList<SoftKey> getKeysFromContainer(ViewGroup container) {
ArrayList<SoftKey> 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;
}
}

View file

@ -0,0 +1,64 @@
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 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);
}
}
@Override
public void render() {
getView();
enableClickHandlers();
for (SoftKey key : getKeys()) {
key.render();
}
}
@Override
protected ArrayList<SoftKey> 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;
}
}

View file

@ -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<SoftKey> getKeys() {
if (view != null && (keys == null || keys.size() == 0)) {
keys = getKeysFromContainer(view.findViewById(R.id.main_soft_keys));
}
return keys;
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,75 @@
package io.github.sspanak.tt9.ui.main.keys;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class SoftBackspaceKey extends SoftKey {
private SettingsStore settings;
long lastBackspaceCall = 0;
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);
}
private SettingsStore getSettings() {
if (settings == null) {
settings = new SettingsStore(getContext());
}
return settings;
}
@Override
final public boolean onTouch(View view, MotionEvent event) {
if (tt9 == null) {
Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
return false;
}
int action = event.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.AXIS_PRESSURE) {
handleHold();
} else if (action == MotionEvent.ACTION_UP) {
handleUp();
} else if (action == MotionEvent.ACTION_DOWN) {
// Fallback for phones that do not report AXIS_PRESSURE, when a key is being held
handlePress(-1);
}
return true;
}
private void handleHold() {
if (System.currentTimeMillis() - lastBackspaceCall < getSettings().getSoftKeyRepeatDelay()) {
return;
}
handlePress(-1);
long now = System.currentTimeMillis();
lastBackspaceCall = lastBackspaceCall == 0 ? getSettings().getSoftKeyInitialDelay() + now : now;
}
private void handleUp() {
lastBackspaceCall = 0;
}
@Override
final protected boolean handlePress(int b) {
return tt9.onBackspace();
}
}

View file

@ -0,0 +1,126 @@
package io.github.sspanak.tt9.ui.main.keys;
import android.content.Context;
import android.graphics.Typeface;
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 {
protected TraditionalT9 tt9;
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);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
super.onTouchEvent(event);
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
return handlePress(view.getId());
}
return false;
}
protected boolean handlePress(int keyId) {
if (tt9 == null) {
Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
return false;
}
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;
}
/**
* Generates the name of the key, for example: "OK", "Backspace", "1", etc...
*/
protected String getKeyNameLabel() {
return null;
}
/**
* Generates a String describing what the key does.
* For example: "ABC" for 2-key; "" for Backspace key, "" for Settings key, and so on.
*
* The function label is optional.
*/
protected String getKeyFunctionLabel() {
return null;
}
/**
* render
* Sets the key label using "getKeyNameLabel()" and "getKeyFunctionLabel()" 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 name = getKeyNameLabel();
String func = getKeyFunctionLabel();
if (name == null) {
return;
} else if (func == null) {
setText(name);
return;
}
SpannableStringBuilder sb = new SpannableStringBuilder(name);
sb.append('\n');
sb.append(func);
sb.setSpan(new RelativeSizeSpan(0.55f), 0, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
sb.setSpan(new StyleSpan(Typeface.ITALIC), 0, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
sb.setSpan(new RelativeSizeSpan(0.75f), 1, sb.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
setText(sb);
}
}

View file

@ -0,0 +1,99 @@
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);
}
protected boolean handlePress(int keyId) {
if (tt9 == null) {
Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
return false;
}
int keyCode = Key.numberToCode(getNumber(keyId));
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;
}
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;
}
@Override
protected String getKeyNameLabel() {
return String.valueOf(getNumber(getId()));
}
@Override
protected String getKeyFunctionLabel() {
if (tt9 == null || tt9.getSettings().getInputMode() == InputMode.MODE_123) {
return null;
}
int number = getNumber(getId());
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 "";
}
if (number == 0) {
return "";
}
if (number == 1) {
return ",:-)";
}
StringBuilder sb = new StringBuilder();
ArrayList<String> 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();
}
}

View file

@ -0,0 +1,55 @@
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);
}
protected boolean handlePress(int keyId) {
if (tt9 == null) {
Logger.w(getClass().getCanonicalName(), "Traditional T9 handler is not set. Ignoring key press.");
return false;
}
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 getKeyNameLabel() {
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";
}
}

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.ui.bottom; package io.github.sspanak.tt9.ui.tray;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.ui.bottom; package io.github.sspanak.tt9.ui.tray;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List; import java.util.List;
public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> { public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.ViewHolder> {
private final SuggestionsBar suggestionsBar;
private final int layout; private final int layout;
private final int textViewResourceId; private final int textViewResourceId;
private final LayoutInflater mInflater; private final LayoutInflater mInflater;
@ -23,7 +24,8 @@ public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.
private int selectedIndex = 0; private int selectedIndex = 0;
public SuggestionsAdapter(Context context, int layout, int textViewResourceId, List<String> suggestions) { public SuggestionsAdapter(Context context, SuggestionsBar suggestionBar, int layout, int textViewResourceId, List<String> suggestions) {
this.suggestionsBar = suggestionBar;
this.layout = layout; this.layout = layout;
this.textViewResourceId = textViewResourceId; this.textViewResourceId = textViewResourceId;
this.mInflater = LayoutInflater.from(context); this.mInflater = LayoutInflater.from(context);
@ -43,6 +45,7 @@ public class SuggestionsAdapter extends RecyclerView.Adapter<SuggestionsAdapter.
holder.suggestionItem.setText(mSuggestions.get(position)); holder.suggestionItem.setText(mSuggestions.get(position));
holder.suggestionItem.setTextColor(colorDefault); holder.suggestionItem.setTextColor(colorDefault);
holder.suggestionItem.setBackgroundColor(selectedIndex == position ? colorHighlight : Color.TRANSPARENT); holder.suggestionItem.setBackgroundColor(selectedIndex == position ? colorHighlight : Color.TRANSPARENT);
holder.suggestionItem.setOnClickListener(v -> suggestionsBar.onItemClick(holder.getAdapterPosition()));
} }

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.ui.bottom; package io.github.sspanak.tt9.ui.tray;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
@ -16,7 +16,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.ime.TraditionalT9;
public class SuggestionsBar { public class SuggestionsBar {
private final List<String> suggestions = new ArrayList<>(); private final List<String> suggestions = new ArrayList<>();
@ -24,14 +24,14 @@ public class SuggestionsBar {
private boolean isDarkThemeEnabled = false; private boolean isDarkThemeEnabled = false;
private final RecyclerView mView; private final RecyclerView mView;
private final SettingsStore settings; private final TraditionalT9 tt9;
private SuggestionsAdapter mSuggestionsAdapter; private SuggestionsAdapter mSuggestionsAdapter;
public SuggestionsBar(SettingsStore settings, View mainView) { public SuggestionsBar(TraditionalT9 tt9, View mainView) {
super(); super();
this.settings = settings; this.tt9 = tt9;
mView = mainView.findViewById(R.id.suggestions_bar); mView = mainView.findViewById(R.id.suggestions_bar);
mView.setLayoutManager(new LinearLayoutManager(mainView.getContext(), RecyclerView.HORIZONTAL,false)); mView.setLayoutManager(new LinearLayoutManager(mainView.getContext(), RecyclerView.HORIZONTAL,false));
@ -45,8 +45,8 @@ public class SuggestionsBar {
private void configureAnimation() { private void configureAnimation() {
DefaultItemAnimator animator = new DefaultItemAnimator(); DefaultItemAnimator animator = new DefaultItemAnimator();
int translateDuration = settings.getSuggestionTranslateAnimationDuration(); int translateDuration = tt9.getSettings().getSuggestionTranslateAnimationDuration();
int selectDuration = settings.getSuggestionSelectAnimationDuration(); int selectDuration = tt9.getSettings().getSuggestionSelectAnimationDuration();
animator.setMoveDuration(selectDuration); animator.setMoveDuration(selectDuration);
animator.setChangeDuration(translateDuration); animator.setChangeDuration(translateDuration);
@ -60,13 +60,14 @@ public class SuggestionsBar {
private void initDataAdapter(Context context) { private void initDataAdapter(Context context) {
mSuggestionsAdapter = new SuggestionsAdapter( mSuggestionsAdapter = new SuggestionsAdapter(
context, context,
R.layout.suggestion_list_view, this,
tt9.getSettings().getShowSoftNumpad() ? R.layout.suggestion_list_numpad : R.layout.suggestion_list,
R.id.suggestion_list_item, R.id.suggestion_list_item,
suggestions suggestions
); );
mView.setAdapter(mSuggestionsAdapter); mView.setAdapter(mSuggestionsAdapter);
setDarkTheme(settings.getDarkTheme()); setDarkTheme(tt9.getSettings().getDarkTheme());
} }
@ -213,4 +214,14 @@ public class SuggestionsBar {
setBackground(newSuggestions); setBackground(newSuggestions);
} }
/**
* onItemClick
* Passes through suggestion selected using the touchscreen.
*/
public void onItemClick(int position) {
selectedIndex = position;
tt9.onOK();
}
} }