Word pair predictions (#616)
This commit is contained in:
parent
1300f8b517
commit
10497af44d
35 changed files with 667 additions and 131 deletions
|
|
@ -8,39 +8,45 @@ import androidx.annotation.NonNull;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.entities.AddWordResult;
|
||||
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
|
||||
import io.github.sspanak.tt9.db.wordPairs.WordPairStore;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.words.WordStore;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
||||
|
||||
public class WordStoreAsync {
|
||||
private static WordStore store;
|
||||
public class DataStore {
|
||||
private static final Handler asyncHandler = new Handler();
|
||||
private static WordPairStore pairs;
|
||||
private static WordStore words;
|
||||
|
||||
|
||||
public static void init(Context context) {
|
||||
store = new WordStore(context);
|
||||
pairs = pairs == null ? new WordPairStore(context) : pairs;
|
||||
words = words == null ? new WordStore(context) : words;
|
||||
}
|
||||
|
||||
|
||||
public static void destroy() {
|
||||
if (store != null) {
|
||||
store = null;
|
||||
}
|
||||
pairs = null;
|
||||
words = null;
|
||||
SQLiteOpener.destroyInstance();
|
||||
}
|
||||
|
||||
|
||||
public static void normalizeNext() {
|
||||
new Thread(() -> store.normalizeNext()).start();
|
||||
new Thread(() -> words.normalizeNext()).start();
|
||||
}
|
||||
|
||||
|
||||
public static void getLastLanguageUpdateTime(ConsumerCompat<String> notification, Language language) {
|
||||
new Thread(() -> notification.accept(store.getLanguageFileHash(language))).start();
|
||||
new Thread(() -> notification.accept(words.getLanguageFileHash(language))).start();
|
||||
}
|
||||
|
||||
|
||||
public static void deleteCustomWord(Runnable notification, Language language, String word) {
|
||||
new Thread(() -> {
|
||||
store.removeCustomWord(language, word);
|
||||
words.removeCustomWord(language, word);
|
||||
notification.run();
|
||||
}).start();
|
||||
}
|
||||
|
|
@ -48,46 +54,83 @@ public class WordStoreAsync {
|
|||
|
||||
public static void deleteWords(Runnable notification, @NonNull ArrayList<Integer> languageIds) {
|
||||
new Thread(() -> {
|
||||
store.remove(languageIds);
|
||||
words.remove(languageIds);
|
||||
notification.run();
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
public static void put(ConsumerCompat<AddWordResult> statusHandler, Language language, String word) {
|
||||
new Thread(() -> statusHandler.accept(store.put(language, word))).start();
|
||||
new Thread(() -> statusHandler.accept(words.put(language, word))).start();
|
||||
}
|
||||
|
||||
|
||||
public static void makeTopWord(@NonNull Language language, @NonNull String word, @NonNull String sequence) {
|
||||
new Thread(() -> store.makeTopWord(language, word, sequence)).start();
|
||||
new Thread(() -> words.makeTopWord(language, word, sequence)).start();
|
||||
}
|
||||
|
||||
|
||||
public static void getWords(ConsumerCompat<ArrayList<String>> dataHandler, Language language, String sequence, String filter, int minWords, int maxWords) {
|
||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||
store.getSimilar(language, sequence, filter, minWords, maxWords)))
|
||||
words.getSimilar(language, sequence, filter, minWords, maxWords)))
|
||||
).start();
|
||||
}
|
||||
|
||||
|
||||
public static void getCustomWords(ConsumerCompat<ArrayList<String>> dataHandler, String wordFilter, int maxWords) {
|
||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||
store.getSimilarCustom(wordFilter, maxWords)))
|
||||
words.getSimilarCustom(wordFilter, maxWords)))
|
||||
).start();
|
||||
}
|
||||
|
||||
|
||||
public static void countCustomWords(ConsumerCompat<Long> dataHandler) {
|
||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||
store.countCustom()))
|
||||
words.countCustom()))
|
||||
).start();
|
||||
}
|
||||
|
||||
|
||||
public static void exists(ConsumerCompat<ArrayList<Integer>> dataHandler, ArrayList<Language> languages) {
|
||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||
store.exists(languages))
|
||||
words.exists(languages))
|
||||
)).start();
|
||||
}
|
||||
|
||||
|
||||
public static void addWordPair(Language language, String word1, String word2, String sequence2) {
|
||||
pairs.add(language, word1, word2, sequence2);
|
||||
}
|
||||
|
||||
|
||||
public static String getWord2(Language language, String word1, String sequence2) {
|
||||
return pairs.getWord2(language, word1, sequence2);
|
||||
}
|
||||
|
||||
|
||||
public static void saveWordPairs() {
|
||||
new Thread(() -> pairs.save()).start();
|
||||
}
|
||||
|
||||
|
||||
public static void loadWordPairs(DictionaryLoader dictionaryLoader, ArrayList<Language> languages) {
|
||||
new Thread(() -> pairs.load(dictionaryLoader, languages)).start();
|
||||
}
|
||||
|
||||
|
||||
public static void clearWordPairCache() {
|
||||
pairs.clearCache();
|
||||
}
|
||||
|
||||
public static void deleteWordPairs(@NonNull ArrayList<Language> languages, @NonNull Runnable onDeleted) {
|
||||
new Thread(() -> {
|
||||
pairs.delete(languages);
|
||||
onDeleted.run();
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
public static String getWordPairStats() {
|
||||
return pairs.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -25,4 +25,8 @@ public class DeleteOps {
|
|||
|
||||
db.delete(Tables.CUSTOM_WORDS, "ROWID IN (" + repeatingWords + ")", null);
|
||||
}
|
||||
|
||||
public static void deleteWordPairs(@NonNull SQLiteDatabase db, int languageId) {
|
||||
db.delete(Tables.getWordPairs(languageId), null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ import android.database.sqlite.SQLiteStatement;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.github.sspanak.tt9.db.entities.Word;
|
||||
import io.github.sspanak.tt9.db.entities.WordPosition;
|
||||
import io.github.sspanak.tt9.db.wordPairs.WordPair;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
|
||||
|
||||
|
|
@ -77,4 +80,14 @@ public class InsertOps {
|
|||
"SELECT -id, word FROM " + Tables.CUSTOM_WORDS + " WHERE langId = " + language.getId()
|
||||
);
|
||||
}
|
||||
|
||||
public static void insertWordPairs(@NonNull SQLiteDatabase db, int langId, Collection<WordPair> pairs) {
|
||||
if (langId <= 0 || pairs == null || pairs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (WordPair pair : pairs) {
|
||||
db.insert(Tables.getWordPairs(langId), null, pair.toContentValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.SlowQueryStats;
|
||||
import io.github.sspanak.tt9.db.entities.NormalizationList;
|
||||
import io.github.sspanak.tt9.db.entities.WordList;
|
||||
import io.github.sspanak.tt9.db.entities.WordPositionsStringBuilder;
|
||||
import io.github.sspanak.tt9.db.wordPairs.WordPair;
|
||||
import io.github.sspanak.tt9.db.words.SlowQueryStats;
|
||||
import io.github.sspanak.tt9.languages.EmojiLanguage;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
|
|
@ -288,4 +289,23 @@ public class ReadOps {
|
|||
|
||||
return new NormalizationList(res);
|
||||
}
|
||||
|
||||
|
||||
@NonNull public ArrayList<WordPair> getWordPairs(@NonNull SQLiteDatabase db, @NonNull Language language, int limit) {
|
||||
ArrayList<WordPair> pairs = new ArrayList<>();
|
||||
|
||||
if (limit <= 0) {
|
||||
return pairs;
|
||||
}
|
||||
|
||||
String[] select = new String[]{"word1", "word2", "sequence2"};
|
||||
|
||||
try (Cursor cursor = db.query(Tables.getWordPairs(language.getId()), select, null, null, null, null, null, String.valueOf(limit))) {
|
||||
while (cursor.moveToNext()) {
|
||||
pairs.add(new WordPair(language, cursor.getString(0), cursor.getString(1), cursor.getString(2)));
|
||||
}
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import android.database.sqlite.SQLiteOpenHelper;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.BuildConfig;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
import io.github.sspanak.tt9.languages.EmojiLanguage;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
|
||||
public class SQLiteOpener extends SQLiteOpenHelper {
|
||||
private static final String LOG_TAG = SQLiteOpener.class.getSimpleName();
|
||||
|
|
@ -38,6 +38,19 @@ public class SQLiteOpener extends SQLiteOpenHelper {
|
|||
}
|
||||
|
||||
|
||||
public static void destroyInstance() {
|
||||
try {
|
||||
if (self != null) {
|
||||
self.close();
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
Logger.e(LOG_TAG, e.getMessage() + ". Ignoring database state and setting reference to NULL.");
|
||||
} finally {
|
||||
self = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
for (String query : Tables.getCreateQueries(allLanguages)) {
|
||||
|
|
|
|||
|
|
@ -14,14 +14,16 @@ public class Tables {
|
|||
static final String CUSTOM_WORDS = "custom_words";
|
||||
private static final String POSITIONS_TABLE_BASE_NAME = "word_positions_";
|
||||
private static final String WORDS_TABLE_BASE_NAME = "words_";
|
||||
private static final String WORD_PAIRS_TABLE_BASE_NAME = "word_pairs_";
|
||||
|
||||
static String getWords(int langId) { return WORDS_TABLE_BASE_NAME + langId; }
|
||||
static String getWordPositions(int langId) { return POSITIONS_TABLE_BASE_NAME + langId; }
|
||||
static String getWordPairs(int langId) { return WORD_PAIRS_TABLE_BASE_NAME + langId; }
|
||||
|
||||
|
||||
static String[] getCreateQueries(ArrayList<Language> languages) {
|
||||
int languageCount = languages.size();
|
||||
String[] queries = new String[languageCount * 2 + 3];
|
||||
String[] queries = new String[languageCount * 3 + 3];
|
||||
|
||||
queries[0] = createCustomWords();
|
||||
queries[1] = createCustomWordsIndex();
|
||||
|
|
@ -31,6 +33,7 @@ public class Tables {
|
|||
for (Language language : languages) {
|
||||
queries[queryId++] = createWordsTable(language.getId());
|
||||
queries[queryId++] = createWordPositions(language.getId());
|
||||
queries[queryId++] = createWordPairs(language.getId());
|
||||
}
|
||||
|
||||
return queries;
|
||||
|
|
@ -100,6 +103,14 @@ public class Tables {
|
|||
return "CREATE INDEX IF NOT EXISTS idx_langId_sequence ON " + CUSTOM_WORDS + " (langId, sequence)";
|
||||
}
|
||||
|
||||
private static String createWordPairs(int langId) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + getWordPairs(langId) + " (" +
|
||||
"word1 TEXT NOT NULL, " +
|
||||
"word2 TEXT NOT NULL, " +
|
||||
"sequence2 TEXT NOT NULL " +
|
||||
")";
|
||||
}
|
||||
|
||||
private static String createLanguagesMeta() {
|
||||
return "CREATE TABLE IF NOT EXISTS " + LANGUAGES_META + " (" +
|
||||
"langId INTEGER UNIQUE NOT NULL, " +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package io.github.sspanak.tt9.db.wordPairs;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.util.Text;
|
||||
|
||||
public class WordPair {
|
||||
private final Language language;
|
||||
@NonNull private final String word1;
|
||||
@NonNull private final String word2;
|
||||
private final String sequence2;
|
||||
private Integer hash = null;
|
||||
|
||||
|
||||
public WordPair(Language language, String word1, String word2, String sequence2) {
|
||||
this.language = language;
|
||||
this.word1 = word1 != null ? word1.toLowerCase(language.getLocale()) : "";
|
||||
this.word2 = word2 != null ? word2.toLowerCase(language.getLocale()) : "";
|
||||
this.sequence2 = sequence2;
|
||||
}
|
||||
|
||||
|
||||
boolean isInvalid() {
|
||||
return
|
||||
language == null
|
||||
|| word1.isEmpty() || word2.isEmpty()
|
||||
|| (word1.length() > SettingsStore.WORD_PAIR_MAX_WORD_LENGTH && word2.length() > SettingsStore.WORD_PAIR_MAX_WORD_LENGTH)
|
||||
|| word1.equals(word2)
|
||||
|| sequence2 == null || word2.length() != sequence2.length()
|
||||
|| !(new Text(word1).isAlphabetic()) || !(new Text(word2).isAlphabetic());
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public String getWord2() {
|
||||
return word2;
|
||||
}
|
||||
|
||||
|
||||
public ContentValues toContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("word1", word1);
|
||||
values.put("word2", word2);
|
||||
values.put("sequence2", sequence2);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hash == null) {
|
||||
hash = !word1.isEmpty() && sequence2 != null ? (word1 + "," + sequence2).hashCode() : 0;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
return obj instanceof WordPair && obj.hashCode() == hashCode();
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + word1 + "," + word2 + "," + sequence2 + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
package io.github.sspanak.tt9.db.wordPairs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.github.sspanak.tt9.db.sqlite.DeleteOps;
|
||||
import io.github.sspanak.tt9.db.sqlite.InsertOps;
|
||||
import io.github.sspanak.tt9.db.sqlite.ReadOps;
|
||||
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.util.Logger;
|
||||
import io.github.sspanak.tt9.util.Timer;
|
||||
|
||||
public class WordPairStore {
|
||||
// data
|
||||
private final SQLiteOpener sqlite;
|
||||
private final ConcurrentHashMap<Integer, HashMap<WordPair, WordPair>> pairs = new ConcurrentHashMap<>();
|
||||
|
||||
// timing
|
||||
private long slowestAddTime = 0;
|
||||
private long slowestLoadTime = 0;
|
||||
private long slowestSaveTime = 0;
|
||||
private long slowestSearchTime = 0;
|
||||
|
||||
|
||||
public WordPairStore(Context context) {
|
||||
sqlite = SQLiteOpener.getInstance(context);
|
||||
}
|
||||
|
||||
|
||||
public void add(Language language, String word1, String word2, String sequence2) {
|
||||
String ADD_TIMER_NAME = "word_pair_add";
|
||||
Timer.start(ADD_TIMER_NAME);
|
||||
|
||||
WordPair pair = new WordPair(language, word1, word2, sequence2);
|
||||
if (pair.isInvalid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<WordPair, WordPair> languagePairs = pairs.get(language.getId());
|
||||
if (languagePairs == null) {
|
||||
languagePairs = new HashMap<>();
|
||||
pairs.put(language.getId(), languagePairs);
|
||||
}
|
||||
|
||||
if (languagePairs.size() >= SettingsStore.WORD_PAIR_MAX) {
|
||||
languagePairs.remove(languagePairs.keySet().iterator().next());
|
||||
}
|
||||
|
||||
languagePairs.put(pair, pair);
|
||||
|
||||
slowestAddTime = Math.max(slowestAddTime, Timer.stop(ADD_TIMER_NAME));
|
||||
}
|
||||
|
||||
|
||||
public void clearCache() {
|
||||
pairs.clear();
|
||||
slowestAddTime = 0;
|
||||
slowestSearchTime = 0;
|
||||
slowestSaveTime = 0;
|
||||
slowestLoadTime = 0;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public String getWord2(Language language, String word1, String sequence2) {
|
||||
String SEARCH_TIMER_NAME = "word_pair_search";
|
||||
Timer.start(SEARCH_TIMER_NAME);
|
||||
|
||||
HashMap<WordPair, WordPair> languagePairs = pairs.get(language.getId());
|
||||
|
||||
if (languagePairs == null) {
|
||||
slowestSearchTime = Math.max(slowestSearchTime, Timer.stop(SEARCH_TIMER_NAME));
|
||||
return null;
|
||||
}
|
||||
|
||||
WordPair pair = languagePairs.get(new WordPair(language, word1, null, sequence2));
|
||||
String word2 = pair == null || pair.getWord2().isEmpty() ? null : pair.getWord2();
|
||||
|
||||
slowestSearchTime = Math.max(slowestSearchTime, Timer.stop(SEARCH_TIMER_NAME));
|
||||
return word2;
|
||||
}
|
||||
|
||||
|
||||
public void save() {
|
||||
String SAVE_TIMER_NAME = "word_pair_save";
|
||||
Timer.start(SAVE_TIMER_NAME);
|
||||
|
||||
for (Map.Entry<Integer, HashMap<WordPair, WordPair>> entry : pairs.entrySet()) {
|
||||
int langId = entry.getKey();
|
||||
HashMap<WordPair, WordPair> languagePairs = entry.getValue();
|
||||
|
||||
sqlite.beginTransaction();
|
||||
DeleteOps.deleteWordPairs(sqlite.getDb(), langId);
|
||||
InsertOps.insertWordPairs(sqlite.getDb(), langId, languagePairs.values());
|
||||
sqlite.finishTransaction();
|
||||
}
|
||||
|
||||
|
||||
long currentTime = Timer.stop(SAVE_TIMER_NAME);
|
||||
slowestSaveTime = Math.max(slowestSaveTime, currentTime);
|
||||
Logger.d(getClass().getSimpleName(), "Saved all word pairs in: " + currentTime + " ms");
|
||||
}
|
||||
|
||||
|
||||
public void load(@NonNull DictionaryLoader dictionaryLoader, ArrayList<Language> languages) {
|
||||
if (dictionaryLoader.isRunning()) {
|
||||
Logger.e(getClass().getSimpleName(), "Cannot load word pairs while the DictionaryLoader is working.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (languages == null) {
|
||||
Logger.e(getClass().getSimpleName(), "Cannot load word pairs for NULL language list.");
|
||||
return;
|
||||
}
|
||||
|
||||
String LOAD_TIMER_NAME = "word_pair_load";
|
||||
Timer.start(LOAD_TIMER_NAME);
|
||||
|
||||
int totalPairs = 0;
|
||||
for (Language language : languages) {
|
||||
HashMap<WordPair, WordPair> wordPairs = pairs.get(language.getId());
|
||||
if (wordPairs == null) {
|
||||
wordPairs = new HashMap<>();
|
||||
pairs.put(language.getId(), wordPairs);
|
||||
} else if (!wordPairs.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int max = SettingsStore.WORD_PAIR_MAX - wordPairs.size();
|
||||
ArrayList<WordPair> dbPairs = new ReadOps().getWordPairs(sqlite.getDb(), language, max);
|
||||
for (WordPair pair : dbPairs) {
|
||||
wordPairs.put(pair, pair);
|
||||
}
|
||||
|
||||
Logger.d(getClass().getSimpleName(), "Loaded " + wordPairs.size() + " word pairs for language: " + language.getId());
|
||||
}
|
||||
|
||||
long currentTime = Timer.stop(LOAD_TIMER_NAME);
|
||||
slowestLoadTime = Math.max(slowestLoadTime, currentTime);
|
||||
Logger.d(getClass().getSimpleName(), "Loaded " + totalPairs + " word pairs in " + currentTime + " ms");
|
||||
}
|
||||
|
||||
|
||||
public void delete(@NonNull ArrayList<Language> languages) {
|
||||
sqlite.beginTransaction();
|
||||
for (Language language : languages) {
|
||||
DeleteOps.deleteWordPairs(sqlite.getDb(), language.getId());
|
||||
}
|
||||
sqlite.finishTransaction();
|
||||
|
||||
slowestLoadTime = 0;
|
||||
slowestSaveTime = 0;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (Map.Entry<Integer, HashMap<WordPair, WordPair>> entry : pairs.entrySet()) {
|
||||
int langId = entry.getKey();
|
||||
HashMap<WordPair, WordPair> languagePairs = entry.getValue();
|
||||
|
||||
sb.append("Language ").append(langId).append(": ");
|
||||
sb.append(languagePairs.size()).append("\n");
|
||||
}
|
||||
|
||||
if (sb.length() == 0) {
|
||||
sb.append("No word pairs.\n");
|
||||
} else {
|
||||
sb.append("\nSlowest add-one: ").append(slowestAddTime).append(" ms\n");
|
||||
sb.append("Slowest search-one: ").append(slowestSearchTime).append(" ms\n");
|
||||
sb.append("Slowest save-all: ").append(slowestSaveTime).append(" ms\n");
|
||||
sb.append("Slowest load-all: ").append(slowestLoadTime).append(" ms\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
package io.github.sspanak.tt9.db.words;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
|
@ -11,6 +11,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.entities.WordBatch;
|
||||
import io.github.sspanak.tt9.db.entities.WordFile;
|
||||
import io.github.sspanak.tt9.db.exceptions.DictionaryImportAbortedException;
|
||||
|
|
@ -122,7 +123,7 @@ public class DictionaryLoader {
|
|||
return false;
|
||||
}
|
||||
|
||||
WordStoreAsync.getLastLanguageUpdateTime(
|
||||
DataStore.getLastLanguageUpdateTime(
|
||||
(hash) -> {
|
||||
self.lastAutoLoadAttemptTime.put(language.getId(), System.currentTimeMillis());
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
package io.github.sspanak.tt9.db.words;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
package io.github.sspanak.tt9.db.words;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
package io.github.sspanak.tt9.db.words;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ public class WordStore {
|
|||
private ReadOps readOps = null;
|
||||
|
||||
|
||||
WordStore(@NonNull Context context) {
|
||||
public WordStore(@NonNull Context context) {
|
||||
try {
|
||||
sqlite = SQLiteOpener.getInstance(context);
|
||||
sqlite.getDb();
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||
import io.github.sspanak.tt9.ime.modes.ModeABC;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
|
|
@ -95,7 +95,7 @@ abstract public class CommandHandler extends TextEditingHandler {
|
|||
if (word.isEmpty()) {
|
||||
UI.toastLong(this, R.string.add_word_no_selection);
|
||||
} else if (settings.getAddWordsNoConfirmation()) {
|
||||
WordStoreAsync.put((res) -> UI.toastLongFromAsync(this, res.toHumanFriendlyString(this)), mLanguage, word);
|
||||
DataStore.put((res) -> UI.toastLongFromAsync(this, res.toHumanFriendlyString(this)), mLanguage, word);
|
||||
} else {
|
||||
AddWordDialog.show(this, mLanguage.getId(), word);
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ abstract public class CommandHandler extends TextEditingHandler {
|
|||
if (mInputMode.isPassthrough() || voiceInputOps.isListening()) {
|
||||
return;
|
||||
} else if (allowedInputModes.size() == 1 && allowedInputModes.contains(InputMode.MODE_123)) {
|
||||
mInputMode = !mInputMode.is123() ? InputMode.getInstance(settings, mLanguage, inputType, InputMode.MODE_123) : mInputMode;
|
||||
mInputMode = !mInputMode.is123() ? InputMode.getInstance(settings, mLanguage, inputType, textField, InputMode.MODE_123) : mInputMode;
|
||||
}
|
||||
// when typing a word or viewing scrolling the suggestions, only change the case
|
||||
else if (!suggestionOps.isEmpty()) {
|
||||
|
|
@ -124,7 +124,7 @@ abstract public class CommandHandler extends TextEditingHandler {
|
|||
mInputMode.nextTextCase();
|
||||
} else {
|
||||
int nextModeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size();
|
||||
mInputMode = InputMode.getInstance(settings, mLanguage, inputType, allowedInputModes.get(nextModeIndex));
|
||||
mInputMode = InputMode.getInstance(settings, mLanguage, inputType, textField, allowedInputModes.get(nextModeIndex));
|
||||
mInputMode.setTextFieldCase(inputType.determineTextCase());
|
||||
mInputMode.determineNextWordTextCase(textField.getStringBeforeCursor());
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package io.github.sspanak.tt9.ime;
|
|||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.ime.helpers.TextField;
|
||||
import io.github.sspanak.tt9.ime.modes.ModePredictive;
|
||||
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ import android.view.inputmethod.InputConnection;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.hacks.InputType;
|
||||
import io.github.sspanak.tt9.ime.modes.ModePredictive;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
import io.github.sspanak.tt9.ui.dialogs.PopupDialog;
|
||||
|
|
@ -121,7 +122,7 @@ public class TraditionalT9 extends MainViewHandler {
|
|||
protected void onInit() {
|
||||
settings.setDemoMode(false);
|
||||
Logger.setLevel(settings.getLogLevel());
|
||||
WordStoreAsync.init(this);
|
||||
DataStore.init(this);
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +144,13 @@ public class TraditionalT9 extends MainViewHandler {
|
|||
initUi();
|
||||
}
|
||||
|
||||
if (new InputType(connection, field).isNotUs(this)) {
|
||||
InputType newInputType = new InputType(connection, field);
|
||||
|
||||
if (newInputType.isText()) {
|
||||
DataStore.loadWordPairs(DictionaryLoader.getInstance(this), LanguageCollection.getAll(this, settings.getEnabledLanguageIds()));
|
||||
}
|
||||
|
||||
if (newInputType.isNotUs(this)) {
|
||||
DictionaryLoader.autoLoad(this, mLanguage);
|
||||
}
|
||||
|
||||
|
|
@ -165,8 +172,11 @@ public class TraditionalT9 extends MainViewHandler {
|
|||
|
||||
normalizationHandler.removeCallbacksAndMessages(null);
|
||||
normalizationHandler.postDelayed(
|
||||
() -> { if (!DictionaryLoader.getInstance(this).isRunning()) WordStoreAsync.normalizeNext(); },
|
||||
SettingsStore.WORD_NORMALIZATION_DELAY
|
||||
() -> {
|
||||
DataStore.saveWordPairs();
|
||||
if (!DictionaryLoader.getInstance(this).isRunning()) DataStore.normalizeNext();
|
||||
},
|
||||
SettingsStore.WORD_BACKGROUND_TASKS_DELAY
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +198,7 @@ public class TraditionalT9 extends MainViewHandler {
|
|||
requestHideSelf(0);
|
||||
onStop();
|
||||
normalizationHandler.removeCallbacksAndMessages(null);
|
||||
WordStoreAsync.destroy();
|
||||
DataStore.destroy();
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.hacks.AppHacks;
|
||||
import io.github.sspanak.tt9.hacks.InputType;
|
||||
import io.github.sspanak.tt9.ime.helpers.CursorOps;
|
||||
|
|
@ -35,7 +35,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
|||
// input
|
||||
protected ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
||||
@NonNull
|
||||
protected InputMode mInputMode = InputMode.getInstance(null, null, null, InputMode.MODE_PASSTHROUGH);
|
||||
protected InputMode mInputMode = InputMode.getInstance(null, null, null, null, InputMode.MODE_PASSTHROUGH);
|
||||
|
||||
// language
|
||||
protected ArrayList<Integer> mEnabledLanguages;
|
||||
|
|
@ -103,7 +103,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
|||
|
||||
protected void onFinishTyping() {
|
||||
suggestionOps.cancelDelayedAccept();
|
||||
mInputMode = InputMode.getInstance(null, null, null, InputMode.MODE_PASSTHROUGH);
|
||||
mInputMode = InputMode.getInstance(null, null, null, null, InputMode.MODE_PASSTHROUGH);
|
||||
setInputField(null, null);
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
|||
}
|
||||
|
||||
if (settings.getBackspaceRecomposing() && !hold && suggestionOps.isEmpty()) {
|
||||
final String previousWord = textField.getWordBeforeCursor(mLanguage);
|
||||
final String previousWord = textField.getWordBeforeCursor(mLanguage, 0, false);
|
||||
if (mInputMode.recompose(previousWord) && textField.recompose(previousWord)) {
|
||||
getSuggestions();
|
||||
} else {
|
||||
|
|
@ -283,7 +283,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
|||
* Same as getInputModeId(), but returns an actual InputMode.
|
||||
*/
|
||||
protected InputMode getInputMode() {
|
||||
return InputMode.getInstance(settings, mLanguage, inputType, getInputModeId());
|
||||
return InputMode.getInstance(settings, mLanguage, inputType, textField, getInputModeId());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,13 @@ public class TextField extends InputField {
|
|||
}
|
||||
|
||||
|
||||
@NonNull public String getWordBeforeCursor(Language language) {
|
||||
/**
|
||||
* Returns a word (String containing of alphabetic) characters before the cursor, only if the cursor is
|
||||
* not in the middle of that word. "skipWords" can be used to return the N-th word before the cursor.
|
||||
* "stopAtPunctuation" can be used to stop searching at the first punctuation character. In case
|
||||
* no complete word is found due to any reason, an empty string is returned.
|
||||
*/
|
||||
@NonNull public String getWordBeforeCursor(Language language, int skipWords, boolean stopAtPunctuation) {
|
||||
if (getTextAfterCursor(1).startsWithWord()) {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -117,17 +123,28 @@ public class TextField extends InputField {
|
|||
return "";
|
||||
}
|
||||
|
||||
int endIndex = before.length();
|
||||
|
||||
for (int i = before.length() - 1; i >= 0; i--) {
|
||||
char currentLetter = before.charAt(i);
|
||||
|
||||
if (stopAtPunctuation && language.getKeyCharacters(1).contains(currentLetter + "")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (
|
||||
!Character.isAlphabetic(currentLetter)
|
||||
&& !(currentLetter == '\'' && (LanguageKind.isHebrew(language) || LanguageKind.isUkrainian(language)))
|
||||
) {
|
||||
return before.substring(i + 1);
|
||||
if (skipWords-- <= 0 || i == 0) {
|
||||
return before.substring(i + 1, endIndex);
|
||||
} else {
|
||||
endIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return before;
|
||||
return endIndex == before.length() ? before : before.substring(0, endIndex);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ abstract public class InputMode {
|
|||
protected int specialCharSelectedGroup = 0;
|
||||
|
||||
|
||||
public static InputMode getInstance(SettingsStore settings, Language language, InputType inputType, int mode) {
|
||||
public static InputMode getInstance(SettingsStore settings, Language language, InputType inputType, TextField textField, int mode) {
|
||||
switch (mode) {
|
||||
case MODE_PREDICTIVE:
|
||||
return new ModePredictive(settings, inputType, language);
|
||||
return new ModePredictive(settings, inputType, textField, language);
|
||||
case MODE_ABC:
|
||||
return new ModeABC(settings, inputType, language);
|
||||
case MODE_PASSTHROUGH:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.hacks.InputType;
|
||||
import io.github.sspanak.tt9.ime.helpers.TextField;
|
||||
import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace;
|
||||
|
|
@ -46,13 +46,13 @@ public class ModePredictive extends InputMode {
|
|||
private boolean isCursorDirectionForward = false;
|
||||
|
||||
|
||||
ModePredictive(SettingsStore settings, InputType inputType, Language lang) {
|
||||
ModePredictive(SettingsStore settings, InputType inputType, TextField textField, Language lang) {
|
||||
changeLanguage(lang);
|
||||
defaultTextCase();
|
||||
|
||||
autoSpace = new AutoSpace(settings);
|
||||
autoTextCase = new AutoTextCase(settings);
|
||||
predictions = new Predictions();
|
||||
predictions = new Predictions(settings, textField);
|
||||
|
||||
this.settings = settings;
|
||||
|
||||
|
|
@ -365,6 +365,7 @@ public class ModePredictive extends InputMode {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
// increment the frequency of the given word
|
||||
try {
|
||||
Language workingLanguage = TextTools.isGraphic(currentWord) ? new EmojiLanguage() : language;
|
||||
|
|
@ -373,14 +374,13 @@ public class ModePredictive extends InputMode {
|
|||
// punctuation and special chars are not in the database, so there is no point in
|
||||
// running queries that would update nothing
|
||||
if (!sequence.equals(NaturalLanguage.PUNCTUATION_KEY) && !sequence.startsWith(NaturalLanguage.SPECIAL_CHARS_KEY)) {
|
||||
WordStoreAsync.makeTopWord(workingLanguage, currentWord, sequence);
|
||||
predictions.onAccept(currentWord, sequence);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.e(LOG_TAG, "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String adjustSuggestionTextCase(String word, int newTextCase) {
|
||||
return autoTextCase.adjustSuggestionTextCase(new Text(language, word), newTextCase);
|
||||
|
|
|
|||
|
|
@ -2,19 +2,24 @@ package io.github.sspanak.tt9.ime.modes.helpers;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.ime.helpers.TextField;
|
||||
import io.github.sspanak.tt9.languages.EmojiLanguage;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.util.Characters;
|
||||
|
||||
public class Predictions {
|
||||
private final SettingsStore settings;
|
||||
private final TextField textField;
|
||||
|
||||
private Language language;
|
||||
private String digitSequence;
|
||||
private boolean isStemFuzzy;
|
||||
private String stem;
|
||||
private String inputWord;
|
||||
private boolean isStemFuzzy;
|
||||
private Language language;
|
||||
private String stem;
|
||||
|
||||
private String lastEnforcedTopWord = "";
|
||||
|
||||
// async operations
|
||||
private Runnable onWordsChanged = () -> {};
|
||||
|
|
@ -24,6 +29,11 @@ public class Predictions {
|
|||
private boolean containsGeneratedWords = false;
|
||||
private ArrayList<String> words = new ArrayList<>();
|
||||
|
||||
public Predictions(SettingsStore settings, TextField textField) {
|
||||
this.settings = settings;
|
||||
this.textField = textField;
|
||||
}
|
||||
|
||||
|
||||
public Predictions setLanguage(Language language) {
|
||||
this.language = language;
|
||||
|
|
@ -68,31 +78,6 @@ public class Predictions {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* suggestStem
|
||||
* Add the current stem filter to the predictions list, when it has length of X and
|
||||
* the user has pressed X keys (otherwise, it makes no sense to add it).
|
||||
*/
|
||||
private void suggestStem() {
|
||||
if (!stem.isEmpty() && stem.length() == digitSequence.length()) {
|
||||
words.add(stem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* suggestMissingWords
|
||||
* Takes a list of words and appends them to the words list, if they are missing.
|
||||
*/
|
||||
private void suggestMissingWords(ArrayList<String> newWords) {
|
||||
for (String newWord : newWords) {
|
||||
if (!words.contains(newWord) && !words.contains(newWord.toLowerCase(language.getLocale()))) {
|
||||
words.add(newWord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* load
|
||||
* Queries the dictionary database for a list of words matching the current language and
|
||||
|
|
@ -109,7 +94,7 @@ public class Predictions {
|
|||
|
||||
boolean retryAllowed = !digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE);
|
||||
|
||||
WordStoreAsync.getWords(
|
||||
DataStore.getWords(
|
||||
(dbWords) -> onDbWords(dbWords, retryAllowed),
|
||||
language,
|
||||
digitSequence,
|
||||
|
|
@ -121,7 +106,7 @@ public class Predictions {
|
|||
}
|
||||
|
||||
private void loadWithoutLeadingPunctuation() {
|
||||
WordStoreAsync.getWords(
|
||||
DataStore.getWords(
|
||||
(dbWords) -> {
|
||||
char firstChar = inputWord.charAt(0);
|
||||
for (int i = 0; i < dbWords.size(); i++) {
|
||||
|
|
@ -160,6 +145,7 @@ public class Predictions {
|
|||
words.addAll(dbWords);
|
||||
} else {
|
||||
suggestStem();
|
||||
dbWords = rearrangeByPairFrequency(dbWords);
|
||||
suggestMissingWords(generatePossibleStemVariations(dbWords));
|
||||
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords);
|
||||
words = insertPunctuationCompletions(words);
|
||||
|
|
@ -169,6 +155,31 @@ public class Predictions {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* suggestStem
|
||||
* Add the current stem filter to the predictions list, when it has length of X and
|
||||
* the user has pressed X keys (otherwise, it makes no sense to add it).
|
||||
*/
|
||||
private void suggestStem() {
|
||||
if (!stem.isEmpty() && stem.length() == digitSequence.length()) {
|
||||
words.add(stem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* suggestMissingWords
|
||||
* Takes a list of words and appends them to the words list, if they are missing.
|
||||
*/
|
||||
private void suggestMissingWords(ArrayList<String> newWords) {
|
||||
for (String newWord : newWords) {
|
||||
if (!words.contains(newWord) && !words.contains(newWord.toLowerCase(language.getLocale()))) {
|
||||
words.add(newWord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* generateWordVariations
|
||||
* When there are no matching suggestions after the last key press, generate a list of possible
|
||||
|
|
@ -288,4 +299,69 @@ public class Predictions {
|
|||
containsGeneratedWords = !variations.isEmpty();
|
||||
return variations;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* onAccept
|
||||
* This stores common word pairs, so they can be used in "rearrangeByPairFrequency()" method.
|
||||
* For example, if the user types "I am an apple", the word "am" will be suggested after "I",
|
||||
* and "an" after "am", even if "am" frequency was boosted right before typing "an". This both
|
||||
* prevents from suggesting the same word twice in row and makes the suggestions more intuitive
|
||||
* when there are many textonyms for a single sequence.
|
||||
*/
|
||||
public void onAccept(String word, String sequence) {
|
||||
if (
|
||||
!settings.getPredictWordPairs()
|
||||
// If the accepted word is longer than the sequence, it is some different word, not a textonym
|
||||
// of the fist suggestion. We don't need to store it.
|
||||
|| word == null || digitSequence == null
|
||||
|| word.length() != digitSequence.length()
|
||||
// If the word is the first suggestion, we have already guessed it right, and it makes no
|
||||
// sense to store it as a popular pair.
|
||||
|| (!words.isEmpty() && words.get(0).equals(word))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataStore.addWordPair(language, textField.getWordBeforeCursor(language, 1, true), word, sequence);
|
||||
if (!word.equals(lastEnforcedTopWord)) {
|
||||
DataStore.makeTopWord(language, word, sequence);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* rearrangeByPairFrequency
|
||||
* Uses the last two words in the text field to rearrange the suggestions, so that the most popular
|
||||
* one in a pair comes first. This is useful for typing phrases, like "I am an apple". Since, in
|
||||
* "onAccept()", we have remembered the "am" comes after "I" and "an" comes after "am", we will
|
||||
* not suggest the textonyms "am" or "an" twice (depending on which has the highest frequency).
|
||||
*/
|
||||
private ArrayList<String> rearrangeByPairFrequency(ArrayList<String> words) {
|
||||
lastEnforcedTopWord = "";
|
||||
|
||||
if (!settings.getPredictWordPairs() || words.size() < 2) {
|
||||
return words;
|
||||
}
|
||||
|
||||
ArrayList<String> rearrangedWords = new ArrayList<>();
|
||||
String penultimateWord = textField.getWordBeforeCursor(language, 1, true);
|
||||
|
||||
String word = DataStore.getWord2(language, penultimateWord, digitSequence);
|
||||
int morePopularIndex = word == null ? -1 : words.indexOf(word);
|
||||
if (morePopularIndex == -1) {
|
||||
return words;
|
||||
}
|
||||
|
||||
lastEnforcedTopWord = word;
|
||||
rearrangedWords.add(word);
|
||||
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
if (i != morePopularIndex) {
|
||||
rearrangedWords.add(words.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return rearrangedWords;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import androidx.preference.Preference;
|
|||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.LegacyDb;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.words.LegacyDb;
|
||||
import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
|
||||
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
||||
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
|
||||
|
|
@ -43,7 +43,7 @@ public class PreferencesActivity extends ActivityWithNavigation implements Prefe
|
|||
Logger.setLevel(settings.getLogLevel());
|
||||
|
||||
try (LegacyDb db = new LegacyDb(this)) { db.clear(); }
|
||||
WordStoreAsync.init(this);
|
||||
DataStore.init(this);
|
||||
|
||||
InputModeValidator.validateEnabledLanguages(this, settings.getEnabledLanguageIds());
|
||||
validateFunctionKeys();
|
||||
|
|
|
|||
|
|
@ -3,14 +3,21 @@ package io.github.sspanak.tt9.preferences.screens;
|
|||
import androidx.preference.Preference;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.SlowQueryStats;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.words.SlowQueryStats;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||
import io.github.sspanak.tt9.preferences.items.ItemText;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
public class UsageStatsScreen extends BaseScreenFragment {
|
||||
final public static String NAME = "UsageStats";
|
||||
final private static String RESET_BUTTON = "pref_slow_queries_reset_stats";
|
||||
final private static String SUMMARY_CONTAINER = "summary_container";
|
||||
final private static String RESET_SLOW_QUERIES_BUTTON = "slow_queries_clear_cache";
|
||||
final private static String RESET_WORD_PAIRS_CACHE_BUTTON = "word_pair_clear_cache";
|
||||
final private static String RESET_WORD_PAIRS_DB_BUTTON = "word_pair_clear_db";
|
||||
|
||||
final private static String SLOW_QUERY_STATS_CONTAINER = "summary_container";
|
||||
final private static String WORD_PAIRS_CONTAINER = "word_pairs_container";
|
||||
private ItemText queryListContainer;
|
||||
|
||||
public UsageStatsScreen() { init(); }
|
||||
|
|
@ -22,26 +29,32 @@ public class UsageStatsScreen extends BaseScreenFragment {
|
|||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
printSummary();
|
||||
print(SLOW_QUERY_STATS_CONTAINER, SlowQueryStats.getSummary());
|
||||
print(WORD_PAIRS_CONTAINER, DataStore.getWordPairStats());
|
||||
printSlowQueries();
|
||||
|
||||
Preference resetButton = findPreference(RESET_BUTTON);
|
||||
if (resetButton != null) {
|
||||
resetButton.setOnPreferenceClickListener((Preference p) -> {
|
||||
SlowQueryStats.clear();
|
||||
printSummary();
|
||||
printSlowQueries();
|
||||
return true;
|
||||
});
|
||||
Preference slowQueriesButton = findPreference(RESET_SLOW_QUERIES_BUTTON);
|
||||
if (slowQueriesButton != null) {
|
||||
slowQueriesButton.setOnPreferenceClickListener(this::resetSlowQueries);
|
||||
}
|
||||
|
||||
Preference wordPairsCacheButton = findPreference(RESET_WORD_PAIRS_CACHE_BUTTON);
|
||||
if (wordPairsCacheButton != null) {
|
||||
wordPairsCacheButton.setOnPreferenceClickListener(this::resetWordPairsCache);
|
||||
}
|
||||
|
||||
Preference wordPairsDbButton = findPreference(RESET_WORD_PAIRS_DB_BUTTON);
|
||||
if (wordPairsDbButton != null) {
|
||||
wordPairsDbButton.setOnPreferenceClickListener(this::deleteWordPairs);
|
||||
}
|
||||
|
||||
resetFontSize(false);
|
||||
}
|
||||
|
||||
private void printSummary() {
|
||||
Preference logsContainer = findPreference(SUMMARY_CONTAINER);
|
||||
if (logsContainer != null) {
|
||||
logsContainer.setSummary(SlowQueryStats.getSummary());
|
||||
private void print(String containerName, String text) {
|
||||
Preference container = findPreference(containerName);
|
||||
if (container != null) {
|
||||
container.setSummary(text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,4 +67,26 @@ public class UsageStatsScreen extends BaseScreenFragment {
|
|||
String slowQueries = SlowQueryStats.getList();
|
||||
queryListContainer.populate(slowQueries.isEmpty() ? "No slow queries." : slowQueries);
|
||||
}
|
||||
|
||||
private boolean resetSlowQueries(Preference ignored) {
|
||||
SlowQueryStats.clear();
|
||||
print(SLOW_QUERY_STATS_CONTAINER, SlowQueryStats.getSummary());
|
||||
printSlowQueries();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean resetWordPairsCache(Preference ignored) {
|
||||
DataStore.clearWordPairCache();
|
||||
print(WORD_PAIRS_CONTAINER, DataStore.getWordPairStats());
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean deleteWordPairs(Preference ignored) {
|
||||
DataStore.deleteWordPairs(
|
||||
LanguageCollection.getAll(activity),
|
||||
() -> UI.toastLongFromAsync(activity, "Word pairs deleted. You must reopen the screen manually.")
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
|
|||
import androidx.preference.PreferenceCategory;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.custom.ScreenPreference;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
|
|
@ -62,7 +62,7 @@ public class PreferenceDeletableWord extends ScreenPreference {
|
|||
|
||||
private void onDeletionConfirmed() {
|
||||
SettingsStore settings = new SettingsStore(getContext());
|
||||
WordStoreAsync.deleteCustomWord(
|
||||
DataStore.deleteCustomWord(
|
||||
this::onWordDeleted,
|
||||
LanguageCollection.getLanguage(getContext(), settings.getInputLanguage()),
|
||||
word
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.preferences.items.ItemTextInput;
|
||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
||||
|
|
@ -49,11 +49,11 @@ public class PreferenceSearchWords extends ItemTextInput {
|
|||
if (onWords == null) {
|
||||
Logger.w(LOG_TAG, "No handler set for the word change event.");
|
||||
} else if (lastSearchTerm.isEmpty()) {
|
||||
WordStoreAsync.countCustomWords(onTotalWords);
|
||||
WordStoreAsync.getCustomWords(onWords, lastSearchTerm, SettingsStore.CUSTOM_WORDS_SEARCH_RESULTS_MAX);
|
||||
DataStore.countCustomWords(onTotalWords);
|
||||
DataStore.getCustomWords(onWords, lastSearchTerm, SettingsStore.CUSTOM_WORDS_SEARCH_RESULTS_MAX);
|
||||
} else {
|
||||
WordStoreAsync.countCustomWords(onTotalWords);
|
||||
WordStoreAsync.getCustomWords(onWords, lastSearchTerm, -1);
|
||||
DataStore.countCustomWords(onTotalWords);
|
||||
DataStore.getCustomWords(onWords, lastSearchTerm, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import androidx.preference.PreferenceCategory;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.languages.NaturalLanguage;
|
||||
|
|
@ -49,7 +49,7 @@ public class LanguageSelectionScreen extends BaseScreenFragment {
|
|||
|
||||
addLanguagesToCategory(languagesCategory, allLanguages);
|
||||
if (!DictionaryLoader.getInstance(activity).isRunning()) {
|
||||
WordStoreAsync.exists(this::addLoadedStatus, allLanguages);
|
||||
DataStore.exists(this::addLoadedStatus, allLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.preference.Preference;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import androidx.preference.Preference;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||
|
|
@ -36,7 +36,7 @@ class ItemTruncateAll extends ItemClickable {
|
|||
for (Language lang : LanguageCollection.getAll(activity, false)) {
|
||||
languageIds.add(lang.getId());
|
||||
}
|
||||
WordStoreAsync.deleteWords(this::onFinishDeleting, languageIds);
|
||||
DataStore.deleteWords(this::onFinishDeleting, languageIds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.preference.Preference;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||
|
|
@ -30,7 +30,7 @@ class ItemTruncateUnselected extends ItemTruncateAll {
|
|||
}
|
||||
|
||||
onStartDeleting();
|
||||
WordStoreAsync.deleteWords(this::onFinishDeleting, unselectedLanguageIds);
|
||||
DataStore.deleteWords(this::onFinishDeleting, unselectedLanguageIds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.customWords.CustomWordsExporter;
|
||||
import io.github.sspanak.tt9.db.customWords.CustomWordsImporter;
|
||||
import io.github.sspanak.tt9.db.customWords.DictionaryExporter;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||
import io.github.sspanak.tt9.preferences.items.ItemClickable;
|
||||
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
|
||||
|
|
|
|||
|
|
@ -32,9 +32,11 @@ public class SettingsStore extends SettingsUI {
|
|||
public final static int SUGGESTIONS_SELECT_ANIMATION_DURATION = 66;
|
||||
public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0;
|
||||
public final static int TEXT_INPUT_DEBOUNCE_TIME = 500; // ms
|
||||
public final static int WORD_BACKGROUND_TASKS_DELAY = 15000; // ms
|
||||
public final static int WORD_FREQUENCY_MAX = 25500;
|
||||
public final static int WORD_FREQUENCY_NORMALIZATION_DIVIDER = 100; // normalized frequency = WORD_FREQUENCY_MAX / WORD_FREQUENCY_NORMALIZATION_DIVIDER
|
||||
public final static int WORD_NORMALIZATION_DELAY = 15000; // ms
|
||||
public final static int WORD_PAIR_MAX = 1000;
|
||||
public final static int WORD_PAIR_MAX_WORD_LENGTH = 6;
|
||||
public final static int ZOMBIE_CHECK_INTERVAL = 666; // ms
|
||||
|
||||
/************* hacks *************/
|
||||
|
|
|
|||
|
|
@ -29,5 +29,10 @@ class SettingsTyping extends SettingsInput {
|
|||
// SharedPreferences return a corrupted string when using the real "\n"... :(
|
||||
return character.equals("\\n") ? "\n" : character;
|
||||
}
|
||||
|
||||
public boolean getPredictWordPairs() {
|
||||
return prefs.getBoolean("pref_predict_word_pairs", true);
|
||||
}
|
||||
|
||||
public boolean getUpsideDownKeys() { return prefs.getBoolean("pref_upside_down_keys", false); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import android.inputmethodservice.InputMethodService;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.db.DataStore;
|
||||
import io.github.sspanak.tt9.db.entities.AddWordResult;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
|
|
@ -47,7 +47,7 @@ public class AddWordDialog extends PopupDialog {
|
|||
|
||||
private void onOK() {
|
||||
if (language != null) {
|
||||
WordStoreAsync.put(this::onAddingFinished, language, word);
|
||||
DataStore.put(this::onAddingFinished, language, word);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import android.content.Intent;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,16 @@ public class Text extends TextTools {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isAlphabetic() {
|
||||
for (int i = 0, end = text == null ? 0 : text.length(); i < end; i++) {
|
||||
if (!Character.isAlphabetic(text.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return text == null || text.isEmpty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,32 @@
|
|||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" app:orderingFromXml="true">
|
||||
|
||||
<Preference
|
||||
app:key="pref_slow_queries_reset_stats"
|
||||
app:title="Clear DB Cache" />
|
||||
app:key="slow_queries_clear_cache"
|
||||
app:title="Clear Query Cache" />
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="Summary"
|
||||
app:singleLineTitle="true">
|
||||
<Preference
|
||||
app:key="word_pair_clear_cache"
|
||||
app:title="Clear Word Pair Memory Cache" />
|
||||
|
||||
<Preference
|
||||
app:key="word_pair_clear_db"
|
||||
app:title="Clear Word Pair DB" />
|
||||
|
||||
<PreferenceCategory app:title="Word Pair Stats" app:singleLineTitle="true">
|
||||
<io.github.sspanak.tt9.preferences.custom.PreferencePlainText
|
||||
app:key="word_pairs_container"
|
||||
app:summary="--">
|
||||
</io.github.sspanak.tt9.preferences.custom.PreferencePlainText>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="DB Query Stats" app:singleLineTitle="true">
|
||||
<io.github.sspanak.tt9.preferences.custom.PreferencePlainText
|
||||
app:key="summary_container"
|
||||
app:summary="--">
|
||||
</io.github.sspanak.tt9.preferences.custom.PreferencePlainText>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="Slow Queries"
|
||||
app:singleLineTitle="true">
|
||||
<PreferenceCategory app:title="Slow Queries" app:singleLineTitle="true">
|
||||
<io.github.sspanak.tt9.preferences.custom.PreferencePlainText
|
||||
app:key="query_list_container"
|
||||
app:summary="--">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue