1
0
Fork 0

separated the Language class into smaller files and reorganized the languages package

This commit is contained in:
sspanak 2024-03-19 15:18:18 +02:00 committed by Dimo Karaivanov
parent 25657d12a8
commit 58f5123bdb
61 changed files with 456 additions and 387 deletions

View file

@ -10,9 +10,9 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.Timer; import io.github.sspanak.tt9.util.Timer;
import io.github.sspanak.tt9.db.entities.WordBatch; import io.github.sspanak.tt9.db.entities.WordBatch;
import io.github.sspanak.tt9.db.entities.WordFile; import io.github.sspanak.tt9.db.entities.WordFile;
import io.github.sspanak.tt9.db.exceptions.DictionaryImportAbortedException; import io.github.sspanak.tt9.db.exceptions.DictionaryImportAbortedException;
@ -23,8 +23,8 @@ 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.EmojiLanguage;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageException;
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;
import io.github.sspanak.tt9.ui.DictionaryLoadingBar; import io.github.sspanak.tt9.ui.DictionaryLoadingBar;

View file

@ -4,7 +4,7 @@ import android.app.Activity;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
public class LegacyDb extends SQLiteOpenHelper { public class LegacyDb extends SQLiteOpenHelper {
private final String LOG_TAG = getClass().getSimpleName(); private final String LOG_TAG = getClass().getSimpleName();

View file

@ -2,9 +2,9 @@ package io.github.sspanak.tt9.db;
import java.util.HashMap; import java.util.HashMap;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.TextTools; import io.github.sspanak.tt9.util.TextTools;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
public class SlowQueryStats { public class SlowQueryStats {

View file

@ -6,8 +6,8 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.Timer; import io.github.sspanak.tt9.util.Timer;
import io.github.sspanak.tt9.db.entities.Word; import io.github.sspanak.tt9.db.entities.Word;
import io.github.sspanak.tt9.db.entities.WordList; import io.github.sspanak.tt9.db.entities.WordList;
import io.github.sspanak.tt9.db.sqlite.DeleteOps; import io.github.sspanak.tt9.db.sqlite.DeleteOps;
@ -18,7 +18,8 @@ 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.EmojiLanguage; 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.NullLanguage;
import io.github.sspanak.tt9.util.Text;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.ui.dialogs.AddWordDialog; import io.github.sspanak.tt9.ui.dialogs.AddWordDialog;
@ -68,7 +69,7 @@ public class WordStore {
return new ArrayList<>(); return new ArrayList<>();
} }
if (language == null) { if (language == null || language instanceof NullLanguage) {
Logger.w(LOG_TAG, "Attempting to get words for NULL language."); Logger.w(LOG_TAG, "Attempting to get words for NULL language.");
return new ArrayList<>(); return new ArrayList<>();
} }
@ -93,12 +94,12 @@ public class WordStore {
@NonNull public ArrayList<String> getSimilarCustom(Language language, String wordFilter) { @NonNull public ArrayList<String> getSimilarCustom(Language language, String wordFilter) {
return language != null && checkOrNotify() ? readOps.getCustomWords(sqlite.getDb(), language, wordFilter) : new ArrayList<>(); return language != null && !(language instanceof NullLanguage) && checkOrNotify() ? readOps.getCustomWords(sqlite.getDb(), language, wordFilter) : new ArrayList<>();
} }
@NonNull public String getLanguageFileHash(Language language) { @NonNull public String getLanguageFileHash(Language language) {
return language != null && checkOrNotify() ? readOps.getLanguageFileHash(sqlite.getDb(), language.getId()) : ""; return language != null && !(language instanceof NullLanguage) && checkOrNotify() ? readOps.getLanguageFileHash(sqlite.getDb(), language.getId()) : "";
} }
@ -126,7 +127,7 @@ public class WordStore {
public void removeCustomWord(Language language, String word) { public void removeCustomWord(Language language, String word) {
if (language == null || !checkOrNotify()) { if (language == null || language instanceof NullLanguage || !checkOrNotify()) {
return; return;
} }
@ -148,7 +149,7 @@ public class WordStore {
return AddWordDialog.CODE_BLANK_WORD; return AddWordDialog.CODE_BLANK_WORD;
} }
if (language == null) { if (language == null || language instanceof NullLanguage) {
return AddWordDialog.CODE_INVALID_LANGUAGE; return AddWordDialog.CODE_INVALID_LANGUAGE;
} }
@ -191,7 +192,7 @@ public class WordStore {
public void makeTopWord(@NonNull Language language, @NonNull String word, @NonNull String sequence) { public void makeTopWord(@NonNull Language language, @NonNull String word, @NonNull String sequence) {
if (!checkOrNotify() || word.isEmpty() || sequence.isEmpty()) { if (!checkOrNotify() || word.isEmpty() || sequence.isEmpty() || language instanceof NullLanguage) {
return; return;
} }
@ -258,7 +259,7 @@ public class WordStore {
public void scheduleNormalization(Language language) { public void scheduleNormalization(Language language) {
if (language != null && checkOrNotify()) { if (language != null && !(language instanceof NullLanguage) && checkOrNotify()) {
UpdateOps.scheduleNormalization(sqlite.getDb(), language); UpdateOps.scheduleNormalization(sqlite.getDb(), language);
} }
} }

View file

@ -7,7 +7,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
public class WordStoreAsync { public class WordStoreAsync {

View file

@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
public class WordBatch { public class WordBatch {

View file

@ -7,7 +7,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
public class WordFile { public class WordFile {
private static final String LOG_TAG = WordFile.class.getSimpleName(); private static final String LOG_TAG = WordFile.class.getSimpleName();

View file

@ -18,7 +18,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
public abstract class AbstractExporter { public abstract class AbstractExporter {
final protected static String FILE_EXTENSION = ".csv"; final protected static String FILE_EXTENSION = ".csv";

View file

@ -7,9 +7,9 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.Timer; import io.github.sspanak.tt9.util.Timer;
import io.github.sspanak.tt9.db.sqlite.ReadOps; 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.languages.Language; import io.github.sspanak.tt9.languages.Language;

View file

@ -9,7 +9,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.db.SlowQueryStats; import io.github.sspanak.tt9.db.SlowQueryStats;
import io.github.sspanak.tt9.db.entities.WordList; import io.github.sspanak.tt9.db.entities.WordList;
import io.github.sspanak.tt9.db.entities.WordPositionsStringBuilder; import io.github.sspanak.tt9.db.entities.WordPositionsStringBuilder;

View file

@ -7,7 +7,7 @@ import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList; 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.util.Logger;
import io.github.sspanak.tt9.languages.EmojiLanguage; 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;

View file

@ -5,9 +5,9 @@ import android.database.sqlite.SQLiteStatement;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
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.util.Text;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;

View file

@ -6,8 +6,8 @@ import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.Timer; import io.github.sspanak.tt9.util.Timer;
import io.github.sspanak.tt9.ime.helpers.Key; import io.github.sspanak.tt9.ime.helpers.Key;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode; import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode;

View file

@ -16,7 +16,6 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;
@ -30,7 +29,7 @@ import io.github.sspanak.tt9.ime.modes.ModePassthrough;
import io.github.sspanak.tt9.ime.modes.ModePredictive; import io.github.sspanak.tt9.ime.modes.ModePredictive;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.languages.Text; import io.github.sspanak.tt9.languages.LanguageKind;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
import io.github.sspanak.tt9.ui.PopupDialogActivity; import io.github.sspanak.tt9.ui.PopupDialogActivity;
@ -38,6 +37,8 @@ import io.github.sspanak.tt9.ui.UI;
import io.github.sspanak.tt9.ui.main.MainView; import io.github.sspanak.tt9.ui.main.MainView;
import io.github.sspanak.tt9.ui.tray.StatusBar; import io.github.sspanak.tt9.ui.tray.StatusBar;
import io.github.sspanak.tt9.ui.tray.SuggestionsBar; import io.github.sspanak.tt9.ui.tray.SuggestionsBar;
import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.util.Text;
public class TraditionalT9 extends KeyPadHandler { public class TraditionalT9 extends KeyPadHandler {
private InputConnection currentInputConnection = null; private InputConnection currentInputConnection = null;
@ -469,7 +470,7 @@ public class TraditionalT9 extends KeyPadHandler {
} }
cancelAutoAccept(); cancelAutoAccept();
backward = systemLanguage.isRTL() != backward; backward = LanguageKind.isRTL(systemLanguage) != backward;
suggestionBar.scrollToSuggestion(backward ? -1 : 1); suggestionBar.scrollToSuggestion(backward ? -1 : 1);
mInputMode.setWordStem(suggestionBar.getCurrentSuggestion(), true); mInputMode.setWordStem(suggestionBar.getCurrentSuggestion(), true);
setComposingTextWithHighlightedStem(suggestionBar.getCurrentSuggestion()); setComposingTextWithHighlightedStem(suggestionBar.getCurrentSuggestion());

View file

@ -4,7 +4,7 @@ import android.content.Context;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;

View file

@ -14,10 +14,11 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.Text; import io.github.sspanak.tt9.languages.LanguageKind;
import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.util.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;
@ -191,8 +192,8 @@ public class TextField {
boolean keepQuote = false; boolean keepQuote = false;
if (language != null) { if (language != null) {
// Hebrew and Ukrainian use the respective special characters as letters // Hebrew and Ukrainian use the respective special characters as letters
keepApostrophe = language.isHebrew() || language.isUkrainian(); keepApostrophe = LanguageKind.isHebrew(language) || LanguageKind.isUkrainian(language);
keepQuote = language.isHebrew(); keepQuote = LanguageKind.isHebrew(language);
} }
return before.subStringEndingWord(keepApostrophe, keepQuote) + after.subStringStartingWord(keepApostrophe, keepQuote); return before.subStringEndingWord(keepApostrophe, keepQuote) + after.subStringStartingWord(keepApostrophe, keepQuote);

View file

@ -4,10 +4,11 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
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.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.NaturalLanguage;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
abstract public class InputMode { abstract public class InputMode {
@ -133,7 +134,7 @@ abstract public class InputMode {
return true; return true;
} }
if (!language.hasUpperCase() || digitSequence.startsWith(Language.PUNCTUATION_KEY) || digitSequence.startsWith(Language.SPECIAL_CHARS_KEY)) { if (!language.hasUpperCase() || digitSequence.startsWith(NaturalLanguage.PUNCTUATION_KEY) || digitSequence.startsWith(NaturalLanguage.SPECIAL_CHARS_KEY)) {
return false; return false;
} }

View file

@ -7,8 +7,9 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import io.github.sspanak.tt9.ime.helpers.InputType; import io.github.sspanak.tt9.ime.helpers.InputType;
import io.github.sspanak.tt9.languages.Characters; import io.github.sspanak.tt9.util.Characters;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.NaturalLanguage;
public class Mode123 extends ModePassthrough { public class Mode123 extends ModePassthrough {
@Override public int getId() { return MODE_123; } @Override public int getId() { return MODE_123; }
@ -82,7 +83,7 @@ public class Mode123 extends ModePassthrough {
} }
@Override protected boolean nextSpecialCharacters() { @Override protected boolean nextSpecialCharacters() {
return digitSequence.equals(Language.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters(); return digitSequence.equals(NaturalLanguage.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters();
} }
@Override public boolean onNumber(int number, boolean hold, int repeat) { @Override public boolean onNumber(int number, boolean hold, int repeat) {

View file

@ -3,6 +3,8 @@ package io.github.sspanak.tt9.ime.modes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.languages.Language; 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.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
public class ModeABC extends InputMode { public class ModeABC extends InputMode {
@ -61,7 +63,7 @@ public class ModeABC extends InputMode {
@Override @Override
protected boolean nextSpecialCharacters() { protected boolean nextSpecialCharacters() {
if (digitSequence.equals(Language.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters()) { if (digitSequence.equals(NaturalLanguage.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters()) {
suggestions.add(language.getKeyNumber(digitSequence.charAt(0) - '0')); suggestions.add(language.getKeyNumber(digitSequence.charAt(0) - '0'));
return true; return true;
} }
@ -102,7 +104,7 @@ public class ModeABC extends InputMode {
} }
String langCode = ""; String langCode = "";
if (language.isLatinBased() || language.isCyrillic()) { if (LanguageKind.isLatinBased(language) || LanguageKind.isCyrillic(language)) {
// There are many languages written using the same alphabet, so if the user has enabled multiple, // There are many languages written using the same alphabet, so if the user has enabled multiple,
// make it clear which one is it, by appending the country code to "ABC" or "АБВ". // make it clear which one is it, by appending the country code to "ABC" or "АБВ".
langCode = language.getLocale().getCountry(); langCode = language.getLocale().getCountry();
@ -110,7 +112,7 @@ public class ModeABC extends InputMode {
langCode = langCode.isEmpty() ? language.getName() : langCode; langCode = langCode.isEmpty() ? language.getName() : langCode;
langCode = " / " + langCode; langCode = " / " + langCode;
} }
String modeString = language.getAbcString() + langCode.toUpperCase(); String modeString = language.getAbcString() + langCode.toUpperCase();
return (textCase == CASE_LOWER) ? modeString.toLowerCase(language.getLocale()) : modeString.toUpperCase(language.getLocale()); return (textCase == CASE_LOWER) ? modeString.toLowerCase(language.getLocale()) : modeString.toUpperCase(language.getLocale());
} }

View file

@ -4,7 +4,6 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger;
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;
@ -13,8 +12,11 @@ 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.EmojiLanguage; 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.LanguageKind;
import io.github.sspanak.tt9.languages.NaturalLanguage;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.util.Text;
public class ModePredictive extends InputMode { public class ModePredictive extends InputMode {
private final String LOG_TAG = getClass().getSimpleName(); private final String LOG_TAG = getClass().getSimpleName();
@ -91,7 +93,7 @@ public class ModePredictive extends InputMode {
digitSequence = EmojiLanguage.validateEmojiSequence(digitSequence, number); digitSequence = EmojiLanguage.validateEmojiSequence(digitSequence, number);
disablePredictions = false; disablePredictions = false;
if (digitSequence.equals(Language.PREFERRED_CHAR_SEQUENCE)) { if (digitSequence.equals(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) {
autoAcceptTimeout = 0; autoAcceptTimeout = 0;
} }
} }
@ -248,7 +250,7 @@ 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.equals(Language.PUNCTUATION_KEY) || digitSequence.equals(Language.SPECIAL_CHARS_KEY)) { if (digitSequence.equals(NaturalLanguage.PUNCTUATION_KEY) || digitSequence.equals(NaturalLanguage.SPECIAL_CHARS_KEY)) {
super.loadSpecialCharacters(language); super.loadSpecialCharacters(language);
onLoad.run(); onLoad.run();
return true; return true;
@ -257,7 +259,7 @@ public class ModePredictive extends InputMode {
suggestions.addAll(new EmojiLanguage().getKeyCharacters(digitSequence.charAt(0) - '0', digitSequence.length() - 2)); suggestions.addAll(new EmojiLanguage().getKeyCharacters(digitSequence.charAt(0) - '0', digitSequence.length() - 2));
onLoad.run(); onLoad.run();
return true; return true;
} else if (digitSequence.startsWith(Language.PREFERRED_CHAR_SEQUENCE)) { } else if (digitSequence.startsWith(NaturalLanguage.PREFERRED_CHAR_SEQUENCE)) {
suggestions.clear(); suggestions.clear();
suggestions.add(settings.getDoubleZeroChar()); suggestions.add(settings.getDoubleZeroChar());
onLoad.run(); onLoad.run();
@ -313,7 +315,7 @@ public class ModePredictive extends InputMode {
// emoji and punctuation are not in the database, so there is no point in // emoji and punctuation are not in the database, so there is no point in
// running queries that would update nothing // running queries that would update nothing
if (!sequence.startsWith(Language.PUNCTUATION_KEY) && !sequence.startsWith(Language.SPECIAL_CHARS_KEY)) { if (!sequence.startsWith(NaturalLanguage.PUNCTUATION_KEY) && !sequence.startsWith(NaturalLanguage.SPECIAL_CHARS_KEY)) {
WordStoreAsync.makeTopWord(language, currentWord, sequence); WordStoreAsync.makeTopWord(language, currentWord, sequence);
} }
} catch (Exception e) { } catch (Exception e) {
@ -340,7 +342,7 @@ 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(NaturalLanguage.SPECIAL_CHARS_KEY) && super.nextSpecialCharacters();
} }
@Override @Override
@ -379,22 +381,22 @@ public class ModePredictive extends InputMode {
} }
// special characters always break words // special characters always break words
if (autoAcceptTimeout == 0 && !digitSequence.startsWith(Language.SPECIAL_CHARS_KEY)) { if (autoAcceptTimeout == 0 && !digitSequence.startsWith(NaturalLanguage.SPECIAL_CHARS_KEY)) {
return true; return true;
} }
// allow apostrophes in the middle or at the end of Hebrew and Ukrainian words // allow apostrophes in the middle or at the end of Hebrew and Ukrainian words
if (language.isHebrew() || language.isUkrainian()) { if (LanguageKind.isHebrew(language) || LanguageKind.isUkrainian(language)) {
return return
predictions.noDbWords() predictions.noDbWords()
&& digitSequence.equals(Language.PUNCTUATION_KEY); && digitSequence.equals(NaturalLanguage.PUNCTUATION_KEY);
} }
// punctuation breaks words, unless there are database matches ('s, qu', по-, etc...) // punctuation breaks words, unless there are database matches ('s, qu', по-, etc...)
return return
!digitSequence.isEmpty() !digitSequence.isEmpty()
&& predictions.noDbWords() && predictions.noDbWords()
&& digitSequence.contains(Language.PUNCTUATION_KEY) && digitSequence.contains(NaturalLanguage.PUNCTUATION_KEY)
&& !digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE) && !digitSequence.startsWith(EmojiLanguage.EMOJI_SEQUENCE)
&& Text.containsOtherThan1(digitSequence); && Text.containsOtherThan1(digitSequence);
} }

View file

@ -2,7 +2,7 @@ package io.github.sspanak.tt9.ime.modes.helpers;
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.languages.Text; import io.github.sspanak.tt9.util.Text;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
public class AutoSpace { public class AutoSpace {

View file

@ -1,6 +1,6 @@
package io.github.sspanak.tt9.ime.modes.helpers; package io.github.sspanak.tt9.ime.modes.helpers;
import io.github.sspanak.tt9.languages.Text; import io.github.sspanak.tt9.util.Text;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;

View file

@ -5,6 +5,9 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import io.github.sspanak.tt9.util.Characters;
import io.github.sspanak.tt9.util.TextTools;
public class EmojiLanguage extends Language { public class EmojiLanguage extends Language {
final public static String EMOJI_SEQUENCE = "11"; final public static String EMOJI_SEQUENCE = "11";
final private static int CUSTOM_EMOJI_KEY = 3; final private static int CUSTOM_EMOJI_KEY = 3;
@ -17,14 +20,21 @@ public class EmojiLanguage extends Language {
name = "Emoji"; name = "Emoji";
} }
@NonNull
@Override @Override
public String getDigitSequenceForWord(String word) { public String getDigitSequenceForWord(String word) {
return TextTools.isGraphic(word) ? CUSTOM_EMOJI_SEQUENCE : null; return TextTools.isGraphic(word) ? CUSTOM_EMOJI_SEQUENCE : "";
}
@NonNull
@Override
public ArrayList<String> getKeyCharacters(int key, int characterGroup) {
return key == 1 && characterGroup >= 0 ? Characters.getEmoji(characterGroup) : new ArrayList<>();
} }
@Override @Override
public ArrayList<String> getKeyCharacters(int key, int characterGroup) { public boolean isValidWord(String word) {
return key == 1 && characterGroup >= 0 ? new ArrayList<>(Characters.getEmoji(characterGroup)) : super.getKeyCharacters(key, characterGroup); return TextTools.isGraphic(word);
} }
public static String validateEmojiSequence(@NonNull String sequence, int next) { public static String validateEmojiSequence(@NonNull String sequence, int next) {

View file

@ -3,303 +3,73 @@ package io.github.sspanak.tt9.languages;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
public class Language implements Comparable<Language> { abstract public class Language {
final public static String SPECIAL_CHARS_KEY = "0";
final public static String PUNCTUATION_KEY = "1";
final public static String PREFERRED_CHAR_SEQUENCE = "00";
protected int id; protected int id;
protected String name;
protected Locale locale;
protected String dictionaryFile;
protected boolean hasUpperCase = true;
protected String abcString; protected String abcString;
protected final ArrayList<ArrayList<String>> layout = new ArrayList<>(); protected String dictionaryFile;
private final HashMap<Character, String> characterKeyMap = new HashMap<>(); protected Locale locale = Locale.ROOT;
protected String name;
protected boolean hasUpperCase = true;
public static Language fromDefinition(LanguageDefinition definition) throws Exception { public int getId() {
if (definition.dictionaryFile.isEmpty()) {
throw new Exception("Invalid definition. Dictionary file must be set.");
}
if (definition.locale.isEmpty()) {
throw new Exception("Invalid definition. Locale cannot be empty.");
}
Locale definitionLocale;
if (definition.locale.equals("en")) {
definitionLocale = Locale.ENGLISH;
} else {
String[] parts = definition.locale.split("-", 2);
if (parts.length == 2) {
definitionLocale = new Locale(parts[0], parts[1]);
} else if (parts.length == 1) {
definitionLocale = new Locale(parts[0]);
} else {
throw new Exception("Unrecognized locale format: '" + definition.locale + "'.");
}
}
Language lang = new Language();
lang.abcString = definition.abcString.isEmpty() ? null : definition.abcString;
lang.dictionaryFile = definition.getDictionaryFile();
lang.hasUpperCase = definition.hasUpperCase;
lang.locale = definitionLocale;
lang.name = definition.name.isEmpty() ? lang.name : definition.name;
for (int key = 0; key <= 9 && key < definition.layout.size(); key++) {
lang.layout.add(keyCharsFromDefinition(key, definition.layout.get(key)));
}
return lang;
}
private static ArrayList<String> keyCharsFromDefinition(int key, ArrayList<String> definitionChars) {
if (key > 1) {
return definitionChars;
}
final String specialCharsPlaceholder = "SPECIAL";
final String punctuationPlaceholder = "PUNCTUATION";
final String arabicStylePlaceholder = punctuationPlaceholder + "_AR";
final String germanStylePlaceholder = punctuationPlaceholder + "_DE";
final String frenchStylePlaceholder = punctuationPlaceholder + "_FR";
ArrayList<String> keyChars = new ArrayList<>();
for (String defChar : definitionChars) {
switch (defChar) {
case specialCharsPlaceholder:
keyChars.addAll(Characters.Special);
break;
case punctuationPlaceholder:
keyChars.addAll(Characters.PunctuationEnglish);
break;
case arabicStylePlaceholder:
keyChars.addAll(Characters.PunctuationArabic);
break;
case germanStylePlaceholder:
keyChars.addAll(Characters.PunctuationGerman);
break;
case frenchStylePlaceholder:
keyChars.addAll(Characters.PunctuationFrench);
break;
default:
keyChars.add(defChar);
break;
}
}
return keyChars;
}
final public int getId() {
if (id == 0) {
id = generateId();
}
return id; return id;
} }
final public Locale getLocale() { @NonNull public String getAbcString() {
return locale;
}
final public String getName() {
if (name == null) {
name = new Text(this, locale.getDisplayLanguage(locale)).capitalize();
}
return name;
}
final public String getDictionaryFile() {
return dictionaryFile;
}
final public String getAbcString() {
if (abcString == null) {
ArrayList<String> lettersList = getKeyCharacters(2);
abcString = "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < lettersList.size() && i < 3; i++) {
sb.append(lettersList.get(i));
}
abcString = sb.toString();
}
return abcString; return abcString;
} }
@NonNull final public String getDictionaryFile() {
return dictionaryFile;
}
public boolean hasUpperCase() { @NonNull final public Locale getLocale() {
return hasUpperCase; return locale;
} }
/** /**
* isLatinBased * Returns the characters that the key would type in ABC or Predictive mode. For example,
* Returns "true" when the language is based on the Latin alphabet or "false" otherwise. * the key 2 in English would return A-B-C.
* Keys that have special characters assigned, may have more than one group assigned. The specific
* group is selected by the characterGroup parameter. By default, group 0 is returned.
*/ */
public boolean isLatinBased() { @NonNull abstract public ArrayList<String> getKeyCharacters(int key, int characterGroup);
return getKeyCharacters(2).contains("a"); @NonNull public ArrayList<String> getKeyCharacters(int key) {
}
public boolean isCyrillic() {
return getKeyCharacters(2).contains("а");
}
public boolean isRTL() {
return isArabic() || isHebrew();
}
public boolean isGreek() {
return getKeyCharacters(2).contains("α");
}
public boolean isArabic() {
return getKeyCharacters(3).contains("ا");
}
public boolean isUkrainian() {
return getKeyCharacters(3).contains("є");
}
public boolean isHebrew() {
return getKeyCharacters(3).contains("א");
}
/* ************ utility ************ */
/**
* generateId
* Uses the letters of the Locale to generate an ID for the language.
* Each letter is converted to uppercase and used as a 5-bit integer. Then the 5-bits
* are packed to form a 10-bit or a 20-bit integer, depending on the Locale length.
*
* Example (2-letter Locale)
* "en"
* -> "E" | "N"
* -> 5 | 448 (shift the 2nd number by 5 bits, so its bits would not overlap with the 1st one)
* -> 543
*
* Example (4-letter Locale)
* "bg-BG"
* -> "B" | "G" | "B" | "G"
* -> 2 | 224 | 2048 | 229376 (shift each 5-bit number, not overlap with the previous ones)
* -> 231650
*
* Minimum ID: "aa" -> 33
* Maximum ID: "zz-ZZ" -> 879450
*/
private int generateId() {
String idString = (locale.getLanguage() + locale.getCountry()).toUpperCase();
int idInt = 0;
for (int i = 0; i < idString.length(); i++) {
idInt |= ((idString.codePointAt(i) & 31) << (i * 5));
}
return idInt;
}
private void generateCharacterKeyMap() {
characterKeyMap.clear();
for (int digit = 0; digit <= 9; digit++) {
for (String keyChar : getKeyCharacters(digit)) {
characterKeyMap.put(keyChar.charAt(0), String.valueOf(digit));
}
}
}
public ArrayList<String> getKeyCharacters(int key, int characterGroup) {
if (key < 0 || key >= layout.size()) {
return new ArrayList<>();
}
ArrayList<String> chars = layout.get(key);
if (key == 0) {
if (characterGroup > 1) {
chars = new ArrayList<>();
} else if (characterGroup == 1) {
chars = new ArrayList<>(Characters.Currency);
}
}
return chars;
}
public ArrayList<String> getKeyCharacters(int key) {
return getKeyCharacters(key, 0); return getKeyCharacters(key, 0);
} }
public String getKeyNumber(int key) { @NonNull public String getKeyNumber(int key) {
if (key > 10 || key < 0) { return String.valueOf(key);
return null;
} else {
return isArabic() ? Characters.ArabicNumbers.get(key) : String.valueOf(key);
}
} }
public String getDigitSequenceForWord(String word) throws InvalidLanguageCharactersException { @NonNull public String getName() {
StringBuilder sequence = new StringBuilder(); return name;
String lowerCaseWord = word.toLowerCase(locale); }
if (characterKeyMap.isEmpty()) { final public boolean hasUpperCase() {
generateCharacterKeyMap(); return hasUpperCase;
} }
for (int i = 0; i < lowerCaseWord.length(); i++) { @NonNull
char letter = lowerCaseWord.charAt(i); @Override
if (!characterKeyMap.containsKey(letter)) { final public String toString() {
throw new InvalidLanguageCharactersException(this, "Failed generating digit sequence for word: '" + word); return getName();
}
sequence.append(characterKeyMap.get(letter));
}
return sequence.toString();
} }
/** /**
* Checks whether the given word contains characters outside of the language alphabet. * Checks whether the given word contains characters outside of the language alphabet.
*/ */
public boolean isValidWord(String word) { abstract public boolean isValidWord(String word);
if (word == null || word.isEmpty() || (word.length() == 1 && Character.isDigit(word.charAt(0)))) {
return true;
}
String lowerCaseWord = word.toLowerCase(locale); /**
if (characterKeyMap.isEmpty()) { * Converts a word to a sequence of digits based on the language's keyboard layout.
generateCharacterKeyMap(); * For example: "food" -> "3663"
} */
@NonNull abstract public String getDigitSequenceForWord(String word) throws InvalidLanguageCharactersException;
for (int i = 0; i < lowerCaseWord.length(); i++) {
if (!characterKeyMap.containsKey(lowerCaseWord.charAt(i))) {
return false;
}
}
return true;
}
@NonNull
@Override
public String toString() {
return getName();
}
@Override
public int compareTo(Language other) {
String key = getName().equals("Suomi") ? "su" : getLocale().toString();
String otherKey = other.getName().equals("Suomi") ? "su" : other.getLocale().toString();
return key.compareTo(otherKey);
}
} }

View file

@ -9,18 +9,18 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.ime.helpers.SystemSettings; import io.github.sspanak.tt9.ime.helpers.SystemSettings;
public class LanguageCollection { public class LanguageCollection {
private static LanguageCollection self; private static LanguageCollection self;
private final HashMap<Integer, Language> languages = new HashMap<>(); private final HashMap<Integer, NaturalLanguage> languages = new HashMap<>();
private LanguageCollection(Context context) { private LanguageCollection(Context context) {
for (String file : LanguageDefinition.getAllFiles(context.getAssets())) { for (String file : LanguageDefinition.getAllFiles(context.getAssets())) {
try { try {
Language lang = Language.fromDefinition(LanguageDefinition.fromFile(context.getAssets(), file)); NaturalLanguage lang = NaturalLanguage.fromDefinition(LanguageDefinition.fromFile(context.getAssets(), file));
languages.put(lang.getId(), lang); languages.put(lang.getId(), lang);
} catch (Exception e) { } catch (Exception e) {
Logger.e("tt9.LanguageCollection", "Skipping invalid language: '" + file + "'. " + e.getMessage()); Logger.e("tt9.LanguageCollection", "Skipping invalid language: '" + file + "'. " + e.getMessage());
@ -38,7 +38,7 @@ public class LanguageCollection {
} }
@Nullable @Nullable
public static Language getLanguage(Context context, int langId) { public static NaturalLanguage getLanguage(Context context, int langId) {
if (getInstance(context).languages.containsKey(langId)) { if (getInstance(context).languages.containsKey(langId)) {
return getInstance(context).languages.get(langId); return getInstance(context).languages.get(langId);
} }
@ -53,8 +53,8 @@ public class LanguageCollection {
} }
@Nullable @Nullable
public static Language getByLocale(Context context, String locale) { public static NaturalLanguage getByLocale(Context context, String locale) {
for (Language lang : getInstance(context).languages.values()) { for (NaturalLanguage lang : getInstance(context).languages.values()) {
if (lang.getLocale().toString().equals(locale)) { if (lang.getLocale().toString().equals(locale)) {
return lang; return lang;
} }
@ -64,14 +64,13 @@ public class LanguageCollection {
} }
public static ArrayList<Language> getAll(Context context, ArrayList<Integer> languageIds, boolean sort) { public static ArrayList<Language> getAll(Context context, ArrayList<Integer> languageIds, boolean sort) {
ArrayList<Language> langList = new ArrayList<>();
if (languageIds == null) { if (languageIds == null) {
return langList; return new ArrayList<>();
} }
ArrayList<NaturalLanguage> langList = new ArrayList<>();
for (int languageId : languageIds) { for (int languageId : languageIds) {
Language lang = getLanguage(context, languageId); NaturalLanguage lang = getLanguage(context, languageId);
if (lang != null) { if (lang != null) {
langList.add(lang); langList.add(lang);
} }
@ -81,8 +80,7 @@ public class LanguageCollection {
Collections.sort(langList); Collections.sort(langList);
} }
return new ArrayList<>(langList);
return langList;
} }
public static ArrayList<Language> getAll(Context context, ArrayList<Integer> languageIds) { public static ArrayList<Language> getAll(Context context, ArrayList<Integer> languageIds) {
@ -90,13 +88,13 @@ public class LanguageCollection {
} }
public static ArrayList<Language> getAll(Context context, boolean sort) { public static ArrayList<Language> getAll(Context context, boolean sort) {
ArrayList<Language> langList = new ArrayList<>(getInstance(context).languages.values()); ArrayList<NaturalLanguage> langList = new ArrayList<>(getInstance(context).languages.values());
if (sort) { if (sort) {
Collections.sort(langList); Collections.sort(langList);
} }
return langList; return new ArrayList<>(langList);
} }
public static ArrayList<Language> getAll(Context context) { public static ArrayList<Language> getAll(Context context) {

View file

@ -12,7 +12,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
public class LanguageDefinition { public class LanguageDefinition {
private static final String languagesDir = "languages"; private static final String languagesDir = "languages";

View file

@ -0,0 +1,11 @@
package io.github.sspanak.tt9.languages;
public class LanguageKind {
public static boolean isArabic(Language language) { return language != null && language.getKeyCharacters(3).contains("ا"); }
public static boolean isCyrillic(Language language) { return language != null && language.getKeyCharacters(2).contains("а"); }
public static boolean isHebrew(Language language) { return language != null && language.getKeyCharacters(3).contains("א"); }
public static boolean isGreek(Language language) { return language != null && language.getKeyCharacters(2).contains("α"); }
public static boolean isLatinBased(Language language) { return language != null && language.getKeyCharacters(2).contains("a"); }
public static boolean isRTL(Language language) { return isArabic(language) || isHebrew(language); }
public static boolean isUkrainian(Language language) { return language != null && language.getKeyCharacters(3).contains("є"); }
}

View file

@ -0,0 +1,247 @@
package io.github.sspanak.tt9.languages;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.util.Characters;
import io.github.sspanak.tt9.util.Text;
public class NaturalLanguage extends Language implements Comparable<NaturalLanguage> {
final public static String SPECIAL_CHARS_KEY = "0";
final public static String PUNCTUATION_KEY = "1";
final public static String PREFERRED_CHAR_SEQUENCE = "00";
protected final ArrayList<ArrayList<String>> layout = new ArrayList<>();
private final HashMap<Character, String> characterKeyMap = new HashMap<>();
public static NaturalLanguage fromDefinition(LanguageDefinition definition) throws Exception {
if (definition.dictionaryFile.isEmpty()) {
throw new Exception("Invalid definition. Dictionary file must be set.");
}
NaturalLanguage lang = new NaturalLanguage();
lang.abcString = definition.abcString.isEmpty() ? null : definition.abcString;
lang.dictionaryFile = definition.getDictionaryFile();
lang.hasUpperCase = definition.hasUpperCase;
lang.name = definition.name.isEmpty() ? lang.name : definition.name;
lang.setLocale(definition);
lang.setLayout(definition);
return lang;
}
private void setLocale(LanguageDefinition definition) throws Exception {
if (definition.locale.isEmpty()) {
throw new Exception("Invalid definition. Locale cannot be empty.");
}
if (definition.locale.equals("en")) {
locale = Locale.ENGLISH;
} else {
String[] parts = definition.locale.split("-", 2);
if (parts.length == 2) {
locale = new Locale(parts[0], parts[1]);
} else if (parts.length == 1) {
locale = new Locale(parts[0]);
} else {
throw new Exception("Unrecognized locale format: '" + definition.locale + "'.");
}
}
}
private void setLayout(LanguageDefinition definition) {
for (int key = 0; key <= 9 && key < definition.layout.size(); key++) {
layout.add(
key,
key > 1 ? definition.layout.get(key) : generateSpecialChars(definition.layout.get(key))
);
}
generateCharacterKeyMap();
}
private ArrayList<String> generateSpecialChars(ArrayList<String> definitionChars) {
final String SPECIAL_CHARS_PLACEHOLDER = "SPECIAL";
final String PUNCTUATION_PLACEHOLDER = "PUNCTUATION";
final String ARABIC_PUNCTUATION_STYLE = PUNCTUATION_PLACEHOLDER + "_AR";
final String GERMAN_PUNCTUATION_STYLE = PUNCTUATION_PLACEHOLDER + "_DE";
final String FRENCH_PUNCTUATION_STYLE = PUNCTUATION_PLACEHOLDER + "_FR";
ArrayList<String> keyChars = new ArrayList<>();
for (String defChar : definitionChars) {
switch (defChar) {
case SPECIAL_CHARS_PLACEHOLDER:
keyChars.addAll(Characters.Special);
break;
case PUNCTUATION_PLACEHOLDER:
keyChars.addAll(Characters.PunctuationEnglish);
break;
case ARABIC_PUNCTUATION_STYLE:
keyChars.addAll(Characters.PunctuationArabic);
break;
case GERMAN_PUNCTUATION_STYLE:
keyChars.addAll(Characters.PunctuationGerman);
break;
case FRENCH_PUNCTUATION_STYLE:
keyChars.addAll(Characters.PunctuationFrench);
break;
default:
keyChars.add(defChar);
break;
}
}
return keyChars;
}
/**
* generateId
* Uses the letters of the Locale to generate an ID for the language.
* Each letter is converted to uppercase and used as a 5-bit integer. Then the 5-bits
* are packed to form a 10-bit or a 20-bit integer, depending on the Locale length.
*
* Example (2-letter Locale)
* "en"
* -> "E" | "N"
* -> 5 | 448 (shift the 2nd number by 5 bits, so its bits would not overlap with the 1st one)
* -> 543
*
* Example (4-letter Locale)
* "bg-BG"
* -> "B" | "G" | "B" | "G"
* -> 2 | 224 | 2048 | 229376 (shift each 5-bit number, not overlap with the previous ones)
* -> 231650
*
* Minimum ID: "aa" -> 33
* Maximum ID: "zz-ZZ" -> 879450
*/
@Override
public int getId() {
if (id == 0) {
String idString = (locale.getLanguage() + locale.getCountry()).toUpperCase();
for (int i = 0; i < idString.length(); i++) {
id |= (idString.codePointAt(i) & 31) << (i * 5);
}
}
return id;
}
@NonNull
@Override
public String getAbcString() {
if (abcString == null) {
ArrayList<String> lettersList = getKeyCharacters(2);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < lettersList.size() && i < 3; i++) {
sb.append(lettersList.get(i));
}
abcString = sb.toString();
}
return abcString;
}
@NonNull
@Override
public String getName() {
if (name == null) {
name = new Text(this, locale.getDisplayLanguage(locale)).capitalize();
}
return name;
}
private void generateCharacterKeyMap() {
characterKeyMap.clear();
for (int digit = 0; digit <= 9; digit++) {
for (String keyChar : getKeyCharacters(digit)) {
characterKeyMap.put(keyChar.charAt(0), String.valueOf(digit));
}
}
}
@NonNull
public ArrayList<String> getKeyCharacters(int key, int characterGroup) {
if (key < 0 || key >= layout.size()) {
return new ArrayList<>();
}
ArrayList<String> chars = layout.get(key);
if (key == 0) {
if (characterGroup > 1) {
chars = new ArrayList<>();
} else if (characterGroup == 1) {
chars = new ArrayList<>(Characters.Currency);
}
}
return chars;
}
@NonNull
public String getKeyNumber(int key) {
return key >= 0 && key < 10 && LanguageKind.isArabic(this) ? Characters.ArabicNumbers.get(key) : super.getKeyNumber(key);
}
@NonNull
public String getDigitSequenceForWord(String word) throws InvalidLanguageCharactersException {
StringBuilder sequence = new StringBuilder();
String lowerCaseWord = word.toLowerCase(locale);
for (int i = 0; i < lowerCaseWord.length(); i++) {
char letter = lowerCaseWord.charAt(i);
if (!characterKeyMap.containsKey(letter)) {
throw new InvalidLanguageCharactersException(this, "Failed generating digit sequence for word: '" + word);
}
sequence.append(characterKeyMap.get(letter));
}
return sequence.toString();
}
public boolean isValidWord(String word) {
if (word == null || word.isEmpty() || (word.length() == 1 && Character.isDigit(word.charAt(0)))) {
return true;
}
String lowerCaseWord = word.toLowerCase(locale);
for (int i = 0; i < lowerCaseWord.length(); i++) {
if (!characterKeyMap.containsKey(lowerCaseWord.charAt(i))) {
return false;
}
}
return true;
}
@Override
public int compareTo(NaturalLanguage other) {
String key = getName().equals("Suomi") ? "su" : getLocale().toString();
String otherKey = other.getName().equals("Suomi") ? "su" : other.getLocale().toString();
return key.compareTo(otherKey);
}
}

View file

@ -2,6 +2,9 @@ package io.github.sspanak.tt9.languages;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
@ -10,6 +13,24 @@ public class NullLanguage extends Language {
public NullLanguage(Context context) { public NullLanguage(Context context) {
locale = Locale.ROOT; locale = Locale.ROOT;
name = context.getString(R.string.no_language); name = context.getString(R.string.no_language);
abcString = "abc"; abcString = "ABC";
hasUpperCase = false;
}
@NonNull
@Override
public ArrayList<String> getKeyCharacters(int key, int characterGroup) {
return new ArrayList<>();
}
@Override
public boolean isValidWord(String word) {
return false;
}
@NonNull
@Override
public String getDigitSequenceForWord(String word) {
return "";
} }
} }

View file

@ -1,4 +1,6 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.languages.exceptions;
import io.github.sspanak.tt9.languages.Language;
public class InvalidLanguageCharactersException extends Exception { public class InvalidLanguageCharactersException extends Exception {

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.languages.exceptions;
public class InvalidLanguageException extends Exception { public class InvalidLanguageException extends Exception {
public InvalidLanguageException() { super("Invalid Language"); } public InvalidLanguageException() { super("Invalid Language"); }

View file

@ -13,7 +13,7 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.LegacyDb; import io.github.sspanak.tt9.db.LegacyDb;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;

View file

@ -13,7 +13,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode; import io.github.sspanak.tt9.preferences.screens.debug.ItemInputHandlingMode;

View file

@ -5,7 +5,7 @@ import androidx.preference.Preference;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
abstract public class ItemClickable { abstract public class ItemClickable {

View file

@ -6,7 +6,7 @@ import androidx.preference.Preference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
public class ItemDropDown { public class ItemDropDown {
private final DropDownPreference item; private final DropDownPreference item;

View file

@ -6,7 +6,7 @@ import android.view.MenuItem;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;
abstract public class BaseScreenFragment extends PreferenceFragmentCompat { abstract public class BaseScreenFragment extends PreferenceFragmentCompat {

View file

@ -10,7 +10,7 @@ import java.util.Arrays;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import io.github.sspanak.tt9.BuildConfig; import io.github.sspanak.tt9.BuildConfig;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.helpers.SystemSettings; import io.github.sspanak.tt9.ime.helpers.SystemSettings;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;

View file

@ -12,7 +12,7 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment; import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;

View file

@ -7,7 +7,7 @@ import androidx.preference.Preference;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.preferences.items.ItemDropDown; import io.github.sspanak.tt9.preferences.items.ItemDropDown;
class ItemLogLevel extends ItemDropDown { class ItemLogLevel extends ItemDropDown {

View file

@ -11,8 +11,8 @@ import androidx.preference.PreferenceViewHolder;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;

View file

@ -7,7 +7,7 @@ import android.text.TextWatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
class TextChangeListener implements TextWatcher { class TextChangeListener implements TextWatcher {

View file

@ -1,7 +1,5 @@
package io.github.sspanak.tt9.preferences.screens.hotkeys; package io.github.sspanak.tt9.preferences.screens.hotkeys;
import android.content.Context;
import androidx.preference.Preference; import androidx.preference.Preference;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;

View file

@ -6,7 +6,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.preferences.helpers.Hotkeys; import io.github.sspanak.tt9.preferences.helpers.Hotkeys;

View file

@ -7,7 +7,7 @@ import androidx.preference.DropDownPreference;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
class ItemSelectZeroKeyCharacter { class ItemSelectZeroKeyCharacter {

View file

@ -2,7 +2,7 @@ package io.github.sspanak.tt9.preferences.screens.languages;
import androidx.preference.Preference; import androidx.preference.Preference;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.db.exporter.DictionaryExporter; import io.github.sspanak.tt9.db.exporter.DictionaryExporter;
import io.github.sspanak.tt9.languages.LanguageCollection; import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.PreferencesActivity; import io.github.sspanak.tt9.preferences.PreferencesActivity;

View file

@ -9,8 +9,8 @@ import java.util.Locale;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exceptions.DictionaryImportException; import io.github.sspanak.tt9.db.exceptions.DictionaryImportException;
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException; import io.github.sspanak.tt9.languages.exceptions.InvalidLanguageException;
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;

View file

@ -5,7 +5,7 @@ import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.ui.dialogs.AddWordDialog; import io.github.sspanak.tt9.ui.dialogs.AddWordDialog;
import io.github.sspanak.tt9.ui.dialogs.ConfirmDictionaryUpdateDialog; import io.github.sspanak.tt9.ui.dialogs.ConfirmDictionaryUpdateDialog;

View file

@ -5,7 +5,7 @@ import android.content.Intent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.WordStoreAsync; import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;

View file

@ -5,7 +5,7 @@ import android.content.Intent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;

View file

@ -5,7 +5,7 @@ import android.content.Intent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.github.sspanak.tt9.ConsumerCompat; import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.ui.UI; import io.github.sspanak.tt9.ui.UI;
abstract public class PopupDialog { abstract public class PopupDialog {

View file

@ -3,8 +3,8 @@ package io.github.sspanak.tt9.ui.main.keys;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import io.github.sspanak.tt9.languages.Characters; import io.github.sspanak.tt9.languages.LanguageKind;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.util.Characters;
public class SoftBackspaceKey extends SoftKey { public class SoftBackspaceKey extends SoftKey {
@ -41,7 +41,6 @@ public class SoftBackspaceKey extends SoftKey {
return "Del"; return "Del";
} }
Language language = getCurrentLanguage(); return LanguageKind.isRTL(getCurrentLanguage()) ? "" : "";
return language != null && language.isRTL() ? "" : "";
} }
} }

View file

@ -15,7 +15,7 @@ import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.TraditionalT9; import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;

View file

@ -6,12 +6,13 @@ import android.view.KeyEvent;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.ime.helpers.Key; import io.github.sspanak.tt9.ime.helpers.Key;
import io.github.sspanak.tt9.ime.modes.InputMode; import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageKind;
import io.github.sspanak.tt9.preferences.SettingsStore; import io.github.sspanak.tt9.preferences.SettingsStore;
import io.github.sspanak.tt9.util.Logger;
public class SoftNumberKey extends SoftKey { public class SoftNumberKey extends SoftKey {
public SoftNumberKey(Context context) { public SoftNumberKey(Context context) {
@ -58,7 +59,7 @@ public class SoftNumberKey extends SoftKey {
int number = getNumber(getId()); int number = getNumber(getId());
Language language = getCurrentLanguage(); Language language = getCurrentLanguage();
if (language != null && language.isArabic() && tt9 != null && !tt9.isInputModeNumeric()) { if (LanguageKind.isArabic(language) && tt9 != null && !tt9.isInputModeNumeric()) {
complexLabelTitleSize = SettingsStore.SOFT_KEY_COMPLEX_LABEL_ARABIC_TITLE_SIZE; complexLabelTitleSize = SettingsStore.SOFT_KEY_COMPLEX_LABEL_ARABIC_TITLE_SIZE;
return language.getKeyNumber(number); return language.getKeyNumber(number);
} else { } else {
@ -106,8 +107,8 @@ public class SoftNumberKey extends SoftKey {
return ""; return "";
} }
boolean isLatinBased = language.isLatinBased(); boolean isLatinBased = LanguageKind.isLatinBased(language);
boolean isGreekBased = language.isGreek(); boolean isGreekBased = LanguageKind.isGreek(language);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
ArrayList<String> chars = language.getKeyCharacters(number); ArrayList<String> chars = language.getKeyCharacters(number);

View file

@ -4,7 +4,7 @@ import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.languages.Language; import io.github.sspanak.tt9.languages.LanguageKind;
public class SoftPunctuationKey extends SoftKey { public class SoftPunctuationKey extends SoftKey {
public SoftPunctuationKey(Context context) { public SoftPunctuationKey(Context context) {
@ -52,8 +52,7 @@ public class SoftPunctuationKey extends SoftKey {
} else { } else {
if (keyId == R.id.soft_key_punctuation_1) return "!"; if (keyId == R.id.soft_key_punctuation_1) return "!";
if (keyId == R.id.soft_key_punctuation_2) { if (keyId == R.id.soft_key_punctuation_2) {
Language language = getCurrentLanguage(); return LanguageKind.isArabic(getCurrentLanguage()) ? "؟" : "?";
return language != null && language.isArabic() ? "؟" : "?";
} }
} }

View file

@ -6,7 +6,7 @@ import android.widget.TextView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import io.github.sspanak.tt9.Logger; import io.github.sspanak.tt9.util.Logger;
import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.R;
public class StatusBar { public class StatusBar {

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.util;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Build; import android.os.Build;

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9; package io.github.sspanak.tt9.util;
/** /**
* ConsumerCompat * ConsumerCompat

View file

@ -1,7 +1,9 @@
package io.github.sspanak.tt9; package io.github.sspanak.tt9.util;
import android.util.Log; import android.util.Log;
import io.github.sspanak.tt9.BuildConfig;
public class Logger { public class Logger {
public static final String TAG_PREFIX = "tt9/"; public static final String TAG_PREFIX = "tt9/";
public static int LEVEL = BuildConfig.DEBUG ? Log.DEBUG : Log.ERROR; public static int LEVEL = BuildConfig.DEBUG ? Log.DEBUG : Log.ERROR;

View file

@ -1,9 +1,11 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.util;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.Locale; import java.util.Locale;
import io.github.sspanak.tt9.languages.Language;
public class Text extends TextTools { public class Text extends TextTools {
private final Language language; private final Language language;
private final String text; private final String text;

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9.languages; package io.github.sspanak.tt9.util;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;

View file

@ -1,4 +1,4 @@
package io.github.sspanak.tt9; package io.github.sspanak.tt9.util;
import java.util.HashMap; import java.util.HashMap;