diff --git a/res/drawable-anydpi-v24/ic_notification.xml b/res/drawable-anydpi-v24/ic_notification.xml new file mode 100644 index 00000000..0b078c79 --- /dev/null +++ b/res/drawable-anydpi-v24/ic_notification.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable-hdpi/ic_notification.png b/res/drawable-hdpi/ic_notification.png new file mode 100644 index 00000000..599f02e3 Binary files /dev/null and b/res/drawable-hdpi/ic_notification.png differ diff --git a/res/drawable-mdpi/ic_notification.png b/res/drawable-mdpi/ic_notification.png new file mode 100644 index 00000000..aeb07b78 Binary files /dev/null and b/res/drawable-mdpi/ic_notification.png differ diff --git a/res/drawable-xhdpi/ic_notification.png b/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 00000000..832d3ad4 Binary files /dev/null and b/res/drawable-xhdpi/ic_notification.png differ diff --git a/res/drawable-xxhdpi/ic_notification.png b/res/drawable-xxhdpi/ic_notification.png new file mode 100644 index 00000000..1b3ff500 Binary files /dev/null and b/res/drawable-xxhdpi/ic_notification.png differ diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index aeb90a02..1fbe753c 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -18,10 +18,13 @@ Изтрий речник Неуспешно зареждане. Невалидна дума \"%1$s\" на ред %2$d за език \"%3$s\". + Зареждането на речник е отменено. Несупешно зареждане на речник за език \"%1$s\" (%2$s). + Готово Зареждане на речник (%1$s)… Зареждане на вашия речник… Зареждане на речник Неуспешно зареждане. Липсва речник за \"%1$s\". - Речникът е изтрит успешно + Речникът е изтрит успешно. + diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index a21f5175..037731c0 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -17,6 +17,8 @@ Supprimer le dictionaire Echec du chargement de dictionnaire pour langue «%1$s» (%2$s). + Chargement du dictionnaire annulée. + Terminé Chargement du dictionnaire (%1$s)… Chargement du dictionnaire utilisateur… Charger le dictionnaire diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 95ad9410..a3c4ca2a 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -16,6 +16,8 @@ Carica dizionario utente Eliminare il dizionario + Caricamento dizionario annullato. + Terminato Caricamento dizionario (%1$s)… Caricamento dizionario utente… Caricamento dizionario diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 7104d296..f9105524 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -15,7 +15,7 @@ Woordenboek laden Gebruikerswoordenboek laden Woordenboek wissen - Woordenboek laden… + Woordenboek laden (%1$s)… Gebruikerswoordenboek laden… Woordenboek laden Laden mislukt. Woordenboek voor %1$s niet gevonden. diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 544169f8..47941b2a 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -17,7 +17,9 @@ Загрузить свой словарь Очистить словарь + Загрузка словаря отменена. Ошибка загрузки словаря для языка «%1$s» (%2$s). + Завершена Загрузка словаря (%1$s)… Загрузка пользовательского словаря… Загрузить словарь diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 6a0a1190..3b8b9080 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -16,7 +16,9 @@ Завантажити свій словник Очистити словник + Завантаження словника скасовано. Помилка завантаження словника для мови «%1$s» (%2$s). + Завершено Завантаження словника (%1$s)… Завантаження словника користувача… Завантажити словник diff --git a/res/values/strings.xml b/res/values/strings.xml index c3634f30..e05a1639 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -22,7 +22,9 @@ Clear dictionary Loading failed. Invalid word \"%1$s\" on line %2$d of language \"%3$s\". + Dictionary import cancelled. Failed importing dictionary for language \"%1$s\" (%2$s). + Done Loading dictionary (%1$s)… Loading user dictionary… Load dictionary diff --git a/src/io/github/sspanak/tt9/db/DictionaryImportAlreadyRunningException.java b/src/io/github/sspanak/tt9/db/DictionaryImportAlreadyRunningException.java new file mode 100644 index 00000000..a50b5b33 --- /dev/null +++ b/src/io/github/sspanak/tt9/db/DictionaryImportAlreadyRunningException.java @@ -0,0 +1,7 @@ +package io.github.sspanak.tt9.db; + +public class DictionaryImportAlreadyRunningException extends Exception{ + public DictionaryImportAlreadyRunningException() { + super("Dictionary import is already running."); + } +} diff --git a/src/io/github/sspanak/tt9/db/DictionaryLoader.java b/src/io/github/sspanak/tt9/db/DictionaryLoader.java index a0d2ceda..311ba18d 100644 --- a/src/io/github/sspanak/tt9/db/DictionaryLoader.java +++ b/src/io/github/sspanak/tt9/db/DictionaryLoader.java @@ -23,11 +23,12 @@ public class DictionaryLoader { private final AssetManager assets; private final T9Preferences prefs; - private boolean isStopped = true; + private final Pattern containsPunctuation = Pattern.compile("\\p{Punct}(? languages) { - new Thread() { + public void load(Handler handler, ArrayList languages) throws DictionaryImportAlreadyRunningException { + if (isRunning()) { + throw new DictionaryImportAlreadyRunningException(); + } + + loadThread = new Thread() { @Override public void run() { currentFile = 0; - isStopped = false; // SQLite does not support parallel queries, so let's import them one by one for (Language lang : languages) { - if (isStopped) { + if (isInterrupted()) { break; } importAll(handler, lang); currentFile++; } } - }.start(); + }; + + loadThread.start(); } public void stop() { - isStopped = true; + loadThread.interrupt(); + } + + + public boolean isRunning() { + return loadThread != null && loadThread.isAlive(); } @@ -160,9 +171,9 @@ public class DictionaryLoader { sendProgressMessage(handler, language, 0, 0); for (String word; (word = br.readLine()) != null; line++) { - if (isStopped) { + if (loadThread.isInterrupted()) { br.close(); - sendProgressMessage(handler, language, 0, 0); + sendProgressMessage(handler, language, -1, 0); throw new DictionaryImportAbortedException(); } diff --git a/src/io/github/sspanak/tt9/preferences/T9Preferences.java b/src/io/github/sspanak/tt9/preferences/T9Preferences.java index 7f1f7643..cc599544 100644 --- a/src/io/github/sspanak/tt9/preferences/T9Preferences.java +++ b/src/io/github/sspanak/tt9/preferences/T9Preferences.java @@ -162,7 +162,7 @@ public class T9Preferences { /************* internal settings *************/ - public int getDictionaryImportProgressUpdateInterval() { return 100; /* ms */ } + public int getDictionaryImportProgressUpdateInterval() { return 250; /* ms */ } public int getDictionaryImportWordChunkSize() { return 1000; /* words */ } public int getSuggestionsMax() { return 20; } public int getSuggestionsMin() { return 8; } diff --git a/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java b/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java new file mode 100644 index 00000000..38ce4c71 --- /dev/null +++ b/src/io/github/sspanak/tt9/ui/DictionaryLoadingBar.java @@ -0,0 +1,92 @@ +package io.github.sspanak.tt9.ui; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; +import android.os.Build; + +import androidx.core.app.NotificationCompat; + +import io.github.sspanak.tt9.R; +import io.github.sspanak.tt9.languages.Language; +import io.github.sspanak.tt9.languages.LanguageCollection; + +public class DictionaryLoadingBar { + private static final int NOTIFICATION_ID = 1; + private static final String NOTIFICATION_CHANNEL_ID = "loading-notifications"; + + private final NotificationManager manager; + private final NotificationCompat.Builder notificationBuilder; + private final Resources resources; + + private int maxProgress = 0; + + + DictionaryLoadingBar(Context context) { + resources = context.getResources(); + + manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + manager.createNotificationChannel(new NotificationChannel( + NOTIFICATION_CHANNEL_ID, + "Dictionary Loading Channel", + NotificationManager.IMPORTANCE_LOW + )); + notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); + } else { + notificationBuilder = new NotificationCompat.Builder(context); + } + + notificationBuilder + .setSmallIcon(R.drawable.ic_notification) + .setCategory(NotificationCompat.CATEGORY_PROGRESS) + .setOnlyAlertOnce(true); + } + + + private String generateTitle(int languageId) { + Language lang = LanguageCollection.getLanguage(languageId); + + if (lang != null) { + return resources.getString(R.string.dictionary_loading, lang.getName()); + } + + return resources.getString(R.string.dictionary_load_title); + } + + + public void show(int currentFile, int currentFileProgress, int languageId) { + int totalProgress = 100 * currentFile + currentFileProgress; + + if (currentFileProgress < 0) { + hide(); + return; + } else if (totalProgress >= maxProgress) { + notificationBuilder + .setContentTitle(generateTitle(-1)) + .setContentText(resources.getString(R.string.dictionary_loaded)) + .setOngoing(false) + .setProgress(0, 0, false); + } else { + notificationBuilder + .setContentTitle(generateTitle(languageId)) + .setContentText(currentFileProgress + "%") + .setOngoing(true) + .setProgress(maxProgress, totalProgress, false); + } + + manager.notify(NOTIFICATION_ID, notificationBuilder.build()); + } + + + public void hide() { + manager.cancel(NOTIFICATION_ID); + } + + + public void setFileCount(int count) { + maxProgress = count * 100; + } +} diff --git a/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java b/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java index 1543bc9d..9693cc68 100644 --- a/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java +++ b/src/io/github/sspanak/tt9/ui/TraditionalT9Settings.java @@ -2,7 +2,6 @@ package io.github.sspanak.tt9.ui; import android.app.AlertDialog; import android.app.ListActivity; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -21,6 +20,7 @@ import java.util.ArrayList; import io.github.sspanak.tt9.R; import io.github.sspanak.tt9.db.DictionaryDb; +import io.github.sspanak.tt9.db.DictionaryImportAlreadyRunningException; import io.github.sspanak.tt9.db.DictionaryImportException; import io.github.sspanak.tt9.db.DictionaryLoader; import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException; @@ -35,7 +35,7 @@ import io.github.sspanak.tt9.settings_legacy.SettingAdapter; public class TraditionalT9Settings extends ListActivity implements DialogInterface.OnCancelListener { private DictionaryLoader loader; - ProgressDialog progressDialog; + DictionaryLoadingBar progressBar; Context mContext = null; @@ -43,8 +43,7 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // maybe need this? - // http://stackoverflow.com/questions/7645880/listview-with-onitemclicklistener-android + progressBar = new DictionaryLoadingBar(this); // get settings T9Preferences prefs = new T9Preferences(getApplicationContext()); @@ -98,18 +97,22 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa } private void truncateWords() { - Handler afterTruncate = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - UI.toast(mContext, R.string.dictionary_truncated); - } - }; - DictionaryDb.truncateWords(afterTruncate); + if (loader != null && loader.isRunning()) { + loader.stop(); + } + + Handler afterTruncate = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + UI.toast(mContext, R.string.dictionary_truncated); + } + }; + DictionaryDb.truncateWords(afterTruncate); } private void loadDictionaries() { ArrayList languages = LanguageCollection.getAll(T9Preferences.getInstance().getEnabledLanguages()); - initProgress(100 * languages.size()); + progressBar.setFileCount(languages.size()); Handler loadHandler = new Handler(Looper.getMainLooper()) { @Override @@ -117,7 +120,7 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa String error = msg.getData().getString("error", null); if (error != null) { - hideProgress(); + progressBar.hide(); handleError( error, msg.getData().getInt("languageId", -1), @@ -125,58 +128,24 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa msg.getData().getString("word", "") ); } else { - int langId = msg.getData().getInt("languageId", -1); - Language lang = LanguageCollection.getLanguage(langId); - String langName = lang != null ? lang.getName() : "???"; - - String title = getResources().getString(R.string.dictionary_loading, langName); - showProgress( + progressBar.show( msg.getData().getInt("currentFile", 0), msg.getData().getInt("progress", 0), - title + msg.getData().getInt("languageId", -1) ); } } }; - loader = new DictionaryLoader(this); - loader.load(loadHandler, languages); - } - - - private void initProgress(int max) { - if (progressDialog == null) { - progressDialog = new ProgressDialog(this); - progressDialog.setOnCancelListener(TraditionalT9Settings.this); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + if (loader == null) { + loader = new DictionaryLoader(this); } - progressDialog.setMax(max); - } - - private void showProgress(int currentFile, int currentFileProgress, String title) { - if (progressDialog == null) { - return; - } - - if (title != null) { - progressDialog.setMessage(title); - } - - int totalProgress = 100 * currentFile + currentFileProgress; - if (totalProgress <= 0 || totalProgress >= progressDialog.getMax()) { - progressDialog.dismiss(); - } else { - progressDialog.setProgress(totalProgress); - if (!progressDialog.isShowing()) { - progressDialog.show(); - } - } - } - - private void hideProgress() { - if (progressDialog != null) { - progressDialog.dismiss(); + try { + loader.load(loadHandler, languages); + } catch (DictionaryImportAlreadyRunningException e) { + loader.stop(); + UI.toast(this, getString(R.string.dictionary_import_cancelled)); } }