1
0
Fork 0

* dictionary exporting

* removed unnecessary passing of DictionaryLoader and DictionaryLoadingBar between the preference fragments
This commit is contained in:
sspanak 2024-02-29 16:45:20 +02:00 committed by Dimo Karaivanov
parent f4c425516e
commit 29b2ac2cb6
30 changed files with 953 additions and 203 deletions

View file

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
android:versionCode="383"
android:versionName="28.0"
android:versionCode="395"
android:versionName="28.12"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- required for words exporting on Android < 10 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="false"

View file

@ -0,0 +1,149 @@
package io.github.sspanak.tt9.db.exporter;
import android.Manifest;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import io.github.sspanak.tt9.ConsumerCompat;
public abstract class AbstractExporter {
final protected static String FILE_EXTENSION = ".csv";
final protected static String MIME_TYPE = "text/csv";
protected Runnable failureHandler;
protected ConsumerCompat<String> successHandler;
protected Thread processThread;
private String outputFile;
public static AbstractExporter getInstance() {
throw new RuntimeException("Not Implemented");
}
private void writeAndroid10(Activity activity) throws Exception {
final String fileName = generateFileName();
outputFile = getOutputDir() + File.pathSeparator + fileName;
final ContentValues file = new ContentValues();
file.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
file.put(MediaStore.MediaColumns.MIME_TYPE, MIME_TYPE);
file.put(MediaStore.MediaColumns.RELATIVE_PATH, getOutputDir());
final ContentResolver resolver = activity.getContentResolver();
Uri uri = null;
try {
uri = resolver.insert(MediaStore.Files.getContentUri("external"), file);
if (uri == null) {
throw new IOException("Failed to create new MediaStore entry.");
}
try (OutputStream stream = resolver.openOutputStream(uri)) {
if (stream == null) {
throw new IOException("Failed to open output stream.");
}
stream.write(getWords(activity));
}
} catch (IOException e) {
if (uri != null) {
resolver.delete(uri, null, null);
}
throw e;
}
}
protected void writeLegacy(Activity activity) throws Exception {
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
) {
activity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
final String exportDir = Environment.getExternalStoragePublicDirectory(getOutputDir()).getAbsolutePath();
final String fileName = generateFileName();
outputFile = getOutputDir() + File.pathSeparator + fileName;
final File file = new File(exportDir, fileName);
if (!file.createNewFile()) {
throw new IOException("Failed to create a new file.");
}
try (OutputStream stream = new FileOutputStream(file)) {
stream.write(getWords(activity));
}
MediaScannerConnection.scanFile(activity, new String[]{file.getAbsolutePath()}, new String[]{MIME_TYPE}, null);
}
protected void write(Activity activity) throws Exception {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
writeAndroid10(activity);
} else {
writeLegacy(activity);
}
}
protected String getOutputFile() {
return outputFile;
}
public String getOutputDir() {
// on some older phones, files may not be visible in the DOCUMENTS directory, so we use DOWNLOADS
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? Environment.DIRECTORY_DOCUMENTS : Environment.DIRECTORY_DOWNLOADS;
}
protected void sendSuccess() {
if (successHandler != null) {
successHandler.accept(outputFile);
}
}
protected void sendFailure() {
if (failureHandler != null) {
failureHandler.run();
}
}
public boolean isRunning() {
return processThread != null && processThread.isAlive();
}
public void setSuccessHandler(ConsumerCompat<String> handler) {
successHandler = handler;
}
public void setFailureHandler(Runnable handler) {
failureHandler = handler;
}
@NonNull abstract protected String generateFileName();
@NonNull abstract protected byte[] getWords(Activity activity) throws Exception;
abstract public boolean export(Activity activity);
}

View file

@ -0,0 +1,59 @@
package io.github.sspanak.tt9.db.exporter;
import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.db.sqlite.ReadOps;
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
public class CustomWordsExporter extends AbstractExporter {
private static CustomWordsExporter customWordsExporterSelf;
public static final String LOG_TAG = "dictionary_export";
private static final String BASE_FILE_NAME = "tt9-added-words-export-";
public static CustomWordsExporter getInstance() {
if (customWordsExporterSelf == null) {
customWordsExporterSelf = new CustomWordsExporter();
}
return customWordsExporterSelf;
}
public boolean export(Activity activity) {
if (isRunning()) {
return false;
}
processThread = new Thread(() -> {
try {
write(activity);
sendSuccess();
} catch (Exception e) {
sendFailure();
}
});
processThread.start();
return true;
}
@Override
@NonNull
protected String generateFileName() {
return BASE_FILE_NAME + "-" + System.currentTimeMillis() + FILE_EXTENSION;
}
@NonNull
@Override
protected byte[] getWords(Activity activity) throws Exception {
SQLiteDatabase db = SQLiteOpener.getInstance(activity).getDb();
if (db == null) {
throw new Exception("Could not open database");
}
return new ReadOps().getWords(db, null, true).getBytes();
}
}

View file

@ -0,0 +1,86 @@
package io.github.sspanak.tt9.db.exporter;
import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.db.sqlite.ReadOps;
import io.github.sspanak.tt9.db.sqlite.SQLiteOpener;
import io.github.sspanak.tt9.languages.Language;
public class DictionaryExporter extends AbstractExporter {
private static DictionaryExporter self;
public static final String LOG_TAG = "dictionary_export";
private static final String BASE_FILE_NAME = "tt9-dictionary-export-";
private ArrayList<Language> languages;
private Language currentLanguage;
public static DictionaryExporter getInstance() {
if (self == null) {
self = new DictionaryExporter();
}
return self;
}
public boolean export(Activity activity) {
if (isRunning()) {
return false;
}
if (languages == null || languages.isEmpty()) {
Logger.d(LOG_TAG, "Nothing to do");
return true;
}
processThread = new Thread(() -> { for (Language l : languages) exportLanguage(activity, l); });
processThread.start();
return true;
}
public DictionaryExporter setLanguages(ArrayList<Language> languages) {
this.languages = languages;
return this;
}
@Override
@NonNull
protected String generateFileName() {
return BASE_FILE_NAME + currentLanguage.getLocale().getLanguage() + "-" + System.currentTimeMillis() + FILE_EXTENSION;
}
@Override
@NonNull
protected byte[] getWords(Activity activity) throws Exception {
SQLiteDatabase db = SQLiteOpener.getInstance(activity).getDb();
if (db == null) {
throw new Exception("Could not open database");
}
return new ReadOps().getWords(db, currentLanguage, false).getBytes();
}
private void exportLanguage(Activity activity, Language language) {
currentLanguage = language;
if (currentLanguage == null) {
Logger.e(LOG_TAG, "Cannot export dictionary for null language");
return;
}
try {
long start = System.currentTimeMillis();
write(activity);
sendSuccess();
Logger.d(LOG_TAG, "All words for language: " + currentLanguage.getName() + " loaded. Time: " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
sendFailure();
Logger.e(LOG_TAG, "Failed exporting dictionary for " + currentLanguage.getName() + " to: " + getOutputFile() + ". " + e);
}
}
}

View file

@ -62,6 +62,31 @@ public class ReadOps {
}
/**
* Gets all words as a ready-to-export CSV string. If the language is null or customWords is true,
* only custom words are returned.
*/
@NonNull
public String getWords(@NonNull SQLiteDatabase db, Language language, boolean customWords) {
StringBuilder words = new StringBuilder();
String table = customWords || language == null ? Tables.CUSTOM_WORDS : Tables.getWords(language.getId());
String[] columns = customWords || language == null ? new String[]{"word", "langId"} : new String[]{"word", "frequency"};
try (Cursor cursor = db.query(table, columns, null, null, null, null, null)) {
while (cursor.moveToNext()) {
words
.append(cursor.getString(0))
.append("\t")
.append(cursor.getInt(1))
.append("\n");
}
}
return words.toString();
}
@NonNull
public WordList getWords(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String positions, String filter, int maximumWords, boolean fullOutput) {
if (positions.isEmpty()) {
@ -69,7 +94,6 @@ public class ReadOps {
return new WordList();
}
String wordsQuery = getWordsQuery(language, positions, filter, maximumWords, fullOutput);
if (wordsQuery.isEmpty()) {
return new WordList();
@ -139,7 +163,6 @@ public class ReadOps {
}
@NonNull private String getCustomWordPositions(@NonNull SQLiteDatabase db, Language language, String sequence, int generations) {
try (Cursor cursor = db.rawQuery(getCustomWordPositionsQuery(language, sequence, generations), null)) {
return new WordPositionsStringBuilder().appendFromDbRanges(cursor).toString();
@ -156,7 +179,6 @@ public class ReadOps {
}
@NonNull private String getFactoryWordPositionsQuery(@NonNull Language language, @NonNull String sequence, int generations) {
StringBuilder sql = new StringBuilder("SELECT `start`, `end` FROM ")
.append(Tables.getWordPositions(language.getId()))

View file

@ -8,7 +8,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
@ -16,7 +15,6 @@ import androidx.preference.PreferenceFragmentCompat;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.db.LegacyDb;
import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.ime.helpers.InputModeValidator;
@ -31,7 +29,6 @@ import io.github.sspanak.tt9.preferences.screens.KeyPadScreen;
import io.github.sspanak.tt9.preferences.screens.MainSettingsScreen;
import io.github.sspanak.tt9.preferences.screens.SetupScreen;
import io.github.sspanak.tt9.preferences.screens.UsageStatsScreen;
import io.github.sspanak.tt9.ui.DictionaryLoadingBar;
public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
public SettingsStore settings;
@ -176,14 +173,4 @@ public class PreferencesActivity extends AppCompatActivity implements Preference
Hotkeys.setDefault(settings);
}
}
public DictionaryLoadingBar getDictionaryProgressBar() {
return DictionaryLoadingBar.getInstance(this);
}
public DictionaryLoader getDictionaryLoader() {
return DictionaryLoader.getInstance(this);
}
}

View file

@ -1,9 +1,9 @@
package io.github.sspanak.tt9.preferences.items;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import java.util.ArrayList;
import java.util.List;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.preferences.SettingsStore;
@ -12,14 +12,43 @@ abstract public class ItemClickable {
private long lastClickTime = 0;
protected final Preference item;
private final ArrayList<ItemClickable> otherItems = new ArrayList<>();
public ItemClickable(Preference item) {
this.item = item;
}
public static void disableAll(@NonNull ArrayList<ItemClickable> items) {
for (ItemClickable i : items) {
i.disable();
}
}
public static void enableAll(@NonNull ArrayList<ItemClickable> items) {
for (ItemClickable i : items) {
i.enable();
}
}
public static void disableOthers(@NonNull ArrayList<ItemClickable> items, @NonNull ItemClickable exclude) {
for (ItemClickable i : items) {
if (i != exclude) {
i.disable();
}
}
}
public static void enableAllClickHandlers(@NonNull ArrayList<ItemClickable> items) {
for (ItemClickable i : items) {
i.enableClickHandler();
}
}
public void disable() {
item.setEnabled(false);
}
@ -35,28 +64,6 @@ abstract public class ItemClickable {
}
public ItemClickable setOtherItems(List<ItemClickable> others) {
otherItems.clear();
otherItems.addAll(others);
return this;
}
protected void disableOtherItems() {
for (ItemClickable i : otherItems) {
i.disable();
}
}
protected void enableOtherItems() {
for (ItemClickable i : otherItems) {
i.enable();
}
}
protected boolean debounceClick(Preference p) {
long now = System.currentTimeMillis();
if (now - lastClickTime < SettingsStore.PREFERENCES_CLICK_DEBOUNCE_TIME) {

View file

@ -0,0 +1,93 @@
package io.github.sspanak.tt9.preferences.items;
import android.app.Activity;
import androidx.preference.Preference;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.AbstractExporter;
import io.github.sspanak.tt9.ui.DictionaryNotification;
abstract public class ItemExportAbstract extends ItemClickable {
final protected Activity activity;
final private Runnable onStart;
final private Runnable onFinish;
public ItemExportAbstract(Preference item, Activity activity, Runnable onStart, Runnable onFinish) {
super(item);
this.activity = activity;
this.onStart = onStart;
this.onFinish = onFinish;
AbstractExporter exporter = getExporter();
exporter.setFailureHandler(() -> onFinishExporting(null));
exporter.setSuccessHandler(this::onFinishExporting);
refreshStatus();
}
abstract protected AbstractExporter getExporter();
public ItemExportAbstract refreshStatus() {
if (item != null) {
if (getExporter().isRunning()) {
setLoadingStatus();
} else {
setReadyStatus();
}
}
return this;
}
@Override
protected boolean onClick(Preference p) {
setLoadingStatus();
if (!onStartExporting()) {
setReadyStatus();
}
return true;
}
abstract protected boolean onStartExporting();
protected void onFinishExporting(String outputFile) {
activity.runOnUiThread(() -> {
setReadyStatus();
if (outputFile == null) {
DictionaryNotification.getInstance(activity).showError(
activity.getString(R.string.dictionary_export_failed),
activity.getString(R.string.dictionary_export_failed_more_info)
);
} else {
DictionaryNotification.getInstance(activity).showMessage(
activity.getString(R.string.dictionary_export_finished),
activity.getString(R.string.dictionary_export_finished_more_info, outputFile),
activity.getString(R.string.dictionary_export_finished_more_info, outputFile)
);
}
});
}
abstract protected String getLoadingMessage();
protected void setLoadingStatus() {
onStart.run();
disable();
String loadingMessage = getLoadingMessage();
item.setSummary(loadingMessage);
DictionaryNotification.getInstance(activity).showLoadingMessage(loadingMessage, "");
}
public void setReadyStatus() {
enable();
onFinish.run();
}
}

View file

@ -0,0 +1,42 @@
package io.github.sspanak.tt9.preferences.items;
import android.app.Activity;
import androidx.preference.Preference;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.CustomWordsExporter;
public class ItemExportCustomWords extends ItemExportAbstract {
final public static String NAME = "dictionary_export_custom";
public ItemExportCustomWords(Preference item, Activity activity, Runnable onStart, Runnable onFinish) {
super(item, activity, onStart, onFinish);
}
@Override
protected CustomWordsExporter getExporter() {
return CustomWordsExporter.getInstance();
}
protected boolean onStartExporting() {
return CustomWordsExporter.getInstance().export(activity);
}
@Override
protected String getLoadingMessage() {
return activity.getString(R.string.dictionary_export_generating_csv);
}
public void setReadyStatus() {
super.setReadyStatus();
item.setSummary(activity.getString(
R.string.dictionary_export_custom_words_summary,
CustomWordsExporter.getInstance().getOutputDir()
));
}
}

View file

@ -0,0 +1,62 @@
package io.github.sspanak.tt9.preferences.items;
import android.app.Activity;
import androidx.preference.Preference;
import io.github.sspanak.tt9.Logger;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.exporter.DictionaryExporter;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class ItemExportDictionary extends ItemExportAbstract {
final public static String NAME = "dictionary_export";
protected final SettingsStore settings;
public ItemExportDictionary(Preference item, Activity activity, SettingsStore settings, Runnable onStart, Runnable onFinish) {
super(item, activity, onStart, onFinish);
this.settings = settings;
}
@Override
public ItemExportAbstract refreshStatus() {
if (item != null) {
item.setVisible(Logger.isDebugLevel());
}
return super.refreshStatus();
}
@Override
protected DictionaryExporter getExporter() {
return DictionaryExporter.getInstance();
}
protected boolean onStartExporting() {
return DictionaryExporter.getInstance()
.setLanguages(LanguageCollection.getAll(activity, settings.getEnabledLanguageIds()))
.export(activity);
}
@Override
protected String getLoadingMessage() {
String message = activity.getString(R.string.dictionary_export_generating_csv);
Language language = LanguageCollection.getLanguage(activity, settings.getInputLanguage());
if (language != null) {
message = activity.getString(R.string.dictionary_export_generating_csv_for_language, language.getName());
}
return message;
}
public void setReadyStatus() {
super.setReadyStatus();
item.setSummary("");
}
}

View file

@ -17,21 +17,27 @@ import io.github.sspanak.tt9.ui.UI;
public class ItemLoadDictionary extends ItemClickable {
public static final String NAME = "dictionary_load";
public final static String NAME = "dictionary_load";
private final Context context;
private final SettingsStore settings;
private final Runnable onStart;
private final Runnable onFinish;
private final DictionaryLoader loader;
private final DictionaryLoadingBar progressBar;
public ItemLoadDictionary(Preference item, Context context, SettingsStore settings, DictionaryLoader loader, DictionaryLoadingBar progressBar) {
public ItemLoadDictionary(Preference item, Context context, SettingsStore settings, Runnable onStart, Runnable onFinish) {
super(item);
this.context = context;
this.loader = loader;
this.progressBar = progressBar;
this.loader = DictionaryLoader.getInstance(context);
this.progressBar = DictionaryLoadingBar.getInstance(context);
this.settings = settings;
this.onStart = onStart;
this.onFinish = onFinish;
loader.setOnStatusChange(this::onLoadingStatusChange);
refreshStatus();
@ -56,7 +62,7 @@ public class ItemLoadDictionary extends ItemClickable {
} else if (progressBar.isFailed()) {
setReadyStatus();
UI.toastFromAsync(context, progressBar.getMessage());
} else if (progressBar.isCompleted()) {
} else if (!progressBar.inProgress()) {
setReadyStatus();
UI.toastFromAsync(context, R.string.dictionary_loaded);
}
@ -78,13 +84,13 @@ public class ItemLoadDictionary extends ItemClickable {
private void setLoadingStatus() {
disableOtherItems();
onStart.run();
item.setTitle(context.getString(R.string.dictionary_cancel_load));
}
private void setReadyStatus() {
enableOtherItems();
onFinish.run();
item.setTitle(context.getString(R.string.dictionary_load_title));
item.setSummary(progressBar.isFailed() || progressBar.isCancelled() ? progressBar.getMessage() : "");
}

View file

@ -6,7 +6,6 @@ import java.util.ArrayList;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
@ -17,22 +16,20 @@ public class ItemTruncateAll extends ItemClickable {
public static final String NAME = "dictionary_truncate";
protected final PreferencesActivity activity;
protected final DictionaryLoader loader;
private final Runnable onStart;
private final Runnable onFinish;
public ItemTruncateAll(Preference item, PreferencesActivity activity, DictionaryLoader loader) {
public ItemTruncateAll(Preference item, PreferencesActivity activity, Runnable onStart, Runnable onFinish) {
super(item);
this.activity = activity;
this.loader = loader;
this.onStart = onStart;
this.onFinish = onFinish;
}
@Override
protected boolean onClick(Preference p) {
if (loader != null && loader.isRunning()) {
return false;
}
onStartDeleting();
ArrayList<Integer> languageIds = new ArrayList<>();
for (Language lang : LanguageCollection.getAll(activity, false)) {
@ -45,7 +42,7 @@ public class ItemTruncateAll extends ItemClickable {
protected void onStartDeleting() {
disableOtherItems();
onStart.run();
disable();
item.setSummary(R.string.dictionary_truncating);
}
@ -53,7 +50,7 @@ public class ItemTruncateAll extends ItemClickable {
protected void onFinishDeleting() {
activity.runOnUiThread(() -> {
enableOtherItems();
onFinish.run();
item.setSummary("");
enable();
UI.toastFromAsync(activity, R.string.dictionary_truncated);

View file

@ -5,7 +5,6 @@ import androidx.preference.Preference;
import java.util.ArrayList;
import io.github.sspanak.tt9.db.WordStoreAsync;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
@ -18,19 +17,14 @@ public class ItemTruncateUnselected extends ItemTruncateAll {
private final SettingsStore settings;
public ItemTruncateUnselected(Preference item, PreferencesActivity context, SettingsStore settings, DictionaryLoader loader) {
super(item, context, loader);
public ItemTruncateUnselected(Preference item, PreferencesActivity context, SettingsStore settings, Runnable onStart, Runnable onFinish) {
super(item, context, onStart, onFinish);
this.settings = settings;
}
@Override
protected boolean onClick(Preference p) {
if (loader != null && loader.isRunning()) {
return false;
}
ArrayList<Integer> unselectedLanguageIds = new ArrayList<>();
ArrayList<Integer> selectedLanguageIds = settings.getEnabledLanguageIds();
for (Language lang : LanguageCollection.getAll(activity, false)) {

View file

@ -1,18 +1,28 @@
package io.github.sspanak.tt9.preferences.screens;
import java.util.Arrays;
import java.util.ArrayList;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.db.DictionaryLoader;
import io.github.sspanak.tt9.db.exporter.CustomWordsExporter;
import io.github.sspanak.tt9.db.exporter.DictionaryExporter;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemClickable;
import io.github.sspanak.tt9.preferences.items.ItemExportCustomWords;
import io.github.sspanak.tt9.preferences.items.ItemExportDictionary;
import io.github.sspanak.tt9.preferences.items.ItemLoadDictionary;
import io.github.sspanak.tt9.preferences.items.ItemSelectLanguage;
import io.github.sspanak.tt9.preferences.items.ItemTruncateAll;
import io.github.sspanak.tt9.preferences.items.ItemTruncateUnselected;
public class DictionariesScreen extends BaseScreenFragment {
final public static String NAME = "Dictionaries";
public static final String NAME = "Dictionaries";
private final ArrayList<ItemClickable> clickables = new ArrayList<>();
private ItemLoadDictionary loadItem;
private ItemExportDictionary exportDictionaryItem;
private ItemExportCustomWords exportCustomWordsItem;
public DictionariesScreen() { init(); }
public DictionariesScreen(PreferencesActivity activity) { init(activity); }
@ -30,35 +40,78 @@ public class DictionariesScreen extends BaseScreenFragment {
);
multiSelect.populate().enableValidation();
loadItem = new ItemLoadDictionary(
findPreference(ItemLoadDictionary.NAME),
loadItem = new ItemLoadDictionary(findPreference(ItemLoadDictionary.NAME),
activity,
activity.settings,
activity.getDictionaryLoader(),
activity.getDictionaryProgressBar()
() -> ItemClickable.disableOthers(clickables, loadItem),
this::onActionFinish
);
ItemTruncateUnselected deleteItem = new ItemTruncateUnselected(
exportDictionaryItem = new ItemExportDictionary(findPreference(ItemExportDictionary.NAME),
activity,
activity.settings,
this::onActionStart,
this::onActionFinish
);
clickables.add(loadItem);
clickables.add(exportDictionaryItem);
clickables.add(new ItemTruncateUnselected(
findPreference(ItemTruncateUnselected.NAME),
activity,
activity.settings,
activity.getDictionaryLoader()
);
this::onActionStart,
this::onActionFinish
));
ItemTruncateAll truncateItem = new ItemTruncateAll(
clickables.add(new ItemTruncateAll(
findPreference(ItemTruncateAll.NAME),
activity,
activity.getDictionaryLoader()
);
this::onActionStart,
this::onActionFinish
));
loadItem.setOtherItems(Arrays.asList(truncateItem, deleteItem)).enableClickHandler();
deleteItem.setOtherItems(Arrays.asList(truncateItem, loadItem)).enableClickHandler();
truncateItem.setOtherItems(Arrays.asList(deleteItem, loadItem)).enableClickHandler();
exportCustomWordsItem = new ItemExportCustomWords(
findPreference(ItemExportCustomWords.NAME),
activity,
this::onActionStart,
this::onActionFinish);
clickables.add(exportCustomWordsItem);
ItemClickable.enableAllClickHandlers(clickables);
refreshItems();
}
@Override
public void onResume() {
super.onResume();
refreshItems();
}
private void refreshItems() {
loadItem.refreshStatus();
exportDictionaryItem.refreshStatus();
exportCustomWordsItem.refreshStatus();
if (DictionaryLoader.getInstance(activity).isRunning()) {
loadItem.refreshStatus();
ItemClickable.disableOthers(clickables, loadItem);
} else if (CustomWordsExporter.getInstance().isRunning() || DictionaryExporter.getInstance().isRunning()) {
onActionStart();
} else {
onActionFinish();
}
}
private void onActionStart() {
ItemClickable.disableAll(clickables);
}
private void onActionFinish() {
ItemClickable.enableAll(clickables);
}
}

View file

@ -1,16 +1,8 @@
package io.github.sspanak.tt9.ui;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import androidx.core.app.NotificationCompat;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Locale;
@ -21,28 +13,15 @@ import io.github.sspanak.tt9.languages.InvalidLanguageCharactersException;
import io.github.sspanak.tt9.languages.InvalidLanguageException;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.languages.LanguageCollection;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.screens.DictionariesScreen;
public class DictionaryLoadingBar {
public class DictionaryLoadingBar extends DictionaryNotification {
private static DictionaryLoadingBar self;
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 boolean isStopped = false;
private boolean hasFailed = false;
private int maxProgress = 0;
private int progress = 0;
private String title = "";
private String message = "";
public static DictionaryLoadingBar getInstance(Context context) {
if (self == null) {
@ -54,39 +33,17 @@ public class DictionaryLoadingBar {
private DictionaryLoadingBar(Context context) {
resources = context.getResources();
manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder = getNotificationBuilderCompat(context);
notificationBuilder
.setContentIntent(createNavigationIntent(context))
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setOnlyAlertOnce(true);
super(context);
}
private PendingIntent createNavigationIntent(Context context) {
Intent intent = new Intent(context, PreferencesActivity.class);
intent.putExtra("screen", DictionariesScreen.NAME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return PendingIntent.getActivity(context, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
public String getMessage() {
return message;
}
private NotificationCompat.Builder getNotificationBuilderCompat(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Dictionary Status",
NotificationManager.IMPORTANCE_LOW
));
return new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
} else {
//noinspection deprecation
return new NotificationCompat.Builder(context);
}
public String getTitle() {
return title;
}
@ -100,26 +57,11 @@ public class DictionaryLoadingBar {
}
public boolean isCompleted() {
return progress >= maxProgress;
}
public boolean isFailed() {
return hasFailed;
}
public String getTitle() {
return title;
}
public String getMessage() {
return message;
}
public void show(Context context, Bundle data) {
String error = data.getString("error", null);
int fileCount = data.getInt("fileCount", -1);
@ -185,7 +127,7 @@ public class DictionaryLoadingBar {
message = currentFileProgress + "%";
}
renderProgress();
renderMessage();
}
@ -207,37 +149,4 @@ public class DictionaryLoadingBar {
renderError();
}
private void hide() {
progress = maxProgress = 0;
manager.cancel(NOTIFICATION_ID);
}
private void renderError() {
NotificationCompat.BigTextStyle bigMessage = new NotificationCompat.BigTextStyle();
bigMessage.setBigContentTitle(title);
bigMessage.bigText(message);
notificationBuilder
.setSmallIcon(android.R.drawable.stat_notify_error)
.setStyle(bigMessage)
.setOngoing(false)
.setProgress(maxProgress, progress, false);
manager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
private void renderProgress() {
notificationBuilder
.setSmallIcon(isCompleted() ? R.drawable.ic_done : android.R.drawable.stat_notify_sync)
.setOngoing(!isCompleted())
.setProgress(maxProgress, progress, false)
.setContentTitle(title)
.setContentText(message);
manager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
}

View file

@ -0,0 +1,154 @@
package io.github.sspanak.tt9.ui;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.screens.DictionariesScreen;
public abstract class DictionaryNotification {
private static DictionaryNotification self;
private static final int NOTIFICATION_ID = 1;
private static final String NOTIFICATION_CHANNEL_ID = "dictionary-notifications";
private final NotificationManager manager;
private final NotificationCompat.Builder notificationBuilder;
protected final Resources resources;
protected int maxProgress = 0;
protected int progress = 0;
protected boolean indeterminate = false;
protected String title = "";
protected String message = "";
protected String messageLong = "";
protected DictionaryNotification(Context context) {
resources = context.getResources();
manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder = getNotificationBuilderCompat(context);
notificationBuilder
.setContentIntent(createNavigationIntent(context))
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setOnlyAlertOnce(true);
}
public static DictionaryNotification getInstance(Context context) {
if (self == null) {
self = new DictionaryNotification(context) {
};
}
return self;
}
private PendingIntent createNavigationIntent(Context context) {
Intent intent = new Intent(context, PreferencesActivity.class);
intent.putExtra("screen", DictionariesScreen.NAME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
return PendingIntent.getActivity(context, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
private NotificationCompat.Builder getNotificationBuilderCompat(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Dictionary Status",
NotificationManager.IMPORTANCE_LOW
));
return new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
} else {
//noinspection deprecation
return new NotificationCompat.Builder(context);
}
}
public void showMessage(@NonNull String title, @NonNull String message, @NonNull String messageLong) {
progress = maxProgress = 0;
indeterminate = false;
this.title = title;
this.message = message;
this.messageLong = messageLong;
renderMessage();
}
public void showLoadingMessage(@NonNull String title, @NonNull String message) {
this.title = title;
this.message = message;
messageLong = "";
indeterminate = true;
progress = 1;
maxProgress = 2;
renderMessage();
}
public void showError(@NonNull String title, @NonNull String message) {
progress = maxProgress = 0;
indeterminate = false;
this.title = title;
this.message = message;
renderError();
}
protected void hide() {
progress = maxProgress = 0;
manager.cancel(NOTIFICATION_ID);
}
public boolean inProgress() {
return progress < maxProgress;
}
protected void renderError() {
NotificationCompat.BigTextStyle bigMessage = new NotificationCompat.BigTextStyle();
bigMessage.setBigContentTitle(title);
bigMessage.bigText(message);
notificationBuilder
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle(title)
.setContentText(message)
.setOngoing(false)
.setStyle(bigMessage)
.setProgress(maxProgress, progress, false);
manager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
protected void renderMessage() {
NotificationCompat.BigTextStyle bigMessage = new NotificationCompat.BigTextStyle();
bigMessage.setBigContentTitle(title);
bigMessage.bigText(messageLong.isEmpty() ? message : messageLong);
notificationBuilder
.setSmallIcon(inProgress() ? android.R.drawable.stat_notify_sync : R.drawable.ic_done)
.setOngoing(inProgress())
.setProgress(maxProgress, progress, indeterminate)
.setStyle(bigMessage)
.setContentTitle(title)
.setContentText(message);
manager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
}

View file

@ -34,6 +34,11 @@
<string name="pref_show_soft_function_keys">Бутони на екрана</string>
<string name="key_back">Назад</string>
<string name="key_call">Зелена слушалка</string>
<string name="dictionary_export">Експортирай избраните</string>
<string name="dictionary_export_custom_words_button">Експортирай</string>
<string name="dictionary_export_custom_words_summary">Експортиране на CSV с всички добавени думи в: „%1$s“.</string>
<string name="dictionary_export_failed">Неуспешно експортиране</string>
<string name="dictionary_export_failed_more_info">За повече информация, активирайте режима за отстраняване на грешки и прегледайте журнала.</string>
<string name="dictionary_update_message">Налично е обновление на речника за „%1$s“. Искате ли да го заредите?</string>
<string name="dictionary_update_update">Зареди</string>
<string name="donate_title">Дарете</string>
@ -86,4 +91,8 @@
<string name="add_word_confirm">Да се добави ли „%1$s“ към %2$s?</string>
<string name="pref_hack_key_pad_debounce_time">Защита от случайно повтарящи бутони</string>
<string name="pref_hack_key_pad_debounce_off">Изключена</string>
<string name="dictionary_export_finished">Експортирането завърши</string>
<string name="dictionary_export_finished_more_info">Думите са експортирани в: „%1$s“.</string>
<string name="dictionary_export_generating_csv">Експортиране на CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Експортиране на CSV (%1$s)…</string>
</resources>

View file

@ -31,6 +31,11 @@
<string name="pref_dark_theme_yes">Ja</string>
<string name="pref_dark_theme_no">Nein</string>
<string name="pref_dark_theme_auto">Automatisch</string>
<string name="dictionary_export">Ausgewählte exportieren</string>
<string name="dictionary_export_custom_words_button">Exportieren</string>
<string name="dictionary_export_custom_words_summary">Exportiere ein CSV mit allen hinzugefügten Wörtern nach: „%1$s“.</string>
<string name="dictionary_export_failed">Export fehlgeschlagen</string>
<string name="dictionary_export_failed_more_info">Für weitere Informationen, aktivieren Sie den Debug-Modus und sehen Sie sich die Protokolle an.</string>
<string name="dictionary_update_message">" Wörterbuchupdate verfügbar für „%1$s“. Möchten Sie es laden?"</string>
<string name="dictionary_update_update">Laden</string>
<string name="donate_title">Spenden</string>
@ -39,4 +44,8 @@
<string name="pref_hack_google_chat">Nachrichten mit \"OK\" in Google Chat senden</string>
<string name="pref_hack_key_pad_debounce_time">Schutz vor versehentlichem Tastenwiederholen</string>
<string name="pref_hack_key_pad_debounce_off">Aus</string>
<string name="dictionary_export_finished">Export abgeschlossen</string>
<string name="dictionary_export_finished_more_info">Wörter exportiert nach: „%1$s“.</string>
<string name="dictionary_export_generating_csv">CSV wird exportiert…</string>
<string name="dictionary_export_generating_csv_for_language">CSV wird exportiert (%1$s)…</string>
</resources>

View file

@ -74,6 +74,11 @@
<string name="pref_dark_theme_yes"></string>
<string name="pref_dark_theme_no">No</string>
<string name="pref_dark_theme_auto">Automática</string>
<string name="dictionary_export">Exportar seleccionados</string>
<string name="dictionary_export_custom_words_button">Exportar</string>
<string name="dictionary_export_custom_words_summary">Exportar un CSV con todas las palabras añadidas a: \"%1$s\".</string>
<string name="dictionary_export_failed">Fallo en la exportación</string>
<string name="dictionary_export_failed_more_info">Para obtener más información, habilita el modo de depuración y consulta los registros.</string>
<string name="dictionary_update_message">Actualización del diccionario disponible para «%1$s». ¿Te gustaría cargarlo?</string>
<string name="dictionary_update_update">Cargar</string>
<string name="donate_title">Donar</string>
@ -82,4 +87,8 @@
<string name="pref_hack_google_chat">Enviar mensajes con «OK» en Google Chat</string>
<string name="pref_hack_key_pad_debounce_time">Protección contra la repetición accidental de teclas</string>
<string name="pref_hack_key_pad_debounce_off">Apagado</string>
<string name="dictionary_export_finished">" Exportación completada"</string>
<string name="dictionary_export_finished_more_info">Palabras exportadas a: \"%1$s\".</string>
<string name="dictionary_export_generating_csv">Exportando CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Exportando CSV (%1$s)…</string>
</resources>

View file

@ -74,10 +74,19 @@
<string name="pref_dark_theme_no">Non</string>
<string name="pref_dark_theme_auto">Automatique</string>
<string name="add_word_confirm">Ajouter mot « %1$s » à %2$s?</string>
<string name="dictionary_export">Exporter les sélectionées</string>
<string name="dictionary_export_custom_words_button">Exporter</string>
<string name="dictionary_export_custom_words_summary">Exporter un CSV avec tous les mots ajoutés vers : «%1$s».</string>
<string name="dictionary_export_failed">Échec de l\'exportation</string>
<string name="dictionary_export_failed_more_info">Pour plus d\'informations, activez le mode de débogage et consultez les journaux.</string>
<string name="dictionary_update_message">Mise à jour du dictionnaire «%1$s» disponible. Souhaitez-vous le charger ?</string>
<string name="dictionary_update_update">Charger</string>
<string name="donate_title">Donner</string>
<string name="donate_summary">Si vous aimez %1$s vous pouvez soutenir son développement à : %2$s</string>
<string name="pref_hack_key_pad_debounce_time">Protection contre la répétition accidentelle des touches</string>
<string name="pref_hack_key_pad_debounce_off">Désactivée</string>
<string name="dictionary_export_finished">Exportation terminée</string>
<string name="dictionary_export_finished_more_info">Mots exportés vers : «%1$s».</string>
<string name="dictionary_export_generating_csv">Exportation CSV en cours…</string>
<string name="dictionary_export_generating_csv_for_language">Exportation CSV en cours (%1$s)…</string>
</resources>

View file

@ -40,6 +40,11 @@
<string name="pref_dark_theme_yes">Si</string>
<string name="pref_dark_theme_no">No</string>
<string name="pref_dark_theme_auto">Automatica</string>
<string name="dictionary_export">Esporta selezionate</string>
<string name="dictionary_export_custom_words_button">Esportare</string>
<string name="dictionary_export_custom_words_summary">Esporta un CSV con tutte le parole aggiunte su: \"%1$s\".</string>
<string name="dictionary_export_failed">Esportazione fallita</string>
<string name="dictionary_export_failed_more_info">Per ulteriori informazioni, abilita la modalità di debug e consulta i log.</string>
<string name="dictionary_update_message">Aggiornamento del dizionario disponibile per \"%1$s\". Vuoi caricarlo?</string>
<string name="dictionary_update_update">Carica</string>
<string name="donate_title">Donare</string>
@ -48,5 +53,9 @@
<string name="pref_hack_google_chat">Inviare messaggi con \"OK\" su Google Chat</string>
<string name="pref_hack_key_pad_debounce_time">Protezione contro la ripetizione accidentale dei tasti</string>
<string name="pref_hack_key_pad_debounce_off">Spento</string>
<string name="dictionary_export_finished">Esportazione completata</string>
<string name="dictionary_export_finished_more_info">Parole esportate su: \"%1$s\".</string>
<string name="dictionary_export_generating_csv">CSV in corso…</string>
<string name="dictionary_export_generating_csv_for_language">CSV in corso (%1$s)…</string>
</resources>

View file

@ -67,6 +67,11 @@
<string name="pref_dark_theme_yes">כן</string>
<string name="pref_dark_theme_no">לא</string>
<string name="pref_dark_theme_auto">אוטומטי</string>
<string name="dictionary_export">ייצוא שנבחר</string>
<string name="dictionary_export_custom_words_button">לְיְצוֹא</string>
<string name="dictionary_export_custom_words_summary">ייצוא CSV עם כל המילים שנוספו ל: \"%1$s\".</string>
<string name="dictionary_export_failed">נכשל בייצוא</string>
<string name="dictionary_export_failed_more_info">"למידע נוסף, הפעל מצב איתור באגים וראה את הלוגים. "</string>
<string name="dictionary_update_message">עדכון מילון זמין עבור \"%1$s\". האם תרצה לטעון אותו?</string>
<string name="dictionary_update_update">טען</string>
<string name="donate_title">לִתְרוֹם</string>
@ -75,4 +80,8 @@
<string name="pref_hack_google_chat">שלח הודעות עם \"OK\" ב-Google Chat</string>
<string name="pref_hack_key_pad_debounce_time">הגנה מפני חזרת מפתח בשוגג</string>
<string name="pref_hack_key_pad_debounce_off">כבוי</string>
<string name="dictionary_export_finished">הייצוא הושלם</string>
<string name="dictionary_export_finished_more_info">המילים יוצאות ל: \"%1$s\".</string>
<string name="dictionary_export_generating_csv">מייצא CSV…</string>
<string name="dictionary_export_generating_csv_for_language">מייצא CSV (%1$s)...</string>
</resources>

View file

@ -31,6 +31,11 @@
<string name="pref_dark_theme_yes">Ja</string>
<string name="pref_dark_theme_no">Nee</string>
<string name="pref_dark_theme_auto">Automatisch</string>
<string name="dictionary_export">Geselecteerde exporteren</string>
<string name="dictionary_export_custom_words_button">Exporteren</string>
<string name="dictionary_export_custom_words_summary">Exporteer een CSV met alle toegevoegde woorden naar: \"%1$s\".</string>
<string name="dictionary_export_failed">Exporteren mislukt</string>
<string name="dictionary_export_failed_more_info">Voor meer informatie, schakel de debug-modus in en bekijk de logs.</string>
<string name="dictionary_update_message">Woordenboekupdate beschikbaar voor \"%1$s\". Wil je het laden?</string>
<string name="dictionary_update_update">Laden</string>
<string name="donate_title">Doneer</string>
@ -39,4 +44,8 @@
<string name="pref_hack_google_chat">Stuur berichten met \"OK\" in Google Chat</string>
<string name="pref_hack_key_pad_debounce_time">Bescherming tegen het per ongeluk herhalen van toetsen</string>
<string name="pref_hack_key_pad_debounce_off">Uit</string>
<string name="dictionary_export_finished">Export voltooid</string>
<string name="dictionary_export_finished_more_info">Woorden geëxporteerd naar: \"%1$s\".</string>
<string name="dictionary_export_generating_csv">CSV exporteren…</string>
<string name="dictionary_export_generating_csv_for_language">CSV exporteren (%1$s)…</string>
</resources>

View file

@ -63,6 +63,11 @@
<string name="pref_dark_theme_yes">Sim</string>
<string name="pref_dark_theme_no">Não</string>
<string name="pref_dark_theme_auto">Automático</string>
<string name="dictionary_export">Exportar selecionados</string>
<string name="dictionary_export_custom_words_button">Exportar</string>
<string name="dictionary_export_custom_words_summary">Exportar um CSV com todas as palavras adicionadas para: \"%1$s\".</string>
<string name="dictionary_export_failed">Falha na exportação</string>
<string name="dictionary_export_failed_more_info">Para mais informações, ative o modo de depuração e veja os registros.</string>
<string name="dictionary_update_message">Atualização do dicionário disponível para \"%1$s\". Você gostaria de carregá-lo?</string>
<string name="dictionary_update_update">Carregar</string>
<string name="donate_title">Doar</string>
@ -71,4 +76,8 @@
<string name="pref_hack_google_chat">Enviar mensagens com \"OK\" no Google Chat</string>
<string name="pref_hack_key_pad_debounce_time">Proteção contra repetição acidental de teclas</string>
<string name="pref_hack_key_pad_debounce_off">Desligado</string>
<string name="dictionary_export_finished">Exportação concluída</string>
<string name="dictionary_export_finished_more_info">Palavras exportadas para: \"%1$s\".</string>
<string name="dictionary_export_generating_csv">Exportando CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Exportando CSV (%1$s)…</string>
</resources>

View file

@ -79,6 +79,11 @@
<string name="pref_dark_theme_no">Нет</string>
<string name="pref_dark_theme_auto">Автоматически</string>
<string name="add_word_confirm">Добавить слово «%1$s» в %2$s?</string>
<string name="dictionary_export">Экспорт выбранные</string>
<string name="dictionary_export_custom_words_button">Экспортировать</string>
<string name="dictionary_export_custom_words_summary">Экспорт CSV со всеми добавленными словами в: «%1$s».</string>
<string name="dictionary_export_failed">Ошибка экспорта</string>
<string name="dictionary_export_failed_more_info">Для получения дополнительной информации включите режим отладки и просмотрите журналы.</string>
<string name="dictionary_update_message">Доступно обновление словаря для «%1$s». Хотите загрузить его?</string>
<string name="dictionary_update_update">Загрузить</string>
<string name="donate_title">Поддержать</string>
@ -86,4 +91,8 @@
<string name="pref_hack_google_chat">Отправка сообщения с «ОК» в Google Chat</string>
<string name="pref_hack_key_pad_debounce_time">Защита от случайного повторения нажатий</string>
<string name="pref_hack_key_pad_debounce_off">Выключена</string>
<string name="dictionary_export_finished">Экспорт завершен</string>
<string name="dictionary_export_finished_more_info">Слова экспортированы в: «%1$s».</string>
<string name="dictionary_export_generating_csv">Экспорт CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Экспорт CSV (%1$s)…</string>
</resources>

View file

@ -64,6 +64,11 @@
<string name="dictionary_truncated">Словник видалено.</string>
<string name="dictionary_truncating">Видаляється…</string>
<string name="dictionary_export">Експорт вибрані</string>
<string name="dictionary_export_custom_words_button">Експортувати</string>
<string name="dictionary_export_custom_words_summary">Експорт CSV з усіма доданими словами в: \"%1$s\".</string>
<string name="dictionary_export_failed">Помилка експорту</string>
<string name="dictionary_export_failed_more_info">Для отримання додаткової інформації увімкніть режим відлагодження та перегляньте журнали.</string>
<string name="dictionary_update_message">Доступне оновлення словника для \"%1$s\". Бажаєте його завантажити?</string>
<string name="dictionary_update_update">Завантажити</string>
@ -97,4 +102,8 @@
<string name="char_newline">Новий рядок</string>
<string name="char_space">Пробіл</string>
<string name="dictionary_export_finished">Експорт завершено</string>
<string name="dictionary_export_finished_more_info">Слова експортовані в: \" %1$s \".</string>
<string name="dictionary_export_generating_csv">Експорт CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Експорт CSV (%1$s)…</string>
</resources>

View file

@ -22,6 +22,7 @@
<string name="pref_category_about">About</string>
<string name="pref_category_abc_mode">ABC Mode</string>
<string name="pref_category_custom_words">Added Words</string>
<string name="pref_category_hacks">Compatibility</string>
<string name="pref_category_appearance">Appearance</string>
<string name="pref_category_debug_options" translatable="false">Debug Options</string>
@ -71,6 +72,16 @@
<string name="dictionary_truncated">Dictionary successfully cleared.</string>
<string name="dictionary_truncating">Deleting…</string>
<string name="dictionary_export">Export Selected</string>
<string name="dictionary_export_custom_words_button">Export</string>
<string name="dictionary_export_custom_words_summary">Export a CSV with all added words in: \"%1$s\".</string>
<string name="dictionary_export_failed">Exporting Failed</string>
<string name="dictionary_export_failed_more_info">For more info, enable debugging mode and see the logs.</string>
<string name="dictionary_export_finished">Exporting Finished</string>
<string name="dictionary_export_finished_more_info">Words exported to: \"%1$s\".</string>
<string name="dictionary_export_generating_csv">Exporting CSV…</string>
<string name="dictionary_export_generating_csv_for_language">Exporting CSV (%1$s)…</string>
<string name="dictionary_update_message">Dictionary update available for \"%1$s\". Would you like to load it?</string>
<string name="dictionary_update_update">Load</string>

View file

@ -11,6 +11,12 @@
app:layout="@layout/pref_text"
app:title="@string/dictionary_load_title" />
<Preference
app:key="dictionary_export"
app:layout="@layout/pref_text"
app:title="@string/dictionary_export"
app:isPreferenceVisible="false" />
<Preference
app:key="dictionary_truncate_unselected"
app:layout="@layout/pref_text"
@ -21,4 +27,16 @@
app:layout="@layout/pref_text"
app:title="@string/dictionary_truncate_title" />
<PreferenceCategory
app:title="@string/pref_category_custom_words"
app:layout="@layout/pref_category"
app:singleLineTitle="true">
<Preference
app:key="dictionary_export_custom"
app:layout="@layout/pref_text"
app:title="@string/dictionary_export_custom_words_button" />
</PreferenceCategory>
</PreferenceScreen>