Zero Improvements
* 0-key now types special/math characters. All characters normally avaialable on a computer keyboard are available now * Predictive Mode: Added many new emoji * updated user manual * Fixed the new line being invisible in the suggestions list * Predictive Mode: punctuation list on 1-key has no priorities and remains static all the time * Added 'automatic space' setting * Added 'auto capitalization' setting * Added missing translations * Unsupported emoji are no longer displayed * Code cleanup and speed optimizations * Fixed incorrect suggestion filter values, that would cause broken emoji
This commit is contained in:
parent
6a2e1806d1
commit
b637a0b9d6
22 changed files with 453 additions and 156 deletions
|
|
@ -47,12 +47,19 @@ _Predictive mode only._
|
|||
- Clear the suggestion filter, if applied.
|
||||
- When no filter is applied, accept the current word as-is, even if it does not fully match a suggestion, then jump before it.
|
||||
|
||||
#### 0-key
|
||||
- **In 123 mode:** type "0" or hold it to type "+".
|
||||
- **In ABC mode:** type secondary punctuation or hold to type "0".
|
||||
- **In Predictive mode:** type space or hold to type "0".
|
||||
#### 0-key:
|
||||
- **In 123 mode:**
|
||||
- **Press:**: type "0".
|
||||
- **Hold:** type "+".
|
||||
- **In ABC mode:**
|
||||
- **Press:** type space, newline or special/math characters.
|
||||
- **Hold:** type "0".
|
||||
- **In Predictive mode:**
|
||||
- **Press:** type space, newline or special/math characters.
|
||||
- **Multiple Press:** type multiple spaces.
|
||||
- **Hold:** type "0".
|
||||
|
||||
#### 1- to 9-key
|
||||
#### 1- to 9-key:
|
||||
- **In 123 mode:** type the respective number.
|
||||
- **In ABC and Predictive mode:** type a letter/punctuation character or hold to type the respective number.
|
||||
|
||||
|
|
@ -67,7 +74,7 @@ Just deletes text.
|
|||
- **Short Press when there is text:** Some applications, most notably Firefox and Spotify, take full control of the "Back" key. This means, it may function as the application authors intended, instead of as backspace. In such cases, you could use the on-screen backspace instead. Unfortunately, nothing else could be done, because this is a restriction posed by Android.
|
||||
- **Long Press**: Whatever the system default action is (i.e. show running applications list).
|
||||
|
||||
All this does not apply, when using other keys. They will just delete text
|
||||
All this does not apply, when using other keys. They will just delete text.
|
||||
|
||||
#### Next Input Mode Key (Default: Press #):
|
||||
- **Press when there are no suggestions:** Cycle the input modes (abc → ABC → Predictive → 123). Note that only 123 mode is available in numeric fields and Predictive mode is not available in password fields.
|
||||
|
|
@ -94,7 +101,7 @@ On the Settings screen, you can choose languages for typing, configure the keypa
|
|||
|
||||
To access it:
|
||||
- Start typing in a text field to wake up TT9.
|
||||
- Use the on-screen gear button or hold the Settings Key.
|
||||
- Use the on-screen gear button or press the Settings Key.
|
||||
|
||||
## License
|
||||
- The source code, the logo image and the icons are licensed under [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<string name="pref_category_about">За приложението</string>
|
||||
<string name="pref_help">Помощ</string>
|
||||
<string name="pref_dark_theme">Тъмен облик</string>
|
||||
<string name="pref_choose_languages">Избор на езици</string>
|
||||
<string name="pref_choose_languages">Езици</string>
|
||||
<string name="dictionary_truncate_title">Изтрий речник</string>
|
||||
|
||||
<string name="pref_category_dictionaries">Речници</string>
|
||||
|
|
@ -41,7 +41,11 @@
|
|||
<string name="function_reset_keys_title">Възстанови стандартните бутони</string>
|
||||
<string name="function_reset_keys_done">Възстановени са стандартните \"бързи\" бутони.</string>
|
||||
<string name="key_hold_key">(задръж)</string>
|
||||
<string name="dictionary_loading_indeterminate">Зареждане на речник</string>
|
||||
<string name="dictionary_loading_indeterminate">Зареждане на речник</string>
|
||||
<string name="dictionary_load_cancelled">Зареждането е отменено.</string>
|
||||
|
||||
<string name="pref_auto_space">Автоматичен интервал</string>
|
||||
<string name="pref_auto_space_summary">Добавяй автоматично интервал след препинателни знаци и думи.</string>
|
||||
<string name="pref_auto_text_case">Автоматични главни букви</string>
|
||||
<string name="pref_auto_text_case_summary">Започвай автоматично изреченията с главни букви.</string>
|
||||
<string name="pref_category_predictive_mode">Подсказващ режим</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@
|
|||
<string name="pref_category_about">Über die Anwendung</string>
|
||||
<string name="pref_help">Hilfe</string>
|
||||
<string name="pref_dark_theme">Dunkles Thema</string>
|
||||
<string name="pref_choose_languages">Sprachen auswählen</string>
|
||||
<string name="dictionary_truncate_title">Wörterbuch löschen</string>
|
||||
<string name="pref_choose_languages">Sprachen</string>
|
||||
<string name="dictionary_truncate_title">Wörterbuch löschen</string>
|
||||
|
||||
<string name="pref_category_dictionaries">Wörterbücher</string>
|
||||
<string name="dictionary_loading">Lade Wörterbuch (%1$s)…</string>
|
||||
<string name="dictionary_load_title">Wörterbuch laden</string>
|
||||
<string name="dictionary_not_found">Wird nicht geladen. Wörterbuch für \"%1$s\" nicht gefunden.</string>
|
||||
<string name="dictionary_loading_indeterminate">Lade Wörterbuch</string>
|
||||
<string name="dictionary_loading_indeterminate">Lade Wörterbuch</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<string name="pref_category_about">À propos de l\'application</string>
|
||||
<string name="pref_help">Aide</string>
|
||||
<string name="pref_dark_theme">Thème sombre</string>
|
||||
<string name="pref_choose_languages">Choisir langues</string>
|
||||
<string name="pref_choose_languages">Langues</string>
|
||||
<string name="dictionary_truncate_title">Vider le dictionaire</string>
|
||||
|
||||
<string name="pref_category_dictionaries">Dictionnaires</string>
|
||||
|
|
@ -31,6 +31,11 @@
|
|||
<string name="dictionary_load_bad_char">Echec du chargement. Mot inadmissible \"%1$s\" à la ligne %2$d de langue \"%3$s\".</string>
|
||||
<string name="dictionary_truncated">Le dictionaire est vidé avec succès.</string>
|
||||
<string name="pref_show_soft_function_keys">Boutons à l\'écran</string>
|
||||
<string name="dictionary_loading_indeterminate">Chargement du dictionnaire</string>
|
||||
<string name="dictionary_loading_indeterminate">Chargement du dictionnaire</string>
|
||||
<string name="dictionary_load_cancelled">Chargement est annulé.</string>
|
||||
<string name="pref_category_predictive_mode">Saisie intuitive</string>
|
||||
<string name="pref_auto_space">Espace automatique</string>
|
||||
<string name="pref_auto_text_case">Majuscules automatiques</string>
|
||||
<string name="pref_auto_space_summary">Ajouter automatiquement un espace après signes de ponctuation et mots.</string>
|
||||
<string name="pref_auto_text_case_summary">Commencer automatiquement les phrases avec une majuscule.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
<string name="pref_category_about">Sull\'applicazione</string>
|
||||
<string name="pref_help">Aiuto</string>
|
||||
<string name="pref_dark_theme">Tema scuro</string>
|
||||
<string name="pref_choose_languages">Scegli le lingue</string>
|
||||
<string name="dictionary_truncate_title">Eliminare il dizionario</string>
|
||||
<string name="pref_choose_languages">Lingue</string>
|
||||
<string name="dictionary_truncate_title">Eliminare il dizionario</string>
|
||||
|
||||
<string name="pref_category_dictionaries">Dizionari</string>
|
||||
<string name="dictionary_cancel_load">Annullare il caricamento</string>
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<string name="dictionary_load_title">Carica il dizionario</string>
|
||||
<string name="dictionary_not_found">Impossibile caricare. Dizionario per “%1$s” non trovato.</string>
|
||||
<string name="pref_category_function_keys">Scorciatoie da tastiera</string>
|
||||
<string name="dictionary_loading_indeterminate">Caricamento del dizionario</string>
|
||||
<string name="dictionary_loading_indeterminate">Caricamento del dizionario</string>
|
||||
<string name="dictionary_load_cancelled">Caricamento annullato.</string>
|
||||
</resources>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@
|
|||
<string name="pref_category_about">Over de applicatie</string>
|
||||
<string name="pref_help">Helpen</string>
|
||||
<string name="pref_dark_theme">Donker thema</string>
|
||||
<string name="pref_choose_languages">Talen kiezen</string>
|
||||
<string name="dictionary_truncate_title">Woordenboek wissen</string>
|
||||
<string name="pref_choose_languages">Talen</string>
|
||||
<string name="dictionary_truncate_title">Woordenboek wissen</string>
|
||||
<string name="pref_category_dictionaries">Woordenboeken</string>
|
||||
<string name="dictionary_loading">Woordenboek laden (%1$s)…</string>
|
||||
<string name="dictionary_load_title">Woordenboek laden</string>
|
||||
<string name="dictionary_not_found">Laden mislukt. Woordenboek voor %1$s niet gevonden.</string>
|
||||
<string name="dictionary_truncated">Woordenboek succesvol gewist.</string>
|
||||
<string name="dictionary_loading_indeterminate">Woordenboek laden</string>
|
||||
<string name="dictionary_loading_indeterminate">Woordenboek laden</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@
|
|||
<string name="pref_category_about">О приложении</string>
|
||||
<string name="pref_help">Помощь</string>
|
||||
<string name="pref_dark_theme">Темная тема</string>
|
||||
<string name="pref_choose_languages">Выбор языков</string>
|
||||
<string name="dictionary_truncate_title">Очистить словарь</string>
|
||||
<string name="pref_choose_languages">Языки</string>
|
||||
<string name="dictionary_truncate_title">Очистить словарь</string>
|
||||
|
||||
<string name="pref_category_dictionaries">Словари</string>
|
||||
<string name="dictionary_cancel_load">Отменить загрузку</string>
|
||||
|
|
@ -27,6 +27,7 @@
|
|||
<string name="dictionary_load_title">Загрузить словарь</string>
|
||||
<string name="dictionary_not_found">Ошибка загрузки. Словарь «%1$s» не найден.</string>
|
||||
<string name="dictionary_truncated">Словарь успешно очищен.</string>
|
||||
<string name="dictionary_loading_indeterminate">Загрузка словаря</string>
|
||||
<string name="dictionary_loading_indeterminate">Загрузка словаря</string>
|
||||
<string name="dictionary_load_cancelled">Загрузка отменена.</string>
|
||||
<string name="pref_category_predictive_mode">Режим подсказки</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
<string name="pref_category_about">Про додаток</string>
|
||||
<string name="pref_help">Допомога</string>
|
||||
<string name="pref_dark_theme">Темна тема</string>
|
||||
<string name="pref_choose_languages">Вибір мови</string>
|
||||
<string name="dictionary_truncate_title">Очистити словник</string>
|
||||
<string name="pref_choose_languages">Мови</string>
|
||||
<string name="dictionary_truncate_title">Очистити словник</string>
|
||||
|
||||
<string name="pref_category_dictionaries">Словники</string>
|
||||
<string name="dictionary_cancel_load">Скасувати завантаження</string>
|
||||
|
|
@ -25,6 +25,6 @@
|
|||
<string name="dictionary_loading">Завантаження словника (%1$s)…</string>
|
||||
<string name="dictionary_load_title">Завантажити словник</string>
|
||||
<string name="dictionary_not_found">Помилка завантаження. Словник «%1$s» не знайдено.</string>
|
||||
<string name="dictionary_loading_indeterminate">Завантаження словника</string>
|
||||
<string name="dictionary_loading_indeterminate">Завантаження словника</string>
|
||||
<string name="dictionary_load_cancelled">Завантаження скасовано.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,14 @@
|
|||
<string name="pref_category_about">About</string>
|
||||
<string name="pref_category_appearance">Appearance</string>
|
||||
<string name="pref_category_dictionaries">Dictionaries</string>
|
||||
<string name="pref_category_predictive_mode">Predictive Mode</string>
|
||||
<string name="pref_category_function_keys">Select Hotkeys</string>
|
||||
|
||||
<string name="pref_choose_languages">Choose Languages</string>
|
||||
<string name="pref_auto_space">Automatic Space</string>
|
||||
<string name="pref_auto_space_summary">Automatically add a space after punctuation or words.</string>
|
||||
<string name="pref_auto_text_case">Automatic Capitalization</string>
|
||||
<string name="pref_auto_text_case_summary">Automatically start sentences with a capital letter.</string>
|
||||
<string name="pref_choose_languages">Languages</string>
|
||||
<string name="pref_dark_theme">Dark Theme</string>
|
||||
<string name="pref_show_soft_function_keys">Show on-screen keys</string>
|
||||
<string name="pref_help">Help</string>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,27 @@
|
|||
app:iconSpaceReserved="false"
|
||||
app:key="dictionary_truncate"
|
||||
app:title="@string/dictionary_truncate_title" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_category_predictive_mode"
|
||||
app:iconSpaceReserved="false"
|
||||
app:singleLineTitle="true">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auto_space"
|
||||
app:title="@string/pref_auto_space"
|
||||
app:summary="@string/pref_auto_space_summary" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auto_text_case"
|
||||
app:summary="@string/pref_auto_text_case_summary"
|
||||
app:title="@string/pref_auto_text_case" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
|
|
@ -87,7 +108,6 @@
|
|||
app:key="key_show_settings"
|
||||
app:title="@string/function_show_settings_key" />
|
||||
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="reset_keys"
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ public class DictionaryLoader {
|
|||
private void importLetters(Language language) {
|
||||
ArrayList<Word> letters = new ArrayList<>();
|
||||
|
||||
for (int key = 0; key <= 9; key++) {
|
||||
for (int key = 2; key <= 9; key++) {
|
||||
for (String langChar : language.getKeyCharacters(key)) {
|
||||
if (langChar.length() == 1 && langChar.charAt(0) >= '0' && langChar.charAt(0) <= '9') {
|
||||
// We do not want 0-9 as "word suggestions" in Predictive mode. It looks confusing
|
||||
|
|
|
|||
|
|
@ -13,7 +13,11 @@ import java.util.regex.Pattern;
|
|||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||
|
||||
|
||||
class InputFieldHelper {
|
||||
public class InputFieldHelper {
|
||||
private static final Pattern beforeCursorWordRegex = Pattern.compile("(\\w+)$");
|
||||
private static final Pattern afterCursorWordRegex = Pattern.compile("^(\\w+)");
|
||||
|
||||
|
||||
public static boolean isThereText(InputConnection currentInputConnection) {
|
||||
if (currentInputConnection == null) {
|
||||
return false;
|
||||
|
|
@ -24,36 +28,6 @@ class InputFieldHelper {
|
|||
}
|
||||
|
||||
|
||||
public static boolean isSpecializedTextField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int variation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return (
|
||||
variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_FILTER
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* isFilterTextField
|
||||
* handle filter list cases... do not hijack DPAD center and make sure back's go through proper
|
||||
*/
|
||||
public static boolean isFilterTextField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int inputType = inputField.inputType & InputType.TYPE_MASK_CLASS;
|
||||
int inputVariation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return inputType == InputType.TYPE_CLASS_TEXT && inputVariation == InputType.TYPE_TEXT_VARIATION_FILTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* isDialerField
|
||||
* Dialer fields seem to take care of numbers and backspace on their own,
|
||||
|
|
@ -67,6 +41,53 @@ class InputFieldHelper {
|
|||
}
|
||||
|
||||
|
||||
public static boolean isEmailField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int variation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return
|
||||
variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* isFilterField
|
||||
* handle filter list cases... do not hijack DPAD center and make sure back's go through proper
|
||||
*/
|
||||
public static boolean isFilterField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int inputType = inputField.inputType & InputType.TYPE_MASK_CLASS;
|
||||
int inputVariation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return inputType == InputType.TYPE_CLASS_TEXT && inputVariation == InputType.TYPE_TEXT_VARIATION_FILTER;
|
||||
}
|
||||
|
||||
|
||||
private static boolean isPasswordField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int variation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return
|
||||
variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isRegularTextField(EditorInfo inputField) {
|
||||
return !isPasswordField(inputField) && !isEmailField(inputField);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* determineInputModes
|
||||
* Determine the typing mode based on the input field being edited. Returns an ArrayList of the allowed modes.
|
||||
|
|
@ -111,7 +132,7 @@ class InputFieldHelper {
|
|||
// normal alphabetic keyboard, and assume that we should
|
||||
// be doing predictive text (showing candidates as the
|
||||
// user types).
|
||||
if (!isSpecializedTextField(inputField)) {
|
||||
if (!isPasswordField(inputField) && !isFilterField(inputField)) {
|
||||
allowedModes.add(InputMode.MODE_PREDICTIVE);
|
||||
}
|
||||
|
||||
|
|
@ -161,15 +182,46 @@ class InputFieldHelper {
|
|||
return "";
|
||||
}
|
||||
|
||||
String before = (String) currentInputConnection.getTextBeforeCursor(50, 0);
|
||||
String after = (String) currentInputConnection.getTextAfterCursor(50, 0);
|
||||
CharSequence before = currentInputConnection.getTextBeforeCursor(50, 0);
|
||||
CharSequence after = currentInputConnection.getTextAfterCursor(50, 0);
|
||||
if (before == null || after == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Matcher beforeMatch = Pattern.compile("(\\w+)$").matcher(before);
|
||||
Matcher afterMatch = Pattern.compile("^(\\w+)").matcher(after);
|
||||
Matcher beforeMatch = beforeCursorWordRegex.matcher(before);
|
||||
Matcher afterMatch = afterCursorWordRegex.matcher(after);
|
||||
|
||||
return (beforeMatch.find() ? beforeMatch.group(1) : "") + (afterMatch.find() ? afterMatch.group(1) : "");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* deletePrecedingSpace
|
||||
* Deletes the preceding space before the given word. The word must be before the cursor.
|
||||
* No action is taken when there is double space or when it's the beginning of the text field.
|
||||
*/
|
||||
public static void deletePrecedingSpace(InputConnection inputConnection, String word) {
|
||||
if (inputConnection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String searchText = " " + word;
|
||||
|
||||
inputConnection.beginBatchEdit();
|
||||
CharSequence beforeText = inputConnection.getTextBeforeCursor(searchText.length() + 1, 0);
|
||||
if (
|
||||
beforeText == null
|
||||
|| beforeText.length() < searchText.length() + 1
|
||||
|| beforeText.charAt(1) != ' ' // preceding char must be " "
|
||||
|| beforeText.charAt(0) == ' ' // but do nothing when there is double space
|
||||
) {
|
||||
inputConnection.endBatchEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
inputConnection.deleteSurroundingText(searchText.length(), 0);
|
||||
inputConnection.commitText(word, 1);
|
||||
|
||||
inputConnection.endBatchEdit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
private int ignoreNextKeyUp = 0;
|
||||
|
||||
private int lastKeyCode = 0;
|
||||
private boolean isKeyRepeated = false;
|
||||
private int keyRepeatCounter = 0;
|
||||
|
||||
private int lastNumKeyCode = 0;
|
||||
private boolean isNumKeyRepeated = false;
|
||||
private int numKeyRepeatCounter = 0;
|
||||
|
||||
// throttling
|
||||
private static final int BACKSPACE_DEBOUNCE_TIME = 80;
|
||||
|
|
@ -84,8 +84,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
@Override
|
||||
public void onStartInput(EditorInfo inputField, boolean restarting) {
|
||||
currentInputConnection = getCurrentInputConnection();
|
||||
// Logger.d("T9.onStartInput", "inputType: " + inputField.inputType + " fieldId: " + inputField.fieldId +
|
||||
// " fieldName: " + inputField.fieldName + " packageName: " + inputField.packageName);
|
||||
// Logger.d("T9.onStartInput", "inputType: " + inputField.inputType + " fieldId: " + inputField.fieldId + " fieldName: " + inputField.fieldName + " packageName: " + inputField.packageName);
|
||||
|
||||
mEditing = NON_EDIT;
|
||||
|
||||
|
|
@ -197,6 +196,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
return true;
|
||||
}
|
||||
|
||||
resetKeyRepeat();
|
||||
ignoreNextKeyUp = keyCode;
|
||||
|
||||
if (handleSpecialFunctionKey(keyCode, true)) {
|
||||
|
|
@ -214,7 +214,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
case KeyEvent.KEYCODE_7:
|
||||
case KeyEvent.KEYCODE_8:
|
||||
case KeyEvent.KEYCODE_9:
|
||||
return onNumber(keyCodeToKeyNumber(keyCode), true, false);
|
||||
return onNumber(keyCodeToKeyNumber(keyCode), true, 0);
|
||||
}
|
||||
|
||||
ignoreNextKeyUp = 0;
|
||||
|
|
@ -239,11 +239,11 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
return true;
|
||||
}
|
||||
|
||||
isKeyRepeated = (lastKeyCode == keyCode);
|
||||
keyRepeatCounter = (lastKeyCode == keyCode) ? keyRepeatCounter + 1 : 0;
|
||||
lastKeyCode = keyCode;
|
||||
|
||||
if (isNumber(keyCode)) {
|
||||
isNumKeyRepeated = (lastNumKeyCode == keyCode);
|
||||
numKeyRepeatCounter = (lastNumKeyCode == keyCode) ? numKeyRepeatCounter + 1 : 0;
|
||||
lastNumKeyCode = keyCode;
|
||||
}
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_0) {
|
||||
return onNumber(0, false, isNumKeyRepeated);
|
||||
return onNumber(keyCodeToKeyNumber(keyCode), false, numKeyRepeatCounter);
|
||||
}
|
||||
|
||||
// dialer fields are similar to pure numeric fields, but for user convenience, holding "0"
|
||||
|
|
@ -281,7 +281,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
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(isKeyRepeated);
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT: return onRight(keyRepeatCounter > 0);
|
||||
case KeyEvent.KEYCODE_1:
|
||||
case KeyEvent.KEYCODE_2:
|
||||
case KeyEvent.KEYCODE_3:
|
||||
|
|
@ -291,7 +291,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
case KeyEvent.KEYCODE_7:
|
||||
case KeyEvent.KEYCODE_8:
|
||||
case KeyEvent.KEYCODE_9:
|
||||
return onNumber(keyCodeToKeyNumber(keyCode), false, isNumKeyRepeated);
|
||||
return onNumber(keyCodeToKeyNumber(keyCode), false, numKeyRepeatCounter);
|
||||
case KeyEvent.KEYCODE_STAR: return onStar();
|
||||
case KeyEvent.KEYCODE_POUND: return onPound();
|
||||
}
|
||||
|
|
@ -353,8 +353,10 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
|
||||
|
||||
protected void resetKeyRepeat() {
|
||||
isNumKeyRepeated = false;
|
||||
numKeyRepeatCounter = 0;
|
||||
keyRepeatCounter = 0;
|
||||
lastNumKeyCode = 0;
|
||||
lastKeyCode = 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -397,7 +399,7 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
abstract protected boolean onDown();
|
||||
abstract protected boolean onLeft();
|
||||
abstract protected boolean onRight(boolean repeat);
|
||||
abstract protected boolean onNumber(int key, boolean hold, boolean repeat);
|
||||
abstract protected boolean onNumber(int key, boolean hold, int repeat);
|
||||
abstract protected boolean onStar();
|
||||
abstract protected boolean onPound();
|
||||
|
||||
|
|
@ -412,27 +414,4 @@ abstract class KeyPadHandler extends InputMethodService {
|
|||
abstract protected void onRestart(EditorInfo inputField);
|
||||
abstract protected void onFinish();
|
||||
abstract protected View createSoftKeyView();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////// THE ONES BELOW MAY BE UNNECESSARY. IMPLEMENT IF NEEDED. /////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/**
|
||||
* Deal with the editor reporting movement of its cursor.
|
||||
*/
|
||||
/* @Override
|
||||
public void onUpdateSelection(
|
||||
int oldSelStart,
|
||||
int oldSelEnd,
|
||||
int newSelStart,
|
||||
int newSelEnd,
|
||||
int candidatesStart,
|
||||
int candidatesEnd
|
||||
) {
|
||||
// @todo: implement if necessary, but probably in TraditionalT9, not here
|
||||
// ... handle any interesting cursor movement
|
||||
// commitCurrentSuggestion()
|
||||
// setSuggestions(null)
|
||||
}*/
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import io.github.sspanak.tt9.ui.SuggestionsView;
|
|||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
public class TraditionalT9 extends KeyPadHandler {
|
||||
private static TraditionalT9 self;
|
||||
// internal settings/data
|
||||
private EditorInfo inputField;
|
||||
|
||||
// input mode
|
||||
private ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
||||
|
|
@ -36,6 +37,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
private SuggestionsView mSuggestionView = null;
|
||||
|
||||
|
||||
private static TraditionalT9 self;
|
||||
public static Context getMainContext() {
|
||||
return self.getApplicationContext();
|
||||
}
|
||||
|
|
@ -82,12 +84,14 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
|
||||
|
||||
protected void onRestart(EditorInfo inputField) {
|
||||
this.inputField = inputField;
|
||||
|
||||
// in case we are back from Settings screen, update the language list
|
||||
mEnabledLanguages = settings.getEnabledLanguageIds();
|
||||
validateLanguages();
|
||||
|
||||
// some input fields support only numbers or do not accept predictions
|
||||
determineAllowedInputModes(inputField);
|
||||
determineAllowedInputModes();
|
||||
mInputMode = InputModeValidator.validateMode(settings, mInputMode, allowedInputModes);
|
||||
|
||||
// Some modes may want to change the default text case based on grammar rules.
|
||||
|
|
@ -151,8 +155,11 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
return sendDefaultEditorAction(false);
|
||||
}
|
||||
|
||||
mInputMode.onAcceptSuggestion(mLanguage, mSuggestionView.getCurrentSuggestion());
|
||||
String word = mSuggestionView.getCurrentSuggestion();
|
||||
|
||||
mInputMode.onAcceptSuggestion(mLanguage, word);
|
||||
commitCurrentSuggestion();
|
||||
autoCorrectSpace(word, true, -1, false);
|
||||
resetKeyRepeat();
|
||||
|
||||
return true;
|
||||
|
|
@ -213,15 +220,21 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
* @param repeat If "true" we are calling the handler, because the key was pressed more than once
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean onNumber(int key, boolean hold, boolean repeat) {
|
||||
if (mInputMode.shouldAcceptCurrentSuggestion(mLanguage, key, hold, repeat)) {
|
||||
mInputMode.onAcceptSuggestion(mLanguage, getComposingText());
|
||||
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,
|
||||
// instead of requiring "OK" before that.
|
||||
if (mInputMode.shouldAcceptCurrentSuggestion(mLanguage, key, hold, repeat > 0)) {
|
||||
mInputMode.onAcceptSuggestion(mLanguage, currentWord);
|
||||
commitCurrentSuggestion(false);
|
||||
autoCorrectSpace(currentWord, false, key, hold);
|
||||
currentWord = "";
|
||||
}
|
||||
|
||||
// Auto-adjust the text case before each word, if the InputMode supports it.
|
||||
// We don't do it too often, because it is somewhat resource-intensive.
|
||||
if (getComposingText().length() == 0) {
|
||||
if (currentWord.length() == 0) {
|
||||
determineNextTextCase();
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +520,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
}
|
||||
|
||||
|
||||
private void determineAllowedInputModes(EditorInfo inputField) {
|
||||
private void determineAllowedInputModes() {
|
||||
allowedInputModes = InputFieldHelper.determineInputModes(inputField);
|
||||
|
||||
int lastInputModeId = settings.getInputMode();
|
||||
|
|
@ -524,13 +537,25 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
} else if (mInputMode.is123() && allowedInputModes.size() == 1) {
|
||||
mEditing = EDITING_STRICT_NUMERIC;
|
||||
} else {
|
||||
mEditing = InputFieldHelper.isFilterTextField(inputField) ? EDITING_NOSHOW : EDITING;
|
||||
mEditing = InputFieldHelper.isFilterField(inputField) ? EDITING_NOSHOW : EDITING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void autoCorrectSpace(String currentWord, boolean isWordAcceptedManually, int incomingKey, boolean hold) {
|
||||
if (mInputMode.shouldDeletePrecedingSpace(inputField)) {
|
||||
InputFieldHelper.deletePrecedingSpace(currentInputConnection, currentWord);
|
||||
}
|
||||
|
||||
if (mInputMode.shouldAddAutoSpace(inputField, isWordAcceptedManually, incomingKey, hold)) {
|
||||
commitText(" ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void determineNextTextCase() {
|
||||
mInputMode.determineNextWordTextCase(
|
||||
settings,
|
||||
InputFieldHelper.isThereText(currentInputConnection),
|
||||
(String) currentInputConnection.getTextBeforeCursor(50, 0)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package io.github.sspanak.tt9.ime.modes;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
@ -42,12 +43,12 @@ abstract public class InputMode {
|
|||
|
||||
// Key handlers. Return "true" when handling the key or "false", when is nothing to do.
|
||||
public boolean onBackspace() { return false; }
|
||||
abstract public boolean onNumber(Language language, int key, boolean hold, boolean repeat);
|
||||
abstract public boolean onNumber(Language language, int key, boolean hold, int repeat);
|
||||
|
||||
// Suggestions
|
||||
public void onAcceptSuggestion(Language language, String suggestion) {}
|
||||
protected void onSuggestionsUpdated(Handler handler) { handler.sendEmptyMessage(0); }
|
||||
public boolean loadSuggestions(Handler handler, Language language, String lastWord) { return false; }
|
||||
public boolean loadSuggestions(Handler handler, Language language, String currentWord) { return false; }
|
||||
|
||||
public ArrayList<String> getSuggestions(Language language) {
|
||||
ArrayList<String> newSuggestions = new ArrayList<>();
|
||||
|
|
@ -69,6 +70,10 @@ abstract public class InputMode {
|
|||
// Utility
|
||||
abstract public int getId();
|
||||
abstract public int getSequenceLength(); // The number of key presses for the current word.
|
||||
|
||||
public boolean shouldAddAutoSpace(EditorInfo inputField, boolean isWordAcceptedManually, int incomingKey, boolean hold) { return false; }
|
||||
public boolean shouldDeletePrecedingSpace(EditorInfo inputField) { return false; }
|
||||
|
||||
public void reset() {
|
||||
suggestions = new ArrayList<>();
|
||||
word = null;
|
||||
|
|
@ -95,7 +100,7 @@ abstract public class InputMode {
|
|||
textCase = allowedTextCases.get(nextIndex);
|
||||
}
|
||||
|
||||
public void determineNextWordTextCase(boolean isThereText, String textBeforeCursor) {}
|
||||
public void determineNextWordTextCase(SettingsStore settings, 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, Language language) { return word; }
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public class Mode123 extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
public boolean onNumber(Language l, int key, boolean hold, boolean repeat) {
|
||||
public boolean onNumber(Language l, int key, boolean hold, int repeat) {
|
||||
if (key != 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public class ModeABC extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
public boolean onNumber(Language language, int key, boolean hold, boolean repeat) {
|
||||
public boolean onNumber(Language language, int key, boolean hold, int repeat) {
|
||||
shouldSelectNextLetter = false;
|
||||
suggestions = language.getKeyCharacters(key);
|
||||
word = null;
|
||||
|
|
@ -23,7 +23,7 @@ public class ModeABC extends InputMode {
|
|||
if (hold) {
|
||||
suggestions = new ArrayList<>();
|
||||
word = String.valueOf(key);
|
||||
} else if (repeat) {
|
||||
} else if (repeat > 0) {
|
||||
shouldSelectNextLetter = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package io.github.sspanak.tt9.ime.modes;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||
import io.github.sspanak.tt9.ime.InputFieldHelper;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.Punctuation;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
|
|
@ -18,8 +20,9 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
public int getId() { return MODE_PREDICTIVE; }
|
||||
|
||||
private boolean isEmoji = false;
|
||||
private String digitSequence = "";
|
||||
private String lastAcceptedWord = "";
|
||||
private String lastAcceptedSequence = "";
|
||||
|
||||
// stem filter
|
||||
private boolean isStemFuzzy = false;
|
||||
|
|
@ -27,11 +30,15 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
// async suggestion handling
|
||||
private Language currentLanguage = null;
|
||||
private String lastInputFieldWord = "";
|
||||
private String currentInputFieldWord = "";
|
||||
private static Handler handleSuggestionsExternal;
|
||||
|
||||
// auto text case selection
|
||||
private final Pattern endOfSentence = Pattern.compile("(?<!\\.)[.?!]\\s*$");
|
||||
private final Pattern endOfSentenceRegex = Pattern.compile("(?<!\\.)[.?!]\\s*$");
|
||||
|
||||
// punctuation/emoji
|
||||
private final Pattern containsOnly1Regex = Pattern.compile("^1+$");
|
||||
private final String maxEmojiSequence;
|
||||
|
||||
|
||||
ModePredictive(SettingsStore settings) {
|
||||
|
|
@ -39,9 +46,18 @@ public class ModePredictive extends InputMode {
|
|||
allowedTextCases.add(CASE_LOWER);
|
||||
allowedTextCases.add(CASE_CAPITALIZE);
|
||||
allowedTextCases.add(CASE_UPPER);
|
||||
|
||||
// digitSequence limiter when selecting emoji
|
||||
// "11" = Emoji level 0, "111" = Emoji level 1,... up to the maximum amount of 1s
|
||||
StringBuilder maxEmojiSequenceBuilder = new StringBuilder();
|
||||
for (int i = 0; i <= Punctuation.getEmojiLevels(); i++) {
|
||||
maxEmojiSequenceBuilder.append("1");
|
||||
}
|
||||
maxEmojiSequence = maxEmojiSequenceBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onBackspace() {
|
||||
if (digitSequence.length() < 1) {
|
||||
clearWordStem();
|
||||
|
|
@ -59,24 +75,17 @@ public class ModePredictive extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
public boolean onNumber(Language l, int key, boolean hold, boolean repeat) {
|
||||
isEmoji = false;
|
||||
|
||||
@Override
|
||||
public boolean onNumber(Language l, int key, boolean hold, int repeat) {
|
||||
if (hold) {
|
||||
// hold to type any digit
|
||||
reset();
|
||||
word = String.valueOf(key);
|
||||
} else if (key == 0) {
|
||||
// "0" is " "
|
||||
} else if (key == 0 && repeat > 0) {
|
||||
// repeat "0" to type spaces
|
||||
reset();
|
||||
word = " ";
|
||||
} else if (key == 1 && repeat) {
|
||||
// emoticons
|
||||
reset();
|
||||
isEmoji = true;
|
||||
suggestions = Punctuation.Emoji;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// words
|
||||
super.reset();
|
||||
digitSequence += key;
|
||||
|
|
@ -86,6 +95,7 @@ public class ModePredictive extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
digitSequence = "";
|
||||
|
|
@ -93,11 +103,98 @@ public class ModePredictive extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* shouldAddAutoSpace
|
||||
* When the "auto-space" settings is enabled, this determines whether to automatically add a space
|
||||
* at the end of a sentence or after accepting a suggestion. This allows faster typing, without
|
||||
* pressing space.
|
||||
*
|
||||
* See the helper functions for the list of rules.
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldAddAutoSpace(EditorInfo inputField, boolean isWordAcceptedManually, int incomingKey, boolean hold) {
|
||||
return
|
||||
settings.getAutoSpace()
|
||||
&& !hold
|
||||
&& (
|
||||
shouldAddAutoSpaceAfterPunctuation(inputField, incomingKey)
|
||||
|| shouldAddAutoSpaceAfterWord(inputField, isWordAcceptedManually)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* shouldDeletePrecedingSpace
|
||||
* When the "auto-space" settings is enabled, determine whether to delete spaces before punctuation.
|
||||
* This allows automatic conversion from: "words ." to: "words."
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldDeletePrecedingSpace(EditorInfo inputField) {
|
||||
return
|
||||
settings.getAutoSpace()
|
||||
&& (
|
||||
lastAcceptedWord.equals(".")
|
||||
|| lastAcceptedWord.equals(",")
|
||||
|| lastAcceptedWord.equals(";")
|
||||
|| lastAcceptedWord.equals(":")
|
||||
|| lastAcceptedWord.equals("!")
|
||||
|| lastAcceptedWord.equals("?")
|
||||
|| lastAcceptedWord.equals(")")
|
||||
|| lastAcceptedWord.equals("]")
|
||||
|| lastAcceptedWord.equals("'")
|
||||
|| lastAcceptedWord.equals("@")
|
||||
)
|
||||
&& InputFieldHelper.isRegularTextField(inputField);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* shouldAddAutoSpaceAfterPunctuation
|
||||
* Determines whether to automatically adding a space after certain punctuation signs makes sense.
|
||||
* The rules are similar to the ones in the standard Android keyboard (with some exceptions,
|
||||
* because we are not using a QWERTY keyboard here).
|
||||
*/
|
||||
private boolean shouldAddAutoSpaceAfterPunctuation(EditorInfo inputField, int incomingKey) {
|
||||
return
|
||||
incomingKey != 0
|
||||
&& (
|
||||
lastAcceptedWord.endsWith(".")
|
||||
|| lastAcceptedWord.endsWith(",")
|
||||
|| lastAcceptedWord.endsWith(";")
|
||||
|| lastAcceptedWord.endsWith(":")
|
||||
|| lastAcceptedWord.endsWith("!")
|
||||
|| lastAcceptedWord.endsWith("?")
|
||||
|| lastAcceptedWord.endsWith(")")
|
||||
|| lastAcceptedWord.endsWith("]")
|
||||
|| lastAcceptedWord.endsWith("%")
|
||||
)
|
||||
&& InputFieldHelper.isRegularTextField(inputField);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* shouldAddAutoSpaceAfterPunctuation
|
||||
* Similar to "shouldAddAutoSpaceAfterPunctuation()", but determines whether to add a space after
|
||||
* words.
|
||||
*/
|
||||
private boolean shouldAddAutoSpaceAfterWord(EditorInfo inputField, boolean isWordAcceptedManually) {
|
||||
return
|
||||
// Do not add space when auto-accepting words, because it feels very confusing when typing.
|
||||
isWordAcceptedManually
|
||||
// Secondary punctuation
|
||||
&& !lastAcceptedSequence.equals("0")
|
||||
// Emoji
|
||||
&& !lastAcceptedSequence.startsWith("1")
|
||||
&& InputFieldHelper.isRegularTextField(inputField);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* shouldAcceptCurrentSuggestion
|
||||
* In this mode, In addition to confirming the suggestion in the input field,
|
||||
* we also increase its' priority. This function determines whether we want to do all this or not.
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldAcceptCurrentSuggestion(Language language, int key, boolean hold, boolean repeat) {
|
||||
return
|
||||
hold
|
||||
|
|
@ -107,7 +204,8 @@ public class ModePredictive extends InputMode {
|
|||
// Also, it must break the current word.
|
||||
|| (!language.isPunctuationPartOfWords() && key == 1 && digitSequence.length() > 0 && !digitSequence.endsWith("1"))
|
||||
// On the other hand, letters also "break" punctuation.
|
||||
|| (!language.isPunctuationPartOfWords() && key != 1 && digitSequence.endsWith("1"));
|
||||
|| (!language.isPunctuationPartOfWords() && key != 1 && digitSequence.endsWith("1"))
|
||||
|| (digitSequence.endsWith("0"));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -115,6 +213,7 @@ public class ModePredictive extends InputMode {
|
|||
* clearWordStem
|
||||
* Do not filter the suggestions by the word set using "setWordStem()", use only the digit sequence.
|
||||
*/
|
||||
@Override
|
||||
public boolean clearWordStem() {
|
||||
stem = "";
|
||||
Logger.d("tt9/setWordStem", "Stem filter cleared");
|
||||
|
|
@ -138,6 +237,7 @@ public class ModePredictive extends InputMode {
|
|||
*
|
||||
* Note that you need to manually get the suggestions again to obtain a filtered list.
|
||||
*/
|
||||
@Override
|
||||
public boolean setWordStem(Language language, String wordStem, boolean exact) {
|
||||
if (language == null || wordStem == null || wordStem.length() < 1) {
|
||||
return false;
|
||||
|
|
@ -146,9 +246,9 @@ public class ModePredictive extends InputMode {
|
|||
try {
|
||||
digitSequence = language.getDigitSequenceForWord(wordStem);
|
||||
isStemFuzzy = !exact;
|
||||
stem = wordStem.toLowerCase(language.getLocale());
|
||||
stem = digitSequence.startsWith("0") || digitSequence.startsWith("1") ? "" : wordStem.toLowerCase(language.getLocale());
|
||||
|
||||
Logger.d("tt9/setWordStem", "Stem is now: " + wordStem);
|
||||
Logger.d("tt9/setWordStem", "Stem is now: " + stem + (isStemFuzzy ? " (fuzzy)" : ""));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
isStemFuzzy = false;
|
||||
|
|
@ -163,11 +263,37 @@ public class ModePredictive extends InputMode {
|
|||
* getWordStem
|
||||
* If "setWordStem()" has accepted a new stem by returning "true", it can be obtained using this.
|
||||
*/
|
||||
@Override
|
||||
public String getWordStem() {
|
||||
return stem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* loadStaticSuggestions
|
||||
* Similar to "loadSuggestions()", but loads suggestions that are not in the database.
|
||||
* Returns "false", when there are no static suggestions for the current digitSequence.
|
||||
*/
|
||||
private boolean loadStaticSuggestions() {
|
||||
if (digitSequence.equals("0")) {
|
||||
stem = "";
|
||||
suggestions = Punctuation.Secondary;
|
||||
} else if (containsOnly1Regex.matcher(digitSequence).matches()) {
|
||||
stem = "";
|
||||
if (digitSequence.length() == 1) {
|
||||
suggestions = Punctuation.Main;
|
||||
} else {
|
||||
digitSequence = digitSequence.length() <= maxEmojiSequence.length() ? digitSequence : maxEmojiSequence;
|
||||
suggestions = Punctuation.getEmoji(digitSequence.length() - 2);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* loadSuggestions
|
||||
* Queries the dictionary database for a list of suggestions matching the current language and
|
||||
|
|
@ -176,19 +302,20 @@ public class ModePredictive extends InputMode {
|
|||
* "lastWord" is used for generating suggestions when there are no results.
|
||||
* See: generatePossibleCompletions()
|
||||
*/
|
||||
public boolean loadSuggestions(Handler handler, Language language, String lastWord) {
|
||||
if (isEmoji) {
|
||||
@Override
|
||||
public boolean loadSuggestions(Handler handler, Language language, String currentWord) {
|
||||
if (loadStaticSuggestions()) {
|
||||
super.onSuggestionsUpdated(handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (digitSequence.length() == 0) {
|
||||
suggestions.clear();
|
||||
suggestions = new ArrayList<>();
|
||||
return false;
|
||||
}
|
||||
|
||||
handleSuggestionsExternal = handler;
|
||||
lastInputFieldWord = lastWord.toLowerCase(language.getLocale());
|
||||
currentInputFieldWord = currentWord.toLowerCase(language.getLocale());
|
||||
currentLanguage = language;
|
||||
super.reset();
|
||||
|
||||
|
|
@ -217,7 +344,7 @@ public class ModePredictive extends InputMode {
|
|||
dbSuggestions = dbSuggestions == null ? new ArrayList<>() : dbSuggestions;
|
||||
|
||||
if (dbSuggestions.size() == 0 && digitSequence.length() > 0) {
|
||||
dbSuggestions = generatePossibleCompletions(currentLanguage, lastInputFieldWord);
|
||||
dbSuggestions = generatePossibleCompletions(currentLanguage, currentInputFieldWord);
|
||||
}
|
||||
|
||||
suggestions.clear();
|
||||
|
|
@ -310,7 +437,7 @@ public class ModePredictive extends InputMode {
|
|||
* Add the current stem filter to the suggestion list, when it has length of X and
|
||||
* the user has pressed X keys.
|
||||
*/
|
||||
public void suggestStem() {
|
||||
private void suggestStem() {
|
||||
if (stem.length() > 0 && stem.length() == digitSequence.length()) {
|
||||
suggestions.add(stem);
|
||||
}
|
||||
|
|
@ -321,7 +448,7 @@ public class ModePredictive extends InputMode {
|
|||
* suggestMoreWords
|
||||
* Takes a list of words and appends them to the suggestion list, if they are missing.
|
||||
*/
|
||||
public void suggestMoreWords(ArrayList<String> newSuggestions) {
|
||||
private void suggestMoreWords(ArrayList<String> newSuggestions) {
|
||||
for (String word : newSuggestions) {
|
||||
if (!suggestions.contains(word)) {
|
||||
suggestions.add(word);
|
||||
|
|
@ -334,7 +461,10 @@ public class ModePredictive extends InputMode {
|
|||
* onAcceptSuggestion
|
||||
* Bring this word up in the suggestions list next time.
|
||||
*/
|
||||
@Override
|
||||
public void onAcceptSuggestion(Language language, String currentWord) {
|
||||
lastAcceptedWord = currentWord;
|
||||
lastAcceptedSequence = digitSequence;
|
||||
reset();
|
||||
|
||||
if (currentWord.length() == 0) {
|
||||
|
|
@ -344,7 +474,12 @@ public class ModePredictive extends InputMode {
|
|||
|
||||
try {
|
||||
String sequence = language.getDigitSequenceForWord(currentWord);
|
||||
DictionaryDb.incrementWordFrequency(language, currentWord, sequence);
|
||||
|
||||
// emoji and punctuation are not in the database, so there is no point in
|
||||
// running queries that would update nothing
|
||||
if (!sequence.startsWith("11") && !sequence.equals("1") && !sequence.equals("0")) {
|
||||
DictionaryDb.incrementWordFrequency(language, currentWord, sequence);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.e("tt9/ModePredictive", "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
||||
}
|
||||
|
|
@ -360,6 +495,7 @@ public class ModePredictive extends InputMode {
|
|||
* for example: "dB", "Mb", proper names, German nouns, that always start with a capital,
|
||||
* or Dutch words such as: "'s-Hertogenbosch".
|
||||
*/
|
||||
@Override
|
||||
protected String adjustSuggestionTextCase(String word, int newTextCase, Language language) {
|
||||
switch (newTextCase) {
|
||||
case CASE_UPPER:
|
||||
|
|
@ -382,7 +518,12 @@ public class ModePredictive extends InputMode {
|
|||
* For example, this function will return CASE_LOWER by default, but CASE_UPPER at the beginning
|
||||
* of a sentence.
|
||||
*/
|
||||
public void determineNextWordTextCase(boolean isThereText, String textBeforeCursor) {
|
||||
@Override
|
||||
public void determineNextWordTextCase(SettingsStore settings, boolean isThereText, String textBeforeCursor) {
|
||||
if (!settings.getAutoTextCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user wants to type in uppercase, this must be for a reason, so we better not override it.
|
||||
if (textCase == CASE_UPPER) {
|
||||
return;
|
||||
|
|
@ -395,7 +536,7 @@ public class ModePredictive extends InputMode {
|
|||
}
|
||||
|
||||
// start of sentence, excluding after "..."
|
||||
if (endOfSentence.matcher(textBeforeCursor).find()) {
|
||||
if (endOfSentenceRegex.matcher(textBeforeCursor).find()) {
|
||||
textCase = CASE_CAPITALIZE;
|
||||
return;
|
||||
}
|
||||
|
|
@ -404,9 +545,8 @@ public class ModePredictive extends InputMode {
|
|||
}
|
||||
|
||||
|
||||
final public boolean isPredictive() { return true; }
|
||||
public int getSequenceLength() { return isEmoji ? 2 : digitSequence.length(); }
|
||||
public boolean shouldTrackUpDown() { return true; }
|
||||
public boolean shouldTrackLeftRight() { return true; }
|
||||
|
||||
@Override final public boolean isPredictive() { return true; }
|
||||
@Override public int getSequenceLength() { return digitSequence.length(); }
|
||||
@Override public boolean shouldTrackUpDown() { return true; }
|
||||
@Override public boolean shouldTrackLeftRight() { return true; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package io.github.sspanak.tt9.languages;
|
||||
|
||||
public class InvalidLanguageCharactersException extends Exception {
|
||||
private Language language;
|
||||
private final Language language;
|
||||
|
||||
public InvalidLanguageCharactersException(Language language, String extraMessage) {
|
||||
super("Some characters are not supported in language: " + language.getName() + ". " + extraMessage);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,57 @@
|
|||
package io.github.sspanak.tt9.languages;
|
||||
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Punctuation {
|
||||
final public static ArrayList<String> Main = new ArrayList<>(Arrays.asList(
|
||||
".", ",", "-", "?", "!", ")", "(", "'", "\"", "@", ":", "/", ";", "%"
|
||||
".", ",", "-", "(", ")", "[", "]", "&", "~", "`", "\"", ":", ";", "'", "!", "?"
|
||||
));
|
||||
|
||||
final public static ArrayList<String> Secondary = new ArrayList<>(Arrays.asList(
|
||||
" ", "+", "\n"
|
||||
" ", "\n", "@", "%", "#", "$", "{", "}", "^", "<", ">", "\\", "/", "=", "*", "+"
|
||||
));
|
||||
|
||||
final public static ArrayList<String> Emoji = new ArrayList<>(Arrays.asList(
|
||||
"👍", "🙂", "😀", "😉", "🙁", "😢", "😛", "😬"
|
||||
final private static ArrayList<String> TextEmoticons = new ArrayList<>(Arrays.asList(
|
||||
":)", ":D", ":P", ";)", "\\m/", ":-O", ":|", ":("
|
||||
));
|
||||
|
||||
final private static ArrayList<ArrayList<String>> Emoji = new ArrayList<>(Arrays.asList(
|
||||
new ArrayList<>(Arrays.asList(
|
||||
"🙂", "😀", "🤣", "😉", "😛", "😳", "😲", "😱", "😭", "😢", "🙁"
|
||||
)),
|
||||
new ArrayList<>(Arrays.asList(
|
||||
"👍", "👋", "✌️", "👏", "🤝", "💪", "🤘", "🖖", "👎"
|
||||
)),
|
||||
new ArrayList<>(Arrays.asList(
|
||||
"❤", "🤗", "😍", "😘", "😈", "🎉", "🤓", "😎", "🤔", "🥶", "😬"
|
||||
))
|
||||
));
|
||||
|
||||
|
||||
public static int getEmojiLevels() {
|
||||
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 1 : Emoji.size();
|
||||
}
|
||||
|
||||
|
||||
public static ArrayList<String> getEmoji(int level) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return TextEmoticons;
|
||||
}
|
||||
|
||||
level = (Emoji.size() > level) ? level : Emoji.size() - 1;
|
||||
|
||||
Paint paint = new Paint();
|
||||
ArrayList<String> availableEmoji = new ArrayList<>();
|
||||
for (String emoji : Emoji.get(level)) {
|
||||
if (paint.hasGlyph(emoji)) {
|
||||
availableEmoji.add(emoji);
|
||||
}
|
||||
}
|
||||
|
||||
return availableEmoji.size() > 0 ? availableEmoji : TextEmoticons;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,10 +197,18 @@ public class SettingsStore {
|
|||
|
||||
public boolean getShowSoftKeys() { return prefs.getBoolean("pref_show_soft_keys", true); }
|
||||
|
||||
|
||||
|
||||
/************* typing settings *************/
|
||||
|
||||
public boolean getAutoSpace() { return prefs.getBoolean("auto_space", false); }
|
||||
public boolean getAutoTextCase() { return prefs.getBoolean("auto_text_case", true); }
|
||||
|
||||
/************* internal settings *************/
|
||||
|
||||
public int getDictionaryImportProgressUpdateInterval() { return 250; /* ms */ }
|
||||
public int getDictionaryImportWordChunkSize() { return 1000; /* words */ }
|
||||
|
||||
public int getSuggestionsMax() { return 20; }
|
||||
public int getSuggestionsMin() { return 8; }
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,11 @@ public class SuggestionsView {
|
|||
|
||||
|
||||
public String getSuggestion(int id) {
|
||||
return id >= 0 && id < suggestions.size() ? suggestions.get(id) : "";
|
||||
if (id < 0 || id >= suggestions.size()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return suggestions.get(id).equals("⏎") ? "\n" : suggestions.get(id);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -108,7 +112,10 @@ public class SuggestionsView {
|
|||
selectedIndex = 0;
|
||||
|
||||
if (newSuggestions != null) {
|
||||
suggestions.addAll(newSuggestions);
|
||||
for (String suggestion : newSuggestions) {
|
||||
// make the new line better readable
|
||||
suggestions.add(suggestion.equals("\n") ? "⏎" : suggestion);
|
||||
}
|
||||
selectedIndex = Math.max(initialSel, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue