1
0
Fork 0

New settings screen stabilization

* Fixed crashes, caused by using the incorrect Android Context

* Default keys are properly set the very first time Settings screen is opened

* Dictionary loading status is now displayed on the Settings screen

* Dictionary loading time is now displayed after operation is completed
This commit is contained in:
sspanak 2022-11-25 10:31:15 +02:00 committed by Dimo Karaivanov
parent b550d5d5dd
commit a6fa129984
14 changed files with 171 additions and 89 deletions

View file

@ -1,5 +1,6 @@
package io.github.sspanak.tt9.db;
import android.content.Context;
import android.database.sqlite.SQLiteConstraintException;
import android.os.Bundle;
import android.os.Handler;
@ -43,18 +44,23 @@ public class DictionaryDb {
};
private static synchronized void createInstance() {
dbInstance = Room.databaseBuilder(TraditionalT9.getMainContext(), T9RoomDb.class, "t9dict.db")
public static synchronized void init(Context context) {
if (dbInstance == null) {
context = context == null ? TraditionalT9.getMainContext() : context;
dbInstance = Room.databaseBuilder(context, T9RoomDb.class, "t9dict.db")
.addCallback(TRIGGER_CALLBACK)
.build();
}
}
public static synchronized void init() {
init(null);
}
private static T9RoomDb getInstance() {
if (dbInstance == null) {
createInstance();
}
init();
return dbInstance;
}

View file

@ -26,8 +26,10 @@ public class DictionaryLoader {
private final SettingsStore settings;
private final Pattern containsPunctuation = Pattern.compile("\\p{Punct}(?<!-)");
private Handler statusHandler = null;
private Thread loadThread;
private long importStartTime = 0;
private int currentFile = 0;
private long lastProgressUpdate = 0;
@ -49,7 +51,17 @@ public class DictionaryLoader {
}
public void load(Handler handler, ArrayList<Language> languages) throws DictionaryImportAlreadyRunningException {
public void setStatusHandler(Handler handler) {
statusHandler = handler;
}
private long getImportTime() {
return System.currentTimeMillis() - importStartTime;
}
public void load(ArrayList<Language> languages) throws DictionaryImportAlreadyRunningException {
if (isRunning()) {
throw new DictionaryImportAlreadyRunningException();
}
@ -58,12 +70,13 @@ public class DictionaryLoader {
@Override
public void run() {
currentFile = 0;
importStartTime = System.currentTimeMillis();
// SQLite does not support parallel queries, so let's import them one by one
for (Language lang : languages) {
if (isInterrupted()) {
break;
}
importAll(handler, lang);
importAll(lang);
currentFile++;
}
}
@ -83,12 +96,12 @@ public class DictionaryLoader {
}
private void importAll(Handler handler, Language language) {
private void importAll(Language language) {
final String logTag = "tt9.DictionaryLoader.importAll";
if (language == null) {
Logger.e(logTag, "Failed loading a dictionary for NULL language.");
sendError(handler, InvalidLanguageException.class.getSimpleName(), -1);
sendError(InvalidLanguageException.class.getSimpleName(), -1);
return;
}
@ -102,7 +115,7 @@ public class DictionaryLoader {
try {
start = System.currentTimeMillis();
importWords(handler, language);
importWords(language);
Logger.i(
logTag,
"Dictionary: '" + language.getDictionaryFile() + "'" +
@ -117,7 +130,7 @@ public class DictionaryLoader {
);
} catch (DictionaryImportException e) {
stop();
sendImportError(handler, DictionaryImportException.class.getSimpleName(), language.getId(), e.line, e.word);
sendImportError(DictionaryImportException.class.getSimpleName(), language.getId(), e.line, e.word);
Logger.e(
logTag,
@ -129,7 +142,7 @@ public class DictionaryLoader {
);
} catch (Exception e) {
stop();
sendError(handler, e.getClass().getSimpleName(), language.getId());
sendError(e.getClass().getSimpleName(), language.getId());
Logger.e(
logTag,
@ -168,12 +181,12 @@ public class DictionaryLoader {
}
private void importWords(Handler handler, Language language) throws Exception {
importWords(handler, language, language.getDictionaryFile());
private void importWords(Language language) throws Exception {
importWords(language, language.getDictionaryFile());
}
private void importWords(Handler handler, Language language, String dictionaryFile) throws Exception {
private void importWords(Language language, String dictionaryFile) throws Exception {
long totalWords = countWords(dictionaryFile);
BufferedReader br = new BufferedReader(new InputStreamReader(assets.open(dictionaryFile), StandardCharsets.UTF_8));
@ -181,17 +194,21 @@ public class DictionaryLoader {
ArrayList<Word> dbWords = new ArrayList<>();
long line = 0;
sendProgressMessage(handler, language, 0, 0);
sendProgressMessage(language, 0, 0);
for (String word; (word = br.readLine()) != null; line++) {
if (loadThread.isInterrupted()) {
br.close();
sendProgressMessage(handler, language, -1, 0);
sendProgressMessage(language, -1, 0);
throw new DictionaryImportAbortedException();
}
validateWord(language, word, line);
try {
dbWords.add(stringToWord(language, word));
} catch (InvalidLanguageCharactersException e) {
throw new DictionaryImportException(dictionaryFile, word, line);
}
if (line % settings.getDictionaryImportWordChunkSize() == 0) {
DictionaryDb.insertWordsSync(dbWords);
@ -200,12 +217,12 @@ public class DictionaryLoader {
if (totalWords > 0) {
int progress = (int) Math.floor(100.0 * line / totalWords);
sendProgressMessage(handler, language, progress, settings.getDictionaryImportProgressUpdateInterval());
sendProgressMessage(language, progress, settings.getDictionaryImportProgressUpdateInterval());
}
}
br.close();
sendProgressMessage(handler, language, 100, 0);
sendProgressMessage(language, 100, 0);
}
@ -242,7 +259,14 @@ public class DictionaryLoader {
}
private void sendProgressMessage(Handler handler, Language language, int progress, int progressUpdateInterval) {
private void sendProgressMessage(Language language, int progress, int progressUpdateInterval) {
if (statusHandler == null) {
Logger.w(
"tt9/DictionaryLoader.sendProgressMessage",
"Cannot send progress without a status Handler. Ignoring message.");
return;
}
long now = System.currentTimeMillis();
if (now - lastProgressUpdate < progressUpdateInterval) {
return;
@ -252,32 +276,43 @@ public class DictionaryLoader {
Bundle bundle = new Bundle();
bundle.putInt("languageId", language.getId());
bundle.putLong("time", getImportTime());
bundle.putInt("progress", progress);
bundle.putInt("currentFile", currentFile);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
statusHandler.sendMessage(msg);
}
private void sendError(Handler handler, String message, int langId) {
private void sendError(String message, int langId) {
if (statusHandler == null) {
Logger.w("tt9/DictionaryLoader.sendError", "Cannot send an error without a status Handler. Ignoring message.");
return;
}
Bundle bundle = new Bundle();
bundle.putString("error", message);
bundle.putInt("languageId", langId);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
statusHandler.sendMessage(msg);
}
private void sendImportError(Handler handler, String message, int langId, long fileLine, String word) {
private void sendImportError(String message, int langId, long fileLine, String word) {
if (statusHandler == null) {
Logger.w("tt9/DictionaryLoader.sendError", "Cannot send an import error without a status Handler. Ignoring message.");
return;
}
Bundle bundle = new Bundle();
bundle.putString("error", message);
bundle.putLong("fileLine", fileLine);
bundle.putLong("fileLine", fileLine + 1);
bundle.putInt("languageId", langId);
bundle.putString("word", word);
Message msg = new Message();
msg.setData(bundle);
handler.sendMessage(msg);
statusHandler.sendMessage(msg);
}
}

View file

@ -48,7 +48,10 @@ public class InputModeValidator {
return inputMode;
}
InputMode newMode = InputMode.getInstance(allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123);
InputMode newMode = InputMode.getInstance(
settings,
allowedModes.size() > 0 ? allowedModes.get(0) : InputMode.MODE_123
);
settings.saveInputMode(newMode);
if (newMode.getId() != inputMode.getId()) {

View file

@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.List;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
@ -43,7 +44,7 @@ public class TraditionalT9 extends KeyPadHandler {
private void loadSettings() {
mLanguage = LanguageCollection.getLanguage(settings.getInputLanguage());
mEnabledLanguages = settings.getEnabledLanguageIds();
mInputMode = InputMode.getInstance(settings.getInputMode());
mInputMode = InputMode.getInstance(settings, settings.getInputMode());
mInputMode.setTextCase(settings.getTextCase());
}
@ -64,12 +65,14 @@ public class TraditionalT9 extends KeyPadHandler {
protected void onInit() {
self = this;
DictionaryDb.init(this);
if (softKeyHandler == null) {
softKeyHandler = new SoftKeyHandler(this);
}
if (mSuggestionView == null) {
mSuggestionView = new SuggestionsView(softKeyHandler.getView());
mSuggestionView = new SuggestionsView(settings, softKeyHandler.getView());
}
loadSettings();
@ -446,7 +449,7 @@ public class TraditionalT9 extends KeyPadHandler {
private void nextInputMode() {
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
clearSuggestions();
mInputMode = InputMode.getInstance(InputMode.MODE_123);
mInputMode = InputMode.getInstance(settings, InputMode.MODE_123);
}
// when typing a word or viewing scrolling the suggestions, only change the case
else if (!isSuggestionViewHidden()) {
@ -459,7 +462,7 @@ public class TraditionalT9 extends KeyPadHandler {
mInputMode.setTextCase(InputMode.CASE_UPPER);
} else {
int modeIndex = (allowedInputModes.indexOf(mInputMode.getId()) + 1) % allowedInputModes.size();
mInputMode = InputMode.getInstance(allowedInputModes.get(modeIndex));
mInputMode = InputMode.getInstance(settings, allowedInputModes.get(modeIndex));
mInputMode.defaultTextCase();
}
@ -509,11 +512,11 @@ public class TraditionalT9 extends KeyPadHandler {
int lastInputModeId = settings.getInputMode();
if (allowedInputModes.contains(lastInputModeId)) {
mInputMode = InputMode.getInstance(lastInputModeId);
mInputMode = InputMode.getInstance(settings, lastInputModeId);
} else if (allowedInputModes.contains(InputMode.MODE_ABC)) {
mInputMode = InputMode.getInstance(InputMode.MODE_ABC);
mInputMode = InputMode.getInstance(settings, InputMode.MODE_ABC);
} else {
mInputMode = InputMode.getInstance(allowedInputModes.get(0));
mInputMode = InputMode.getInstance(settings, allowedInputModes.get(0));
}
if (InputFieldHelper.isDialerField(inputField)) {

View file

@ -6,6 +6,7 @@ import java.util.ArrayList;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.SettingsStore;
abstract public class InputMode {
// typing mode
@ -26,10 +27,10 @@ abstract public class InputMode {
protected String word = null;
public static InputMode getInstance(int mode) {
public static InputMode getInstance(SettingsStore settings, int mode) {
switch (mode) {
case MODE_PREDICTIVE:
return new ModePredictive();
return new ModePredictive(settings);
case MODE_ABC:
return new ModeABC();
default:

View file

@ -14,6 +14,8 @@ import io.github.sspanak.tt9.languages.Punctuation;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class ModePredictive extends InputMode {
private final SettingsStore settings;
public int getId() { return MODE_PREDICTIVE; }
private boolean isEmoji = false;
@ -32,7 +34,8 @@ public class ModePredictive extends InputMode {
private final Pattern endOfSentence = Pattern.compile("(?<!\\.)[.?!]\\s*$");
ModePredictive() {
ModePredictive(SettingsStore settings) {
this.settings = settings;
allowedTextCases.add(CASE_LOWER);
allowedTextCases.add(CASE_CAPITALIZE);
allowedTextCases.add(CASE_UPPER);
@ -194,8 +197,8 @@ public class ModePredictive extends InputMode {
language,
digitSequence,
stem,
SettingsStore.getInstance().getSuggestionsMin(),
SettingsStore.getInstance().getSuggestionsMax()
settings.getSuggestionsMin(),
settings.getSuggestionsMax()
);
return true;

View file

@ -22,19 +22,25 @@ public class ItemLoadDictionary extends ItemClickable {
public static final String NAME = "dictionary_load";
private final Context context;
private final SettingsStore settings;
private final DictionaryLoader loader;
private final DictionaryLoadingBar progressBar;
ItemLoadDictionary(Preference item, Context context, DictionaryLoader loader, DictionaryLoadingBar progressBar) {
ItemLoadDictionary(Preference item, Context context, SettingsStore settings, DictionaryLoader loader, DictionaryLoadingBar progressBar) {
super(item);
this.context = context;
this.loader = loader;
this.progressBar = progressBar;
this.settings = settings;
loader.setStatusHandler(onDictionaryLoading);
if (!progressBar.isCompleted() && !progressBar.isFailed()) {
changeToCancelButton();
} else {
changeToLoadButton();
}
}
@ -43,6 +49,7 @@ public class ItemLoadDictionary extends ItemClickable {
@Override
public void handleMessage(Message msg) {
progressBar.show(msg.getData());
item.setSummary(progressBar.getTitle() + " " + progressBar.getMessage());
if (progressBar.isCompleted()) {
changeToLoadButton();
@ -57,11 +64,11 @@ public class ItemLoadDictionary extends ItemClickable {
@Override
protected boolean onClick(Preference p) {
ArrayList<Language> languages = LanguageCollection.getAll(SettingsStore.getInstance().getEnabledLanguageIds());
ArrayList<Language> languages = LanguageCollection.getAll(settings.getEnabledLanguageIds());
progressBar.setFileCount(languages.size());
try {
loader.load(onDictionaryLoading, languages);
loader.load(languages);
changeToCancelButton();
} catch (DictionaryImportAlreadyRunningException e) {
loader.stop();
@ -79,5 +86,6 @@ public class ItemLoadDictionary extends ItemClickable {
public void changeToLoadButton() {
item.setTitle(context.getString(R.string.dictionary_load_title));
item.setSummary(progressBar.isFailed() ? progressBar.getMessage() : "");
}
}

View file

@ -16,7 +16,7 @@ public class ItemResetKeys extends ItemClickable {
private final SettingsStore settings;
ItemResetKeys(Preference item, Context context, SectionKeymap dropdowns, SettingsStore settings) {
ItemResetKeys(Preference item, Context context, SettingsStore settings, SectionKeymap dropdowns) {
super(item);
this.context = context;
this.dropdowns = dropdowns;

View file

@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryDb;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.ui.DictionaryLoadingBar;
@ -17,7 +18,9 @@ public class PreferencesActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
settings = new SettingsStore(this);
DictionaryDb.init(this);
validateFunctionKeys();
applyTheme();
buildScreen();
}
@ -39,6 +42,13 @@ public class PreferencesActivity extends AppCompatActivity {
}
private void validateFunctionKeys() {
if (!settings.areFunctionKeysSet()) {
settings.setDefaultKeys();
}
}
DictionaryLoadingBar getDictionaryProgressBar() {
return DictionaryLoadingBar.getInstance(this);
}

View file

@ -63,6 +63,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
ItemLoadDictionary loadItem = new ItemLoadDictionary(
findPreference(ItemLoadDictionary.NAME),
activity,
activity.settings,
activity.getDictionaryLoader(),
activity.getDictionaryProgressBar()
);
@ -94,7 +95,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
SectionKeymap section = new SectionKeymap(Arrays.asList(dropDowns), activity, activity.settings);
section.populate().activate();
(new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, section, activity.settings))
(new ItemResetKeys(findPreference(ItemResetKeys.NAME), activity, activity.settings, section))
.enableClickHandler();
}

View file

@ -13,13 +13,11 @@ import java.util.HashSet;
import java.util.Set;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.ime.TraditionalT9;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.languages.LanguageCollection;
public class SettingsStore {
private static SettingsStore self;
private final SharedPreferences prefs;
private final SharedPreferences.Editor prefsEditor;
@ -31,15 +29,6 @@ public class SettingsStore {
}
public static SettingsStore getInstance() {
if (self == null) {
self = new SettingsStore(TraditionalT9.getMainContext());
}
return self;
}
/************* validators *************/
private boolean doesLanguageExist(int langId) {

View file

@ -23,12 +23,15 @@ public class AddWordAct extends AppCompatActivity {
private View main;
private int lang;
private SettingsStore settings;
private String word;
@Override
protected void onCreate(Bundle savedData) {
settings = new SettingsStore(this);
AppCompatDelegate.setDefaultNightMode(
SettingsStore.getInstance().getDarkTheme() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
settings.getDarkTheme() ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
);
super.onCreate(savedData);
@ -53,7 +56,7 @@ public class AddWordAct extends AppCompatActivity {
switch (msg.what) {
case 0:
Logger.d("onAddedWord", "Added word: '" + word + "'...");
SettingsStore.getInstance().saveLastWord(word);
settings.saveLastWord(word);
break;
case 1:

View file

@ -11,6 +11,7 @@ import androidx.core.app.NotificationCompat;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Locale;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryImportException;
@ -30,9 +31,11 @@ public class DictionaryLoadingBar {
private final NotificationCompat.Builder notificationBuilder;
private final Resources resources;
private boolean hasFailed = false;
private int maxProgress = 0;
private int progress = 0;
private boolean hasFailed = false;
private String title = "";
private String message = "";
public static DictionaryLoadingBar getInstance(Context context) {
@ -52,7 +55,7 @@ public class DictionaryLoadingBar {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Dictionary Loading Channel",
"Dictionary Status",
NotificationManager.IMPORTANCE_LOW
));
notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
@ -83,6 +86,16 @@ public class DictionaryLoadingBar {
}
public String getTitle() {
return title;
}
public String getMessage() {
return message;
}
public void show(Bundle data) {
String error = data.getString("error", null);
@ -97,6 +110,7 @@ public class DictionaryLoadingBar {
} else {
hasFailed = false;
showProgress(
data.getLong("time", 0),
data.getInt("currentFile", 0),
data.getInt("progress", 0),
data.getInt("languageId", -1)
@ -116,32 +130,31 @@ public class DictionaryLoadingBar {
}
private void showProgress(int currentFile, int currentFileProgress, int languageId) {
progress = 100 * currentFile + currentFileProgress;
private void showProgress(long time, int currentFile, int currentFileProgress, int languageId) {
if (currentFileProgress < 0) {
hide();
} else if (progress >= maxProgress) {
renderProgress(
generateTitle(-1),
resources.getString(R.string.completed),
0,
0
);
} else {
renderProgress(
generateTitle(languageId),
currentFileProgress + "%",
progress,
maxProgress
);
return;
}
progress = 100 * currentFile + currentFileProgress;
if (progress >= maxProgress) {
progress = maxProgress = 0;
title = generateTitle(-1);
String timeFormat = time > 60000 ? " (%1.0fs)" : " (%1.1fs)";
message = resources.getString(R.string.completed) + String.format(Locale.ENGLISH, timeFormat, time / 1000.0);
} else {
title = generateTitle(languageId);
message = currentFileProgress + "%";
}
renderProgress();
}
private void showError(String errorType, int langId, long line, String word) {
Language lang = LanguageCollection.getLanguage(langId);
String message;
if (lang == null || errorType.equals(InvalidLanguageException.class.getSimpleName())) {
message = resources.getString(R.string.add_word_invalid_language);
@ -156,16 +169,20 @@ public class DictionaryLoadingBar {
message = resources.getString(R.string.dictionary_load_error, languageName, errorType);
}
renderError(generateTitle(-1), message);
title = generateTitle(-1);
progress = maxProgress = 0;
renderError();
}
private void hide() {
progress = maxProgress = 0;
manager.cancel(NOTIFICATION_ID);
}
private void renderError(String title, String message) {
private void renderError() {
NotificationCompat.BigTextStyle bigMessage = new NotificationCompat.BigTextStyle();
bigMessage.setBigContentTitle(title);
bigMessage.bigText(message);
@ -174,16 +191,16 @@ public class DictionaryLoadingBar {
.setSmallIcon(android.R.drawable.stat_notify_error)
.setStyle(bigMessage)
.setOngoing(false)
.setProgress(0, 0, false);
.setProgress(maxProgress, progress, false);
manager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
private void renderProgress(String title, String message, int progress, int maxProgress) {
private void renderProgress() {
notificationBuilder
.setSmallIcon(progress < maxProgress ? android.R.drawable.stat_notify_sync : R.drawable.ic_done)
.setOngoing(progress < maxProgress)
.setSmallIcon(isCompleted() ? R.drawable.ic_done : android.R.drawable.stat_notify_sync)
.setOngoing(!isCompleted())
.setProgress(maxProgress, progress, false)
.setContentTitle(title)
.setContentText(message);

View file

@ -22,12 +22,15 @@ public class SuggestionsView {
protected int selectedIndex = 0;
private final RecyclerView mView;
private final SettingsStore settings;
private SuggestionsAdapter mSuggestionsAdapter;
public SuggestionsView(View mainView) {
public SuggestionsView(SettingsStore settings, View mainView) {
super();
this.settings = settings;
mView = mainView.findViewById(R.id.main_suggestions_list);
mView.setLayoutManager(new LinearLayoutManager(mainView.getContext(), RecyclerView.HORIZONTAL,false));
@ -40,8 +43,8 @@ public class SuggestionsView {
private void configureAnimation() {
DefaultItemAnimator animator = new DefaultItemAnimator();
int translateDuration = SettingsStore.getInstance().getSuggestionTranslateAnimationDuration();
int selectDuration = SettingsStore.getInstance().getSuggestionSelectAnimationDuration();
int translateDuration = settings.getSuggestionTranslateAnimationDuration();
int selectDuration = settings.getSuggestionSelectAnimationDuration();
animator.setMoveDuration(selectDuration);
animator.setChangeDuration(translateDuration);