Automatic language IDs (#196)
* Language IDs are now automatically generated. No more manual housekeeping. * The database words are properly migrated to avoid dictionary re-import, because of the new IDs * No unnecessary notifications when 'change language' key is pressed, but there is only one language * Database code cleanup * Moved the database exceptions and room classes to separate packages * Migrations done the right way * Updated the contribution guide
This commit is contained in:
parent
935ca590c9
commit
6f349d66aa
32 changed files with 213 additions and 78 deletions
|
|
@ -50,13 +50,17 @@ To support a new language one needs to:
|
||||||
- Create a proper icon for each screen size. The icon needs to contain the abbreviation of the language. (e.g. "En" for "English").
|
- Create a proper icon for each screen size. The icon needs to contain the abbreviation of the language. (e.g. "En" for "English").
|
||||||
- The font must be Roboto Lt at an adequate size to fit the icon square with minimum padding.
|
- The font must be Roboto Lt at an adequate size to fit the icon square with minimum padding.
|
||||||
- The text must be white and the background must be transparent as per the [official Android guide](https://android-doc.github.io/guide/practices/ui_guidelines/icon_design_status_bar.html).
|
- The text must be white and the background must be transparent as per the [official Android guide](https://android-doc.github.io/guide/practices/ui_guidelines/icon_design_status_bar.html).
|
||||||
- To simplify the process, you could use Android Studio. It has a built-in icon generator accessible by right-cicking on "drawable" folder -> New -> Image Asset. Then choose "Icon Type": "Notification Icons", "Asset Type": Text, "Trim": No, "Padding": 0%.
|
- To simplify the process, you could use Android Studio. It has a built-in icon generator accessible by right-clicking on "drawable" folder -> New -> Image Asset. Then choose "Icon Type": "Notification Icons", "Asset Type": Text, "Trim": No, "Padding": 0%.
|
||||||
- Find a suitable dictionary and add it to `assets` folder. Two file formats are supported, [see below](#dictionary-formats).
|
- Find a suitable dictionary and add it to `assets/` folder. Two file formats are supported, [see below](#dictionary-formats).
|
||||||
- Do not forget to include the dictionary license (or readme) file in the `docs/` folder.
|
- Do not forget to include the dictionary license (or readme) file in the `docs/` folder.
|
||||||
- Create a new language class in `languages/definitions/`. Make sure to set all properties.
|
- Create a new language class in `languages/definitions/` and define its properties.
|
||||||
- `ID` must be the next available number.
|
- `name` is the native name of the language (e.g. "English", "Deutsch", "Українська").
|
||||||
- Set `isPunctuationPartOfWords` to `true`, if you need to use the 1-key for typing words, such as: `it's`, `a'tje` or `п'ят`. Otherwise, it would not be possible to type them, nor will they appear as suggestions. `false` will allow faster typing when apostrophes or other punctuation are not part of the words.
|
- `locale` contains the language and the country codes (e.g. "en-US", "es-AR", "it-IT"). Refer to the list of [supported locales in Java](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html#util-text).
|
||||||
- Add the new language to the list in `LanguageCollection.java`. You only need to add it in one place, in the constructor. Please, be nice and maintain the alphabetical order.
|
- `dictionaryFile` is the name of the dictionary in `assets/` folder.
|
||||||
|
- `icon`, `abcLowerCaseIcon` and `abcUpperCaseIcon` are the respective status icons for Predictive mode, ABC (lowercase) and ABC (uppercase).
|
||||||
|
- Set `isPunctuationPartOfWords` to `true`, if the dictionary contains words with apostrophes or dashes, such as: `it's`, `you'll`, `a'tje` or `п'ят`. This will allow using 1-key for typing them (they will appear as suggestions). `false` will enable faster typing when apostrophes or other punctuation are not part of the words (no such words will be suggested).
|
||||||
|
- `characterMap` contains the letters and punctuation marks associated with each key.
|
||||||
|
- Finally, add the new language to the list in `LanguageCollection.java`. You only need to add it in one place, in the constructor. Please, be nice and maintain the alphabetical order.
|
||||||
|
|
||||||
|
|
||||||
### Dictionary Formats
|
### Dictionary Formats
|
||||||
|
|
@ -67,7 +71,7 @@ The most basic format is just a list of words where each word is on a new line.
|
||||||
Constraints:
|
Constraints:
|
||||||
- No single lowercase letters. The application will add them automatically.
|
- No single lowercase letters. The application will add them automatically.
|
||||||
- No repeating words.
|
- No repeating words.
|
||||||
- No digits or garbadge characters as part of the words.
|
- No digits or garbage characters as part of the words.
|
||||||
|
|
||||||
_The constraints will be verified automatically upon building._
|
_The constraints will be verified automatically upon building._
|
||||||
|
|
||||||
|
|
@ -108,8 +112,7 @@ Alternatively, if you don't have Android Studio, you could just use `res/values/
|
||||||
## Adding Support for Keys
|
## Adding Support for Keys
|
||||||
TT9 allows assigning hotkeys for performing different functions. If your phone has a special key that does not appear on the Hotkey configuration screen, you can easily add support for it.
|
TT9 allows assigning hotkeys for performing different functions. If your phone has a special key that does not appear on the Hotkey configuration screen, you can easily add support for it.
|
||||||
|
|
||||||
- Find [preferences/helpers/Hotkeys.java](io/github/sspanak/tt9/preferences/helpers/Hotkeys.java).
|
- In `preferences/helpers/Hotkeys.java`, find the `generateList()` function.
|
||||||
- In the file, find the `generateList()` function.
|
|
||||||
- Add the new key there. The order of adding is used when displaying the dropdown options.
|
- Add the new key there. The order of adding is used when displaying the dropdown options.
|
||||||
- Optionally, you can translate the name of the key in different languages in the `res/values-XX/strings.xml` files.
|
- Optionally, you can translate the name of the key in different languages in the `res/values-XX/strings.xml` files.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,38 +6,26 @@ import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.room.Room;
|
|
||||||
import androidx.room.RoomDatabase;
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.Logger;
|
import io.github.sspanak.tt9.Logger;
|
||||||
|
import io.github.sspanak.tt9.db.exceptions.InsertBlankWordException;
|
||||||
|
import io.github.sspanak.tt9.db.room.TT9Room;
|
||||||
|
import io.github.sspanak.tt9.db.room.Word;
|
||||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||||
|
|
||||||
public class DictionaryDb {
|
public class DictionaryDb {
|
||||||
private static T9RoomDb dbInstance;
|
private static TT9Room dbInstance;
|
||||||
|
|
||||||
private static final RoomDatabase.Callback DROP_NORMALIZATION_TRIGGER = new RoomDatabase.Callback() {
|
|
||||||
@Override
|
|
||||||
public void onOpen(@NonNull SupportSQLiteDatabase db) {
|
|
||||||
super.onOpen(db);
|
|
||||||
db.execSQL("DROP TRIGGER IF EXISTS normalize_freq");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public static synchronized void init(Context context) {
|
public static synchronized void init(Context context) {
|
||||||
if (dbInstance == null) {
|
if (dbInstance == null) {
|
||||||
context = context == null ? TraditionalT9.getMainContext() : context;
|
context = context == null ? TraditionalT9.getMainContext() : context;
|
||||||
dbInstance = Room.databaseBuilder(context, T9RoomDb.class, "t9dict.db")
|
dbInstance = TT9Room.getInstance(context);
|
||||||
.addCallback(DROP_NORMALIZATION_TRIGGER) // @todo: Remove trigger dropping after December 2023. Assuming everyone would have upgraded by then.
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +35,7 @@ public class DictionaryDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static T9RoomDb getInstance() {
|
private static TT9Room getInstance() {
|
||||||
init();
|
init();
|
||||||
return dbInstance;
|
return dbInstance;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,12 @@ import java.io.InputStreamReader;
|
||||||
import java.io.LineNumberReader;
|
import java.io.LineNumberReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import io.github.sspanak.tt9.Logger;
|
import io.github.sspanak.tt9.Logger;
|
||||||
|
import io.github.sspanak.tt9.db.exceptions.DictionaryImportAbortedException;
|
||||||
|
import io.github.sspanak.tt9.db.exceptions.DictionaryImportAlreadyRunningException;
|
||||||
|
import io.github.sspanak.tt9.db.exceptions.DictionaryImportException;
|
||||||
|
import io.github.sspanak.tt9.db.room.Word;
|
||||||
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
|
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
|
||||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package io.github.sspanak.tt9.db;
|
|
||||||
|
|
||||||
import androidx.room.Database;
|
|
||||||
import androidx.room.RoomDatabase;
|
|
||||||
|
|
||||||
@Database(version = 5, entities = Word.class, exportSchema = false)
|
|
||||||
abstract class T9RoomDb extends RoomDatabase {
|
|
||||||
public abstract WordsDao wordsDao();
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db;
|
package io.github.sspanak.tt9.db.exceptions;
|
||||||
|
|
||||||
public class DictionaryImportAbortedException extends Exception{
|
public class DictionaryImportAbortedException extends Exception{
|
||||||
public DictionaryImportAbortedException() {
|
public DictionaryImportAbortedException() {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db;
|
package io.github.sspanak.tt9.db.exceptions;
|
||||||
|
|
||||||
public class DictionaryImportAlreadyRunningException extends Exception{
|
public class DictionaryImportAlreadyRunningException extends Exception{
|
||||||
public DictionaryImportAlreadyRunningException() {
|
public DictionaryImportAlreadyRunningException() {
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package io.github.sspanak.tt9.db;
|
package io.github.sspanak.tt9.db.exceptions;
|
||||||
|
|
||||||
public class DictionaryImportException extends Exception {
|
public class DictionaryImportException extends Exception {
|
||||||
public final String file;
|
public final String file;
|
||||||
public final String word;
|
public final String word;
|
||||||
public final long line;
|
public final long line;
|
||||||
|
|
||||||
DictionaryImportException(String file, String word, long line) {
|
public DictionaryImportException(String file, String word, long line) {
|
||||||
super("Dictionary import failed");
|
super("Dictionary import failed");
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.word = word;
|
this.word = word;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package io.github.sspanak.tt9.db;
|
package io.github.sspanak.tt9.db.exceptions;
|
||||||
|
|
||||||
public class InsertBlankWordException extends Exception {
|
public class InsertBlankWordException extends Exception {
|
||||||
protected InsertBlankWordException() {
|
public InsertBlankWordException() {
|
||||||
super("Cannot insert a blank word.");
|
super("Cannot insert a blank word.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
13
src/io/github/sspanak/tt9/db/migrations/DB6.java
Normal file
13
src/io/github/sspanak/tt9/db/migrations/DB6.java
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package io.github.sspanak.tt9.db.migrations;
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration;
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
|
||||||
|
public class DB6 {
|
||||||
|
public static final Migration MIGRATION = new Migration(5, 6) {
|
||||||
|
@Override
|
||||||
|
public void migrate(SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("DROP TRIGGER IF EXISTS normalize_freq");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
82
src/io/github/sspanak/tt9/db/migrations/DB7.java
Normal file
82
src/io/github/sspanak/tt9/db/migrations/DB7.java
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package io.github.sspanak.tt9.db.migrations;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration;
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.Bulgarian;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.Dutch;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.English;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.French;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.German;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.Italian;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.Russian;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.Spanish;
|
||||||
|
import io.github.sspanak.tt9.languages.definitions.Ukrainian;
|
||||||
|
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||||
|
|
||||||
|
public class DB7 {
|
||||||
|
private Context ctx;
|
||||||
|
|
||||||
|
public Migration getMigration(Context context) {
|
||||||
|
ctx = context;
|
||||||
|
return migration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNewLanguageId(int oldId) {
|
||||||
|
switch (oldId) {
|
||||||
|
default:
|
||||||
|
return oldId;
|
||||||
|
case 1:
|
||||||
|
return new English().getId();
|
||||||
|
case 2:
|
||||||
|
return new Russian().getId();
|
||||||
|
case 3:
|
||||||
|
return new German().getId();
|
||||||
|
case 4:
|
||||||
|
return new French().getId();
|
||||||
|
case 5:
|
||||||
|
return new Italian().getId();
|
||||||
|
case 6:
|
||||||
|
return new Ukrainian().getId();
|
||||||
|
case 7:
|
||||||
|
return new Bulgarian().getId();
|
||||||
|
case 8:
|
||||||
|
return new Dutch().getId();
|
||||||
|
case 9:
|
||||||
|
return new Spanish().getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Migration migration = new Migration(6, 7) {
|
||||||
|
private void migrateSQL(SupportSQLiteDatabase database) {
|
||||||
|
for (int oldLangId = 1; oldLangId <= 9; oldLangId++) {
|
||||||
|
database.execSQL(
|
||||||
|
"UPDATE words " +
|
||||||
|
" SET lang = " + getNewLanguageId(oldLangId) +
|
||||||
|
" WHERE lang = " + oldLangId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void migrateSettings() {
|
||||||
|
SettingsStore settings = new SettingsStore(ctx);
|
||||||
|
|
||||||
|
ArrayList<Integer> newLangIds = new ArrayList<>();
|
||||||
|
for (int langId : settings.getEnabledLanguageIds()) {
|
||||||
|
newLangIds.add(getNewLanguageId(langId));
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.saveEnabledLanguageIds(newLangIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(SupportSQLiteDatabase database) {
|
||||||
|
migrateSQL(database);
|
||||||
|
migrateSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
22
src/io/github/sspanak/tt9/db/room/TT9Room.java
Normal file
22
src/io/github/sspanak/tt9/db/room/TT9Room.java
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package io.github.sspanak.tt9.db.room;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.room.Database;
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.room.RoomDatabase;
|
||||||
|
|
||||||
|
import io.github.sspanak.tt9.db.migrations.DB6;
|
||||||
|
import io.github.sspanak.tt9.db.migrations.DB7;
|
||||||
|
|
||||||
|
@Database(version = 7, entities = Word.class, exportSchema = false)
|
||||||
|
public abstract class TT9Room extends RoomDatabase {
|
||||||
|
public abstract WordsDao wordsDao();
|
||||||
|
|
||||||
|
public static synchronized TT9Room getInstance(Context context) {
|
||||||
|
return Room
|
||||||
|
.databaseBuilder(context, TT9Room.class, "t9dict.db")
|
||||||
|
.addMigrations(DB6.MIGRATION, new DB7().getMigration(context))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db;
|
package io.github.sspanak.tt9.db.room;
|
||||||
|
|
||||||
import androidx.room.ColumnInfo;
|
import androidx.room.ColumnInfo;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package io.github.sspanak.tt9.db;
|
package io.github.sspanak.tt9.db.room;
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
|
|
@ -9,12 +9,12 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface WordsDao {
|
public interface WordsDao {
|
||||||
@Query("SELECT COUNT(id) FROM words WHERE :langId < 0 OR lang = :langId")
|
@Query("SELECT COUNT(id) FROM words WHERE :langId < 0 OR lang = :langId")
|
||||||
int count(int langId);
|
int count(int langId);
|
||||||
|
|
||||||
@Query("DELETE FROM words WHERE LANG IN(:langIds)")
|
@Query("DELETE FROM words WHERE LANG IN(:langIds)")
|
||||||
int deleteByLanguage(ArrayList<Integer> langIds);
|
void deleteByLanguage(ArrayList<Integer> langIds);
|
||||||
|
|
||||||
@Query("SELECT COUNT(id) FROM words WHERE lang = :langId AND word = :word")
|
@Query("SELECT COUNT(id) FROM words WHERE lang = :langId AND word = :word")
|
||||||
int doesWordExist(int langId, String word);
|
int doesWordExist(int langId, String word);
|
||||||
|
|
@ -63,7 +63,7 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
|
|
||||||
|
|
||||||
private void validateFunctionKeys() {
|
private void validateFunctionKeys() {
|
||||||
if (!settings.areFunctionKeysSet()) {
|
if (settings.isSettingsKeyMissing()) {
|
||||||
settings.setDefaultKeys();
|
settings.setDefaultKeys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -531,10 +531,15 @@ public class TraditionalT9 extends KeyPadHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when only one language is enabled, just acknowledge the key was pressed
|
||||||
|
if (mEnabledLanguages.size() < 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// select the next language
|
// select the next language
|
||||||
int previousLangId = mEnabledLanguages.indexOf(mLanguage.getId());
|
int previous = mEnabledLanguages.indexOf(mLanguage.getId());
|
||||||
int nextLangId = previousLangId == -1 ? 0 : (previousLangId + 1) % mEnabledLanguages.size();
|
int next = (previous + 1) % mEnabledLanguages.size();
|
||||||
mLanguage = LanguageCollection.getLanguage(mEnabledLanguages.get(nextLangId));
|
mLanguage = LanguageCollection.getLanguage(mEnabledLanguages.get(next));
|
||||||
|
|
||||||
validateLanguages();
|
validateLanguages();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import io.github.sspanak.tt9.Logger;
|
||||||
import io.github.sspanak.tt9.ime.modes.InputMode;
|
import io.github.sspanak.tt9.ime.modes.InputMode;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||||
import io.github.sspanak.tt9.languages.definitions.English;
|
|
||||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||||
|
|
||||||
public class InputModeValidator {
|
public class InputModeValidator {
|
||||||
|
|
@ -17,7 +16,7 @@ public class InputModeValidator {
|
||||||
validLanguageIds.add(lang.getId());
|
validLanguageIds.add(lang.getId());
|
||||||
}
|
}
|
||||||
if (validLanguageIds.size() == 0) {
|
if (validLanguageIds.size() == 0) {
|
||||||
validLanguageIds.add(1);
|
validLanguageIds.add(LanguageCollection.getDefault().getId());
|
||||||
Logger.e("tt9/validateEnabledLanguages", "The language list seems to be corrupted. Resetting to first language only.");
|
Logger.e("tt9/validateEnabledLanguages", "The language list seems to be corrupted. Resetting to first language only.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,8 +33,7 @@ public class InputModeValidator {
|
||||||
String error = language != null ? "Language: " + language.getId() + " is not enabled." : "Invalid language.";
|
String error = language != null ? "Language: " + language.getId() + " is not enabled." : "Invalid language.";
|
||||||
|
|
||||||
Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0));
|
Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0));
|
||||||
validLanguage = validLanguage == null ? LanguageCollection.getLanguage(1) : validLanguage;
|
validLanguage = validLanguage != null ? validLanguage : LanguageCollection.getDefault();
|
||||||
validLanguage = validLanguage == null ? new English() : validLanguage;
|
|
||||||
settings.saveInputLanguage(validLanguage.getId());
|
settings.saveInputLanguage(validLanguage.getId());
|
||||||
|
|
||||||
Logger.w("tt9/validateSavedLanguage", error + " Enforcing language: " + validLanguage.getId());
|
Logger.w("tt9/validateSavedLanguage", error + " Enforcing language: " + validLanguage.getId());
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import java.util.Locale;
|
||||||
|
|
||||||
|
|
||||||
public class Language {
|
public class Language {
|
||||||
protected int id;
|
private int id;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected Locale locale;
|
protected Locale locale;
|
||||||
protected int icon;
|
protected int icon;
|
||||||
|
|
@ -22,6 +22,10 @@ public class Language {
|
||||||
protected boolean isPunctuationPartOfWords; // see the getter for more info
|
protected boolean isPunctuationPartOfWords; // see the getter for more info
|
||||||
|
|
||||||
final public int getId() {
|
final public int getId() {
|
||||||
|
if (id == 0) {
|
||||||
|
id = generateId();
|
||||||
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +49,6 @@ public class Language {
|
||||||
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon;
|
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* isPunctuationPartOfWords
|
* isPunctuationPartOfWords
|
||||||
* This plays a role in Predictive mode only.
|
* This plays a role in Predictive mode only.
|
||||||
|
|
@ -67,6 +70,34 @@ public class Language {
|
||||||
|
|
||||||
/* ************ utility ************ */
|
/* ************ utility ************ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generateId
|
||||||
|
* Uses the letters of the Locale to generate an ID for the language.
|
||||||
|
* Each letter is converted to uppercase and used as n 5-bit integer. Then the the 5-bits
|
||||||
|
* are packed to form a 10-bit or a 20-bit integer, depending on the Locale.
|
||||||
|
*
|
||||||
|
* Example (2-letter Locale)
|
||||||
|
* "en"
|
||||||
|
* -> "E" | "N"
|
||||||
|
* -> 5 | 448 (shift the 2nd number by 5 bits, so its bits would not overlap with the 1st one)
|
||||||
|
* -> 543
|
||||||
|
*
|
||||||
|
* Example (4-letter Locale)
|
||||||
|
* "bg-BG"
|
||||||
|
* -> "B" | "G" | "B" | "G"
|
||||||
|
* -> 2 | 224 | 2048 | 229376 (shift each 5-bit number, not overlap with the previous ones)
|
||||||
|
* -> 231650
|
||||||
|
*/
|
||||||
|
private int generateId() {
|
||||||
|
String idString = (locale.getLanguage() + locale.getCountry()).toUpperCase();
|
||||||
|
int idInt = 0;
|
||||||
|
for (int i = 0; i < idString.length(); i++) {
|
||||||
|
idInt |= ((idString.charAt(i) & 31) << (i * 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
return idInt;
|
||||||
|
}
|
||||||
|
|
||||||
private void generateReverseCharacterMap() {
|
private void generateReverseCharacterMap() {
|
||||||
reverseCharacterMap.clear();
|
reverseCharacterMap.clear();
|
||||||
for (int digit = 0; digit <= 9; digit++) {
|
for (int digit = 0; digit <= 9; digit++) {
|
||||||
|
|
@ -76,7 +107,6 @@ public class Language {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String capitalize(String word) {
|
public String capitalize(String word) {
|
||||||
return word != null ? word.substring(0, 1).toUpperCase(locale) + word.substring(1).toLowerCase(locale) : null;
|
return word != null ? word.substring(0, 1).toUpperCase(locale) + word.substring(1).toLowerCase(locale) : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import io.github.sspanak.tt9.languages.definitions.*;
|
||||||
public class LanguageCollection {
|
public class LanguageCollection {
|
||||||
private static LanguageCollection self;
|
private static LanguageCollection self;
|
||||||
|
|
||||||
|
private final Language defaultLanguage = new English();
|
||||||
private final HashMap<Integer, Language> languages = new HashMap<>();
|
private final HashMap<Integer, Language> languages = new HashMap<>();
|
||||||
|
|
||||||
private LanguageCollection() {
|
private LanguageCollection() {
|
||||||
|
|
@ -61,6 +62,10 @@ public class LanguageCollection {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Language getDefault() {
|
||||||
|
return getInstance().defaultLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
public static ArrayList<Language> getAll(ArrayList<Integer> languageIds, boolean sort) {
|
public static ArrayList<Language> getAll(ArrayList<Integer> languageIds, boolean sort) {
|
||||||
ArrayList<Language> langList = new ArrayList<>();
|
ArrayList<Language> langList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import io.github.sspanak.tt9.languages.Characters;
|
||||||
|
|
||||||
public class Bulgarian extends Language {
|
public class Bulgarian extends Language {
|
||||||
public Bulgarian() {
|
public Bulgarian() {
|
||||||
id = 7;
|
|
||||||
name = "Български";
|
name = "Български";
|
||||||
locale = new Locale("bg","BG");
|
locale = new Locale("bg","BG");
|
||||||
dictionaryFile = "bg-utf8.csv";
|
dictionaryFile = "bg-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ public class Dutch extends English {
|
||||||
public Dutch() {
|
public Dutch() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
id = 8;
|
|
||||||
name = "Nederlands";
|
name = "Nederlands";
|
||||||
locale = new Locale("nl","NL");
|
locale = new Locale("nl","NL");
|
||||||
dictionaryFile = "nl-utf8.csv";
|
dictionaryFile = "nl-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import io.github.sspanak.tt9.languages.Characters;
|
||||||
|
|
||||||
public class English extends Language {
|
public class English extends Language {
|
||||||
public English() {
|
public English() {
|
||||||
id = 1;
|
|
||||||
name = "English";
|
name = "English";
|
||||||
locale = Locale.ENGLISH;
|
locale = Locale.ENGLISH;
|
||||||
dictionaryFile = "en-utf8.csv";
|
dictionaryFile = "en-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ public class French extends English {
|
||||||
public French() {
|
public French() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
id = 4;
|
|
||||||
name = "Français";
|
name = "Français";
|
||||||
locale = Locale.FRENCH;
|
locale = Locale.FRENCH;
|
||||||
dictionaryFile = "fr-utf8.csv";
|
dictionaryFile = "fr-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ public class German extends English {
|
||||||
public German() {
|
public German() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
id = 3;
|
|
||||||
name = "Deutsch";
|
name = "Deutsch";
|
||||||
locale = Locale.GERMAN;
|
locale = Locale.GERMAN;
|
||||||
dictionaryFile = "de-utf8.csv";
|
dictionaryFile = "de-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ public class Italian extends English {
|
||||||
public Italian() {
|
public Italian() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
id = 5;
|
|
||||||
name = "Italiano";
|
name = "Italiano";
|
||||||
locale = Locale.ITALIAN;
|
locale = Locale.ITALIAN;
|
||||||
dictionaryFile = "it-utf8.csv";
|
dictionaryFile = "it-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import io.github.sspanak.tt9.languages.Characters;
|
||||||
|
|
||||||
public class Russian extends Language {
|
public class Russian extends Language {
|
||||||
public Russian() {
|
public Russian() {
|
||||||
id = 2;
|
|
||||||
name = "Русский";
|
name = "Русский";
|
||||||
locale = new Locale("ru","RU");
|
locale = new Locale("ru","RU");
|
||||||
dictionaryFile = "ru-utf8.csv";
|
dictionaryFile = "ru-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ public class Spanish extends English {
|
||||||
public Spanish() {
|
public Spanish() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
id = 9;
|
|
||||||
name = "Español";
|
name = "Español";
|
||||||
locale = new Locale("es", "ES");
|
locale = new Locale("es", "ES");
|
||||||
dictionaryFile = "es-utf8.csv";
|
dictionaryFile = "es-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import io.github.sspanak.tt9.languages.Characters;
|
||||||
|
|
||||||
public class Ukrainian extends Language {
|
public class Ukrainian extends Language {
|
||||||
public Ukrainian() {
|
public Ukrainian() {
|
||||||
id = 6;
|
|
||||||
name = "Українська";
|
name = "Українська";
|
||||||
locale = new Locale("uk","UA");
|
locale = new Locale("uk","UA");
|
||||||
dictionaryFile = "uk-utf8.csv";
|
dictionaryFile = "uk-utf8.csv";
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||||
|
import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
|
||||||
import io.github.sspanak.tt9.preferences.screens.AppearanceScreen;
|
import io.github.sspanak.tt9.preferences.screens.AppearanceScreen;
|
||||||
import io.github.sspanak.tt9.preferences.screens.DictionariesScreen;
|
import io.github.sspanak.tt9.preferences.screens.DictionariesScreen;
|
||||||
import io.github.sspanak.tt9.preferences.screens.HotkeysScreen;
|
import io.github.sspanak.tt9.preferences.screens.HotkeysScreen;
|
||||||
|
|
@ -33,6 +34,8 @@ public class PreferencesActivity extends AppCompatActivity implements Preference
|
||||||
DictionaryDb.init(this);
|
DictionaryDb.init(this);
|
||||||
DictionaryDb.normalizeWordFrequencies(settings);
|
DictionaryDb.normalizeWordFrequencies(settings);
|
||||||
|
|
||||||
|
InputModeValidator.validateEnabledLanguages(settings, settings.getEnabledLanguageIds());
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
validateFunctionKeys();
|
validateFunctionKeys();
|
||||||
buildLayout();
|
buildLayout();
|
||||||
|
|
@ -125,7 +128,7 @@ public class PreferencesActivity extends AppCompatActivity implements Preference
|
||||||
|
|
||||||
|
|
||||||
private void validateFunctionKeys() {
|
private void validateFunctionKeys() {
|
||||||
if (!settings.areFunctionKeysSet()) {
|
if (settings.isSettingsKeyMissing()) {
|
||||||
settings.setDefaultKeys();
|
settings.setDefaultKeys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,9 @@ public class SettingsStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getEnabledLanguagesIdsAsStrings() {
|
public Set<String> getEnabledLanguagesIdsAsStrings() {
|
||||||
return prefs.getStringSet("pref_languages", new HashSet<>(Collections.singletonList("1")));
|
return prefs.getStringSet("pref_languages", new HashSet<>(Collections.singletonList(
|
||||||
|
String.valueOf(LanguageCollection.getDefault().getId())
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveEnabledLanguageIds(ArrayList<Integer> languageIds) {
|
public void saveEnabledLanguageIds(ArrayList<Integer> languageIds) {
|
||||||
|
|
@ -123,7 +125,7 @@ public class SettingsStore {
|
||||||
|
|
||||||
|
|
||||||
public int getInputLanguage() {
|
public int getInputLanguage() {
|
||||||
return prefs.getInt("pref_input_language", 1);
|
return prefs.getInt("pref_input_language", LanguageCollection.getDefault().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveInputLanguage(int language) {
|
public void saveInputLanguage(int language) {
|
||||||
|
|
@ -151,8 +153,8 @@ public class SettingsStore {
|
||||||
|
|
||||||
/************* function key settings *************/
|
/************* function key settings *************/
|
||||||
|
|
||||||
public boolean areFunctionKeysSet() {
|
public boolean isSettingsKeyMissing() {
|
||||||
return getKeyShowSettings() != 0;
|
return getKeyShowSettings() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultKeys() {
|
public void setDefaultKeys() {
|
||||||
|
|
@ -198,7 +200,6 @@ public class SettingsStore {
|
||||||
public boolean getNotifyNextLanguageInModeAbc() { return prefs.getBoolean("notify_next_language_in_mode_abc", true); }
|
public boolean getNotifyNextLanguageInModeAbc() { return prefs.getBoolean("notify_next_language_in_mode_abc", true); }
|
||||||
|
|
||||||
public boolean getDarkTheme() { return prefs.getBoolean("pref_dark_theme", true); }
|
public boolean getDarkTheme() { return prefs.getBoolean("pref_dark_theme", true); }
|
||||||
public void setDarkTheme(boolean yes) { prefsEditor.putBoolean("pref_dark_theme", yes); }
|
|
||||||
|
|
||||||
|
|
||||||
public boolean getShowSoftKeys() { return prefs.getBoolean("pref_show_soft_keys", true); }
|
public boolean getShowSoftKeys() { return prefs.getBoolean("pref_show_soft_keys", true); }
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ public class Hotkeys {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* generateList
|
* generateList
|
||||||
* These keys will appears as options only if Android says the device has them.
|
* Generates a list of all supported hotkeys for associating functions in the Settings.
|
||||||
*
|
*
|
||||||
* NOTE: Some TT9 functions do not support all keys. Here you just list all possible options.
|
* NOTE: Some TT9 functions do not support all keys. Here you just list all possible options.
|
||||||
* Actual validation and assigning happens in SectionKeymap.populate().
|
* Actual validation and assigning happens in SectionKeymap.populate().
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,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.DictionaryImportAlreadyRunningException;
|
import io.github.sspanak.tt9.db.exceptions.DictionaryImportAlreadyRunningException;
|
||||||
import io.github.sspanak.tt9.db.DictionaryLoader;
|
import io.github.sspanak.tt9.db.DictionaryLoader;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import io.github.sspanak.tt9.Logger;
|
import io.github.sspanak.tt9.Logger;
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||||
import io.github.sspanak.tt9.db.InsertBlankWordException;
|
import io.github.sspanak.tt9.db.exceptions.InsertBlankWordException;
|
||||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||||
import io.github.sspanak.tt9.preferences.SettingsStore;
|
import io.github.sspanak.tt9.preferences.SettingsStore;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
import io.github.sspanak.tt9.db.DictionaryImportException;
|
import io.github.sspanak.tt9.db.exceptions.DictionaryImportException;
|
||||||
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
|
import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
|
||||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||||
import io.github.sspanak.tt9.languages.Language;
|
import io.github.sspanak.tt9.languages.Language;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue