db migrations
simplified the Add Word dialog added a popup confirmation when there are new dictionary words
This commit is contained in:
parent
0e8dfbe578
commit
4907671aa3
37 changed files with 497 additions and 313 deletions
|
|
@ -25,12 +25,14 @@ tasks.register('copyLanguages', Copy) {
|
|||
into LANGUAGES_OUTPUT_DIR
|
||||
}
|
||||
|
||||
tasks.register('calculateDictionarySizes') {
|
||||
tasks.register('writeDictionaryProperties') {
|
||||
inputs.dir fileTree(dir: DICTIONARIES_INPUT_DIR)
|
||||
outputs.dir DICTIONARIES_OUTPUT_DIR
|
||||
|
||||
doLast {
|
||||
getDictionarySizes(DICTIONARIES_INPUT_DIR, DICTIONARIES_OUTPUT_DIR)
|
||||
[getDictionarySizes, getDictionaryHashes].parallelStream().forEach { action ->
|
||||
action(DICTIONARIES_INPUT_DIR, DICTIONARIES_OUTPUT_DIR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,12 +91,12 @@ android {
|
|||
|
||||
applicationVariants.configureEach { variant ->
|
||||
tasks.named("generate${variant.name.capitalize()}Assets")?.configure {
|
||||
dependsOn(validateLanguages, copyLanguages, calculateDictionarySizes)
|
||||
dependsOn(validateLanguages, copyLanguages, writeDictionaryProperties)
|
||||
}
|
||||
|
||||
["lintAnalyzeDebug", "generateDebugLintReportModel", "lintVitalAnalyzeRelease", "generateReleaseLintVitalReportModel"].each { taskName ->
|
||||
tasks.named(taskName)?.configure {
|
||||
dependsOn(validateLanguages, copyLanguages, calculateDictionarySizes)
|
||||
dependsOn(validateLanguages, copyLanguages, writeDictionaryProperties)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ ext.getDictionarySizes = { dictionariesDir, sizesDir ->
|
|||
}
|
||||
}
|
||||
|
||||
ext.getDictionaryTimestamps = { dictionariesDir, timestampsDir ->
|
||||
ext.getDictionaryHashes = { dictionariesDir, timestampsDir ->
|
||||
fileTree(dir: dictionariesDir).getFiles().parallelStream().forEach {dictionary ->
|
||||
def dictionaryTimestamp = dictionary.exists() ? dictionary.lastModified() : 0
|
||||
new File(timestampsDir, "${dictionary.getName()}.timestamp").text = dictionaryTimestamp
|
||||
def hash = dictionary.exists() ? dictionary.text.digest("SHA-1") : ""
|
||||
new File(timestampsDir, "${dictionary.getName()}.hash").text = hash
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@
|
|||
|
||||
<activity
|
||||
android:excludeFromRecents="true"
|
||||
android:label="@string/add_word_title"
|
||||
android:name="io.github.sspanak.tt9.ui.AddWordAct"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.Dialog.MinWidth"/>
|
||||
android:label=""
|
||||
android:name="io.github.sspanak.tt9.ui.PopupDialogActivity"
|
||||
android:theme="@style/alertDialog" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -6,24 +6,26 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.ConsumerCompat;
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.db.entities.WordBatch;
|
||||
import io.github.sspanak.tt9.db.entities.WordFile;
|
||||
import io.github.sspanak.tt9.db.exceptions.DictionaryImportAbortedException;
|
||||
import io.github.sspanak.tt9.db.exceptions.DictionaryImportException;
|
||||
import io.github.sspanak.tt9.db.sqlite.DeleteOps;
|
||||
import io.github.sspanak.tt9.db.sqlite.InsertOps;
|
||||
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
|
||||
import io.github.sspanak.tt9.db.sqlite.Tables;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
|
||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
import io.github.sspanak.tt9.ui.DictionaryLoadingBar;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
public class DictionaryLoader {
|
||||
private static final String LOG_TAG = "DictionaryLoader";
|
||||
|
|
@ -101,6 +103,34 @@ public class DictionaryLoader {
|
|||
}
|
||||
|
||||
|
||||
public static void load(Context context, Language language) {
|
||||
DictionaryLoadingBar progressBar = new DictionaryLoadingBar(context);
|
||||
getInstance(context).setOnStatusChange(status -> progressBar.show(context, status));
|
||||
self.load(new ArrayList<Language>() {{ add(language); }});
|
||||
}
|
||||
|
||||
|
||||
public static void autoLoad(TraditionalT9 context, Language language) {
|
||||
if (getInstance(context).isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
WordStoreAsync.getLastLanguageUpdateTime(
|
||||
(hash) -> {
|
||||
// no words at all, load without confirmation
|
||||
if (hash.isEmpty()) {
|
||||
load(context, language);
|
||||
}
|
||||
// or if the database is outdated, compared to the dictionary file, ask for confirmation and load
|
||||
else if (!hash.equals(new WordFile(language.getDictionaryFile(), self.assets).getHash())) {
|
||||
UI.showConfirmDictionaryUpdateDialog(context, language.getId());
|
||||
}
|
||||
},
|
||||
language
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public void stop() {
|
||||
loadThread.interrupt();
|
||||
}
|
||||
|
|
@ -210,22 +240,21 @@ public class DictionaryLoader {
|
|||
|
||||
|
||||
private void importWordFile(Language language, int positionShift, float minProgress, float maxProgress) throws Exception {
|
||||
WordFile wordFile = new WordFile(language.getDictionaryFile(), assets);
|
||||
WordBatch batch = new WordBatch(language, wordFile.getTotalLines());
|
||||
int currentLine = 1;
|
||||
int totalLines = getFileSize(language.getDictionaryFile());
|
||||
float progressRatio = (maxProgress - minProgress) / totalLines;
|
||||
float progressRatio = (maxProgress - minProgress) / wordFile.getTotalLines();
|
||||
|
||||
WordBatch batch = new WordBatch(language, totalLines);
|
||||
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(assets.open(language.getDictionaryFile()), StandardCharsets.UTF_8))) {
|
||||
try (BufferedReader br = wordFile.getReader()) {
|
||||
for (String line; (line = br.readLine()) != null; currentLine++) {
|
||||
if (loadThread.isInterrupted()) {
|
||||
sendProgressMessage(language, 0, 0);
|
||||
throw new DictionaryImportAbortedException();
|
||||
}
|
||||
|
||||
String[] parts = splitLine(line);
|
||||
String[] parts = WordFile.splitLine(line);
|
||||
String word = parts[0];
|
||||
short frequency = getFrequency(parts);
|
||||
short frequency = WordFile.getFrequencyFromLineParts(parts);
|
||||
|
||||
try {
|
||||
boolean isFinalized = batch.add(word, frequency, currentLine + positionShift);
|
||||
|
|
@ -237,14 +266,14 @@ public class DictionaryLoader {
|
|||
throw new DictionaryImportException(word, currentLine);
|
||||
}
|
||||
|
||||
if (totalLines > 0) {
|
||||
if (wordFile.getTotalLines() > 0) {
|
||||
sendProgressMessage(language, minProgress + progressRatio * currentLine, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveWordBatch(batch);
|
||||
InsertOps.insertLanguageMeta(sqlite.getDb(), language.getId());
|
||||
InsertOps.replaceLanguageMeta(sqlite.getDb(), language.getId(), wordFile.getHash());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -261,44 +290,6 @@ public class DictionaryLoader {
|
|||
}
|
||||
|
||||
|
||||
private String[] splitLine(String line) {
|
||||
String[] parts = { line, "" };
|
||||
|
||||
// This is faster than String.split() by around 10%, so it's worth having it.
|
||||
// It runs very often, so any other optimizations are welcome.
|
||||
for (int i = 0 ; i < line.length(); i++) {
|
||||
if (line.charAt(i) == ' ') { // the delimiter is TAB
|
||||
parts[0] = line.substring(0, i);
|
||||
parts[1] = i < line.length() - 1 ? line.substring(i + 1) : "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
private int getFileSize(String filename) {
|
||||
String sizeFilename = filename + ".size";
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(sizeFilename), StandardCharsets.UTF_8))) {
|
||||
return Integer.parseInt(reader.readLine());
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Could not read the size of: " + filename + " from: " + sizeFilename + ". " + e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private short getFrequency(String[] lineParts) {
|
||||
try {
|
||||
return Short.parseShort(lineParts[1]);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendStartMessage(int fileCount) {
|
||||
if (onStatusChange == null) {
|
||||
Logger.w(LOG_TAG, "Cannot send file count without a status Handler. Ignoring message.");
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import io.github.sspanak.tt9.ime.TraditionalT9;
|
|||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.Text;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
import io.github.sspanak.tt9.ui.AddWordAct;
|
||||
import io.github.sspanak.tt9.ui.dialogs.AddWordDialog;
|
||||
|
||||
|
||||
public class WordStore {
|
||||
|
|
@ -90,6 +90,11 @@ public class WordStore {
|
|||
}
|
||||
|
||||
|
||||
@NonNull public String getLanguageFileHash(Language language) {
|
||||
return language != null && checkOrNotify() ? readOps.getLanguageFileHash(sqlite.getDb(), language.getId()) : "";
|
||||
}
|
||||
|
||||
|
||||
public boolean exists(Language language) {
|
||||
return language != null && checkOrNotify() && readOps.exists(sqlite.getDb(), language.getId());
|
||||
}
|
||||
|
|
@ -120,20 +125,20 @@ public class WordStore {
|
|||
|
||||
public int put(Language language, String word) {
|
||||
if (word == null || word.isEmpty()) {
|
||||
return AddWordAct.CODE_BLANK_WORD;
|
||||
return AddWordDialog.CODE_BLANK_WORD;
|
||||
}
|
||||
|
||||
if (language == null) {
|
||||
return AddWordAct.CODE_INVALID_LANGUAGE;
|
||||
return AddWordDialog.CODE_INVALID_LANGUAGE;
|
||||
}
|
||||
|
||||
if (!checkOrNotify()) {
|
||||
return AddWordAct.CODE_GENERAL_ERROR;
|
||||
return AddWordDialog.CODE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
try {
|
||||
if (readOps.exists(sqlite.getDb(), language, word)) {
|
||||
return AddWordAct.CODE_WORD_EXISTS;
|
||||
return AddWordDialog.CODE_WORD_EXISTS;
|
||||
}
|
||||
|
||||
String sequence = language.getDigitSequenceForWord(word);
|
||||
|
|
@ -146,10 +151,10 @@ public class WordStore {
|
|||
} catch (Exception e) {
|
||||
String msg = "Failed inserting word: '" + word + "' for language: " + language.getId() + ". " + e.getMessage();
|
||||
Logger.e("insertWord", msg);
|
||||
return AddWordAct.CODE_GENERAL_ERROR;
|
||||
return AddWordDialog.CODE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
return AddWordAct.CODE_SUCCESS;
|
||||
return AddWordDialog.CODE_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ public class WordStoreAsync {
|
|||
new Thread(() -> notification.accept(getStore().exists(language))).start();
|
||||
}
|
||||
|
||||
public static void getLastLanguageUpdateTime(ConsumerCompat<String> notification, Language language) {
|
||||
new Thread(() -> notification.accept(getStore().getLanguageFileHash(language))).start();
|
||||
}
|
||||
|
||||
|
||||
public static void deleteWords(Runnable notification, @NonNull ArrayList<Integer> languageIds) {
|
||||
new Thread(() -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
package io.github.sspanak.tt9.db.entities;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
|
||||
public class WordFile {
|
||||
private static final String LOG_TAG = WordFile.class.getSimpleName();
|
||||
|
||||
private final AssetManager assets;
|
||||
private final String name;
|
||||
private String hash = null;
|
||||
private int totalLines = -1;
|
||||
|
||||
public WordFile(String name, AssetManager assets) {
|
||||
this.assets = assets;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static String[] splitLine(String line) {
|
||||
String[] parts = { line, "" };
|
||||
|
||||
// This is faster than String.split() by around 10%, so it's worth having it.
|
||||
// It runs very often, so any other optimizations are welcome.
|
||||
for (int i = 0 ; i < line.length(); i++) {
|
||||
if (line.charAt(i) == ' ') { // the delimiter is TAB
|
||||
parts[0] = line.substring(0, i);
|
||||
parts[1] = i < line.length() - 1 ? line.substring(i + 1) : "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static short getFrequencyFromLineParts(String[] frequencyParts) {
|
||||
try {
|
||||
return Short.parseShort(frequencyParts[1]);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedReader getReader() throws IOException {
|
||||
return new BufferedReader(new InputStreamReader(assets.open(name), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public int getTotalLines() {
|
||||
if (totalLines < 0) {
|
||||
String rawTotalLines = getProperty("size");
|
||||
try {
|
||||
totalLines = Integer.parseInt(rawTotalLines);
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Invalid 'size' property of: " + name + ". Expecting an integer, got: '" + rawTotalLines + "'.");
|
||||
totalLines = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return totalLines;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
if (hash == null) {
|
||||
hash = getProperty("hash");
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private String getProperty(String propertyName) {
|
||||
String propertyFilename = name + "." + propertyName;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(propertyFilename)))) {
|
||||
return reader.readLine();
|
||||
} catch (Exception e) {
|
||||
Logger.w(LOG_TAG, "Could not read the '" + propertyName + "' property of: " + name + " from: " + propertyFilename + ". " + e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,9 +39,10 @@ public class InsertOps {
|
|||
}
|
||||
|
||||
|
||||
public static void insertLanguageMeta(@NonNull SQLiteDatabase db, int langId) {
|
||||
SQLiteStatement query = CompiledQueryCache.get(db, "REPLACE INTO " + Tables.LANGUAGES_META + " (langId) VALUES (?)");
|
||||
public static void replaceLanguageMeta(@NonNull SQLiteDatabase db, int langId, String fileHash) {
|
||||
SQLiteStatement query = CompiledQueryCache.get(db, "REPLACE INTO " + Tables.LANGUAGES_META + " (langId, fileHash) VALUES (?, ?)");
|
||||
query.bindLong(1, langId);
|
||||
query.bindString(2, fileHash);
|
||||
query.execute();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package io.github.sspanak.tt9.db.sqlite;
|
||||
|
||||
class Migration {
|
||||
static final Migration[] LIST = {
|
||||
new Migration(
|
||||
"ALTER TABLE " + Tables.LANGUAGES_META + " ADD COLUMN fileHash TEXT NOT NULL DEFAULT 0",
|
||||
true
|
||||
)
|
||||
};
|
||||
|
||||
String query;
|
||||
boolean mayFail;
|
||||
private Migration(String query, boolean mayFail) {
|
||||
this.query = query;
|
||||
this.mayFail = mayFail;
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,20 @@ public class ReadOps {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the language file at the time of the last import into the database.
|
||||
*/
|
||||
public String getLanguageFileHash(@NonNull SQLiteDatabase db, int langId) {
|
||||
SQLiteStatement query = CompiledQueryCache.get(db, "SELECT fileHash FROM " + Tables.LANGUAGES_META + " WHERE langId = ?");
|
||||
query.bindLong(1, langId);
|
||||
try {
|
||||
return query.simpleQueryForString();
|
||||
} catch (SQLiteDoneException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public WordList getWords(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String positions, String filter, int maximumWords, boolean fullOutput) {
|
||||
if (positions.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import android.database.sqlite.SQLiteOpenHelper;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.BuildConfig;
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
|
||||
public class SQLiteOpener extends SQLiteOpenHelper {
|
||||
private static final String LOG_TAG = SQLiteOpener.class.getSimpleName();
|
||||
private static final String DATABASE_NAME = "tt9.db";
|
||||
private static final int DATABASE_VERSION = BuildConfig.VERSION_CODE;
|
||||
private static SQLiteOpener self;
|
||||
|
|
@ -52,6 +54,19 @@ public class SQLiteOpener extends SQLiteOpenHelper {
|
|||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
onCreate(db);
|
||||
for (Migration migration : Migration.LIST) {
|
||||
try {
|
||||
db.execSQL(migration.query);
|
||||
Logger.d(LOG_TAG, "Migration succeeded: '" + migration.query);
|
||||
} catch (Exception e) {
|
||||
if (migration.mayFail) {
|
||||
Logger.e(LOG_TAG, "Ignoring migration: '" + migration.query + "'. ");
|
||||
} else {
|
||||
Logger.e(LOG_TAG, "Migration failed: '" + migration.query + "'. " + e.getMessage() + "\nAborting all subsequent migrations.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,8 @@ public class Tables {
|
|||
private static String createLanguagesMeta() {
|
||||
return "CREATE TABLE IF NOT EXISTS " + LANGUAGES_META + " (" +
|
||||
"langId INTEGER UNIQUE NOT NULL, " +
|
||||
"normalizationPending INT2 NOT NULL DEFAULT 0 " +
|
||||
"normalizationPending INT2 NOT NULL DEFAULT 0," +
|
||||
"fileHash TEXT NOT NULL DEFAULT 0 " +
|
||||
")";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
public class EmptyDatabaseWarning {
|
||||
private static final HashMap<Integer, Long> warningDisplayedTime = new HashMap<>();
|
||||
|
||||
private Context context;
|
||||
private Language language;
|
||||
|
||||
public EmptyDatabaseWarning() {
|
||||
for (Language lang : LanguageCollection.getAll(context)) {
|
||||
if (!warningDisplayedTime.containsKey(lang.getId())) {
|
||||
warningDisplayedTime.put(lang.getId(), 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void emitOnce(Language language) {
|
||||
context = context == null ? TraditionalT9.getMainContext() : context;
|
||||
this.language = language;
|
||||
|
||||
if (isItTimeAgain(TraditionalT9.getMainContext())) {
|
||||
WordStoreAsync.areThereWords(this::show, language);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isItTimeAgain(Context context) {
|
||||
if (this.language == null || context == null || !warningDisplayedTime.containsKey(language.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
Long lastWarningTime = warningDisplayedTime.get(language.getId());
|
||||
return lastWarningTime != null && now - lastWarningTime > SettingsStore.DICTIONARY_MISSING_WARNING_INTERVAL;
|
||||
}
|
||||
|
||||
private void show(boolean areThereWords) {
|
||||
if (areThereWords) {
|
||||
return;
|
||||
}
|
||||
|
||||
warningDisplayedTime.put(language.getId(), System.currentTimeMillis());
|
||||
UI.toastLongFromAsync(
|
||||
context,
|
||||
context.getString(R.string.dictionary_missing_go_load_it, language.getName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ import io.github.sspanak.tt9.languages.Language;
|
|||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
import io.github.sspanak.tt9.preferences.helpers.Hotkeys;
|
||||
import io.github.sspanak.tt9.ui.AddWordAct;
|
||||
import io.github.sspanak.tt9.ui.PopupDialogActivity;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
import io.github.sspanak.tt9.ui.main.MainView;
|
||||
import io.github.sspanak.tt9.ui.tray.StatusBar;
|
||||
|
|
@ -139,7 +139,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
int result = super.onStartCommand(intent, flags, startId);
|
||||
|
||||
String message = intent != null ? intent.getStringExtra(AddWordAct.INTENT_FILTER) : null;
|
||||
String message = intent != null ? intent.getStringExtra(PopupDialogActivity.DIALOG_CLOSED_INTENT) : null;
|
||||
if (message != null && !message.isEmpty()) {
|
||||
forceShowWindowIfHidden();
|
||||
UI.toastLong(self, message);
|
||||
|
|
@ -239,6 +239,8 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
clearSuggestions();
|
||||
statusBar.setText("--");
|
||||
|
||||
DictionaryLoader.autoLoad(this, mLanguage);
|
||||
|
||||
normalizationHandler.removeCallbacksAndMessages(null);
|
||||
normalizationHandler.postDelayed(
|
||||
() -> { if (!DictionaryLoader.getInstance(this).isRunning()) WordStoreAsync.normalizeNext(); },
|
||||
|
|
@ -483,6 +485,10 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
mainView.render();
|
||||
forceShowWindowIfHidden();
|
||||
|
||||
if (mInputMode instanceof ModePredictive) {
|
||||
DictionaryLoader.autoLoad(this, mLanguage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -499,9 +505,8 @@ public class TraditionalT9 extends KeyPadHandler {
|
|||
scheduleAutoAccept(mInputMode.getAutoAcceptTimeout()); // restart the timer
|
||||
nextInputMode();
|
||||
mainView.render();
|
||||
|
||||
|
||||
forceShowWindowIfHidden();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@ package io.github.sspanak.tt9.ime.modes.helpers;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.ime.EmptyDatabaseWarning;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
|
||||
public class Predictions {
|
||||
private final EmptyDatabaseWarning emptyDbWarning;
|
||||
|
||||
private Language language;
|
||||
private String digitSequence;
|
||||
|
|
@ -25,7 +23,6 @@ public class Predictions {
|
|||
|
||||
|
||||
public Predictions() {
|
||||
emptyDbWarning = new EmptyDatabaseWarning();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -155,10 +152,6 @@ public class Predictions {
|
|||
return;
|
||||
}
|
||||
|
||||
if (dbWords.isEmpty() && !digitSequence.isEmpty()) {
|
||||
emptyDbWarning.emitOnce(language);
|
||||
}
|
||||
|
||||
words.clear();
|
||||
suggestStem();
|
||||
suggestMissingWords(generatePossibleStemVariations(dbWords));
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ public class SettingsStore {
|
|||
|
||||
public final static int DICTIONARY_IMPORT_BATCH_SIZE = 5000; // words
|
||||
public final static int DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME = 250; // ms
|
||||
public final static int DICTIONARY_MISSING_WARNING_INTERVAL = 30000; // ms
|
||||
public final static int DICTIONARY_CONFIRM_UPDATE_COOLDOWN_TIME = 120000; // ms
|
||||
public final static byte SLOW_QUERY_TIME = 50; // ms
|
||||
public final static int SOFT_KEY_REPEAT_DELAY = 40; // ms
|
||||
public final static float SOFT_KEY_COMPLEX_LABEL_TITLE_SIZE = 0.55f;
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
package io.github.sspanak.tt9.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
|
||||
public class AddWordAct extends AppCompatActivity {
|
||||
public static final int CODE_SUCCESS = 0;
|
||||
public static final int CODE_BLANK_WORD = 1;
|
||||
public static final int CODE_INVALID_LANGUAGE = 2;
|
||||
public static final int CODE_WORD_EXISTS = 3;
|
||||
public static final int CODE_GENERAL_ERROR = 666;
|
||||
|
||||
public static final String INTENT_FILTER = "tt9.add_word";
|
||||
|
||||
private Language language;
|
||||
private String word;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedData) {
|
||||
super.onCreate(savedData);
|
||||
readInput();
|
||||
render(getMessage());
|
||||
}
|
||||
|
||||
|
||||
private void readInput() {
|
||||
Intent i = getIntent();
|
||||
word = i.getStringExtra("io.github.sspanak.tt9.word");
|
||||
language = LanguageCollection.getLanguage(this, i.getIntExtra("io.github.sspanak.tt9.lang", -1));
|
||||
}
|
||||
|
||||
private String getMessage() {
|
||||
if (language == null) {
|
||||
Logger.e("WordManager.confirmAddWord", "Cannot insert a word for NULL language");
|
||||
UI.toastLong(getApplicationContext(), R.string.add_word_invalid_language);
|
||||
return null;
|
||||
}
|
||||
|
||||
return getString(R.string.add_word_confirm, word, language.getName());
|
||||
}
|
||||
|
||||
private void render(String message) {
|
||||
if (message == null || word == null || word.isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
View main = View.inflate(this, R.layout.addwordview, null);
|
||||
((TextView) main.findViewById(R.id.add_word_dialog_text)).append(message);
|
||||
setContentView(main);
|
||||
}
|
||||
|
||||
|
||||
private void onAddedWord(int statusCode) {
|
||||
String message;
|
||||
switch (statusCode) {
|
||||
case CODE_SUCCESS:
|
||||
message = getString(R.string.add_word_success, word);
|
||||
break;
|
||||
|
||||
case CODE_WORD_EXISTS:
|
||||
message = getResources().getString(R.string.add_word_exist, word);
|
||||
break;
|
||||
|
||||
case CODE_BLANK_WORD:
|
||||
message = getString(R.string.add_word_blank);
|
||||
break;
|
||||
|
||||
case CODE_INVALID_LANGUAGE:
|
||||
message = getResources().getString(R.string.add_word_invalid_language);
|
||||
break;
|
||||
|
||||
default:
|
||||
message = getString(R.string.error_unexpected);
|
||||
break;
|
||||
}
|
||||
|
||||
finish();
|
||||
sendMessageToMain(message);
|
||||
}
|
||||
|
||||
|
||||
public void addWord(View v) {
|
||||
WordStoreAsync.put(this::onAddedWord, language, word);
|
||||
}
|
||||
|
||||
|
||||
private void sendMessageToMain(String message) {
|
||||
Intent intent = new Intent(this, TraditionalT9.class);
|
||||
intent.putExtra(INTENT_FILTER, message);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
|
||||
public void cancelAddingWord(View v) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package io.github.sspanak.tt9.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
import io.github.sspanak.tt9.ui.dialogs.AddWordDialog;
|
||||
import io.github.sspanak.tt9.ui.dialogs.ConfirmDictionaryUpdateDialog;
|
||||
import io.github.sspanak.tt9.ui.dialogs.PopupDialog;
|
||||
|
||||
public class PopupDialogActivity extends AppCompatActivity {
|
||||
private static final String LOG_TAG = PopupDialogActivity.class.getSimpleName();
|
||||
public static final String DIALOG_ADD_WORD_INTENT = "tt9.popup_dialog.add_word";
|
||||
public static final String DIALOG_CONFIRM_WORDS_UPDATE_INTENT = "tt9.popup_dialog.confirm_words_update";
|
||||
public static final String DIALOG_CLOSED_INTENT = "tt9.popup_dialog.closed";
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedData) {
|
||||
super.onCreate(savedData);
|
||||
PopupDialog dialog = getDialog();
|
||||
if (dialog != null) {
|
||||
dialog.render();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private PopupDialog getDialog() {
|
||||
Intent i = getIntent();
|
||||
|
||||
String popupType = i != null ? i.getStringExtra("popup_type") : "";
|
||||
popupType = popupType != null ? popupType : "";
|
||||
|
||||
switch (popupType) {
|
||||
case DIALOG_ADD_WORD_INTENT:
|
||||
return new AddWordDialog(this, i, this::onDialogClose);
|
||||
case DIALOG_CONFIRM_WORDS_UPDATE_INTENT:
|
||||
return new ConfirmDictionaryUpdateDialog(this, i, this::onDialogClose);
|
||||
default:
|
||||
Logger.w(LOG_TAG, "Unknown popup type: '" + popupType + "'. Not displaying anything.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDialogClose(String message) {
|
||||
finish();
|
||||
if (message != null && !message.isEmpty()) {
|
||||
sendMessageToMain(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessageToMain(String message) {
|
||||
Intent intent = new Intent(this, TraditionalT9.class);
|
||||
intent.putExtra(DIALOG_CLOSED_INTENT, message);
|
||||
startService(intent);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,12 +12,23 @@ import io.github.sspanak.tt9.preferences.PreferencesActivity;
|
|||
|
||||
public class UI {
|
||||
public static void showAddWordDialog(TraditionalT9 tt9, int language, String currentWord) {
|
||||
Intent awIntent = new Intent(tt9, AddWordAct.class);
|
||||
awIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
awIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
awIntent.putExtra("io.github.sspanak.tt9.word", currentWord);
|
||||
awIntent.putExtra("io.github.sspanak.tt9.lang", language);
|
||||
tt9.startActivity(awIntent);
|
||||
Intent intent = new Intent(tt9, PopupDialogActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
intent.putExtra("word", currentWord);
|
||||
intent.putExtra("lang", language);
|
||||
intent.putExtra("popup_type", PopupDialogActivity.DIALOG_ADD_WORD_INTENT);
|
||||
tt9.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
public static void showConfirmDictionaryUpdateDialog(TraditionalT9 tt9, int language) {
|
||||
Intent intent = new Intent(tt9, PopupDialogActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
intent.putExtra("lang", language);
|
||||
intent.putExtra("popup_type", PopupDialogActivity.DIALOG_CONFIRM_WORDS_UPDATE_INTENT);
|
||||
tt9.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -43,6 +54,16 @@ public class UI {
|
|||
.show();
|
||||
}
|
||||
|
||||
public static void confirm(Context context, String title, String message, String OKLabel, Runnable onOk, Runnable onCancel) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(OKLabel, (dialog, whichButton) -> { if (onOk != null) onOk.run(); })
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { if (onCancel != null) onCancel.run(); })
|
||||
.setOnCancelListener(dialog -> { if (onCancel != null) onCancel.run(); })
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void toast(Context context, CharSequence msg) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
|
@ -72,12 +93,4 @@ public class UI {
|
|||
public static void toastLong(Context context, CharSequence msg) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void toastLongFromAsync(Context context, CharSequence msg) {
|
||||
if (Looper.myLooper() == null) {
|
||||
Looper.prepare();
|
||||
}
|
||||
toastLong(context, msg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package io.github.sspanak.tt9.ui.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.github.sspanak.tt9.ConsumerCompat;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.WordStoreAsync;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
|
||||
public class AddWordDialog extends PopupDialog {
|
||||
public static final int CODE_SUCCESS = 0;
|
||||
public static final int CODE_BLANK_WORD = 1;
|
||||
public static final int CODE_INVALID_LANGUAGE = 2;
|
||||
public static final int CODE_WORD_EXISTS = 3;
|
||||
public static final int CODE_GENERAL_ERROR = 666;
|
||||
|
||||
private Language language;
|
||||
private String word;
|
||||
|
||||
|
||||
public AddWordDialog(@NonNull Context context, @NonNull Intent intent, ConsumerCompat<String> activityFinisher) {
|
||||
super(context, intent, activityFinisher);
|
||||
|
||||
title = context.getResources().getString(R.string.add_word_title);
|
||||
OKLabel = context.getResources().getString(R.string.add_word_add);
|
||||
if (language == null) {
|
||||
message = context.getString(R.string.add_word_invalid_language);
|
||||
} else {
|
||||
message = context.getString(R.string.add_word_confirm, word, language.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void parseIntent(Context context, Intent intent) {
|
||||
word = intent.getStringExtra("word");
|
||||
language = LanguageCollection.getLanguage(context, intent.getIntExtra("lang", -1));
|
||||
}
|
||||
|
||||
|
||||
public void render() {
|
||||
if (message == null || word == null || word.isEmpty()) {
|
||||
if (activityFinisher != null) activityFinisher.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable OKAction = language == null ? null : () -> WordStoreAsync.put(this::onAddedWord, language, word);
|
||||
super.render(OKAction);
|
||||
}
|
||||
|
||||
private void onAddedWord(int statusCode) {
|
||||
String response;
|
||||
switch (statusCode) {
|
||||
case CODE_SUCCESS:
|
||||
response = context.getString(R.string.add_word_success, word);
|
||||
break;
|
||||
|
||||
case CODE_WORD_EXISTS:
|
||||
response = context.getResources().getString(R.string.add_word_exist, word);
|
||||
break;
|
||||
|
||||
case CODE_BLANK_WORD:
|
||||
response = context.getString(R.string.add_word_blank);
|
||||
break;
|
||||
|
||||
case CODE_INVALID_LANGUAGE:
|
||||
response = context.getResources().getString(R.string.add_word_invalid_language);
|
||||
break;
|
||||
|
||||
default:
|
||||
response = context.getString(R.string.error_unexpected);
|
||||
break;
|
||||
}
|
||||
|
||||
activityFinisher.accept(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package io.github.sspanak.tt9.ui.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.github.sspanak.tt9.ConsumerCompat;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||
|
||||
public class ConfirmDictionaryUpdateDialog extends PopupDialog {
|
||||
private static long lastDisplayTime = 0;
|
||||
private Language language;
|
||||
public ConfirmDictionaryUpdateDialog(@NonNull Context context, @NonNull Intent intent, ConsumerCompat<String> activityFinisher) {
|
||||
super(context, intent, activityFinisher);
|
||||
|
||||
title = context.getString(R.string.dictionary_update_title);
|
||||
OKLabel = context.getString(R.string.dictionary_update_update);
|
||||
String langName = language != null ? language.getName() : "";
|
||||
message = context.getResources().getString(R.string.dictionary_update_message, langName);
|
||||
}
|
||||
|
||||
protected void parseIntent(Context context, Intent intent) {
|
||||
language = LanguageCollection.getLanguage(context, intent.getIntExtra("lang", -1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
if (System.currentTimeMillis() - lastDisplayTime < SettingsStore.DICTIONARY_CONFIRM_UPDATE_COOLDOWN_TIME) {
|
||||
activityFinisher.accept(null);
|
||||
} else {
|
||||
super.render(this::loadDictionary);
|
||||
lastDisplayTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadDictionary() {
|
||||
DictionaryLoader.load(context, language);
|
||||
activityFinisher.accept(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package io.github.sspanak.tt9.ui.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.github.sspanak.tt9.ConsumerCompat;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
abstract public class PopupDialog {
|
||||
protected final Context context;
|
||||
protected final ConsumerCompat<String> activityFinisher;
|
||||
protected String title;
|
||||
protected String message;
|
||||
protected String OKLabel;
|
||||
|
||||
public PopupDialog(@NonNull Context context, @NonNull Intent intent, ConsumerCompat<String> activityFinisher) {
|
||||
this.activityFinisher = activityFinisher;
|
||||
this.context = context;
|
||||
parseIntent(context, intent);
|
||||
}
|
||||
|
||||
abstract protected void parseIntent(Context context, Intent intent);
|
||||
abstract public void render();
|
||||
|
||||
protected void render(Runnable OKAction) {
|
||||
UI.confirm(context, title, message, OKLabel, OKAction, () -> activityFinisher.accept(null));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="6dp"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/add_word_dialog_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp">
|
||||
</TextView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:onClick="cancelAddingWord"
|
||||
android:text="@android:string/cancel" />
|
||||
|
||||
<Button
|
||||
style="@style/Widget.AppCompat.Button.Borderless.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:onClick="addWord"
|
||||
android:text="@string/add_word_add" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -34,6 +34,9 @@
|
|||
<string name="pref_show_soft_function_keys">Бутони на екрана</string>
|
||||
<string name="key_back">Назад</string>
|
||||
<string name="key_call">Зелена слушалка</string>
|
||||
<string name="dictionary_update_title">Налично е обновление на речника</string>
|
||||
<string name="dictionary_update_message">Има нови думи за „%1$s“. Искате ли да ги заредите?</string>
|
||||
<string name="dictionary_update_update">Зареди</string>
|
||||
<string name="donate_title">Дарете</string>
|
||||
<string name="donate_summary">Ако харесвате %1$s, подкрепете разработката му на: %2$s.</string>
|
||||
<string name="function_add_word_key">Добавяне на нова дума</string>
|
||||
|
|
@ -65,7 +68,6 @@
|
|||
<string name="pref_auto_text_case">Автоматични главни букви</string>
|
||||
<string name="pref_auto_text_case_summary">Започвай автоматично изреченията с главни букви.</string>
|
||||
<string name="pref_category_predictive_mode">Подсказващ режим</string>
|
||||
<string name="dictionary_missing_go_load_it">Няма речник за език „%1$s“. Заредете го в Настройки.</string>
|
||||
<string name="pref_category_keypad">Клавиатура</string>
|
||||
<string name="pref_double_zero_char">Символ при двойно натисната \"0\"</string>
|
||||
<string name="char_newline">Нов ред</string>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@
|
|||
<string name="pref_dark_theme_yes">Ja</string>
|
||||
<string name="pref_dark_theme_no">Nein</string>
|
||||
<string name="pref_dark_theme_auto">Automatisch</string>
|
||||
<string name="dictionary_update_title">Wörterbuchupdate verfügbar</string>
|
||||
<string name="dictionary_update_message">Es gibt neue Wörter für „%1$s“. Möchten Sie sie laden?</string>
|
||||
<string name="dictionary_update_update">Laden</string>
|
||||
<string name="donate_title">Spenden</string>
|
||||
<string name="donate_summary">Wenn Ihnen %1$s gefällt, könnten Sie die Entwicklung auf %2$s unterstützen.</string>
|
||||
<string name="pref_hack_fb_messenger">Mit \"OK\" in Facebook Messenger senden</string>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
<string name="dictionary_loading_indeterminate">Cargando diccionario</string>
|
||||
<string name="dictionary_loading">Cargando diccionario (%1$s)…</string>
|
||||
<string name="pref_category_about">Acerca de esta aplicación</string>
|
||||
<string name="dictionary_missing_go_load_it">No hay diccionario para el idioma \"%1$s\". Vaya a Configuración para cargarlo.</string>
|
||||
<string name="dictionary_not_found">Falló al cargar. No se encontró el diccionario para \"%1$s\".</string>
|
||||
<string name="dictionary_truncate_title">Borrar todos</string>
|
||||
<string name="dictionary_truncate_unselected">Borrar no seleccionados</string>
|
||||
|
|
@ -75,6 +74,9 @@
|
|||
<string name="pref_dark_theme_yes">Sí</string>
|
||||
<string name="pref_dark_theme_no">No</string>
|
||||
<string name="pref_dark_theme_auto">Automática</string>
|
||||
<string name="dictionary_update_title">Actualización del Diccionario Disponible</string>
|
||||
<string name="dictionary_update_message">Hay nuevas palabras para «%1$s». ¿Te gustaría cargarlas?</string>
|
||||
<string name="dictionary_update_update">Cargar</string>
|
||||
<string name="donate_title">Donar</string>
|
||||
<string name="donate_summary">Si te gusta %1$s, podrías apoyar su desarrollo en: %2$s.</string>
|
||||
<string name="pref_hack_fb_messenger">Enviar con «OK» en Facebook Messenger</string>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
<string name="pref_auto_text_case">Majuscules automatiques</string>
|
||||
<string name="pref_auto_space_summary">Ajouter automatiquement un espace après signes de ponctuation et mots.</string>
|
||||
<string name="pref_auto_text_case_summary">Commencer automatiquement les phrases avec une majuscule.</string>
|
||||
<string name="dictionary_missing_go_load_it">Pas de dictionnaire pour langue «%1$s». Veuillez le charger à l\'écran Paramètres.</string>
|
||||
<string name="pref_category_keypad">Clavier</string>
|
||||
<string name="char_space">Espace</string>
|
||||
<string name="function_add_word_key">Ajouter un mot</string>
|
||||
|
|
@ -75,6 +74,9 @@
|
|||
<string name="pref_dark_theme_no">Non</string>
|
||||
<string name="pref_dark_theme_auto">Automatique</string>
|
||||
<string name="add_word_confirm">Ajouter mot « %1$s » à %2$s?</string>
|
||||
<string name="dictionary_update_title">Mise à jour du dictionnaire disponible</string>
|
||||
<string name="dictionary_update_message">Il y a de nouveaux mots pour «%1$s». Souhaitez-vous les charger ?</string>
|
||||
<string name="dictionary_update_update">Charger</string>
|
||||
<string name="donate_title">Donner</string>
|
||||
<string name="donate_summary">Si vous aimez %1$s vous pouvez soutenir son développement à : %2$s</string>
|
||||
<string name="pref_hack_key_pad_debounce_time">Protection multi-presse</string>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@
|
|||
<string name="pref_dark_theme_yes">Si</string>
|
||||
<string name="pref_dark_theme_no">No</string>
|
||||
<string name="pref_dark_theme_auto">Automatica</string>
|
||||
<string name="dictionary_update_title">Aggiornamento del dizionario disponibile</string>
|
||||
<string name="dictionary_update_message">Ci sono nuove parole per \"%1$s\". Vuoi caricarle?</string>
|
||||
<string name="dictionary_update_update">Carica</string>
|
||||
<string name="donate_title">Donare</string>
|
||||
<string name="donate_summary">Se ti piace %1$s, potresti supportarne lo sviluppo su: %2$s.</string>
|
||||
<string name="pref_hack_fb_messenger">Inviare con \"OK\" su Facebook Messenger</string>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
<string name="dictionary_load_title">טעינת מילון</string>
|
||||
<string name="setup_keyboard_status">סטטוס</string>
|
||||
<string name="setup_default_keyboard">בחר מקלדת ברירת מחדל</string>
|
||||
<string name="dictionary_missing_go_load_it">אין מילון עבור \"%1$s\". טען את המילון דרך ההגדרות.</string>
|
||||
<string name="dictionary_not_found">הטעינה נכשלה, לא נמצא מילון עבור \"%1$s\".</string>
|
||||
<string name="dictionary_truncate_title">מחק הכל</string>
|
||||
<string name="dictionary_truncate_unselected">מחק את הבלתי נבחר</string>
|
||||
|
|
@ -68,6 +67,9 @@
|
|||
<string name="pref_dark_theme_yes">כן</string>
|
||||
<string name="pref_dark_theme_no">לא</string>
|
||||
<string name="pref_dark_theme_auto">אוטומטי</string>
|
||||
<string name="dictionary_update_title">עדכון מילון זמין</string>
|
||||
<string name="dictionary_update_message">יש מילים חדשות עבור \"%1$s\". האם תרצה לטעון אותם?</string>
|
||||
<string name="dictionary_update_update">טען</string>
|
||||
<string name="donate_title">לִתְרוֹם</string>
|
||||
<string name="donate_summary">אם אתה אוהב את %1$s, תוכל לתמוך בפיתוח שלו בכתובת: %2$s</string>
|
||||
<string name="pref_hack_fb_messenger">שלח עם \"OK\" ב-Facebook Messenger.</string>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@
|
|||
<string name="pref_dark_theme_yes">Ja</string>
|
||||
<string name="pref_dark_theme_no">Nee</string>
|
||||
<string name="pref_dark_theme_auto">Automatisch</string>
|
||||
<string name="dictionary_update_title">Woordenboekupdate Beschikbaar</string>
|
||||
<string name="dictionary_update_message">Er zijn nieuwe woorden voor \"%1$s\". Wil je ze laden?</string>
|
||||
<string name="dictionary_update_update">Laden</string>
|
||||
<string name="donate_title">Doneer</string>
|
||||
<string name="donate_summary">Als je %1$s leuk vindt, zou je de ontwikkeling kunnen ondersteunen op: %2$s.</string>
|
||||
<string name="pref_hack_fb_messenger">Verstuur met \"OK\" in Facebook Messenger</string>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@
|
|||
<string name="dictionary_loading_indeterminate">Carregando dicionário…</string>
|
||||
<string name="dictionary_loading_please_wait">Aguarde o carregamento do dicionário, por favor</string>
|
||||
<string name="dictionary_load_title">Carregar Dicionário</string>
|
||||
<string name="dictionary_missing_go_load_it">Não há um dicionário para o idioma \"%1$s\". É possível carregá-lo em "Configurações".</string>
|
||||
<string name="dictionary_not_found">Falha no carregamento. Não foi encontrado um dicionário para o idioma \"%1$s\".</string>
|
||||
<string name="dictionary_truncate_title">Limpar Dicionário</string>
|
||||
<string name="dictionary_truncated">Dicionário apagado com sucesso.</string>
|
||||
|
|
@ -64,6 +63,9 @@
|
|||
<string name="pref_dark_theme_yes">Sim</string>
|
||||
<string name="pref_dark_theme_no">Não</string>
|
||||
<string name="pref_dark_theme_auto">Automático</string>
|
||||
<string name="dictionary_update_title">Atualização do Dicionário Disponível</string>
|
||||
<string name="dictionary_update_message">Há novas palavras para \"%1$s\". Você gostaria de carregá-las?</string>
|
||||
<string name="dictionary_update_update">Carregar</string>
|
||||
<string name="donate_title">Doar</string>
|
||||
<string name="donate_summary">Se você gosta de %1$s, você poderia apoiar o seu desenvolvimento em: %2$s.</string>
|
||||
<string name="pref_hack_fb_messenger">Enviar com \"OK\" no Facebook Messenger</string>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@
|
|||
<string name="pref_hack_fb_messenger">Отправка с «ОК» в Messenger</string>
|
||||
<string name="pref_show_soft_function_keys">Кнопки на экране</string>
|
||||
<string name="dictionary_load_bad_char">Не удалось загрузить словарь. Проблема в слове «%1$s» в строке %2$d для языка «%3$s».</string>
|
||||
<string name="dictionary_missing_go_load_it">Отсутствует словарь для языка «%1$s». Вы можете загрузить его в Настройках.</string>
|
||||
<string name="function_add_word_key">Добавить новое слово</string>
|
||||
<string name="function_backspace_key">Стереть</string>
|
||||
<string name="function_change_keyboard_key">Выбор клавиатуры</string>
|
||||
|
|
@ -80,6 +79,9 @@
|
|||
<string name="pref_dark_theme_no">Нет</string>
|
||||
<string name="pref_dark_theme_auto">Автоматически</string>
|
||||
<string name="add_word_confirm">Добавить слово «%1$s» в %2$s?</string>
|
||||
<string name="dictionary_update_title">Доступно обновление словаря</string>
|
||||
<string name="dictionary_update_message">Для «%1$s» доступны новые слова. Хотите загрузить их?</string>
|
||||
<string name="dictionary_update_update">Загрузить</string>
|
||||
<string name="donate_title">Поддержать</string>
|
||||
<string name="donate_summary">Если вам нравится %1$s, вы можете поддержать его разработку по: %2$s.</string>
|
||||
<string name="pref_hack_google_chat">Отправка сообщения с «ОК» в Google Chat</string>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@
|
|||
<string name="function_reset_keys_done">Налаштування кнопок за замовчуванням відновлено</string>
|
||||
<string name="function_reset_keys_title">Повернути кнопки за замовчуванням</string>
|
||||
<string name="dictionary_truncated">Словник успішно видалено.</string>
|
||||
<string name="dictionary_missing_go_load_it">Немає словника для мови «%1$s». Перейдіть до Налаштувань, щоб завантажити його.</string>
|
||||
<string name="dictionary_load_bad_char">Помилка завантаження. Недійсне слово «%1$s» в рядку %2$d мови «%3$s».</string>
|
||||
<string name="pref_upside_down_keys">Зворотна клавіатура</string>
|
||||
<string name="pref_upside_down_keys_summary">Використовуйте налаштування, якщо 7–8–9 у першому рядку замість 1–2–3.</string>
|
||||
|
|
@ -80,6 +79,9 @@
|
|||
<string name="pref_dark_theme_no">Ні</string>
|
||||
<string name="pref_dark_theme_auto">Автоматично</string>
|
||||
<string name="add_word_confirm">Додати слово «%1$s» до %2$s?</string>
|
||||
<string name="dictionary_update_title">Доступне оновлення словника</string>
|
||||
<string name="dictionary_update_message">Є нові слова для «%1$s». Бажаєте їх завантажити?</string>
|
||||
<string name="dictionary_update_update">Завантажити</string>
|
||||
<string name="donate_title">Підтримуйте</string>
|
||||
<string name="donate_summary">Якщо вам подобається %1$s, ви можете підтримати його розробку за: %2$s.</string>
|
||||
<string name="pref_hack_google_chat">Надсилати повідомлення з «ОК» до Google Chat</string>
|
||||
|
|
|
|||
|
|
@ -65,13 +65,16 @@
|
|||
<string name="dictionary_loading_indeterminate">Loading dictionary</string>
|
||||
<string name="dictionary_loading_please_wait">Please wait for the dictionary to load.</string>
|
||||
<string name="dictionary_load_title">Load Dictionary</string>
|
||||
<string name="dictionary_missing_go_load_it">No dictionary for language \"%1$s\". Go to Settings to load it.</string>
|
||||
<string name="dictionary_not_found">Loading failed. Dictionary for \"%1$s\" not found.</string>
|
||||
<string name="dictionary_truncate_title">Delete All</string>
|
||||
<string name="dictionary_truncate_unselected">Delete Unselected</string>
|
||||
<string name="dictionary_truncated">Dictionary successfully cleared.</string>
|
||||
<string name="dictionary_truncating">Deleting…</string>
|
||||
|
||||
<string name="dictionary_update_title">Dictionary Update Available</string>
|
||||
<string name="dictionary_update_message">There are new words for \"%1$s\". Would you like to load them?</string>
|
||||
<string name="dictionary_update_update">Load</string>
|
||||
|
||||
<string name="donate_title">Donate</string>
|
||||
<string name="donate_summary">If you like %1$s, you could support its development at: %2$s.</string>
|
||||
<string name="donate_url" translatable="false">https://www.buymeacoffee.com/sspanak</string>
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@
|
|||
<item name="android:layout_height">1dp</item>
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
</style>
|
||||
|
||||
<style name="alertDialog" parent="Theme.AppCompat.DayNight.Dialog.Alert">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue