'full' and 'lite' flavors
This commit is contained in:
parent
11e042e707
commit
6670bccc50
16 changed files with 182 additions and 64 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -27,8 +27,8 @@ jobs:
|
||||||
- name: Validate Dictionaries
|
- name: Validate Dictionaries
|
||||||
run: ./gradlew validateLanguages
|
run: ./gradlew validateLanguages
|
||||||
- name: Build Languages
|
- name: Build Languages
|
||||||
run: ./gradlew copyLanguages writeDictionaryProperties
|
run: ./gradlew copyDefinitions copyDictionaries writeDictionaryProperties
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: ./gradlew lint # this actually runs mergeResources, so it must come after the dictionary tasks
|
run: ./gradlew lint # this actually runs mergeResources, so it must come after the dictionary tasks
|
||||||
- name: Build Release APK
|
- name: Build all APK variants
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -16,6 +16,8 @@ gen/
|
||||||
.gradle/
|
.gradle/
|
||||||
assets/
|
assets/
|
||||||
build/
|
build/
|
||||||
|
full/
|
||||||
|
lite/
|
||||||
release/
|
release/
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
# Local configuration file (sdk path, etc)
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ Thanks to your donations, a brand new testing device is available, a Sonim XP380
|
||||||
## 💪 Privacy Policy and Philosophy
|
## 💪 Privacy Policy and Philosophy
|
||||||
- No ads, no premium or paid features. It's all free.
|
- No ads, no premium or paid features. It's all free.
|
||||||
- No spying, no tracking, no telemetry or reports. No nothing!
|
- No spying, no tracking, no telemetry or reports. No nothing!
|
||||||
- No network connectivity, except when voice input is active.
|
- Network connectivity is only used for voice input and downloading dictionaries from Github. You can also use the Full version that includes all languages and requires no Internet permission.
|
||||||
- It only does its job.
|
- It only does its job.
|
||||||
- Open-source, so you can verify all the above yourself.
|
- Open-source, so you can verify all the above yourself.
|
||||||
- Created with help from the entire community.
|
- Created with help from the entire community.
|
||||||
|
|
|
||||||
|
|
@ -17,22 +17,25 @@ tasks.register('validateLanguages') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('copyLanguages', Copy) {
|
tasks.register('copyDefinitions', Copy) {
|
||||||
from LANGUAGES_INPUT_DIR
|
from LANGUAGES_INPUT_DIR
|
||||||
include '**/*.csv'
|
|
||||||
include '**/*.txt'
|
|
||||||
include '**/*.yml'
|
include '**/*.yml'
|
||||||
into LANGUAGES_OUTPUT_DIR
|
into LANGUAGES_OUTPUT_DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register('copyDictionaries', Copy) {
|
||||||
|
from DICTIONARIES_INPUT_DIR
|
||||||
|
include '**/*.csv'
|
||||||
|
include '**/*.txt'
|
||||||
|
into DICTIONARIES_OUTPUT_DIR
|
||||||
|
}
|
||||||
|
|
||||||
tasks.register('writeDictionaryProperties') {
|
tasks.register('writeDictionaryProperties') {
|
||||||
inputs.dir fileTree(dir: DICTIONARIES_INPUT_DIR)
|
inputs.dir fileTree(dir: DICTIONARIES_INPUT_DIR)
|
||||||
outputs.dir DICTIONARIES_OUTPUT_DIR
|
outputs.dir DICTIONARY_META_OUTPUT_DIR
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
[getDictionarySizes, getDictionaryHashes].parallelStream().forEach { action ->
|
getDictionaryProperties(DICTIONARIES_INPUT_DIR, DICTIONARY_META_OUTPUT_DIR)
|
||||||
action(DICTIONARIES_INPUT_DIR, DICTIONARIES_OUTPUT_DIR)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,6 +47,7 @@ tasks.register('updateManifest') {
|
||||||
|
|
||||||
clean {
|
clean {
|
||||||
delete LANGUAGES_OUTPUT_DIR
|
delete LANGUAGES_OUTPUT_DIR
|
||||||
|
delete DICTIONARIES_OUTPUT_DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
// using the exported Closures directly causes weird values, hence the extra wrappers here
|
// using the exported Closures directly causes weird values, hence the extra wrappers here
|
||||||
|
|
@ -87,24 +91,35 @@ android {
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.configureEach { variant ->
|
flavorDimensions = ['app']
|
||||||
tasks.named("generate${variant.name.capitalize()}Assets")?.configure {
|
productFlavors {
|
||||||
dependsOn(validateLanguages, copyLanguages, writeDictionaryProperties)
|
full { dimension 'app' }
|
||||||
}
|
lite { dimension 'app' }
|
||||||
|
}
|
||||||
|
|
||||||
["lintAnalyzeDebug", "generateDebugLintReportModel", "lintVitalAnalyzeRelease", "generateReleaseLintVitalReportModel"].each { taskName ->
|
applicationVariants.configureEach { variant ->
|
||||||
|
[
|
||||||
|
"merge${variant.name.capitalize()}Assets",
|
||||||
|
"lintAnalyze${variant.name.capitalize()}",
|
||||||
|
"generate${variant.name.capitalize()}LintReportModel",
|
||||||
|
"lintVitalAnalyze${variant.name.capitalize()}",
|
||||||
|
"generate${variant.name.capitalize()}LintVitalReportModel"
|
||||||
|
].each { taskName ->
|
||||||
try {
|
try {
|
||||||
tasks.named(taskName)?.configure {
|
tasks.named(taskName)?.configure {
|
||||||
dependsOn(validateLanguages, copyLanguages, writeDictionaryProperties)
|
dependsOn(validateLanguages, copyDefinitions, copyDictionaries, writeDictionaryProperties)
|
||||||
}
|
}
|
||||||
} catch (UnknownTaskException ignored) {}
|
} catch (UnknownTaskException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
assembleDebug.finalizedBy(updateManifest)
|
assembleLiteDebug.finalizedBy(updateManifest)
|
||||||
assembleRelease.finalizedBy(updateManifest)
|
assembleFullDebug.finalizedBy(updateManifest)
|
||||||
|
assembleLiteRelease.finalizedBy(updateManifest)
|
||||||
|
assembleFullRelease.finalizedBy(updateManifest)
|
||||||
|
|
||||||
variant.outputs.configureEach {
|
variant.outputs.configureEach {
|
||||||
outputFileName = "${APP_NAME}-v${getVerName()}.apk"
|
def suffix = variant.flavorName == 'full' ? '-full' : ''
|
||||||
|
outputFileName = "${APP_NAME}-v${getVerName()}${suffix}.apk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,17 @@ ext.DICTIONARIES_DIR_NAME = 'dictionaries'
|
||||||
ext.DICTIONARY_SIZES_DIR_NAME = 'dictionary-sizes'
|
ext.DICTIONARY_SIZES_DIR_NAME = 'dictionary-sizes'
|
||||||
|
|
||||||
def ROOT_DIR = "${project.rootDir}/app"
|
def ROOT_DIR = "${project.rootDir}/app"
|
||||||
def ASSETS_DIR = "${ROOT_DIR}/src/main/assets"
|
def MAIN_ASSETS_DIR = "${ROOT_DIR}/src/main/assets"
|
||||||
|
def FULL_VERSION_ASSETS_DIR = "${ROOT_DIR}/src/full/assets"
|
||||||
|
|
||||||
ext.LANGUAGES_INPUT_DIR = "${ROOT_DIR}/${LANGUAGES_DIR_NAME}"
|
ext.LANGUAGES_INPUT_DIR = "${ROOT_DIR}/${LANGUAGES_DIR_NAME}"
|
||||||
ext.DEFINITIONS_INPUT_DIR = "${LANGUAGES_INPUT_DIR}/${DEFINITIONS_DIR_NAME}"
|
ext.DEFINITIONS_INPUT_DIR = "${LANGUAGES_INPUT_DIR}/${DEFINITIONS_DIR_NAME}"
|
||||||
ext.DICTIONARIES_INPUT_DIR = "${LANGUAGES_INPUT_DIR}/${DICTIONARIES_DIR_NAME}"
|
ext.DICTIONARIES_INPUT_DIR = "${LANGUAGES_INPUT_DIR}/${DICTIONARIES_DIR_NAME}"
|
||||||
|
|
||||||
ext.LANGUAGES_OUTPUT_DIR = "${ASSETS_DIR}/${LANGUAGES_DIR_NAME}"
|
ext.LANGUAGES_OUTPUT_DIR = "${MAIN_ASSETS_DIR}/${LANGUAGES_DIR_NAME}"
|
||||||
ext.DEFINITIONS_OUTPUT_DIR = "${LANGUAGES_OUTPUT_DIR}/${DEFINITIONS_DIR_NAME}"
|
ext.DEFINITIONS_OUTPUT_DIR = "${LANGUAGES_OUTPUT_DIR}/${DEFINITIONS_DIR_NAME}"
|
||||||
ext.DICTIONARIES_OUTPUT_DIR = "${LANGUAGES_OUTPUT_DIR}/${DICTIONARIES_DIR_NAME}"
|
ext.DICTIONARY_META_OUTPUT_DIR = "${LANGUAGES_OUTPUT_DIR}/${DICTIONARIES_DIR_NAME}"
|
||||||
|
ext.DICTIONARIES_OUTPUT_DIR = "${FULL_VERSION_ASSETS_DIR}/${LANGUAGES_DIR_NAME}/${DICTIONARIES_DIR_NAME}"
|
||||||
|
|
||||||
ext.LANGUAGE_VALIDATION_DIR = layout.buildDirectory.dir("langValidation")
|
ext.LANGUAGE_VALIDATION_DIR = layout.buildDirectory.dir("langValidation")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
ext.getDictionarySizes = { dictionariesDir, sizesDir ->
|
ext.getDictionaryProperties = { dictionariesDir, sizesDir ->
|
||||||
fileTree(dir: dictionariesDir).getFiles().parallelStream().forEach {dictionary ->
|
|
||||||
def dictionarySize = dictionary.exists() ? dictionary.text.split("\n").length : 0
|
|
||||||
new File(sizesDir, "${dictionary.getName()}.size").text = dictionarySize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ext.getDictionaryHashes = { dictionariesDir, timestampsDir ->
|
|
||||||
fileTree(dir: dictionariesDir).getFiles().parallelStream().forEach {dictionary ->
|
fileTree(dir: dictionariesDir).getFiles().parallelStream().forEach {dictionary ->
|
||||||
def hash = dictionary.exists() ? dictionary.text.digest("SHA-1") : ""
|
def hash = dictionary.exists() ? dictionary.text.digest("SHA-1") : ""
|
||||||
new File(timestampsDir, "${dictionary.getName()}.hash").text = hash
|
def revision = dictionary.exists() ? exec("git log --pretty=tformat:%H -n 1 ${dictionary}") : ""
|
||||||
|
def words = dictionary.exists() ? dictionary.text.split("\n").length : 0
|
||||||
|
|
||||||
|
new File(sizesDir, "${dictionary.getName()}.props.yml").text = "hash: ${hash}\nrevision: ${revision}\nwords: ${words}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3
app/src/lite/AndroidManifest.xml
Normal file
3
app/src/lite/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/> <!-- allows dictionary download -->
|
||||||
|
</manifest>
|
||||||
|
|
@ -68,7 +68,7 @@ public class DictionaryLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean load(ArrayList<Language> languages) {
|
public boolean load(Context context, ArrayList<Language> languages) {
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ public class DictionaryLoader {
|
||||||
if (isInterrupted()) {
|
if (isInterrupted()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
importAll(lang);
|
importAll(context, lang);
|
||||||
currentFile++;
|
currentFile++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@ public class DictionaryLoader {
|
||||||
public static void load(Context context, Language language) {
|
public static void load(Context context, Language language) {
|
||||||
DictionaryLoadingBar progressBar = DictionaryLoadingBar.getInstance(context);
|
DictionaryLoadingBar progressBar = DictionaryLoadingBar.getInstance(context);
|
||||||
getInstance(context).setOnStatusChange(status -> progressBar.show(context, status));
|
getInstance(context).setOnStatusChange(status -> progressBar.show(context, status));
|
||||||
self.load(new ArrayList<Language>() {{ add(language); }});
|
self.load(context, new ArrayList<Language>() {{ add(language); }});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -129,7 +129,7 @@ public class DictionaryLoader {
|
||||||
load(context, language);
|
load(context, language);
|
||||||
}
|
}
|
||||||
// or if the database is outdated, compared to the dictionary file, ask for confirmation and load
|
// 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())) {
|
else if (!hash.equals(new WordFile(context, language.getDictionaryFile(), self.assets).getHash())) {
|
||||||
new DictionaryUpdateNotification(context, language).show();
|
new DictionaryUpdateNotification(context, language).show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -151,7 +151,7 @@ public class DictionaryLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void importAll(Language language) {
|
private void importAll(Context context, Language language) {
|
||||||
if (language == null) {
|
if (language == null) {
|
||||||
Logger.e(LOG_TAG, "Failed loading a dictionary for NULL language.");
|
Logger.e(LOG_TAG, "Failed loading a dictionary for NULL language.");
|
||||||
sendError(InvalidLanguageException.class.getSimpleName(), -1);
|
sendError(InvalidLanguageException.class.getSimpleName(), -1);
|
||||||
|
|
@ -178,7 +178,7 @@ public class DictionaryLoader {
|
||||||
sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
sendProgressMessage(language, ++progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
||||||
logLoadingStep("Letters imported", language, Timer.restart());
|
logLoadingStep("Letters imported", language, Timer.restart());
|
||||||
|
|
||||||
importWordFile(language, lettersCount, progress, 88);
|
importWordFile(context, language, lettersCount, progress, 88);
|
||||||
progress = 88;
|
progress = 88;
|
||||||
sendProgressMessage(language, progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
sendProgressMessage(language, progress, SettingsStore.DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME);
|
||||||
logLoadingStep("Dictionary file imported", language, Timer.restart());
|
logLoadingStep("Dictionary file imported", language, Timer.restart());
|
||||||
|
|
@ -252,8 +252,8 @@ public class DictionaryLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void importWordFile(Language language, int positionShift, float minProgress, float maxProgress) throws Exception {
|
private void importWordFile(Context context, Language language, int positionShift, float minProgress, float maxProgress) throws Exception {
|
||||||
WordFile wordFile = new WordFile(language.getDictionaryFile(), assets);
|
WordFile wordFile = new WordFile(context, language.getDictionaryFile(), assets);
|
||||||
WordBatch batch = new WordBatch(language, wordFile.getTotalLines());
|
WordBatch batch = new WordBatch(language, wordFile.getTotalLines());
|
||||||
int currentLine = 1;
|
int currentLine = 1;
|
||||||
float progressRatio = (maxProgress - minProgress) / wordFile.getTotalLines();
|
float progressRatio = (maxProgress - minProgress) / wordFile.getTotalLines();
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,39 @@
|
||||||
package io.github.sspanak.tt9.db.entities;
|
package io.github.sspanak.tt9.db.entities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import io.github.sspanak.tt9.BuildConfig;
|
||||||
|
import io.github.sspanak.tt9.R;
|
||||||
|
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
|
||||||
import io.github.sspanak.tt9.util.Logger;
|
import io.github.sspanak.tt9.util.Logger;
|
||||||
|
|
||||||
public class WordFile {
|
public class WordFile {
|
||||||
private static final String LOG_TAG = WordFile.class.getSimpleName();
|
private static final String LOG_TAG = WordFile.class.getSimpleName();
|
||||||
|
|
||||||
private final AssetManager assets;
|
private final AssetManager assets;
|
||||||
|
private final Context context;
|
||||||
private final String name;
|
private final String name;
|
||||||
private String hash = null;
|
private String hash = null;
|
||||||
|
private String downloadUrl = null;
|
||||||
private int totalLines = -1;
|
private int totalLines = -1;
|
||||||
|
|
||||||
public WordFile(String name, AssetManager assets) {
|
|
||||||
|
public WordFile(Context context, String name, AssetManager assets) {
|
||||||
this.assets = assets;
|
this.assets = assets;
|
||||||
|
this.context = context;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String[] splitLine(String line) {
|
public static String[] splitLine(String line) {
|
||||||
String[] parts = { line, "" };
|
String[] parts = { line, "" };
|
||||||
|
|
||||||
|
|
@ -38,6 +50,7 @@ public class WordFile {
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static short getFrequencyFromLineParts(String[] frequencyParts) {
|
public static short getFrequencyFromLineParts(String[] frequencyParts) {
|
||||||
try {
|
try {
|
||||||
return Short.parseShort(frequencyParts[1]);
|
return Short.parseShort(frequencyParts[1]);
|
||||||
|
|
@ -46,40 +59,115 @@ public class WordFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedReader getReader() throws IOException {
|
|
||||||
return new BufferedReader(new InputStreamReader(assets.open(name), StandardCharsets.UTF_8));
|
public boolean exists() {
|
||||||
|
try {
|
||||||
|
assets.open(name).close();
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalLines() {
|
|
||||||
if (totalLines < 0) {
|
public InputStream getRemoteStream() throws IOException {
|
||||||
String rawTotalLines = getProperty("size");
|
URLConnection connection = new URL(getDownloadUrl()).openConnection();
|
||||||
try {
|
connection.setConnectTimeout(SettingsStore.DICTIONARY_DOWNLOAD_CONNECTION_TIMEOUT);
|
||||||
totalLines = Integer.parseInt(rawTotalLines);
|
connection.setReadTimeout(SettingsStore.DICTIONARY_DOWNLOAD_READ_TIMEOUT);
|
||||||
} catch (Exception e) {
|
return connection.getInputStream();
|
||||||
Logger.w(LOG_TAG, "Invalid 'size' property of: " + name + ". Expecting an integer, got: '" + rawTotalLines + "'.");
|
}
|
||||||
totalLines = 0;
|
|
||||||
}
|
|
||||||
|
public BufferedReader getReader() throws IOException {
|
||||||
|
InputStream stream = exists() ? assets.open(name) : getRemoteStream();
|
||||||
|
return new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getDownloadUrl() {
|
||||||
|
if (downloadUrl == null) {
|
||||||
|
loadProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalLines;
|
return downloadUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setDownloadUrl(String rawProperty, String rawValue) {
|
||||||
|
if (!rawProperty.equals("revision")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String revision = rawValue == null || rawValue.isEmpty() ? "" : rawValue;
|
||||||
|
downloadUrl = revision.isEmpty() ? null : context.getString(R.string.dictionary_url, revision, name);
|
||||||
|
|
||||||
|
if (revision.isEmpty()) {
|
||||||
|
Logger.w(LOG_TAG, "Invalid 'revision' property of: " + name + ". Expecting a string, got: '" + rawValue + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getHash() {
|
public String getHash() {
|
||||||
if (hash == null) {
|
if (hash == null) {
|
||||||
hash = getProperty("hash");
|
loadProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getProperty(String propertyName) {
|
|
||||||
String propertyFilename = name + "." + propertyName;
|
private void setHash(String rawProperty, String rawValue) {
|
||||||
|
if (!rawProperty.equals("hash")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash = rawValue == null || rawValue.isEmpty() ? "" : rawValue;
|
||||||
|
|
||||||
|
if (hash.isEmpty()) {
|
||||||
|
Logger.w(LOG_TAG, "Invalid 'hash' property of: " + name + ". Expecting a string, got: '" + rawValue + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getTotalLines() {
|
||||||
|
if (totalLines < 0) {
|
||||||
|
loadProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setTotalLines(String rawProperty, String rawValue) {
|
||||||
|
if (!rawProperty.equals("words")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
totalLines = Integer.parseInt(rawValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.w(LOG_TAG, "Invalid 'words' property of: " + name + ". Expecting an integer, got: '" + rawValue + "'.");
|
||||||
|
totalLines = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void loadProperties() {
|
||||||
|
String propertyFilename = name + ".props.yml";
|
||||||
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(propertyFilename)))) {
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(assets.open(propertyFilename)))) {
|
||||||
return reader.readLine();
|
for (String line; (line = reader.readLine()) != null; ) {
|
||||||
|
String[] parts = line.split("\\s*:\\s*");
|
||||||
|
if (parts.length < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDownloadUrl(parts[0], parts[1]);
|
||||||
|
setHash(parts[0], parts[1]);
|
||||||
|
setTotalLines(parts[0], parts[1]);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.w(LOG_TAG, "Could not read the '" + propertyName + "' property of: " + name + " from: " + propertyFilename + ". " + e.getMessage());
|
Logger.w(LOG_TAG, "Could not read the property file: " + propertyFilename + ". " + e.getMessage());
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class ItemLoadDictionary extends ItemClickable {
|
||||||
ArrayList<Language> languages = LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds());
|
ArrayList<Language> languages = LanguageCollection.getAll(activity, activity.getSettings().getEnabledLanguageIds());
|
||||||
|
|
||||||
setLoadingStatus();
|
setLoadingStatus();
|
||||||
if (!loader.load(languages)) {
|
if (!loader.load(activity, languages)) {
|
||||||
loader.stop();
|
loader.stop();
|
||||||
setReadyStatus();
|
setReadyStatus();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ public class SettingsStore extends SettingsUI {
|
||||||
/************* internal settings *************/
|
/************* internal settings *************/
|
||||||
public final static int DELETE_WORDS_SEARCH_DELAY = 500; // ms
|
public final static int DELETE_WORDS_SEARCH_DELAY = 500; // ms
|
||||||
public final static int DICTIONARY_AUTO_LOAD_COOLDOWN_TIME = 1200000; // 20 minutes in ms
|
public final static int DICTIONARY_AUTO_LOAD_COOLDOWN_TIME = 1200000; // 20 minutes in ms
|
||||||
|
public final static int DICTIONARY_DOWNLOAD_CONNECTION_TIMEOUT = 10000; // ms
|
||||||
|
public final static int DICTIONARY_DOWNLOAD_READ_TIMEOUT = 10000; // ms
|
||||||
public final static int DICTIONARY_IMPORT_BATCH_SIZE = 5000; // words
|
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_IMPORT_PROGRESS_UPDATE_TIME = 250; // ms
|
||||||
public final static byte SLOW_QUERY_TIME = 50; // ms
|
public final static byte SLOW_QUERY_TIME = 50; // ms
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import android.os.Bundle;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import io.github.sspanak.tt9.R;
|
import io.github.sspanak.tt9.R;
|
||||||
|
|
@ -138,6 +140,8 @@ public class DictionaryLoadingBar extends DictionaryProgressNotification {
|
||||||
message = resources.getString(R.string.add_word_invalid_language);
|
message = resources.getString(R.string.add_word_invalid_language);
|
||||||
} else if (errorType.equals(DictionaryImportException.class.getSimpleName()) || errorType.equals(InvalidLanguageCharactersException.class.getSimpleName())) {
|
} else if (errorType.equals(DictionaryImportException.class.getSimpleName()) || errorType.equals(InvalidLanguageCharactersException.class.getSimpleName())) {
|
||||||
message = resources.getString(R.string.dictionary_load_bad_char, word, line, lang.getName());
|
message = resources.getString(R.string.dictionary_load_bad_char, word, line, lang.getName());
|
||||||
|
} else if (errorType.equals(UnknownHostException.class.getSimpleName()) || errorType.equals(SocketException.class.getSimpleName())) {
|
||||||
|
message = resources.getString(R.string.dictionary_load_no_internet, lang.getName());
|
||||||
} else if (errorType.equals(IOException.class.getSimpleName()) || errorType.equals(FileNotFoundException.class.getSimpleName())) {
|
} else if (errorType.equals(IOException.class.getSimpleName()) || errorType.equals(FileNotFoundException.class.getSimpleName())) {
|
||||||
message = resources.getString(R.string.dictionary_not_found, lang.getName());
|
message = resources.getString(R.string.dictionary_not_found, lang.getName());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string translatable="false" name="dictionary_url">https://raw.githubusercontent.com/sspanak/tt9/%1$s/app/%2$s</string>
|
||||||
<string translatable="false" name="help_url">https://github.com/sspanak/tt9/blob/master/docs/user-manual.md</string>
|
<string translatable="false" name="help_url">https://github.com/sspanak/tt9/blob/master/docs/user-manual.md</string>
|
||||||
<string name="app_name" translatable="false">Traditional T9</string>
|
<string name="app_name" translatable="false">Traditional T9</string>
|
||||||
<string name="app_name_short" translatable="false">TT9</string>
|
<string name="app_name_short" translatable="false">TT9</string>
|
||||||
|
|
@ -80,6 +81,7 @@
|
||||||
<string name="dictionary_cancel_load">Cancel Loading</string>
|
<string name="dictionary_cancel_load">Cancel Loading</string>
|
||||||
<string name="dictionary_load_bad_char">Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".</string>
|
<string name="dictionary_load_bad_char">Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\".</string>
|
||||||
<string name="dictionary_load_error">Failed loading the dictionary for language \"%1$s\" (%2$s).</string>
|
<string name="dictionary_load_error">Failed loading the dictionary for language \"%1$s\" (%2$s).</string>
|
||||||
|
<string name="dictionary_load_no_internet">Failed downloading the dictionary for language \"%1$s\". Check your Internet connection.</string>
|
||||||
<string name="dictionary_load_cancelled">Dictionary load cancelled.</string>
|
<string name="dictionary_load_cancelled">Dictionary load cancelled.</string>
|
||||||
<string name="dictionary_loaded">Dictionary load completed.</string>
|
<string name="dictionary_loaded">Dictionary load completed.</string>
|
||||||
<string name="dictionary_loading">Loading dictionary (%1$s)…</string>
|
<string name="dictionary_loading">Loading dictionary (%1$s)…</string>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ def generateVersionName() {
|
||||||
return "$versionTagsCount.$commitsSinceLastTag$betaString"
|
return "$versionTagsCount.$commitsSinceLastTag$betaString"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext.exec = { command ->
|
||||||
|
return execThing(command)
|
||||||
|
}
|
||||||
|
|
||||||
ext.getVersionName = { ->
|
ext.getVersionName = { ->
|
||||||
return generateVersionName()
|
return generateVersionName()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
Traditional T9 е 12-клавишна клавиатура за устройства с копчета. Поддържа подскаващ текста на повече от 25 езика и бързи клавиши, а виртуалната клавиатура пресъздава доброто старо усещане за Нокия на съвременните телефони със сензорен екран. И най-хубавото е, че не ви шпионира.
|
Traditional T9 е 12-клавишна клавиатура за устройства с копчета. Поддържа подскаващ текст на повече от 25 езика и бързи клавиши, а виртуалната клавиатура пресъздава доброто старо усещане за Нокия на съвременните телефони със сензорен екран. И най-хубавото е, че не ви шпионира.
|
||||||
|
|
||||||
Поддържани езици: арабски, български, хърватски, чешки, датски, холандски, английски, финландски, френски, немски, гръцки, иврит, унгарски, индонезийски, италиански, кисуахили, норвежки, полски, португалски (европейски и бразилски), румънски, руски, испански, шведски, турски, украински, идиш.
|
Поддържани езици: арабски, български, хърватски, чешки, датски, холандски, английски, финландски, френски, немски, гръцки, иврит, унгарски, индонезийски, италиански, кисуахили, норвежки, полски, португалски (европейски и бразилски), румънски, руски, испански, шведски, турски, украински, идиш.
|
||||||
|
|
||||||
Философия и защита не личните данни:
|
Философия и защита не личните данни:
|
||||||
- Без реклами, специални или платени функции. Всичко е напълно безплатно.
|
- Без реклами, специални или платени функции. Всичко е напълно безплатно.
|
||||||
- Без шпиониране, следене, телеметрия и отчети. Без глупости!
|
- Без шпиониране, следене, телеметрия и отчети. Без глупости!
|
||||||
- Без връзка към интернет, освен когато е активно гласовото въвеждане.
|
- Използва интернет само при активно гласово въвеждане и за изтегляне на речници от Github. Можете да изберете и пълната версия, която съдържа всички езици и не изисква разрешението за интернет.
|
||||||
- Единствено си върши работата.
|
- Единствено си върши работата.
|
||||||
- С отворен код, така че може да проверите горното и сами.
|
- С отворен код, така че може да проверите горното и сами.
|
||||||
- Създадена с помощта на цялата общност.
|
- Създадена с помощта на цялата общност.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Supported languages: Arabic, Bulgarian, Croatian, Czech, Danish, Dutch, English,
|
||||||
Privacy Policy and Philosophy:
|
Privacy Policy and Philosophy:
|
||||||
- No ads, no premium or paid features. It's all free.
|
- No ads, no premium or paid features. It's all free.
|
||||||
- No spying, no tracking, no telemetry or reports. No nothing!
|
- No spying, no tracking, no telemetry or reports. No nothing!
|
||||||
- No network connectivity, except when voice input is active.
|
- Network connectivity is only used for voice input and downloading dictionaries from Github. You can also use the Full version that includes all languages and requires no Internet permission.
|
||||||
- It only does its job.
|
- It only does its job.
|
||||||
- Open-source, so you can verify all the above yourself.
|
- Open-source, so you can verify all the above yourself.
|
||||||
- Created with help from the entire community.
|
- Created with help from the entire community.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue