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.Tables;
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.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language;
@ -170,6 +171,7 @@ public class DictionaryLoader {
start = System.currentTimeMillis();
DeleteOps.delete(sqlite, language.getId());
DeleteOps.delete(sqlite, new EmojiLanguage().getId());
sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
logLoadingStep("Storage cleared", language, start);
@ -180,6 +182,7 @@ public class DictionaryLoader {
start = System.currentTimeMillis();
InsertOps.restoreCustomWords(sqlite.getDb(), language);
InsertOps.restoreCustomWords(sqlite.getDb(), new EmojiLanguage());
sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
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.UpdateOps;
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.Text;
import io.github.sspanak.tt9.preferences.SettingsStore;
@ -136,6 +138,8 @@ public class WordStore {
return AddWordDialog.CODE_GENERAL_ERROR;
}
language = Characters.isGraphic(word) ? new EmojiLanguage() : language;
try {
if (readOps.exists(sqlite.getDb(), language, word)) {
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.Logger;
import io.github.sspanak.tt9.languages.EmojiLanguage;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
@ -23,7 +24,8 @@ public class SQLiteOpener extends SQLiteOpenHelper {
public SQLiteOpener(Context context) {
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 static final int IME_ACTION_ENTER = EditorInfo.IME_MASK_ACTION + 1;
private static final Pattern beforeCursorWordRegex = Pattern.compile("(\\w+)(?!\n)$");
private static final Pattern afterCursorWordRegex = Pattern.compile("^(?<!\n)(\\w+)");
private static final Pattern beforeCursorUkrainianRegex = Pattern.compile("([\\w']+)(?!\n)$");
private static final Pattern afterCursorUkrainianRegex = Pattern.compile("^(?<!\n)([\\w']+)");
private static final Pattern beforeCursorWordRegex = Pattern.compile("([^\\s\\d\\p{P}]+)(?!\n)$");
private static final Pattern afterCursorWordRegex = Pattern.compile("^(?<!\n)([^\\s\\d\\p{P}]+)");
private static final Pattern beforeCursorUkrainianRegex = Pattern.compile("([(?:^\\s\\d\\p{P}|')]+)(?!\n)$");
private static final Pattern afterCursorUkrainianRegex = Pattern.compile("^(?<!\n)([(?:^\\s\\d\\p{P}|')]+)");
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,
* special chars do not have a text case, but we use this trick to alternate the char groups.
*/
protected boolean nextSpecialCharacters() {
if (language == null || digitSequence.isEmpty()) {
protected boolean nextSpecialCharacters() { return nextSpecialCharacters(language); }
protected boolean nextSpecialCharacters(Language altLanguage) {
if (altLanguage == null || digitSequence.isEmpty()) {
return false;
}
int previousGroup = specialCharSelectedGroup;
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) {
specialCharSelectedGroup = 0;
return false;
} else if (chars.isEmpty()) {
specialCharSelectedGroup = 0;
chars = language.getKeyCharacters(key, specialCharSelectedGroup);
chars = altLanguage.getKeyCharacters(key, specialCharSelectedGroup);
}
suggestions.clear();

View file

@ -5,22 +5,21 @@ import androidx.annotation.NonNull;
import java.util.ArrayList;
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.ime.helpers.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.AutoTextCase;
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.Text;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class ModePredictive extends InputMode {
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;
@ -66,6 +65,11 @@ public class ModePredictive extends InputMode {
return false;
}
if (digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE) && specialCharSelectedGroup > 0) {
specialCharSelectedGroup -= 2;
return true;
}
digitSequence = digitSequence.substring(0, digitSequence.length() - 1);
if (digitSequence.length() == 0) {
clearWordStem();
@ -88,13 +92,17 @@ public class ModePredictive extends InputMode {
disablePredictions = true;
suggestions.add(language.getKeyNumber(number));
} else {
// words
super.reset();
disablePredictions = false;
digitSequence += number;
if (number == 0 && repeat > 0) {
disablePredictions = false;
if (digitSequence.equals(Language.PREFERRED_CHAR_SEQUENCE)) {
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;
@ -225,12 +233,14 @@ public class ModePredictive extends InputMode {
return;
}
Language searchLanguage = digitSequence.startsWith(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE) ? new EmojiLanguage() : language;
onSuggestionsUpdated = onLoad;
predictions
.setDigitSequence(digitSequence)
.setIsStemFuzzy(isStemFuzzy)
.setStem(stem)
.setLanguage(language)
.setLanguage(searchLanguage)
.setInputWord(currentWord)
.setWordsChangedHandler(this::getPredictions)
.load();
@ -243,13 +253,16 @@ public class ModePredictive extends InputMode {
* options for the current digitSequence.
*/
private boolean loadStaticSuggestions(Runnable onLoad) {
if (digitSequence.startsWith(EMOJI_SEQUENCE)) {
digitSequence = digitSequence.substring(0, Math.min(digitSequence.length(), Characters.getEmojiLevels() + 1));
specialCharSelectedGroup = digitSequence.length() - 2;
if (digitSequence.equals(Language.PUNCTUATION_KEY)) {
super.nextSpecialCharacters();
onLoad.run();
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.add(settings.getDoubleZeroChar());
onLoad.run();
@ -265,7 +278,12 @@ public class ModePredictive extends InputMode {
* Gets the currently available Predictions and sends them over to the external caller.
*/
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.addAll(predictions.getList());
@ -327,7 +345,9 @@ public class ModePredictive extends InputMode {
@Override
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

View file

@ -3,6 +3,7 @@ package io.github.sspanak.tt9.ime.modes.helpers;
import java.util.ArrayList;
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.preferences.SettingsStore;
@ -36,10 +37,6 @@ public class Predictions {
return this;
}
public String getDigitSequence() {
return digitSequence;
}
public Predictions setIsStemFuzzy(boolean yes) {
this.isStemFuzzy = yes;
return this;
@ -106,8 +103,10 @@ public class Predictions {
return;
}
boolean retryAllowed = !digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE);
WordStoreAsync.getWords(
(words) -> onDbWords(words, true),
(words) -> onDbWords(words, retryAllowed),
language,
digitSequence,
stem,
@ -153,10 +152,14 @@ public class Predictions {
}
words.clear();
suggestStem();
suggestMissingWords(generatePossibleStemVariations(dbWords));
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords);
words = insertPunctuationCompletions(words);
if (digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE)) {
words.addAll(dbWords);
} else {
suggestStem();
suggestMissingWords(generatePossibleStemVariations(dbWords));
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords);
words = insertPunctuationCompletions(words);
}
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> Special = new ArrayList<>(Arrays.asList(
@ -58,13 +58,24 @@ public class Characters {
))
));
public static boolean noEmojiSupported() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
public static boolean isGraphic(String str) {
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 int getEmojiLevels() {
return noEmojiSupported() ? 1 : Emoji.size();
public static boolean noEmojiSupported() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
}
@ -73,7 +84,9 @@ public class Characters {
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();
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 static String SPECIAL_CHARS_KEY = "0";
public static String PUNCTUATION_KEY = "1";
final public static String SPECIAL_CHARS_KEY = "0";
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 Locale locale;
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)
* -> 231650
*
* Maximum ID is: "zz-ZZ" -> 879450
* Minimum ID: "aa" -> 33
* Maximum ID: "zz-ZZ" -> 879450
*/
private int generateId() {
String idString = (locale.getLanguage() + locale.getCountry()).toUpperCase();
@ -219,14 +221,12 @@ public class Language implements Comparable<Language> {
return new ArrayList<>();
}
ArrayList<String> chars = new ArrayList<>(layout.get(key));
if (characterGroup > 0) {
if (key == 0 && characterGroup == 1) {
chars = new ArrayList<>(Characters.Currency);
} else if (key == 1) {
chars = new ArrayList<>(Characters.getEmoji(characterGroup - 1));
} else {
ArrayList<String> chars = layout.get(key);
if (key == 0) {
if (characterGroup > 1) {
chars = new ArrayList<>();
} else if (characterGroup == 1) {
chars = new ArrayList<>(Characters.Currency);
}
}