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 java.util.ArrayList;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.db.entities.AddWordResult;
|
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.languages.Language;
|
||||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
import io.github.sspanak.tt9.util.ConsumerCompat;
|
||||||
|
|
||||||
public class WordStoreAsync {
|
public class DataStore {
|
||||||
private static WordStore store;
|
|
||||||
private static final Handler asyncHandler = new Handler();
|
private static final Handler asyncHandler = new Handler();
|
||||||
|
private static WordPairStore pairs;
|
||||||
|
private static WordStore words;
|
||||||
|
|
||||||
|
|
||||||
public static void init(Context context) {
|
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() {
|
public static void destroy() {
|
||||||
if (store != null) {
|
pairs = null;
|
||||||
store = null;
|
words = null;
|
||||||
}
|
SQLiteOpener.destroyInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void normalizeNext() {
|
public static void normalizeNext() {
|
||||||
new Thread(() -> store.normalizeNext()).start();
|
new Thread(() -> words.normalizeNext()).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void getLastLanguageUpdateTime(ConsumerCompat<String> notification, Language language) {
|
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) {
|
public static void deleteCustomWord(Runnable notification, Language language, String word) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
store.removeCustomWord(language, word);
|
words.removeCustomWord(language, word);
|
||||||
notification.run();
|
notification.run();
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
@ -48,46 +54,83 @@ public class WordStoreAsync {
|
||||||
|
|
||||||
public static void deleteWords(Runnable notification, @NonNull ArrayList<Integer> languageIds) {
|
public static void deleteWords(Runnable notification, @NonNull ArrayList<Integer> languageIds) {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
store.remove(languageIds);
|
words.remove(languageIds);
|
||||||
notification.run();
|
notification.run();
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void put(ConsumerCompat<AddWordResult> statusHandler, Language language, String word) {
|
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) {
|
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) {
|
public static void getWords(ConsumerCompat<ArrayList<String>> dataHandler, Language language, String sequence, String filter, int minWords, int maxWords) {
|
||||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||||
store.getSimilar(language, sequence, filter, minWords, maxWords)))
|
words.getSimilar(language, sequence, filter, minWords, maxWords)))
|
||||||
).start();
|
).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void getCustomWords(ConsumerCompat<ArrayList<String>> dataHandler, String wordFilter, int maxWords) {
|
public static void getCustomWords(ConsumerCompat<ArrayList<String>> dataHandler, String wordFilter, int maxWords) {
|
||||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||||
store.getSimilarCustom(wordFilter, maxWords)))
|
words.getSimilarCustom(wordFilter, maxWords)))
|
||||||
).start();
|
).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void countCustomWords(ConsumerCompat<Long> dataHandler) {
|
public static void countCustomWords(ConsumerCompat<Long> dataHandler) {
|
||||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||||
store.countCustom()))
|
words.countCustom()))
|
||||||
).start();
|
).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void exists(ConsumerCompat<ArrayList<Integer>> dataHandler, ArrayList<Language> languages) {
|
public static void exists(ConsumerCompat<ArrayList<Integer>> dataHandler, ArrayList<Language> languages) {
|
||||||
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
new Thread(() -> asyncHandler.post(() -> dataHandler.accept(
|
||||||
store.exists(languages))
|
words.exists(languages))
|
||||||
)).start();
|
)).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);
|
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 androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.db.entities.Word;
|
import io.github.sspanak.tt9.db.entities.Word;
|
||||||
import io.github.sspanak.tt9.db.entities.WordPosition;
|
import io.github.sspanak.tt9.db.entities.WordPosition;
|
||||||
|
import io.github.sspanak.tt9.db.wordPairs.WordPair;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
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()
|
"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 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.NormalizationList;
|
||||||
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;
|
||||||
|
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.EmojiLanguage;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
import io.github.sspanak.tt9.util.Logger;
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
@ -288,4 +289,23 @@ public class ReadOps {
|
||||||
|
|
||||||
return new NormalizationList(res);
|
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 java.util.ArrayList;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.BuildConfig;
|
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.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;
|
||||||
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
||||||
public class SQLiteOpener extends SQLiteOpenHelper {
|
public class SQLiteOpener extends SQLiteOpenHelper {
|
||||||
private static final String LOG_TAG = SQLiteOpener.class.getSimpleName();
|
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
|
@Override
|
||||||
public void onCreate(SQLiteDatabase db) {
|
public void onCreate(SQLiteDatabase db) {
|
||||||
for (String query : Tables.getCreateQueries(allLanguages)) {
|
for (String query : Tables.getCreateQueries(allLanguages)) {
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,16 @@ public class Tables {
|
||||||
static final String CUSTOM_WORDS = "custom_words";
|
static final String CUSTOM_WORDS = "custom_words";
|
||||||
private static final String POSITIONS_TABLE_BASE_NAME = "word_positions_";
|
private static final String POSITIONS_TABLE_BASE_NAME = "word_positions_";
|
||||||
private static final String WORDS_TABLE_BASE_NAME = "words_";
|
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 getWords(int langId) { return WORDS_TABLE_BASE_NAME + langId; }
|
||||||
static String getWordPositions(int langId) { return POSITIONS_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) {
|
static String[] getCreateQueries(ArrayList<Language> languages) {
|
||||||
int languageCount = languages.size();
|
int languageCount = languages.size();
|
||||||
String[] queries = new String[languageCount * 2 + 3];
|
String[] queries = new String[languageCount * 3 + 3];
|
||||||
|
|
||||||
queries[0] = createCustomWords();
|
queries[0] = createCustomWords();
|
||||||
queries[1] = createCustomWordsIndex();
|
queries[1] = createCustomWordsIndex();
|
||||||
|
|
@ -31,6 +33,7 @@ public class Tables {
|
||||||
for (Language language : languages) {
|
for (Language language : languages) {
|
||||||
queries[queryId++] = createWordsTable(language.getId());
|
queries[queryId++] = createWordsTable(language.getId());
|
||||||
queries[queryId++] = createWordPositions(language.getId());
|
queries[queryId++] = createWordPositions(language.getId());
|
||||||
|
queries[queryId++] = createWordPairs(language.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
return queries;
|
return queries;
|
||||||
|
|
@ -100,6 +103,14 @@ public class Tables {
|
||||||
return "CREATE INDEX IF NOT EXISTS idx_langId_sequence ON " + CUSTOM_WORDS + " (langId, sequence)";
|
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() {
|
private static String createLanguagesMeta() {
|
||||||
return "CREATE TABLE IF NOT EXISTS " + LANGUAGES_META + " (" +
|
return "CREATE TABLE IF NOT EXISTS " + LANGUAGES_META + " (" +
|
||||||
"langId INTEGER UNIQUE NOT NULL, " +
|
"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.Context;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
|
|
@ -11,6 +11,7 @@ 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.db.DataStore;
|
||||||
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;
|
||||||
|
|
@ -122,7 +123,7 @@ public class DictionaryLoader {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
WordStoreAsync.getLastLanguageUpdateTime(
|
DataStore.getLastLanguageUpdateTime(
|
||||||
(hash) -> {
|
(hash) -> {
|
||||||
self.lastAutoLoadAttemptTime.put(language.getId(), System.currentTimeMillis());
|
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.app.Activity;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
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;
|
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;
|
import android.content.Context;
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class WordStore {
|
||||||
private ReadOps readOps = null;
|
private ReadOps readOps = null;
|
||||||
|
|
||||||
|
|
||||||
WordStore(@NonNull Context context) {
|
public WordStore(@NonNull Context context) {
|
||||||
try {
|
try {
|
||||||
sqlite = SQLiteOpener.getInstance(context);
|
sqlite = SQLiteOpener.getInstance(context);
|
||||||
sqlite.getDb();
|
sqlite.getDb();
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package io.github.sspanak.tt9.ime;
|
package io.github.sspanak.tt9.ime;
|
||||||
|
|
||||||
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.DataStore;
|
||||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||||
import io.github.sspanak.tt9.ime.modes.ModeABC;
|
import io.github.sspanak.tt9.ime.modes.ModeABC;
|
||||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||||
|
|
@ -95,7 +95,7 @@ abstract public class CommandHandler extends TextEditingHandler {
|
||||||
if (word.isEmpty()) {
|
if (word.isEmpty()) {
|
||||||
UI.toastLong(this, R.string.add_word_no_selection);
|
UI.toastLong(this, R.string.add_word_no_selection);
|
||||||
} else if (settings.getAddWordsNoConfirmation()) {
|
} 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 {
|
} else {
|
||||||
AddWordDialog.show(this, mLanguage.getId(), word);
|
AddWordDialog.show(this, mLanguage.getId(), word);
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ abstract public class CommandHandler extends TextEditingHandler {
|
||||||
if (mInputMode.isPassthrough() || voiceInputOps.isListening()) {
|
if (mInputMode.isPassthrough() || voiceInputOps.isListening()) {
|
||||||
return;
|
return;
|
||||||
} else if (allowedInputModes.size() == 1 && allowedInputModes.contains(InputMode.MODE_123)) {
|
} 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
|
// when typing a word or viewing scrolling the suggestions, only change the case
|
||||||
else if (!suggestionOps.isEmpty()) {
|
else if (!suggestionOps.isEmpty()) {
|
||||||
|
|
@ -124,7 +124,7 @@ abstract public class CommandHandler extends TextEditingHandler {
|
||||||
mInputMode.nextTextCase();
|
mInputMode.nextTextCase();
|
||||||
} else {
|
} else {
|
||||||
int nextModeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size();
|
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.setTextFieldCase(inputType.determineTextCase());
|
||||||
mInputMode.determineNextWordTextCase(textField.getStringBeforeCursor());
|
mInputMode.determineNextWordTextCase(textField.getStringBeforeCursor());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package io.github.sspanak.tt9.ime;
|
||||||
|
|
||||||
import android.view.KeyEvent;
|
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.helpers.TextField;
|
||||||
import io.github.sspanak.tt9.ime.modes.ModePredictive;
|
import io.github.sspanak.tt9.ime.modes.ModePredictive;
|
||||||
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ import android.view.inputmethod.InputConnection;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
import io.github.sspanak.tt9.db.DataStore;
|
||||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||||
import io.github.sspanak.tt9.hacks.InputType;
|
import io.github.sspanak.tt9.hacks.InputType;
|
||||||
import io.github.sspanak.tt9.ime.modes.ModePredictive;
|
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.preferences.settings.SettingsStore;
|
||||||
import io.github.sspanak.tt9.ui.UI;
|
import io.github.sspanak.tt9.ui.UI;
|
||||||
import io.github.sspanak.tt9.ui.dialogs.PopupDialog;
|
import io.github.sspanak.tt9.ui.dialogs.PopupDialog;
|
||||||
|
|
@ -121,7 +122,7 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
protected void onInit() {
|
protected void onInit() {
|
||||||
settings.setDemoMode(false);
|
settings.setDemoMode(false);
|
||||||
Logger.setLevel(settings.getLogLevel());
|
Logger.setLevel(settings.getLogLevel());
|
||||||
WordStoreAsync.init(this);
|
DataStore.init(this);
|
||||||
super.onInit();
|
super.onInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +144,13 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
initUi();
|
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);
|
DictionaryLoader.autoLoad(this, mLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,8 +172,11 @@ public class TraditionalT9 extends MainViewHandler {
|
||||||
|
|
||||||
normalizationHandler.removeCallbacksAndMessages(null);
|
normalizationHandler.removeCallbacksAndMessages(null);
|
||||||
normalizationHandler.postDelayed(
|
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);
|
requestHideSelf(0);
|
||||||
onStop();
|
onStop();
|
||||||
normalizationHandler.removeCallbacksAndMessages(null);
|
normalizationHandler.removeCallbacksAndMessages(null);
|
||||||
WordStoreAsync.destroy();
|
DataStore.destroy();
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
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.words.DictionaryLoader;
|
||||||
import io.github.sspanak.tt9.hacks.AppHacks;
|
import io.github.sspanak.tt9.hacks.AppHacks;
|
||||||
import io.github.sspanak.tt9.hacks.InputType;
|
import io.github.sspanak.tt9.hacks.InputType;
|
||||||
import io.github.sspanak.tt9.ime.helpers.CursorOps;
|
import io.github.sspanak.tt9.ime.helpers.CursorOps;
|
||||||
|
|
@ -35,7 +35,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
// input
|
// input
|
||||||
protected ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
protected ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
||||||
@NonNull
|
@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
|
// language
|
||||||
protected ArrayList<Integer> mEnabledLanguages;
|
protected ArrayList<Integer> mEnabledLanguages;
|
||||||
|
|
@ -103,7 +103,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
|
|
||||||
protected void onFinishTyping() {
|
protected void onFinishTyping() {
|
||||||
suggestionOps.cancelDelayedAccept();
|
suggestionOps.cancelDelayedAccept();
|
||||||
mInputMode = InputMode.getInstance(null, null, null, InputMode.MODE_PASSTHROUGH);
|
mInputMode = InputMode.getInstance(null, null, null, null, InputMode.MODE_PASSTHROUGH);
|
||||||
setInputField(null, null);
|
setInputField(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.getBackspaceRecomposing() && !hold && suggestionOps.isEmpty()) {
|
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)) {
|
if (mInputMode.recompose(previousWord) && textField.recompose(previousWord)) {
|
||||||
getSuggestions();
|
getSuggestions();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -283,7 +283,7 @@ public abstract class TypingHandler extends KeyPadHandler {
|
||||||
* Same as getInputModeId(), but returns an actual InputMode.
|
* Same as getInputModeId(), but returns an actual InputMode.
|
||||||
*/
|
*/
|
||||||
protected InputMode getInputMode() {
|
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()) {
|
if (getTextAfterCursor(1).startsWithWord()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
@ -117,17 +123,28 @@ public class TextField extends InputField {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int endIndex = before.length();
|
||||||
|
|
||||||
for (int i = before.length() - 1; i >= 0; i--) {
|
for (int i = before.length() - 1; i >= 0; i--) {
|
||||||
char currentLetter = before.charAt(i);
|
char currentLetter = before.charAt(i);
|
||||||
|
|
||||||
|
if (stopAtPunctuation && language.getKeyCharacters(1).contains(currentLetter + "")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!Character.isAlphabetic(currentLetter)
|
!Character.isAlphabetic(currentLetter)
|
||||||
&& !(currentLetter == '\'' && (LanguageKind.isHebrew(language) || LanguageKind.isUkrainian(language)))
|
&& !(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;
|
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) {
|
switch (mode) {
|
||||||
case MODE_PREDICTIVE:
|
case MODE_PREDICTIVE:
|
||||||
return new ModePredictive(settings, inputType, language);
|
return new ModePredictive(settings, inputType, textField, language);
|
||||||
case MODE_ABC:
|
case MODE_ABC:
|
||||||
return new ModeABC(settings, inputType, language);
|
return new ModeABC(settings, inputType, language);
|
||||||
case MODE_PASSTHROUGH:
|
case MODE_PASSTHROUGH:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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.hacks.InputType;
|
||||||
import io.github.sspanak.tt9.ime.helpers.TextField;
|
import io.github.sspanak.tt9.ime.helpers.TextField;
|
||||||
import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace;
|
import io.github.sspanak.tt9.ime.modes.helpers.AutoSpace;
|
||||||
|
|
@ -46,13 +46,13 @@ public class ModePredictive extends InputMode {
|
||||||
private boolean isCursorDirectionForward = false;
|
private boolean isCursorDirectionForward = false;
|
||||||
|
|
||||||
|
|
||||||
ModePredictive(SettingsStore settings, InputType inputType, Language lang) {
|
ModePredictive(SettingsStore settings, InputType inputType, TextField textField, Language lang) {
|
||||||
changeLanguage(lang);
|
changeLanguage(lang);
|
||||||
defaultTextCase();
|
defaultTextCase();
|
||||||
|
|
||||||
autoSpace = new AutoSpace(settings);
|
autoSpace = new AutoSpace(settings);
|
||||||
autoTextCase = new AutoTextCase(settings);
|
autoTextCase = new AutoTextCase(settings);
|
||||||
predictions = new Predictions();
|
predictions = new Predictions(settings, textField);
|
||||||
|
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
|
|
@ -365,6 +365,7 @@ public class ModePredictive extends InputMode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// increment the frequency of the given word
|
// increment the frequency of the given word
|
||||||
try {
|
try {
|
||||||
Language workingLanguage = TextTools.isGraphic(currentWord) ? new EmojiLanguage() : language;
|
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
|
// punctuation and special chars 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.equals(NaturalLanguage.PUNCTUATION_KEY) && !sequence.startsWith(NaturalLanguage.SPECIAL_CHARS_KEY)) {
|
if (!sequence.equals(NaturalLanguage.PUNCTUATION_KEY) && !sequence.startsWith(NaturalLanguage.SPECIAL_CHARS_KEY)) {
|
||||||
WordStoreAsync.makeTopWord(workingLanguage, currentWord, sequence);
|
predictions.onAccept(currentWord, sequence);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e(LOG_TAG, "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
Logger.e(LOG_TAG, "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String adjustSuggestionTextCase(String word, int newTextCase) {
|
protected String adjustSuggestionTextCase(String word, int newTextCase) {
|
||||||
return autoTextCase.adjustSuggestionTextCase(new Text(language, word), 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 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.EmojiLanguage;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||||
import io.github.sspanak.tt9.util.Characters;
|
import io.github.sspanak.tt9.util.Characters;
|
||||||
|
|
||||||
public class Predictions {
|
public class Predictions {
|
||||||
|
private final SettingsStore settings;
|
||||||
|
private final TextField textField;
|
||||||
|
|
||||||
private Language language;
|
|
||||||
private String digitSequence;
|
private String digitSequence;
|
||||||
private boolean isStemFuzzy;
|
|
||||||
private String stem;
|
|
||||||
private String inputWord;
|
private String inputWord;
|
||||||
|
private boolean isStemFuzzy;
|
||||||
|
private Language language;
|
||||||
|
private String stem;
|
||||||
|
|
||||||
|
private String lastEnforcedTopWord = "";
|
||||||
|
|
||||||
// async operations
|
// async operations
|
||||||
private Runnable onWordsChanged = () -> {};
|
private Runnable onWordsChanged = () -> {};
|
||||||
|
|
@ -24,6 +29,11 @@ public class Predictions {
|
||||||
private boolean containsGeneratedWords = false;
|
private boolean containsGeneratedWords = false;
|
||||||
private ArrayList<String> words = new ArrayList<>();
|
private ArrayList<String> words = new ArrayList<>();
|
||||||
|
|
||||||
|
public Predictions(SettingsStore settings, TextField textField) {
|
||||||
|
this.settings = settings;
|
||||||
|
this.textField = textField;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Predictions setLanguage(Language language) {
|
public Predictions setLanguage(Language language) {
|
||||||
this.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
|
* load
|
||||||
* Queries the dictionary database for a list of words matching the current language and
|
* 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);
|
boolean retryAllowed = !digitSequence.equals(EmojiLanguage.CUSTOM_EMOJI_SEQUENCE);
|
||||||
|
|
||||||
WordStoreAsync.getWords(
|
DataStore.getWords(
|
||||||
(dbWords) -> onDbWords(dbWords, retryAllowed),
|
(dbWords) -> onDbWords(dbWords, retryAllowed),
|
||||||
language,
|
language,
|
||||||
digitSequence,
|
digitSequence,
|
||||||
|
|
@ -121,7 +106,7 @@ public class Predictions {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadWithoutLeadingPunctuation() {
|
private void loadWithoutLeadingPunctuation() {
|
||||||
WordStoreAsync.getWords(
|
DataStore.getWords(
|
||||||
(dbWords) -> {
|
(dbWords) -> {
|
||||||
char firstChar = inputWord.charAt(0);
|
char firstChar = inputWord.charAt(0);
|
||||||
for (int i = 0; i < dbWords.size(); i++) {
|
for (int i = 0; i < dbWords.size(); i++) {
|
||||||
|
|
@ -160,6 +145,7 @@ public class Predictions {
|
||||||
words.addAll(dbWords);
|
words.addAll(dbWords);
|
||||||
} else {
|
} else {
|
||||||
suggestStem();
|
suggestStem();
|
||||||
|
dbWords = rearrangeByPairFrequency(dbWords);
|
||||||
suggestMissingWords(generatePossibleStemVariations(dbWords));
|
suggestMissingWords(generatePossibleStemVariations(dbWords));
|
||||||
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords);
|
suggestMissingWords(dbWords.isEmpty() ? generateWordVariations(inputWord) : dbWords);
|
||||||
words = insertPunctuationCompletions(words);
|
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
|
* generateWordVariations
|
||||||
* When there are no matching suggestions after the last key press, generate a list of possible
|
* 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();
|
containsGeneratedWords = !variations.isEmpty();
|
||||||
return variations;
|
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 androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
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.DataStore;
|
||||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
import io.github.sspanak.tt9.db.words.LegacyDb;
|
||||||
import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
|
import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
|
||||||
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
||||||
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
|
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
|
||||||
|
|
@ -43,7 +43,7 @@ public class PreferencesActivity extends ActivityWithNavigation implements Prefe
|
||||||
Logger.setLevel(settings.getLogLevel());
|
Logger.setLevel(settings.getLogLevel());
|
||||||
|
|
||||||
try (LegacyDb db = new LegacyDb(this)) { db.clear(); }
|
try (LegacyDb db = new LegacyDb(this)) { db.clear(); }
|
||||||
WordStoreAsync.init(this);
|
DataStore.init(this);
|
||||||
|
|
||||||
InputModeValidator.validateEnabledLanguages(this, settings.getEnabledLanguageIds());
|
InputModeValidator.validateEnabledLanguages(this, settings.getEnabledLanguageIds());
|
||||||
validateFunctionKeys();
|
validateFunctionKeys();
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,21 @@ package io.github.sspanak.tt9.preferences.screens;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
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.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemText;
|
import io.github.sspanak.tt9.preferences.items.ItemText;
|
||||||
|
import io.github.sspanak.tt9.ui.UI;
|
||||||
|
|
||||||
public class UsageStatsScreen extends BaseScreenFragment {
|
public class UsageStatsScreen extends BaseScreenFragment {
|
||||||
final public static String NAME = "UsageStats";
|
final public static String NAME = "UsageStats";
|
||||||
final private static String RESET_BUTTON = "pref_slow_queries_reset_stats";
|
final private static String RESET_SLOW_QUERIES_BUTTON = "slow_queries_clear_cache";
|
||||||
final private static String SUMMARY_CONTAINER = "summary_container";
|
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;
|
private ItemText queryListContainer;
|
||||||
|
|
||||||
public UsageStatsScreen() { init(); }
|
public UsageStatsScreen() { init(); }
|
||||||
|
|
@ -22,26 +29,32 @@ public class UsageStatsScreen extends BaseScreenFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate() {
|
protected void onCreate() {
|
||||||
printSummary();
|
print(SLOW_QUERY_STATS_CONTAINER, SlowQueryStats.getSummary());
|
||||||
|
print(WORD_PAIRS_CONTAINER, DataStore.getWordPairStats());
|
||||||
printSlowQueries();
|
printSlowQueries();
|
||||||
|
|
||||||
Preference resetButton = findPreference(RESET_BUTTON);
|
Preference slowQueriesButton = findPreference(RESET_SLOW_QUERIES_BUTTON);
|
||||||
if (resetButton != null) {
|
if (slowQueriesButton != null) {
|
||||||
resetButton.setOnPreferenceClickListener((Preference p) -> {
|
slowQueriesButton.setOnPreferenceClickListener(this::resetSlowQueries);
|
||||||
SlowQueryStats.clear();
|
}
|
||||||
printSummary();
|
|
||||||
printSlowQueries();
|
Preference wordPairsCacheButton = findPreference(RESET_WORD_PAIRS_CACHE_BUTTON);
|
||||||
return true;
|
if (wordPairsCacheButton != null) {
|
||||||
});
|
wordPairsCacheButton.setOnPreferenceClickListener(this::resetWordPairsCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
Preference wordPairsDbButton = findPreference(RESET_WORD_PAIRS_DB_BUTTON);
|
||||||
|
if (wordPairsDbButton != null) {
|
||||||
|
wordPairsDbButton.setOnPreferenceClickListener(this::deleteWordPairs);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFontSize(false);
|
resetFontSize(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printSummary() {
|
private void print(String containerName, String text) {
|
||||||
Preference logsContainer = findPreference(SUMMARY_CONTAINER);
|
Preference container = findPreference(containerName);
|
||||||
if (logsContainer != null) {
|
if (container != null) {
|
||||||
logsContainer.setSummary(SlowQueryStats.getSummary());
|
container.setSummary(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,4 +67,26 @@ public class UsageStatsScreen extends BaseScreenFragment {
|
||||||
String slowQueries = SlowQueryStats.getList();
|
String slowQueries = SlowQueryStats.getList();
|
||||||
queryListContainer.populate(slowQueries.isEmpty() ? "No slow queries." : slowQueries);
|
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 androidx.preference.PreferenceCategory;
|
||||||
|
|
||||||
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.DataStore;
|
||||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||||
import io.github.sspanak.tt9.preferences.custom.ScreenPreference;
|
import io.github.sspanak.tt9.preferences.custom.ScreenPreference;
|
||||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||||
|
|
@ -62,7 +62,7 @@ public class PreferenceDeletableWord extends ScreenPreference {
|
||||||
|
|
||||||
private void onDeletionConfirmed() {
|
private void onDeletionConfirmed() {
|
||||||
SettingsStore settings = new SettingsStore(getContext());
|
SettingsStore settings = new SettingsStore(getContext());
|
||||||
WordStoreAsync.deleteCustomWord(
|
DataStore.deleteCustomWord(
|
||||||
this::onWordDeleted,
|
this::onWordDeleted,
|
||||||
LanguageCollection.getLanguage(getContext(), settings.getInputLanguage()),
|
LanguageCollection.getLanguage(getContext(), settings.getInputLanguage()),
|
||||||
word
|
word
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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.items.ItemTextInput;
|
||||||
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
import io.github.sspanak.tt9.util.ConsumerCompat;
|
||||||
|
|
@ -49,11 +49,11 @@ public class PreferenceSearchWords extends ItemTextInput {
|
||||||
if (onWords == null) {
|
if (onWords == null) {
|
||||||
Logger.w(LOG_TAG, "No handler set for the word change event.");
|
Logger.w(LOG_TAG, "No handler set for the word change event.");
|
||||||
} else if (lastSearchTerm.isEmpty()) {
|
} else if (lastSearchTerm.isEmpty()) {
|
||||||
WordStoreAsync.countCustomWords(onTotalWords);
|
DataStore.countCustomWords(onTotalWords);
|
||||||
WordStoreAsync.getCustomWords(onWords, lastSearchTerm, SettingsStore.CUSTOM_WORDS_SEARCH_RESULTS_MAX);
|
DataStore.getCustomWords(onWords, lastSearchTerm, SettingsStore.CUSTOM_WORDS_SEARCH_RESULTS_MAX);
|
||||||
} else {
|
} else {
|
||||||
WordStoreAsync.countCustomWords(onTotalWords);
|
DataStore.countCustomWords(onTotalWords);
|
||||||
WordStoreAsync.getCustomWords(onWords, lastSearchTerm, -1);
|
DataStore.getCustomWords(onWords, lastSearchTerm, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import androidx.preference.PreferenceCategory;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
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.DataStore;
|
||||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
import io.github.sspanak.tt9.db.words.DictionaryLoader;
|
||||||
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.NaturalLanguage;
|
import io.github.sspanak.tt9.languages.NaturalLanguage;
|
||||||
|
|
@ -49,7 +49,7 @@ public class LanguageSelectionScreen extends BaseScreenFragment {
|
||||||
|
|
||||||
addLanguagesToCategory(languagesCategory, allLanguages);
|
addLanguagesToCategory(languagesCategory, allLanguages);
|
||||||
if (!DictionaryLoader.getInstance(activity).isRunning()) {
|
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 java.util.ArrayList;
|
||||||
|
|
||||||
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.words.DictionaryLoader;
|
||||||
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.preferences.PreferencesActivity;
|
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import androidx.preference.Preference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
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.DataStore;
|
||||||
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.preferences.PreferencesActivity;
|
import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
||||||
|
|
@ -36,7 +36,7 @@ class ItemTruncateAll extends ItemClickable {
|
||||||
for (Language lang : LanguageCollection.getAll(activity, false)) {
|
for (Language lang : LanguageCollection.getAll(activity, false)) {
|
||||||
languageIds.add(lang.getId());
|
languageIds.add(lang.getId());
|
||||||
}
|
}
|
||||||
WordStoreAsync.deleteWords(this::onFinishDeleting, languageIds);
|
DataStore.deleteWords(this::onFinishDeleting, languageIds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import androidx.preference.Preference;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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.Language;
|
||||||
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;
|
||||||
|
|
@ -30,7 +30,7 @@ class ItemTruncateUnselected extends ItemTruncateAll {
|
||||||
}
|
}
|
||||||
|
|
||||||
onStartDeleting();
|
onStartDeleting();
|
||||||
WordStoreAsync.deleteWords(this::onFinishDeleting, unselectedLanguageIds);
|
DataStore.deleteWords(this::onFinishDeleting, unselectedLanguageIds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
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.customWords.CustomWordsExporter;
|
import io.github.sspanak.tt9.db.customWords.CustomWordsExporter;
|
||||||
import io.github.sspanak.tt9.db.customWords.CustomWordsImporter;
|
import io.github.sspanak.tt9.db.customWords.CustomWordsImporter;
|
||||||
import io.github.sspanak.tt9.db.customWords.DictionaryExporter;
|
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.PreferencesActivity;
|
||||||
import io.github.sspanak.tt9.preferences.items.ItemClickable;
|
import io.github.sspanak.tt9.preferences.items.ItemClickable;
|
||||||
import io.github.sspanak.tt9.preferences.screens.BaseScreenFragment;
|
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_SELECT_ANIMATION_DURATION = 66;
|
||||||
public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0;
|
public final static int SUGGESTIONS_TRANSLATE_ANIMATION_DURATION = 0;
|
||||||
public final static int TEXT_INPUT_DEBOUNCE_TIME = 500; // ms
|
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_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_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
|
public final static int ZOMBIE_CHECK_INTERVAL = 666; // ms
|
||||||
|
|
||||||
/************* hacks *************/
|
/************* hacks *************/
|
||||||
|
|
|
||||||
|
|
@ -29,5 +29,10 @@ class SettingsTyping extends SettingsInput {
|
||||||
// SharedPreferences return a corrupted string when using the real "\n"... :(
|
// SharedPreferences return a corrupted string when using the real "\n"... :(
|
||||||
return character.equals("\\n") ? "\n" : character;
|
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); }
|
public boolean getUpsideDownKeys() { return prefs.getBoolean("pref_upside_down_keys", false); }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import android.inputmethodservice.InputMethodService;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
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.DataStore;
|
||||||
import io.github.sspanak.tt9.db.entities.AddWordResult;
|
import io.github.sspanak.tt9.db.entities.AddWordResult;
|
||||||
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;
|
||||||
|
|
@ -47,7 +47,7 @@ public class AddWordDialog extends PopupDialog {
|
||||||
|
|
||||||
private void onOK() {
|
private void onOK() {
|
||||||
if (language != null) {
|
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 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.Language;
|
||||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||||
import io.github.sspanak.tt9.util.ConsumerCompat;
|
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() {
|
public boolean isEmpty() {
|
||||||
return text == null || text.isEmpty();
|
return text == null || text.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,32 @@
|
||||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" app:orderingFromXml="true">
|
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" app:orderingFromXml="true">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
app:key="pref_slow_queries_reset_stats"
|
app:key="slow_queries_clear_cache"
|
||||||
app:title="Clear DB Cache" />
|
app:title="Clear Query Cache" />
|
||||||
|
|
||||||
<PreferenceCategory
|
<Preference
|
||||||
app:title="Summary"
|
app:key="word_pair_clear_cache"
|
||||||
app:singleLineTitle="true">
|
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
|
<io.github.sspanak.tt9.preferences.custom.PreferencePlainText
|
||||||
app:key="summary_container"
|
app:key="summary_container"
|
||||||
app:summary="--">
|
app:summary="--">
|
||||||
</io.github.sspanak.tt9.preferences.custom.PreferencePlainText>
|
</io.github.sspanak.tt9.preferences.custom.PreferencePlainText>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory app:title="Slow Queries" app:singleLineTitle="true">
|
||||||
app:title="Slow Queries"
|
|
||||||
app:singleLineTitle="true">
|
|
||||||
<io.github.sspanak.tt9.preferences.custom.PreferencePlainText
|
<io.github.sspanak.tt9.preferences.custom.PreferencePlainText
|
||||||
app:key="query_list_container"
|
app:key="query_list_container"
|
||||||
app:summary="--">
|
app:summary="--">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue