1
0
Fork 0

emoji can now be added as custom words

This commit is contained in:
sspanak 2024-02-16 12:11:48 +02:00 committed by Dimo Karaivanov
parent e2409f4354
commit 0e9954864e
11 changed files with 143 additions and 54 deletions

View file

@ -21,6 +21,7 @@ import io.github.sspanak.tt9.db.sqlite.InsertOps;
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener; import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
import io.github.sspanak.tt9.db.sqlite.Tables; import io.github.sspanak.tt9.db.sqlite.Tables;
import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException; import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException; import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
@ -170,6 +171,7 @@ public class DictionaryLoader {
start = System.currentTimeMillis(); start = System.currentTimeMillis();
DeleteOps.delete(sqlite, language.getId()); DeleteOps.delete(sqlite, language.getId());
DeleteOps.delete(sqlite, new EmojiLanguage().getId());
sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME); sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
logLoadingStep("Storage cleared", language, start); logLoadingStep("Storage cleared", language, start);
@ -180,6 +182,7 @@ public class DictionaryLoader {
start = System.currentTimeMillis(); start = System.currentTimeMillis();
InsertOps.restoreCustomWords(sqlite.getDb(), language); InsertOps.restoreCustomWords(sqlite.getDb(), language);
InsertOps.restoreCustomWords(sqlite.getDb(), new EmojiLanguage());
sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME); sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
logLoadingStep("Custom words restored", language, start); logLoadingStep("Custom words restored", language, start);

View file

@ -15,6 +15,8 @@ import io.github.sspanak.tt9.db.sqlite.ReadOps;
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener; import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
import io.github.sspanak.tt9.db.sqlite.UpdateOps; import io.github.sspanak.tt9.db.sqlite.UpdateOps;
import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.languages.Characters;
import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.Text; import io.github.sspanak.tt9.languages.Text;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
@ -136,6 +138,8 @@ public class WordStore {
return AddWordDialog.CODE_GENERAL_ERROR; return AddWordDialog.CODE_GENERAL_ERROR;
} }
language = Characters.isGraphic(word) ? new EmojiLanguage() : language;
try { try {
if (readOps.exists(sqlite.getDb(), language, word)) { if (readOps.exists(sqlite.getDb(), language, word)) {
return AddWordDialog.CODE_WORD_EXISTS; return AddWordDialog.CODE_WORD_EXISTS;

View file

@ -8,6 +8,7 @@ import java.util.ArrayList;
import io.github.sspanak.tt9.BuildConfig; import io.github.sspanak.tt9.BuildConfig;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
@ -23,7 +24,8 @@ public class SQLiteOpener extends SQLiteOpenHelper {
public SQLiteOpener(Context context) { public SQLiteOpener(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
allLanguages = LanguageCollection.getAll(context); allLanguages = new ArrayList<>(LanguageCollection.getAll(context));
allLanguages.add(new EmojiLanguage());
} }

View file

@ -24,10 +24,10 @@ import io.github.sspanak.tt9.languages.Text;
public class TextField { public class TextField {
public static final int IME_ACTION_ENTER = EditorInfo.IME_MASK_ACTION + 1; public static final int IME_ACTION_ENTER = EditorInfo.IME_MASK_ACTION + 1;
private static final Pattern beforeCursorWordRegex = Pattern.compile("(\\w+)(?!\n)$"); private static final Pattern beforeCursorWordRegex = Pattern.compile("([^\\s\\d\\p{P}]+)(?!\n)$");
private static final Pattern afterCursorWordRegex = Pattern.compile("^(?<!\n)(\\w+)"); private static final Pattern afterCursorWordRegex = Pattern.compile("^(?<!\n)([^\\s\\d\\p{P}]+)");
private static final Pattern beforeCursorUkrainianRegex = Pattern.compile("([\\w']+)(?!\n)$"); private static final Pattern beforeCursorUkrainianRegex = Pattern.compile("([(?:^\\s\\d\\p{P}|')]+)(?!\n)$");
private static final Pattern afterCursorUkrainianRegex = Pattern.compile("^(?<!\n)([\\w']+)"); private static final Pattern afterCursorUkrainianRegex = Pattern.compile("^(?<!\n)([(?:^\\s\\d\\p{P}|')]+)");
public final InputConnection connection; public final InputConnection connection;

View file

@ -133,21 +133,22 @@ abstract public class InputMode {
* This is used in nextTextCase() for switching to the next set of characters. Obviously, * This is used in nextTextCase() for switching to the next set of characters. Obviously,
* special chars do not have a text case, but we use this trick to alternate the char groups. * special chars do not have a text case, but we use this trick to alternate the char groups.
*/ */
protected boolean nextSpecialCharacters() { protected boolean nextSpecialCharacters() { return nextSpecialCharacters(language); }
if (language == null || digitSequence.isEmpty()) { protected boolean nextSpecialCharacters(Language altLanguage) {
if (altLanguage == null || digitSequence.isEmpty()) {
return false; return false;
} }
int previousGroup = specialCharSelectedGroup; int previousGroup = specialCharSelectedGroup;
int key = digitSequence.charAt(0) - '0'; int key = digitSequence.charAt(0) - '0';
ArrayList<String> chars = language.getKeyCharacters(key, ++specialCharSelectedGroup); ArrayList<String> chars = altLanguage.getKeyCharacters(key, ++specialCharSelectedGroup);
if (chars.isEmpty() && specialCharSelectedGroup == 1) { if (chars.isEmpty() && specialCharSelectedGroup == 1) {
specialCharSelectedGroup = 0; specialCharSelectedGroup = 0;
return false; return false;
} else if (chars.isEmpty()) { } else if (chars.isEmpty()) {
specialCharSelectedGroup = 0; specialCharSelectedGroup = 0;
chars = language.getKeyCharacters(key, specialCharSelectedGroup); chars = altLanguage.getKeyCharacters(key, specialCharSelectedGroup);
} }
suggestions.clear(); suggestions.clear();

View file

@ -5,22 +5,21 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.Text;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.ime.helpers.InputType; import io.github.sspanak.tt9.ime.helpers.InputType;
import io.github.sspanak.tt9.ime.helpers.TextField; import io.github.sspanak.tt9.ime.helpers.TextField;
import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace; import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace;
import io.github.sspanak.tt9.ime.modes.helpers.AutoTextCase; import io.github.sspanak.tt9.ime.modes.helpers.AutoTextCase;
import io.github.sspanak.tt9.ime.modes.helpers.Predictions; import io.github.sspanak.tt9.ime.modes.helpers.Predictions;
import io.github.sspanak.tt9.languages.Characters; import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.Text;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
public class ModePredictive extends InputMode { public class ModePredictive extends InputMode {
private final String LOG_TAG = getClass().getSimpleName(); private final String LOG_TAG = getClass().getSimpleName();
private final static String PREFERRED_CHAR_SEQUENCE = "00";
private final static String EMOJI_SEQUENCE = "11";
private final SettingsStore settings; private final SettingsStore settings;
@ -66,6 +65,11 @@ public class ModePredictive extends InputMode {
return false; return false;
} }
if (digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE) && specialCharSelectedGroup > 0) {
specialCharSelectedGroup -= 2;
return true;
}
digitSequence = digitSequence.substring(0, digitSequence.length() - 1); digitSequence = digitSequence.substring(0, digitSequence.length() - 1);
if (digitSequence.length() == 0) { if (digitSequence.length() == 0) {
clearWordStem(); clearWordStem();
@ -88,13 +92,17 @@ public class ModePredictive extends InputMode {
disablePredictions = true; disablePredictions = true;
suggestions.add(language.getKeyNumber(number)); suggestions.add(language.getKeyNumber(number));
} else { } else {
// words
super.reset(); super.reset();
disablePredictions = false;
digitSequence += number; digitSequence += number;
if (number == 0 && repeat > 0) { disablePredictions = false;
if (digitSequence.equals(Language.PREFERRED_CHAR_SEQUENCE)) {
autoAcceptTimeout = 0; autoAcceptTimeout = 0;
} }
// custom emoji are longest 1-key sequence, so do not allow typing any longer than that,
// to prevent side effects
digitSequence = digitSequence.startsWith(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE) ? EmojiLanguage.CUSTOM_EMOJI_SEQUENCE : digitSequence;
} }
return true; return true;
@ -225,12 +233,14 @@ public class ModePredictive extends InputMode {
return; return;
} }
Language searchLanguage = digitSequence.startsWith(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE) ? new EmojiLanguage() : language;
onSuggestionsUpdated = onLoad; onSuggestionsUpdated = onLoad;
predictions predictions
.setDigitSequence(digitSequence) .setDigitSequence(digitSequence)
.setIsStemFuzzy(isStemFuzzy) .setIsStemFuzzy(isStemFuzzy)
.setStem(stem) .setStem(stem)
.setLanguage(language) .setLanguage(searchLanguage)
.setInputWord(currentWord) .setInputWord(currentWord)
.setWordsChangedHandler(this::getPredictions) .setWordsChangedHandler(this::getPredictions)
.load(); .load();
@ -243,13 +253,16 @@ public class ModePredictive extends InputMode {
* options for the current digitSequence. * options for the current digitSequence.
*/ */
private boolean loadStaticSuggestions(Runnable onLoad) { private boolean loadStaticSuggestions(Runnable onLoad) {
if (digitSequence.startsWith(EMOJI_SEQUENCE)) { if (digitSequence.equals(Language.PUNCTUATION_KEY)) {
digitSequence = digitSequence.substring(0, Math.min(digitSequence.length(), Characters.getEmojiLevels() + 1));
specialCharSelectedGroup = digitSequence.length() - 2;
super.nextSpecialCharacters(); super.nextSpecialCharacters();
onLoad.run(); onLoad.run();
return true; return true;
} else if (digitSequence.startsWith(PREFERRED_CHAR_SEQUENCE)) { } else if (digitSequence.equals(EmojiLanguage.EMOJI_SEQUENCE)) {
specialCharSelectedGroup = -1;
nextSpecialCharacters(new EmojiLanguage());
onLoad.run();
return true;
} else if (digitSequence.startsWith(Language.PREFERRED_CHAR_SEQUENCE)) {
suggestions.clear(); suggestions.clear();
suggestions.add(settings.getDoubleZeroChar()); suggestions.add(settings.getDoubleZeroChar());
onLoad.run(); onLoad.run();
@ -265,7 +278,12 @@ public class ModePredictive extends InputMode {
* Gets the currently available Predictions and sends them over to the external caller. * Gets the currently available Predictions and sends them over to the external caller.
*/ */
private void getPredictions() { private void getPredictions() {
digitSequence = predictions.getDigitSequence(); // in case the user hasn't added any custom emoji, do not allow advancing to the empty character group
if (predictions.getList().isEmpty() && digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE)) {
digitSequence = EmojiLanguage.EMOJI_SEQUENCE;
return;
}
suggestions.clear(); suggestions.clear();
suggestions.addAll(predictions.getList()); suggestions.addAll(predictions.getList());
@ -327,7 +345,9 @@ public class ModePredictive extends InputMode {
@Override @Override
protected boolean nextSpecialCharacters() { protected boolean nextSpecialCharacters() {
return digitSequence.equals(Language.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters(); return
(digitSequence.equals(EmojiLanguage.EMOJI_SEQUENCE) && super.nextSpecialCharacters(new EmojiLanguage()))
|| (digitSequence.equals(Language.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters());
} }
@Override @Override

View file

@ -3,6 +3,7 @@ package io.github.sspanak.tt9.ime.modes.helpers;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
@ -36,10 +37,6 @@ public class Predictions {
return this; return this;
} }
public String getDigitSequence() {
return digitSequence;
}
public Predictions setIsStemFuzzy(boolean yes) { public Predictions setIsStemFuzzy(boolean yes) {
this.isStemFuzzy = yes; this.isStemFuzzy = yes;
return this; return this;
@ -106,8 +103,10 @@ public class Predictions {
return; return;
} }
boolean retryAllowed = !digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE);
WordStoreAsync.getWords( WordStoreAsync.getWords(
(words) -> onDbWords(words, true), (words) -> onDbWords(words, retryAllowed),
language, language,
digitSequence, digitSequence,
stem, stem,
@ -153,10 +152,14 @@ public class Predictions {
} }
words.clear(); words.clear();
suggestStem(); if (digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE)) {
suggestMissingWords(generatePossibleStemVariations(dbWords)); words.addAll(dbWords);
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords); } else {
words = insertPunctuationCompletions(words); suggestStem();
suggestMissingWords(generatePossibleStemVariations(dbWords));
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords);
words = insertPunctuationCompletions(words);
}
onWordsChanged.run(); onWordsChanged.run();
} }

View file

@ -28,7 +28,7 @@ public class Characters {
)); ));
final public static ArrayList<String> Currency = new ArrayList<>(Arrays.asList( final public static ArrayList<String> Currency = new ArrayList<>(Arrays.asList(
"$", "", "", "", "", "¢", "", "", "¥", "", "£" "$", "", "", "", "", "¢", "¤", "", "", "¥", "", "£"
)); ));
final public static ArrayList<String> Special = new ArrayList<>(Arrays.asList( final public static ArrayList<String> Special = new ArrayList<>(Arrays.asList(
@ -58,13 +58,24 @@ public class Characters {
)) ))
)); ));
public static boolean noEmojiSupported() { public static boolean isGraphic(String str) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M; if (str == null) {
return false;
}
for (int i = 0, end = str.length(); i < end; i++) {
char ch = str.charAt(i);
if (ch < 256 || Character.isLetterOrDigit(ch) || Character.isAlphabetic(ch)) {
return false;
}
}
return true;
} }
public static boolean noEmojiSupported() {
public static int getEmojiLevels() { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
return noEmojiSupported() ? 1 : Emoji.size();
} }
@ -73,7 +84,9 @@ public class Characters {
return new ArrayList<>(TextEmoticons); return new ArrayList<>(TextEmoticons);
} }
level = (Emoji.size() > level) ? level : Emoji.size() - 1; if (level < 0 || level >= Emoji.size()) {
return new ArrayList<>();
}
Paint paint = new Paint(); Paint paint = new Paint();
ArrayList<String> availableEmoji = new ArrayList<>(); ArrayList<String> availableEmoji = new ArrayList<>();

View file

@ -0,0 +1,26 @@
package io.github.sspanak.tt9.languages;
import java.util.ArrayList;
import java.util.Locale;
public class EmojiLanguage extends Language {
final public static String EMOJI_SEQUENCE = "11";
final public static String CUSTOM_EMOJI_SEQUENCE = EMOJI_SEQUENCE + "1";
public EmojiLanguage() {
id = Integer.parseInt(EMOJI_SEQUENCE);
locale = Locale.ROOT;
abcString = "emoji";
name = "Emoji";
}
@Override
public String getDigitSequenceForWord(String word) {
return Characters.isGraphic(word) ? CUSTOM_EMOJI_SEQUENCE : null;
}
@Override
public ArrayList<String> getKeyCharacters(int key, int characterGroup) {
return key == 1 && characterGroup >= 0 ? new ArrayList<>(Characters.getEmoji(characterGroup)) : super.getKeyCharacters(key, characterGroup);
}
}

View file

@ -8,10 +8,11 @@ import java.util.Locale;
public class Language implements Comparable<Language> { public class Language implements Comparable<Language> {
public static String SPECIAL_CHARS_KEY = "0"; final public static String SPECIAL_CHARS_KEY = "0";
public static String PUNCTUATION_KEY = "1"; final public static String PUNCTUATION_KEY = "1";
final public static String PREFERRED_CHAR_SEQUENCE = "00";
private int id; protected int id;
protected String name; protected String name;
protected Locale locale; protected Locale locale;
protected String dictionaryFile; protected String dictionaryFile;
@ -193,7 +194,8 @@ public class Language implements Comparable<Language> {
* -> 2 | 224 | 2048 | 229376 (shift each 5-bit number, not overlap with the previous ones) * -> 2 | 224 | 2048 | 229376 (shift each 5-bit number, not overlap with the previous ones)
* -> 231650 * -> 231650
* *
* Maximum ID is: "zz-ZZ" -> 879450 * Minimum ID: "aa" -> 33
* Maximum ID: "zz-ZZ" -> 879450
*/ */
private int generateId() { private int generateId() {
String idString = (locale.getLanguage() + locale.getCountry()).toUpperCase(); String idString = (locale.getLanguage() + locale.getCountry()).toUpperCase();
@ -219,14 +221,12 @@ public class Language implements Comparable<Language> {
return new ArrayList<>(); return new ArrayList<>();
} }
ArrayList<String> chars = new ArrayList<>(layout.get(key)); ArrayList<String> chars = layout.get(key);
if (characterGroup > 0) { if (key == 0) {
if (key == 0 && characterGroup == 1) { if (characterGroup > 1) {
chars = new ArrayList<>(Characters.Currency);
} else if (key == 1) {
chars = new ArrayList<>(Characters.getEmoji(characterGroup - 1));
} else {
chars = new ArrayList<>(); chars = new ArrayList<>();
} else if (characterGroup == 1) {
chars = new ArrayList<>(Characters.Currency);
} }
} }

View file

@ -77,15 +77,32 @@ _**Note2:** In messaging applications, you need to enable their "Send with ENTER
- **Press:** type space, newline or special/math characters. - **Press:** type space, newline or special/math characters.
- **Double Press:** type the character assigned in Predictive mode settings. (Default: ".") - **Double Press:** type the character assigned in Predictive mode settings. (Default: ".")
- **Hold:** type "0". - **Hold:** type "0".
- **Press "0", then Press "Next Mode" (Default: Press "0", "#"):** type currency characters - **Press "0", then press "Next Mode" (Default: Press "0", "#"):** type currency characters
#### 1- to 9-key: #### 1-key:
- **In 123 mode:** type the respective number or hold to type punctuation. - **In 123 mode:**
- **In ABC and Predictive mode:** type a letter/punctuation character or hold to type the respective number. - **Press:** type "1".
- **Hold:** type sentence characters
- **In ABC mode:**
- **Press:** type sentence characters
- **Hold:** type "1".
- **In Predictive mode:**
- **Press:** type sentence characters
- **Double Press:** type emoji
- **Triple Press:** type custom added emoji (you must have added some using [the Add Word key](#add-word-key-default-press-))
- **Hold:** type "1".
- **Double Press "1", then press "Next Mode" (Default: Press "1", "1", then keep pressing "#"):** select between the predefined emoji groups
#### 2- to 9-key:
- **In 123 mode:** type the respective number
- **In ABC and Predictive mode:** type a letter character or hold to type the respective number.
#### Add Word Key (Default: Press ✱): #### Add Word Key (Default: Press ✱):
Add a new word to the dictionary for the current language. Add a new word to the dictionary for the current language.
You can also add new emoji. Then access them by pressing 1-1-# (change "#" with the "Next Mode" key, if you have changed the default). Regardless of the selected language at the time of adding, all emoji will be available in all languages.
#### Backspace Key: #### Backspace Key:
Just deletes text. Just deletes text.