diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java index afe8eee9..746939aa 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/CommandHandler.java @@ -162,7 +162,7 @@ abstract public class CommandHandler extends TextEditingHandler { determineTextCase(); } - // save the settings for the next time + suggestionOps.setInputMode(mInputMode); settings.saveInputMode(mInputMode.getId()); } @@ -206,7 +206,7 @@ abstract public class CommandHandler extends TextEditingHandler { getSuggestions(null); setStatusIcon(mInputMode, mLanguage); statusBar.setText(mInputMode); - suggestionOps.setRTL(isLanguageRTL); + suggestionOps.setLanguage(mLanguage); mainView.render(); if (settings.isMainLayoutStealth() && !settings.isStatusIconEnabled()) { UI.toastShortSingle(this, mInputMode.getClass().getSimpleName(), mInputMode.toString()); diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java index 7881ec48..b7c94059 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/HotkeyHandler.java @@ -36,8 +36,8 @@ public abstract class HotkeyHandler extends CommandHandler { suggestionOps.cancelDelayedAccept(); if (!suggestionOps.isEmpty()) { - if (mInputMode.shouldReplacePreviousSuggestion()) { - mInputMode.onReplaceSuggestion(suggestionOps.getCurrent()); + if (mInputMode.shouldReplacePreviousSuggestion(suggestionOps.getCurrent())) { + mInputMode.onReplaceSuggestion(suggestionOps.getCurrentRaw()); } else { onAcceptSuggestionManually(suggestionOps.acceptCurrent(), KeyEvent.KEYCODE_ENTER); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java index be09e8af..821b7ad4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TextEditingHandler.java @@ -14,7 +14,7 @@ abstract public class TextEditingHandler extends VoiceHandler { protected void initTray() { super.initTray(); detectRTL(); - suggestionOps.setRTL(isLanguageRTL); + suggestionOps.setLanguage(LanguageCollection.getLanguage(settings.getInputLanguage())); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java index 9efdc1fe..b024285d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TypingHandler.java @@ -72,6 +72,7 @@ public abstract class TypingHandler extends KeyPadHandler { mInputMode = determineInputMode(); determineTextCase(); suggestionOps.set(null); + suggestionOps.setInputMode(mInputMode); return true; } @@ -89,6 +90,7 @@ public abstract class TypingHandler extends KeyPadHandler { // changing the TextField and notifying all interested classes is an atomic operation appHacks = new AppHacks(inputType, textField, textSelection); suggestionOps.setTextField(textField); + suggestionOps.setInputMode(mInputMode); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java index 4854a85b..fe044cef 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/helpers/SuggestionOps.java @@ -8,7 +8,10 @@ import androidx.annotation.Nullable; import java.util.ArrayList; +import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.LanguageKind; +import io.github.sspanak.tt9.languages.NullLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.ui.main.ResizableMainView; import io.github.sspanak.tt9.ui.tray.SuggestionsBar; @@ -18,6 +21,10 @@ import io.github.sspanak.tt9.util.Text; public class SuggestionOps { @NonNull private final Handler delayedAcceptHandler; @NonNull private final ConsumerCompat onDelayedAccept; + + @Nullable private InputMode inputMode; + @NonNull private Language language; + @Nullable private final SettingsStore settings; @Nullable protected SuggestionsBar suggestionBar; @NonNull private TextField textField; @@ -26,16 +33,25 @@ public class SuggestionOps { delayedAcceptHandler = new Handler(Looper.getMainLooper()); this.onDelayedAccept = onDelayedAccept != null ? onDelayedAccept : s -> {}; + language = new NullLanguage(); + this.settings = settings; this.textField = textField != null ? textField : new TextField(null, null, null); + if (settings != null && mainView != null && onSuggestionClick != null) { suggestionBar = new SuggestionsBar(settings, mainView, onSuggestionClick); } } - public void setRTL(boolean yes) { + public void setInputMode(@Nullable InputMode inputMode) { + this.inputMode = inputMode; + } + + + public void setLanguage(@Nullable Language language) { + this.language = language == null ? new NullLanguage() : language; if (suggestionBar != null) { - suggestionBar.setRTL(yes); + suggestionBar.setRTL(LanguageKind.isRTL(language)); } } @@ -154,6 +170,11 @@ public class SuggestionOps { } + public String getCurrentRaw() { + return suggestionBar != null ? suggestionBar.getRaw(getCurrentIndex()) : ""; + } + + public String getCurrent(Language language, int maxLength) { if (maxLength == 0 || isEmpty()) { return ""; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java index 0250ca80..3472e377 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/InputMode.java @@ -7,11 +7,14 @@ import java.util.ArrayList; import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.languages.NullLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; +import io.github.sspanak.tt9.ui.tray.SuggestionsBar; import io.github.sspanak.tt9.util.Logger; +import io.github.sspanak.tt9.util.chars.Characters; abstract public class InputMode { // typing mode @@ -40,6 +43,7 @@ abstract public class InputMode { @NonNull protected final ArrayList suggestions = new ArrayList<>(); @NonNull protected Runnable onSuggestionsUpdated = () -> {}; protected int specialCharSelectedGroup = 0; + @NonNull protected Sequences seq = new Sequences(); protected InputMode(SettingsStore settings, InputType inputType) { @@ -83,7 +87,15 @@ abstract public class InputMode { public void onAcceptSuggestion(@NonNull String word) { onAcceptSuggestion(word, false); } public void onAcceptSuggestion(@NonNull String word, boolean preserveWordList) {} public void onCursorMove(@NonNull String word) { if (!digitSequence.isEmpty()) onAcceptSuggestion(word); } - public void onReplaceSuggestion(@NonNull String word) {} + public void onReplaceSuggestion(@NonNull String rawWord) { + if (SuggestionsBar.SHOW_SPECIAL_CHARS_SUGGESTION.equals(rawWord)) { + Logger.d("InputMode", "Loading special characters for: " + seq.SPECIAL_CHAR_SEQUENCE); + } + + if (SuggestionsBar.SHOW_CURRENCIES_SUGGESTION.equals(rawWord)) { + Logger.d("InputMode", "Loading special characters for: " + seq.CURRENCY_SEQUENCE); + } + } /** * loadSuggestions @@ -140,7 +152,7 @@ abstract public class InputMode { // Interaction with the IME. Return "true" if it should perform the respective action. public boolean shouldAcceptPreviousSuggestion(String unacceptedText) { return false; } public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { return false; } - public boolean shouldReplacePreviousSuggestion() { return false; } + public boolean shouldReplacePreviousSuggestion(@Nullable String currentWord) { return Characters.PLACEHOLDER.equals(currentWord); } public boolean shouldAddTrailingSpace(boolean isWordAcceptedManually, int nextKey) { return false; } public boolean shouldAddPrecedingSpace() { return false; } public boolean shouldDeletePrecedingSpace() { return false; } @@ -190,6 +202,14 @@ abstract public class InputMode { protected String adjustSuggestionTextCase(String word, int newTextCase) { return word; } + protected ArrayList getAbbreviatedSpecialChars() { + ArrayList special = Characters.getWhitespaces(language); + special.add(SuggestionsBar.SHOW_CURRENCIES_SUGGESTION); + special.add(SuggestionsBar.SHOW_SPECIAL_CHARS_SUGGESTION); + return special; + } + + protected boolean loadSpecialCharacters() { int key = digitSequence.charAt(0) - '0'; ArrayList chars = settings.getOrderedKeyChars(language, key, specialCharSelectedGroup); @@ -218,14 +238,22 @@ abstract public class InputMode { return new ArrayList<>(unordered); } + return orderSpecialChars(unordered, settings.getOrderedKeyChars(language, key)); + } + + + public ArrayList orderSpecialChars(@NonNull ArrayList unordered, @Nullable ArrayList order) { ArrayList ordered = new ArrayList<>(); + if (unordered.isEmpty() || order == null || order.isEmpty()) { + return ordered; + } if (isEmailMode) { if (unordered.contains("@")) ordered.add("@"); if (unordered.contains("_")) ordered.add("_"); } - for (String ch : settings.getOrderedKeyChars(language, key)) { + for (String ch : order) { if (isEmailMode && (ch.charAt(0) == '@' || ch.charAt(0) == '_')) { continue; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java index d85824f3..061d157d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/Mode123.java @@ -1,6 +1,7 @@ package io.github.sspanak.tt9.ime.modes; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.ArrayList; @@ -28,7 +29,7 @@ class Mode123 extends ModePassthrough { if (inputType.isPhoneNumber()) { setSpecificSpecialCharacters(Characters.Phone, false); } else if (inputType.isNumeric()) { - setSpecificSpecialCharacters(Characters.getSpecialForNumbers(inputType.isDecimal(), inputType.isSignedNumber()), false); + setSpecificSpecialCharacters(Characters.getAllForDecimal(inputType.isDecimal(), inputType.isSignedNumber()), false); } else if (isEmailMode) { setSpecificSpecialCharacters(Characters.Email, true); } else { @@ -52,16 +53,15 @@ class Mode123 extends ModePassthrough { */ private void setDefaultSpecialCharacters() { Language english = LanguageCollection.getByLocale("en"); + KEY_CHARACTERS.add(getAbbreviatedSpecialChars()); KEY_CHARACTERS.add( - TextTools.removeLettersFromList(applyNumericFieldCharacterOrder(settings.getOrderedKeyChars(english, 0))) - ); - KEY_CHARACTERS.add( - TextTools.removeLettersFromList(applyNumericFieldCharacterOrder(settings.getOrderedKeyChars(english, 1))) + TextTools.removeLettersFromList(orderSpecialChars(settings.getOrderedKeyChars(english, 1), null)) ); } - protected ArrayList applyNumericFieldCharacterOrder(ArrayList unordered) { + @Override + public ArrayList orderSpecialChars(@NonNull ArrayList unordered, @Nullable ArrayList o) { ArrayList ordered = new ArrayList<>(); if (unordered.contains(".")) { diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java index d5a9c205..11ca8aec 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeABC.java @@ -25,6 +25,9 @@ class ModeABC extends InputMode { if (isEmailMode) { KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(0), 0)); KEY_CHARACTERS.add(applyPunctuationOrder(Characters.Email.get(1), 1)); + } else { + KEY_CHARACTERS.add(getAbbreviatedSpecialChars()); + KEY_CHARACTERS.add(settings.getOrderedKeyChars(language, 1)); } } @@ -54,7 +57,7 @@ class ModeABC extends InputMode { autoAcceptTimeout = settings.getAbcAutoAcceptTimeout(); digitSequence = String.valueOf(number); shouldSelectNextLetter = false; - suggestions.addAll(KEY_CHARACTERS.size() > number ? KEY_CHARACTERS.get(number) : settings.getOrderedKeyChars(language, number)); + suggestions.addAll(KEY_CHARACTERS.size() > number ? KEY_CHARACTERS.get(number) : new ArrayList<>()); suggestions.add(language.getKeyNumeral(number)); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java index 179c8c97..ee699174 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeBopomofo.java @@ -4,20 +4,19 @@ import androidx.annotation.Nullable; import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; -import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.TextTools; import io.github.sspanak.tt9.util.chars.Characters; public class ModeBopomofo extends ModePinyin { - private static final String SPECIAL_CHAR_SEQUENCE_PREFIX = "S0"; - private static final String PUNCTUATION_SEQUENCE_PREFIX = "S1"; protected ModeBopomofo(SettingsStore settings, Language lang, InputType inputType, TextField textField) { super(settings, lang, inputType, textField); + seq = new Sequences("S1", "S0"); } @@ -47,7 +46,7 @@ public class ModeBopomofo extends ModePinyin { */ protected void setCustomSpecialCharacters() { // special - KEY_CHARACTERS.add(TextTools.removeLettersFromList(applyPunctuationOrder(Characters.getSpecial(language), 0))); + KEY_CHARACTERS.add(getAbbreviatedSpecialChars()); KEY_CHARACTERS.get(0).add(0, "0"); // punctuation @@ -56,20 +55,11 @@ public class ModeBopomofo extends ModePinyin { ); } - - protected void setSpecialCharacterConstants() { - CUSTOM_EMOJI_SEQUENCE = PUNCTUATION_SEQUENCE_PREFIX + EmojiLanguage.CUSTOM_EMOJI_SEQUENCE; - EMOJI_SEQUENCE = PUNCTUATION_SEQUENCE_PREFIX + EmojiLanguage.EMOJI_SEQUENCE; - PUNCTUATION_SEQUENCE = PUNCTUATION_SEQUENCE_PREFIX + NaturalLanguage.PUNCTUATION_KEY; - SPECIAL_CHAR_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + NaturalLanguage.SPECIAL_CHAR_KEY; - } - - /***************************** TYPING *********************************/ @Override public boolean onBackspace() { - if (digitSequence.equals(PUNCTUATION_SEQUENCE) || digitSequence.equals(SPECIAL_CHAR_SEQUENCE)) { + if (digitSequence.equals(seq.PUNCTUATION_SEQUENCE) || digitSequence.equals(seq.WHITESPACE_SEQUENCE)) { digitSequence = ""; return false; } else { @@ -80,8 +70,8 @@ public class ModeBopomofo extends ModePinyin { @Override protected void onNumberPress(int nextNumber) { - if (digitSequence.startsWith(PUNCTUATION_SEQUENCE)) { - digitSequence = PUNCTUATION_SEQUENCE_PREFIX + EmojiLanguage.validateEmojiSequence(digitSequence.substring(PUNCTUATION_SEQUENCE_PREFIX.length()), nextNumber); + if (digitSequence.startsWith(seq.PUNCTUATION_SEQUENCE)) { + digitSequence = EmojiLanguage.validateEmojiSequence(seq, digitSequence, nextNumber); } else { digitSequence += String.valueOf(nextNumber); } @@ -92,10 +82,10 @@ public class ModeBopomofo extends ModePinyin { protected void onNumberHold(int number) { if (number == 0) { disablePredictions = false; - digitSequence = SPECIAL_CHAR_SEQUENCE; + digitSequence = seq.WHITESPACE_SEQUENCE; } else if (number == 1) { disablePredictions = false; - digitSequence = PUNCTUATION_SEQUENCE; + digitSequence = seq.PUNCTUATION_SEQUENCE; } else { autoAcceptTimeout = 0; suggestions.add(language.getKeyNumeral(number)); @@ -112,7 +102,7 @@ public class ModeBopomofo extends ModePinyin { public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { String newSequence = digitSequence + (char)(nextKey + '0'); return hold - || newSequence.startsWith(SPECIAL_CHAR_SEQUENCE) - || (newSequence.startsWith(PUNCTUATION_SEQUENCE) && nextKey != NaturalLanguage.PUNCTUATION_KEY.charAt(0) - '0'); + || newSequence.startsWith(seq.WHITESPACE_SEQUENCE) + || (newSequence.startsWith(seq.PUNCTUATION_SEQUENCE) && nextKey != Sequences.PUNCTUATION_KEY); } } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java index f1a7e09e..1fafa7f4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeCheonjiin.java @@ -9,13 +9,13 @@ import io.github.sspanak.tt9.hacks.InputType; 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.Cheonjiin; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.ime.modes.predictions.Predictions; import io.github.sspanak.tt9.ime.modes.predictions.SyllablePredictions; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageKind; -import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.TextTools; import io.github.sspanak.tt9.util.chars.Characters; @@ -26,11 +26,8 @@ class ModeCheonjiin extends InputMode { protected final ArrayList> KEY_CHARACTERS = new ArrayList<>(); // special chars and emojis - private static String SPECIAL_CHAR_SEQUENCE_PREFIX; - protected String CUSTOM_EMOJI_SEQUENCE; - protected String EMOJI_SEQUENCE; - protected String PUNCTUATION_SEQUENCE; - protected String SPECIAL_CHAR_SEQUENCE; + private final String PUNCTUATION_SEQUENCE_PREFIX = "11"; + private final String SPECIAL_CHAR_SEQUENCE_PREFIX = "00"; // predictions protected boolean disablePredictions = false; @@ -46,18 +43,15 @@ class ModeCheonjiin extends InputMode { protected ModeCheonjiin(SettingsStore settings, InputType inputType, TextField textField) { super(settings, inputType); - SPECIAL_CHAR_SEQUENCE_PREFIX = "11"; - - autoSpace = new AutoSpace(settings); - digitSequence = ""; allowedTextCases.add(CASE_LOWER); + digitSequence = ""; + seq = new Sequences(PUNCTUATION_SEQUENCE_PREFIX, SPECIAL_CHAR_SEQUENCE_PREFIX); this.inputType = inputType; this.textField = textField; setLanguage(LanguageCollection.getLanguage(LanguageKind.KOREAN)); initPredictions(); - setSpecialCharacterConstants(); } @@ -68,7 +62,7 @@ class ModeCheonjiin extends InputMode { */ protected void setCustomSpecialCharacters() { // special - KEY_CHARACTERS.add(TextTools.removeLettersFromList(applyPunctuationOrder(Characters.getSpecial(language), 0))); + KEY_CHARACTERS.add(getAbbreviatedSpecialChars()); KEY_CHARACTERS.get(0).add(0, "0"); // punctuation @@ -94,14 +88,6 @@ class ModeCheonjiin extends InputMode { } - protected void setSpecialCharacterConstants() { - CUSTOM_EMOJI_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + EmojiLanguage.CUSTOM_EMOJI_SEQUENCE; - EMOJI_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + EmojiLanguage.EMOJI_SEQUENCE; - PUNCTUATION_SEQUENCE = SPECIAL_CHAR_SEQUENCE_PREFIX + NaturalLanguage.PUNCTUATION_KEY; - SPECIAL_CHAR_SEQUENCE = "000"; - } - - protected void initPredictions() { predictions = new SyllablePredictions(settings); predictions @@ -113,9 +99,9 @@ class ModeCheonjiin extends InputMode { @Override public boolean onBackspace() { - if (digitSequence.equals(PUNCTUATION_SEQUENCE)) { + if (digitSequence.equals(seq.PUNCTUATION_SEQUENCE)) { digitSequence = ""; - } else if (digitSequence.equals(SPECIAL_CHAR_SEQUENCE) || (!digitSequence.startsWith(PUNCTUATION_SEQUENCE) && Cheonjiin.isSingleJamo(digitSequence))) { + } else if (digitSequence.equals(seq.WHITESPACE_SEQUENCE) || (!digitSequence.startsWith(seq.PUNCTUATION_SEQUENCE) && Cheonjiin.isSingleJamo(digitSequence))) { digitSequence = ""; } else if (!digitSequence.isEmpty()) { digitSequence = digitSequence.substring(0, digitSequence.length() - 1); @@ -145,10 +131,10 @@ class ModeCheonjiin extends InputMode { protected void onNumberHold(int number) { if (number == 0) { disablePredictions = false; - digitSequence = SPECIAL_CHAR_SEQUENCE; + digitSequence = seq.WHITESPACE_SEQUENCE; } else if (number == 1) { disablePredictions = false; - digitSequence = PUNCTUATION_SEQUENCE; + digitSequence = seq.PUNCTUATION_SEQUENCE; } else { autoAcceptTimeout = 0; suggestions.add(language.getKeyNumeral(number)); @@ -162,8 +148,8 @@ class ModeCheonjiin extends InputMode { digitSequence = digitSequence.substring(0, digitSequence.length() - rewindAmount); } - if (digitSequence.startsWith(PUNCTUATION_SEQUENCE)) { - digitSequence = SPECIAL_CHAR_SEQUENCE_PREFIX + EmojiLanguage.validateEmojiSequence(digitSequence.substring(SPECIAL_CHAR_SEQUENCE_PREFIX.length()), nextNumber); + if (digitSequence.startsWith(seq.PUNCTUATION_SEQUENCE)) { + digitSequence = EmojiLanguage.validateEmojiSequence(seq, digitSequence, nextNumber); } else { digitSequence += String.valueOf(nextNumber); } @@ -175,8 +161,8 @@ class ModeCheonjiin extends InputMode { final int repeatingDigits = digitSequence.length() > 1 && digitSequence.charAt(digitSequence.length() - 1) == nextChar ? Cheonjiin.getRepeatingEndingDigits(digitSequence) : 0; final int keyCharsCount = nextNumber == 0 ? 2 : language.getKeyCharacters(nextNumber).size(); - if (SPECIAL_CHAR_SEQUENCE.equals(digitSequence)) { - return SPECIAL_CHAR_SEQUENCE.length(); + if (seq.WHITESPACE_SEQUENCE.equals(digitSequence)) { + return seq.WHITESPACE_SEQUENCE.length(); } if (repeatingDigits == 0 || keyCharsCount < 2) { @@ -214,16 +200,16 @@ class ModeCheonjiin extends InputMode { return; } - String seq = digitSequence; + String currentSeq = digitSequence; if (shouldDisplayCustomEmojis()) { - seq = digitSequence.substring(SPECIAL_CHAR_SEQUENCE_PREFIX.length()); + currentSeq = digitSequence.substring(PUNCTUATION_SEQUENCE_PREFIX.length()); } else if (!previousJamoSequence.isEmpty()) { - seq = previousJamoSequence; + currentSeq = previousJamoSequence; } predictions - .setLanguage(shouldDisplayCustomEmojis() ? new EmojiLanguage() : language) - .setDigitSequence(seq) + .setLanguage(shouldDisplayCustomEmojis() ? new EmojiLanguage(seq) : language) + .setDigitSequence(currentSeq) .load(); } @@ -231,7 +217,7 @@ class ModeCheonjiin extends InputMode { protected boolean loadEmojis() { if (shouldDisplayEmojis()) { suggestions.clear(); - suggestions.addAll(new EmojiLanguage().getKeyCharacters(digitSequence.charAt(digitSequence.length() - 1) - '0', getEmojiGroup())); + suggestions.addAll(new EmojiLanguage(seq).getKeyCharacters(digitSequence.charAt(digitSequence.length() - 1) - '0', getEmojiGroup())); return true; } @@ -240,17 +226,17 @@ class ModeCheonjiin extends InputMode { protected int getEmojiGroup() { - return digitSequence.length() - EMOJI_SEQUENCE.length(); + return digitSequence.length() - seq.EMOJI_SEQUENCE.length(); } protected boolean shouldDisplayEmojis() { - return !isEmailMode && digitSequence.startsWith(EMOJI_SEQUENCE) && !digitSequence.equals(CUSTOM_EMOJI_SEQUENCE); + return !isEmailMode && digitSequence.startsWith(seq.EMOJI_SEQUENCE) && !digitSequence.equals(seq.CUSTOM_EMOJI_SEQUENCE); } protected boolean shouldDisplayCustomEmojis() { - return !isEmailMode && digitSequence.equals(CUSTOM_EMOJI_SEQUENCE); + return !isEmailMode && digitSequence.equals(seq.CUSTOM_EMOJI_SEQUENCE); } @@ -285,7 +271,7 @@ class ModeCheonjiin extends InputMode { protected boolean shouldDisplaySpecialCharacters() { - return digitSequence.equals(PUNCTUATION_SEQUENCE) || digitSequence.equals(SPECIAL_CHAR_SEQUENCE); + return digitSequence.equals(seq.PUNCTUATION_SEQUENCE) || digitSequence.equals(seq.WHITESPACE_SEQUENCE); } @@ -295,8 +281,8 @@ class ModeCheonjiin extends InputMode { */ protected void onPredictions() { // in case the user hasn't added any custom emoji, do not allow advancing to the empty character group - if (predictions.getList().isEmpty() && digitSequence.startsWith(EMOJI_SEQUENCE)) { - digitSequence = EMOJI_SEQUENCE; + if (predictions.getList().isEmpty() && digitSequence.startsWith(seq.EMOJI_SEQUENCE)) { + digitSequence = seq.EMOJI_SEQUENCE; return; } @@ -351,8 +337,8 @@ class ModeCheonjiin extends InputMode { public boolean shouldAcceptPreviousSuggestion(int nextKey, boolean hold) { return (hold && !digitSequence.isEmpty()) - || (digitSequence.equals(SPECIAL_CHAR_SEQUENCE) && nextKey != 0) - || (digitSequence.startsWith(PUNCTUATION_SEQUENCE) && nextKey != 1); + || (digitSequence.equals(seq.WHITESPACE_SEQUENCE) && nextKey != 0) + || (digitSequence.startsWith(seq.PUNCTUATION_SEQUENCE) && nextKey != 1); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeHiragana.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeHiragana.java index 292df051..bc404b24 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeHiragana.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeHiragana.java @@ -14,7 +14,7 @@ public class ModeHiragana extends ModeKanji { @Override protected void initPredictions() { - predictions = new KanaPredictions(settings, textField, false); + predictions = new KanaPredictions(settings, textField, seq, false); predictions.setWordsChangedHandler(this::onPredictions); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java index 436a63e6..1c7bbefa 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeIdeograms.java @@ -53,7 +53,7 @@ public class ModeIdeograms extends ModeWords { @Override protected void initPredictions() { - predictions = new IdeogramPredictions(settings, textField); + predictions = new IdeogramPredictions(settings, textField, seq); predictions.setWordsChangedHandler(this::onPredictions); } @@ -117,7 +117,7 @@ public class ModeIdeograms extends ModeWords { boolean lastDigitBelongsToNewWord = preserveWords && initialLength >= 2; try { - if (!digitSequence.equals(SPECIAL_CHAR_SEQUENCE) && !digitSequence.equals(PUNCTUATION_SEQUENCE)) { + if (!digitSequence.equals(seq.WHITESPACE_SEQUENCE) && !digitSequence.equals(seq.PUNCTUATION_SEQUENCE)) { lastAcceptedWord = currentWord; lastAcceptedSequence = lastDigitBelongsToNewWord ? digitSequence.substring(0, initialLength - 1) : digitSequence; @@ -143,9 +143,9 @@ public class ModeIdeograms extends ModeWords { return digitSequence.length() > 1 && predictions.noDbWords() - && !digitSequence.equals(EMOJI_SEQUENCE) - && !digitSequence.equals(PUNCTUATION_SEQUENCE) - && !digitSequence.equals(SPECIAL_CHAR_SEQUENCE); + && !digitSequence.equals(seq.EMOJI_SEQUENCE) + && !digitSequence.equals(seq.PUNCTUATION_SEQUENCE) + && !digitSequence.equals(seq.WHITESPACE_SEQUENCE); } @@ -164,8 +164,8 @@ public class ModeIdeograms extends ModeWords { return TextTools.containsOtherThan1(nextSequence) && ( - nextSequence.endsWith(EMOJI_SEQUENCE) || nextSequence.startsWith(EMOJI_SEQUENCE) || - nextSequence.endsWith(PUNCTUATION_SEQUENCE) || nextSequence.startsWith(PUNCTUATION_SEQUENCE) + nextSequence.endsWith(seq.EMOJI_SEQUENCE) || nextSequence.startsWith(seq.EMOJI_SEQUENCE) || + nextSequence.endsWith(seq.PUNCTUATION_SEQUENCE) || nextSequence.startsWith(seq.PUNCTUATION_SEQUENCE) ); } @@ -196,8 +196,8 @@ public class ModeIdeograms extends ModeWords { * accept it. */ @Override - public boolean shouldReplacePreviousSuggestion() { - return isFiltering; + public boolean shouldReplacePreviousSuggestion(@Nullable String word) { + return isFiltering || super.shouldReplacePreviousSuggestion(word); } /********************************* FILTERING *********************************/ diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java index 4fde4ea4..1dcb91f4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKanji.java @@ -31,7 +31,7 @@ public class ModeKanji extends ModePinyin { } String nextSequence = digitSequence + (char)(nextKey + '0'); - if (nextSequence.endsWith(PUNCTUATION_SEQUENCE) && !predictions.noDbWords()) { + if (nextSequence.endsWith(seq.PUNCTUATION_SEQUENCE) && !predictions.noDbWords()) { return false; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKatakana.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKatakana.java index dd3aff6d..daf6f2f8 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKatakana.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeKatakana.java @@ -14,7 +14,7 @@ public class ModeKatakana extends ModeHiragana { @Override protected void initPredictions() { - predictions = new KanaPredictions(settings, textField, true); + predictions = new KanaPredictions(settings, textField, seq, true); predictions.setWordsChangedHandler(this::onPredictions); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java index d5130687..d30b7926 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModePinyin.java @@ -4,14 +4,13 @@ import androidx.annotation.Nullable; import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; -import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.chars.Characters; public class ModePinyin extends ModeIdeograms { - private final int SPECIAL_CHAR_KEY = NaturalLanguage.SPECIAL_CHAR_KEY.charAt(0) - '0'; private boolean ignoreNextSpace = false; @@ -44,7 +43,7 @@ public class ModePinyin extends ModeIdeograms { @Override protected void onNumberPress(int number) { - if (ignoreNextSpace && number == SPECIAL_CHAR_KEY) { + if (ignoreNextSpace && number == Sequences.SPECIAL_CHAR_KEY) { ignoreNextSpace = false; return; } @@ -66,7 +65,7 @@ public class ModePinyin extends ModeIdeograms { // In East Asian languages, Space must accept the current word, or type a space when there is no word. // Here, we handle the case when 0-key is Space, unlike the Space hotkey in HotkeyHandler, // which could be a different key, assigned by the user. - if (!digitSequence.isEmpty() && !digitSequence.endsWith(SPECIAL_CHAR_SEQUENCE) && nextKey == SPECIAL_CHAR_KEY) { + if (!digitSequence.isEmpty() && !digitSequence.endsWith(seq.WHITESPACE_SEQUENCE) && nextKey == Sequences.SPECIAL_CHAR_KEY) { ignoreNextSpace = true; } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java index 60123121..0af08767 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/ModeWords.java @@ -9,11 +9,11 @@ import java.util.List; import io.github.sspanak.tt9.hacks.InputType; import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.modes.helpers.AutoTextCase; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.ime.modes.predictions.WordPredictions; import io.github.sspanak.tt9.languages.EmojiLanguage; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; -import io.github.sspanak.tt9.languages.NaturalLanguage; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Logger; @@ -39,6 +39,7 @@ class ModeWords extends ModeCheonjiin { super(settings, inputType, textField); autoTextCase = new AutoTextCase(settings, inputType); + seq = new Sequences(); changeLanguage(lang); defaultTextCase(); @@ -46,20 +47,14 @@ class ModeWords extends ModeCheonjiin { } - @Override protected void setCustomSpecialCharacters() {} // we use the default ones - - - protected void setSpecialCharacterConstants() { - PUNCTUATION_SEQUENCE = NaturalLanguage.PUNCTUATION_KEY; - EMOJI_SEQUENCE = EmojiLanguage.EMOJI_SEQUENCE; - CUSTOM_EMOJI_SEQUENCE = EmojiLanguage.CUSTOM_EMOJI_SEQUENCE; - SPECIAL_CHAR_SEQUENCE = NaturalLanguage.SPECIAL_CHAR_KEY; + @Override protected void setCustomSpecialCharacters() { + KEY_CHARACTERS.add(getAbbreviatedSpecialChars()); // special } @Override protected void initPredictions() { - predictions = new WordPredictions(settings, textField); + predictions = new WordPredictions(settings, textField, seq); predictions.setWordsChangedHandler(this::onPredictions); } @@ -100,9 +95,9 @@ class ModeWords extends ModeCheonjiin { @Override protected void onNumberPress(int number) { - digitSequence = EmojiLanguage.validateEmojiSequence(digitSequence, number); + digitSequence = EmojiLanguage.validateEmojiSequence(seq, digitSequence, number); - if (digitSequence.equals(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) { + if (digitSequence.equals(seq.PREFERRED_CHAR_SEQUENCE)) { autoAcceptTimeout = 0; } } @@ -286,13 +281,13 @@ class ModeWords extends ModeCheonjiin { .setIsStemFuzzy(isStemFuzzy) .setStem(stem) .setDigitSequence(digitSequence) - .setLanguage(shouldDisplayCustomEmojis() ? new EmojiLanguage() : language) + .setLanguage(shouldDisplayCustomEmojis() ? new EmojiLanguage(seq) : language) .load(); } protected boolean loadPreferredChar() { - if (digitSequence.startsWith(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) { + if (digitSequence.startsWith(seq.PREFERRED_CHAR_SEQUENCE)) { suggestions.clear(); suggestions.add(getPreferredChar()); return true; @@ -334,13 +329,13 @@ class ModeWords extends ModeCheonjiin { try { // special chars are not in the database, no need to run queries on them - String digitSequence = language.getDigitSequenceForWord(currentWord); - if (digitSequence.equals(SPECIAL_CHAR_SEQUENCE) || digitSequence.equals(PUNCTUATION_SEQUENCE)) { + String currentWordSeq = language.getDigitSequenceForWord(currentWord); + if (seq.isAnySpecialCharSequence(currentWordSeq)) { return; } // increment the frequency of the given word - predictions.onAccept(currentWord, digitSequence); + predictions.onAccept(currentWord, currentWordSeq); } catch (Exception e) { Logger.e(LOG_TAG, "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage()); } @@ -382,8 +377,7 @@ class ModeWords extends ModeCheonjiin { changed = super.nextTextCase(); } - boolean onlySpecialChars = digitSequence.startsWith(PUNCTUATION_SEQUENCE) || digitSequence.startsWith(SPECIAL_CHAR_SEQUENCE) || digitSequence.startsWith(EMOJI_SEQUENCE); - if (onlySpecialChars && textCase == CASE_CAPITALIZE) { + if (seq.startsWithAnySpecialCharSequence(digitSequence) && textCase == CASE_CAPITALIZE) { super.nextTextCase(); } @@ -411,20 +405,17 @@ class ModeWords extends ModeCheonjiin { return true; } - final char SPECIAL_CHAR_KEY_CODE = SPECIAL_CHAR_SEQUENCE.charAt(SPECIAL_CHAR_SEQUENCE.length() - 1); - final int SPECIAL_CHAR_KEY = SPECIAL_CHAR_KEY_CODE - '0'; - // Prevent typing the preferred character when the user has scrolled the special char suggestions. // For example, it makes more sense to allow typing "+ " with 0 + scroll + 0, instead of clearing // the "+" and replacing it with the preferred character. - if (!stem.isEmpty() && nextKey == SPECIAL_CHAR_KEY && digitSequence.charAt(0) == SPECIAL_CHAR_KEY_CODE) { + if (!stem.isEmpty() && nextKey == Sequences.SPECIAL_CHAR_KEY && digitSequence.charAt(0) == Sequences.SPECIAL_CHAR_CODE) { return true; } return !digitSequence.isEmpty() && ( - (nextKey == SPECIAL_CHAR_KEY && digitSequence.charAt(digitSequence.length() - 1) != SPECIAL_CHAR_KEY_CODE) - || (nextKey != SPECIAL_CHAR_KEY && digitSequence.charAt(digitSequence.length() - 1) == SPECIAL_CHAR_KEY_CODE) + (nextKey == Sequences.SPECIAL_CHAR_KEY && digitSequence.charAt(digitSequence.length() - 1) != Sequences.SPECIAL_CHAR_CODE) + || (nextKey != Sequences.SPECIAL_CHAR_KEY && digitSequence.charAt(digitSequence.length() - 1) == Sequences.SPECIAL_CHAR_CODE) ); } @@ -448,8 +439,8 @@ class ModeWords extends ModeCheonjiin { return !digitSequence.isEmpty() && predictions.noDbWords() - && digitSequence.contains(PUNCTUATION_SEQUENCE) - && !digitSequence.startsWith(EMOJI_SEQUENCE) + && digitSequence.contains(seq.PUNCTUATION_SEQUENCE) + && !digitSequence.startsWith(seq.EMOJI_SEQUENCE) && Text.containsOtherThan1(digitSequence); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Sequences.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Sequences.java new file mode 100644 index 00000000..3d8b32c6 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/helpers/Sequences.java @@ -0,0 +1,70 @@ +package io.github.sspanak.tt9.ime.modes.helpers; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class Sequences { + public static final int SPECIAL_CHAR_KEY = 0; + public static final int SPECIAL_CHAR_CODE = SPECIAL_CHAR_KEY + '0'; + public static final int PUNCTUATION_KEY = 1; + public static final int CUSTOM_EMOJI_KEY = 3; + + public final int PUNCTUATION_PREFIX_LENGTH; + + @NonNull public final String PUNCTUATION_SEQUENCE; + @NonNull public final String EMOJI_SEQUENCE; + @NonNull public final String CUSTOM_EMOJI_SEQUENCE; + + @NonNull public final String CURRENCY_SEQUENCE; + @NonNull public final String PREFERRED_CHAR_SEQUENCE; + @NonNull public final String SPECIAL_CHAR_SEQUENCE; + @NonNull public final String WHITESPACE_SEQUENCE; + + public Sequences() { + this(null, null); + } + + public Sequences(@Nullable String punctuationPrefix, @Nullable String specialCharPrefix) { + final String PUNCTUATION_PREFIX = punctuationPrefix != null ? punctuationPrefix : ""; + final String SPECIAL_CHAR_PREFIX = specialCharPrefix != null ? specialCharPrefix : ""; + + PUNCTUATION_SEQUENCE = PUNCTUATION_PREFIX + PUNCTUATION_KEY; + PUNCTUATION_PREFIX_LENGTH = PUNCTUATION_PREFIX.length(); + + EMOJI_SEQUENCE = PUNCTUATION_SEQUENCE + PUNCTUATION_KEY; + CUSTOM_EMOJI_SEQUENCE = EMOJI_SEQUENCE + CUSTOM_EMOJI_KEY; + + WHITESPACE_SEQUENCE = SPECIAL_CHAR_PREFIX + SPECIAL_CHAR_KEY; + PREFERRED_CHAR_SEQUENCE = WHITESPACE_SEQUENCE + SPECIAL_CHAR_KEY; + SPECIAL_CHAR_SEQUENCE = SPECIAL_CHAR_PREFIX + SPECIAL_CHAR_KEY + SPECIAL_CHAR_KEY + SPECIAL_CHAR_KEY; + CURRENCY_SEQUENCE = SPECIAL_CHAR_SEQUENCE + SPECIAL_CHAR_KEY; + } + + public boolean isAnySpecialCharSequence(String sequence) { + if (sequence == null) { + return false; + } + + return + sequence.equals(PUNCTUATION_SEQUENCE) + || sequence.equals(WHITESPACE_SEQUENCE) + || sequence.equals(EMOJI_SEQUENCE) + || sequence.equals(PREFERRED_CHAR_SEQUENCE) + || sequence.equals(SPECIAL_CHAR_SEQUENCE) + || sequence.equals(CURRENCY_SEQUENCE); + } + + public boolean startsWithAnySpecialCharSequence(String sequence) { + if (sequence == null) { + return false; + } + + return + sequence.startsWith(PUNCTUATION_SEQUENCE) + || sequence.startsWith(WHITESPACE_SEQUENCE) + || sequence.startsWith(EMOJI_SEQUENCE) + || sequence.startsWith(PREFERRED_CHAR_SEQUENCE) + || sequence.startsWith(SPECIAL_CHAR_SEQUENCE) + || sequence.startsWith(CURRENCY_SEQUENCE); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/IdeogramPredictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/IdeogramPredictions.java index c0548120..f4b4e0cd 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/IdeogramPredictions.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/IdeogramPredictions.java @@ -8,6 +8,7 @@ import java.util.HashSet; import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -20,8 +21,8 @@ public class IdeogramPredictions extends WordPredictions { @NonNull protected ArrayList lastTranscriptions = new ArrayList<>(); - public IdeogramPredictions(SettingsStore settings, TextField textField) { - super(settings, textField); + public IdeogramPredictions(SettingsStore settings, TextField textField, Sequences sequences) { + super(settings, textField, sequences); minWords = 1; onlyExactMatches = true; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/KanaPredictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/KanaPredictions.java index fb37999e..2e0490a3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/KanaPredictions.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/KanaPredictions.java @@ -5,6 +5,7 @@ import androidx.annotation.NonNull; import java.util.ArrayList; import io.github.sspanak.tt9.ime.helpers.TextField; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -13,8 +14,8 @@ public class KanaPredictions extends IdeogramPredictions { @NonNull private final String STEM_PREFIX; private final int STEM_PREFIX_LENGTH; - public KanaPredictions(SettingsStore settings, TextField textField, boolean isKatakana) { - super(settings, textField); + public KanaPredictions(SettingsStore settings, TextField textField, Sequences sequences, boolean isKatakana) { + super(settings, textField, sequences); SEQUENCE_PREFIX = isKatakana ? '1' : '0'; STEM_PREFIX = isKatakana ? "Qk" : "Qh"; diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java index 220e2d5a..1bcd0b1e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/modes/predictions/WordPredictions.java @@ -6,7 +6,7 @@ import java.util.ArrayList; import io.github.sspanak.tt9.db.DataStore; import io.github.sspanak.tt9.ime.helpers.TextField; -import io.github.sspanak.tt9.languages.EmojiLanguage; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; import io.github.sspanak.tt9.preferences.settings.SettingsStore; @@ -17,6 +17,7 @@ import io.github.sspanak.tt9.util.chars.Characters; public class WordPredictions extends Predictions { protected final TextField textField; private LocaleWordsSorter localeWordsSorter; + private final Sequences seq; private String inputWord; private boolean isStemFuzzy; @@ -25,11 +26,12 @@ public class WordPredictions extends Predictions { protected String penultimateWord; - public WordPredictions(SettingsStore settings, TextField textField) { + public WordPredictions(SettingsStore settings, TextField textField, Sequences sequences) { super(settings); lastEnforcedTopWord = ""; localeWordsSorter = new LocaleWordsSorter(null); penultimateWord = ""; + seq = sequences; stem = ""; this.textField = textField; } @@ -92,7 +94,7 @@ public class WordPredictions extends Predictions { @Override protected boolean isRetryAllowed() { - return !EmojiLanguage.CUSTOM_EMOJI_SEQUENCE.equals(digitSequence); + return !seq.CUSTOM_EMOJI_SEQUENCE.equals(digitSequence); } /** @@ -113,7 +115,7 @@ public class WordPredictions extends Predictions { } words.clear(); - if (digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE)) { + if (digitSequence.equals(seq.CUSTOM_EMOJI_SEQUENCE)) { words.addAll(dbWords); } else { suggestStem(); diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/EmojiLanguage.java b/app/src/main/java/io/github/sspanak/tt9/languages/EmojiLanguage.java index c65d4697..9aa9c84a 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/EmojiLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/EmojiLanguage.java @@ -5,27 +5,31 @@ import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.Locale; +import io.github.sspanak.tt9.ime.modes.helpers.Sequences; import io.github.sspanak.tt9.util.TextTools; import io.github.sspanak.tt9.util.chars.Characters; public class EmojiLanguage extends Language { - final public static String EMOJI_SEQUENCE = "11"; - final private static int CUSTOM_EMOJI_KEY = 3; - final public static String CUSTOM_EMOJI_SEQUENCE = EMOJI_SEQUENCE + CUSTOM_EMOJI_KEY; + private final Sequences seq; public EmojiLanguage() { - id = Integer.parseInt(EMOJI_SEQUENCE); + this(null); + } + + public EmojiLanguage(Sequences sequences) { + id = Integer.parseInt(new Sequences().EMOJI_SEQUENCE); // always use the unprefixed sequence for ID locale = Locale.ROOT; abcString = "emoji"; code = "emj"; currency = ""; name = "Emoji"; + seq = sequences == null ? new Sequences() : sequences; } @NonNull @Override public String getDigitSequenceForWord(String word) { - return TextTools.isGraphic(word) ? CUSTOM_EMOJI_SEQUENCE : ""; + return isValidWord(word) ? seq.EMOJI_SEQUENCE : ""; } @NonNull @@ -39,10 +43,10 @@ public class EmojiLanguage extends Language { return TextTools.isGraphic(word); } - public static String validateEmojiSequence(@NonNull String sequence, int next) { - if (sequence.startsWith(CUSTOM_EMOJI_SEQUENCE) || (sequence.equals(EMOJI_SEQUENCE) && next == CUSTOM_EMOJI_KEY)) { - return CUSTOM_EMOJI_SEQUENCE; - } else if (sequence.startsWith(EMOJI_SEQUENCE) && (next > 1 || sequence.length() == Characters.getMaxEmojiLevel() + 1)) { + public static String validateEmojiSequence(@NonNull Sequences seq, @NonNull String sequence, int next) { + if (sequence.startsWith(seq.CUSTOM_EMOJI_SEQUENCE) || (sequence.equals(seq.EMOJI_SEQUENCE) && next == Sequences.CUSTOM_EMOJI_KEY)) { + return seq.CUSTOM_EMOJI_SEQUENCE; + } else if (sequence.startsWith(seq.EMOJI_SEQUENCE) && (next > 1 || sequence.length() - seq.PUNCTUATION_PREFIX_LENGTH == Characters.getMaxEmojiLevel() + 1)) { return sequence; } else { return sequence + next; diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java b/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java index 1af08074..65b64bf3 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java @@ -14,11 +14,6 @@ import io.github.sspanak.tt9.util.chars.Characters; public class NaturalLanguage extends TranscribedLanguage { - final public static String SPECIAL_CHAR_KEY = "0"; - final public static String PUNCTUATION_KEY = "1"; - final public static String PREFERRED_CHAR_SEQUENCE = "00"; - - protected final ArrayList> layout = new ArrayList<>(); private final HashMap characterKeyMap = new HashMap<>(); @NonNull private HashMap numerals = new HashMap<>(); @@ -86,7 +81,7 @@ public class NaturalLanguage extends TranscribedLanguage { final String PUNCTUATION_PLACEHOLDER = "PUNCTUATION"; final Map> specialChars = new HashMap<>(); - specialChars.put(SPECIAL_CHARS_PLACEHOLDER, Characters.getSpecial(this)); + specialChars.put(SPECIAL_CHARS_PLACEHOLDER, new ArrayList<>(Characters.Special)); specialChars.put(PUNCTUATION_PLACEHOLDER, Characters.PunctuationEnglish); specialChars.put(PUNCTUATION_PLACEHOLDER + "_AR", Characters.PunctuationArabic); specialChars.put(PUNCTUATION_PLACEHOLDER + "_BP", Characters.PunctuationChineseBopomofo); @@ -222,17 +217,7 @@ public class NaturalLanguage extends TranscribedLanguage { return new ArrayList<>(); } - ArrayList chars = layout.get(key); - if (key == 0) { - if (characterGroup > 1) { - chars = new ArrayList<>(); - } else if (characterGroup == 1) { - chars = new ArrayList<>(Characters.Currency); - if (!currency.isEmpty()) chars.add(2, currency); - } - } - - return chars; + return layout.get(key); } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java index 13752a20..0ca51e81 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/settings/SettingsPunctuation.java @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; import java.util.ArrayList; import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.util.chars.Characters; class SettingsPunctuation extends SettingsInput { private final static String KEY_PREFIX_PUNCTUATION = "pref_punctuation_"; @@ -27,7 +28,9 @@ class SettingsPunctuation extends SettingsInput { public void saveSpecialChars(@NonNull Language language, @NonNull String specialChars) { - String safeChars = specialChars.replace("\n", "⏎"); + String safeChars = specialChars + .replace("\n", "⏎") + .replace("\t", Characters.TAB); prefsEditor.putString(KEY_PREFIX_SPECIAL + language.getId(), safeChars); prefsEditor.apply(); } @@ -65,7 +68,7 @@ class SettingsPunctuation extends SettingsInput { String safeChars = prefs.getString(KEY_PREFIX_SPECIAL + language.getId(), null); return getCharsAsList( - safeChars == null ? null : safeChars.replace("⏎", "\n"), + safeChars == null ? null : safeChars.replace("⏎", "\n").replace(Characters.TAB, "\t"), language.getKeyCharacters(0) ); } diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber2to9.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber2to9.java index cb649652..a19c7721 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber2to9.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/keys/SoftKeyNumber2to9.java @@ -109,10 +109,10 @@ public class SoftKeyNumber2to9 extends SoftKeyNumber { */ private String abbreviateCharList(String chars, String abbreviationSign, Locale locale, boolean isUppercase) { String firstLetter = chars.substring(0, 1); - firstLetter = TextTools.isCombining(firstLetter) ? Characters.COMBINING_ZERO_BASE + firstLetter : firstLetter; + firstLetter = TextTools.isCombining(firstLetter) ? Characters.PLACEHOLDER + firstLetter : firstLetter; String lastLetter = chars.substring(chars.length() - 1); - lastLetter = TextTools.isCombining(lastLetter) ? Characters.COMBINING_ZERO_BASE + lastLetter : lastLetter; + lastLetter = TextTools.isCombining(lastLetter) ? Characters.PLACEHOLDER + lastLetter : lastLetter; String list = firstLetter + abbreviationSign + lastLetter; return isUppercase ? list.toUpperCase(locale) : list; diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java index 314b6948..149eca2b 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/tray/SuggestionsBar.java @@ -27,6 +27,9 @@ import io.github.sspanak.tt9.util.TextTools; import io.github.sspanak.tt9.util.chars.Characters; public class SuggestionsBar { + public static final String SHOW_SPECIAL_CHARS_SUGGESTION = "(@#*…)"; + public static final String SHOW_CURRENCIES_SUGGESTION = "($€£…)"; + private final String SHOW_MORE_SUGGESTION = "(...)"; private final String STEM_SUFFIX = "… +"; private final String STEM_VARIATION_PREFIX = "…"; @@ -140,15 +143,16 @@ public class SuggestionsBar { @NonNull public String get(int id) { - if (id < 0 || id >= visibleSuggestions.size()) { - return ""; + String suggestion = getRaw(id); + + // show more... + if (suggestion.equals(SHOW_MORE_SUGGESTION) || suggestion.equals(SHOW_CURRENCIES_SUGGESTION) || suggestion.equals(SHOW_SPECIAL_CHARS_SUGGESTION)) { + return Characters.PLACEHOLDER; } - String suggestion = visibleSuggestions.get(id); - // single char - if (suggestion.equals(SHOW_MORE_SUGGESTION)) return Characters.COMBINING_ZERO_BASE; if (suggestion.equals(Characters.NEW_LINE)) return "\n"; + if (suggestion.equals(Characters.TAB)) return "\t"; suggestion = suggestion.replace(Characters.ZWNJ_GRAPHIC, Characters.ZWNJ); suggestion = suggestion.replace(Characters.ZWJ_GRAPHIC, Characters.ZWJ); @@ -161,7 +165,7 @@ public class SuggestionsBar { // "..." prefix int startIndex = 0; - String[] prefixes = {STEM_VARIATION_PREFIX, STEM_PUNCTUATION_VARIATION_PREFIX, Characters.COMBINING_ZERO_BASE}; + String[] prefixes = {STEM_VARIATION_PREFIX, STEM_PUNCTUATION_VARIATION_PREFIX, Characters.PLACEHOLDER}; for (String prefix : prefixes) { int prefixIndex = suggestion.indexOf(prefix) + 1; if (prefixIndex < endIndex) { // do not match the prefix chars when they are part of STEM_SUFFIX @@ -177,6 +181,16 @@ public class SuggestionsBar { } + @NonNull + public String getRaw(int id) { + if (id < 0 || id >= visibleSuggestions.size()) { + return ""; + } + + return visibleSuggestions.get(id); + } + + public void setRTL(boolean yes) { if (mView != null) { mView.setLayoutDirection(yes ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); @@ -310,11 +324,12 @@ public class SuggestionsBar { private String formatUnreadableSuggestion(String suggestion) { if (TextTools.isCombining(suggestion)) { - return Characters.COMBINING_ZERO_BASE + suggestion; + return Characters.PLACEHOLDER + suggestion; } return switch (suggestion) { case "\n" -> Characters.NEW_LINE; + case "\t" -> Characters.TAB; case Characters.ZWJ -> Characters.ZWJ_GRAPHIC; case Characters.ZWNJ -> Characters.ZWNJ_GRAPHIC; default -> suggestion; diff --git a/app/src/main/java/io/github/sspanak/tt9/util/chars/Characters.java b/app/src/main/java/io/github/sspanak/tt9/util/chars/Characters.java index 1fafb5f2..0f510d21 100644 --- a/app/src/main/java/io/github/sspanak/tt9/util/chars/Characters.java +++ b/app/src/main/java/io/github/sspanak/tt9/util/chars/Characters.java @@ -1,5 +1,7 @@ package io.github.sspanak.tt9.util.chars; +import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.Arrays; @@ -7,51 +9,79 @@ import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind; public class Characters extends Emoji { - public static final String COMBINING_ZERO_BASE = "◌"; + public static final String PLACEHOLDER = "◌"; public static final String IDEOGRAPHIC_SPACE = " "; + public static final String TAB = "↹"; - final public static ArrayList Currency = new ArrayList<>(Arrays.asList( + + public static final ArrayList Currency = new ArrayList<>(Arrays.asList( "$", "€", "₿", "¢", "¤", "₱", "¥", "£" )); + /** * The English punctuation filtered to contain only valid email characters. */ - final public static ArrayList> Email = new ArrayList<>(Arrays.asList( + public static final ArrayList> Email = new ArrayList<>(Arrays.asList( new ArrayList<>(Arrays.asList("@", "_", " ", "#", "%", "{", "}", "|", "^", "/", "=", "*", "+")), new ArrayList<>(Arrays.asList(".", "-", "&", "~", "`", "'", "!", "?")) )); + /** * Special characters for phone number fields, including both characters for conveniently typing a phone number: "()-", * as well as command characters such as "," = "slight pause" and ";" = "wait" used in Japan and some other countries. */ - final public static ArrayList> Phone = new ArrayList<>(Arrays.asList( + public static final ArrayList> Phone = new ArrayList<>(Arrays.asList( new ArrayList<>(Arrays.asList("+", " ")), new ArrayList<>(Arrays.asList("-", "(", ")", ".", ";", ",")) )); + + + /** + * Commonly used special and math characters. + */ + public static final ArrayList Special = new ArrayList<>(Arrays.asList( + "@", "_", "#", "%", "[", "]", "{", "}", "§", "|", "^", "<", ">", "\\", "/", "=", "*", "+" + )); + + + /** + * Returns a language-specific currency list. + */ + public static ArrayList getCurrencies(@Nullable Language language) { + ArrayList chars = new ArrayList<>(Characters.Currency); + if (language != null && !language.getCurrency().isEmpty()) { + chars.add(2, language.getCurrency()); + } + return chars; + } + + /** * Returns the language-specific space character. */ - public static String getSpace(Language language) { + public static String getSpace(@Nullable Language language) { return LanguageKind.isChinese(language) || LanguageKind.isJapanese(language) ? IDEOGRAPHIC_SPACE : " "; } + /** - * Standard special characters with automatic Space selection based on the language. Useful for - * text fields. + * Whitespace characters with language-specific Space. Useful for text fields. */ - public static ArrayList getSpecial(Language language) { + public static ArrayList getWhitespaces(@Nullable Language language) { return new ArrayList<>(Arrays.asList( - getSpace(language), "\n", "@", "_", "#", "%", "[", "]", "{", "}", "§", "|", "^", "<", ">", "\\", "/", "=", "*", "+" + getSpace(language), "\n", "\t" )); } + /** - * Special characters for all kinds of numeric fields: integer, decimal with +/- included as necessary. + * Special and punctuation characters for all kinds of numeric fields: integer, decimal with +/-, + * included as necessary. */ - public static ArrayList> getSpecialForNumbers(boolean decimal, boolean signed) { + public static ArrayList> getAllForDecimal(boolean decimal, boolean signed) { ArrayList> keyCharacters = new ArrayList<>(); keyCharacters.add(signed ? new ArrayList<>(Arrays.asList("-", "+")) : new ArrayList<>()); if (decimal) { @@ -60,14 +90,18 @@ public class Characters extends Emoji { return keyCharacters; } + + public static boolean isCurrency(Language language, String c) { return Currency.contains(c) || (language != null && language.getCurrency().equals(c)); } + public static boolean isFathatan(char ch) { return ch == 0x064B; } + public static boolean isOm(char ch) { return ch == 0x0950 // Devanagari