mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-19 08:20:15 +00:00
allow users to select dictionaries
https://github.com/openboard-team/openboard/pull/569 https://github.com/openboard-team/openboard/pull/578 modified so dictionaries use the correct type instead of always main
This commit is contained in:
parent
62e55fd70b
commit
29c252066d
62 changed files with 502 additions and 16 deletions
|
@ -9,8 +9,8 @@ Plan / to do:
|
|||
* ~upgrade dependencies~
|
||||
* upgrade NDK, https://github.com/openboard-team/openboard/issues/782
|
||||
* maybe: rename (package, app, icon), so it can be installed parallel to OpenBoard, and published on F-Droid
|
||||
* user-selectable dictionaries, https://github.com/openboard-team/openboard/pull/578
|
||||
* make additional dictionaries available for download, and link from app
|
||||
* ~user-selectable dictionaries, https://github.com/openboard-team/openboard/pull/578~
|
||||
* make additional dictionaries available for download (from OpenBoard PRs)
|
||||
* multi-lingual typing, https://github.com/openboard-team/openboard/pull/593
|
||||
* suggestion fixes, https://github.com/openboard-team/openboard/pull/694, https://github.com/openboard-team/openboard/issues/795, https://github.com/openboard-team/openboard/issues/660
|
||||
* improve auto-space insertion, https://github.com/openboard-team/openboard/pull/576
|
||||
|
@ -23,6 +23,10 @@ Plan / to do:
|
|||
Changes:
|
||||
* Updated dependencies
|
||||
* Debug version can be installed along OpenBoard
|
||||
* Allow users to add and replace built-in dictionaries
|
||||
* modified / improved from https://github.com/openboard-team/openboard/pull/569 and https://github.com/openboard-team/openboard/pull/578
|
||||
* dictionaries are available at https://github.com/Helium314/openboard/dictionaries/dict
|
||||
* dictionary files starting with "main_" replace the built-in dictionary for the language, all other names work as add-on dictionaries
|
||||
|
||||
-----
|
||||
|
||||
|
|
|
@ -42,7 +42,8 @@ android {
|
|||
|
||||
ndkVersion '23.2.8568313'
|
||||
androidResources {
|
||||
noCompress 'dict'
|
||||
noCompress 'main.dict'
|
||||
noCompress 'empty.dict'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.content.SharedPreferences;
|
|||
import android.content.res.AssetFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.common.FileUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils;
|
||||
import org.dslul.openboard.inputmethod.latin.define.DecoderSpecificConstants;
|
||||
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader;
|
||||
|
@ -62,6 +63,8 @@ final public class BinaryDictionaryGetter {
|
|||
public static final String MAIN_DICTIONARY_CATEGORY = "main";
|
||||
public static final String ID_CATEGORY_SEPARATOR = ":";
|
||||
|
||||
public static final String ASSETS_DICTIONARY_FOLDER = "dicts";
|
||||
|
||||
// The key considered to read the version attribute in a dictionary file.
|
||||
private static String VERSION_KEY = "version";
|
||||
|
||||
|
@ -170,8 +173,8 @@ final public class BinaryDictionaryGetter {
|
|||
for (File directory : directoryList) {
|
||||
if (!directory.isDirectory()) continue;
|
||||
final String dirLocale =
|
||||
DictionaryInfoUtils.getWordListIdFromFileName(directory.getName());
|
||||
final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale);
|
||||
DictionaryInfoUtils.getWordListIdFromFileName(directory.getName()).toLowerCase(Locale.ENGLISH);
|
||||
final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale.toLowerCase(Locale.ENGLISH));
|
||||
if (LocaleUtils.isMatch(matchLevel)) {
|
||||
final File[] wordLists = directory.listFiles();
|
||||
if (null != wordLists) {
|
||||
|
@ -265,9 +268,16 @@ final public class BinaryDictionaryGetter {
|
|||
}
|
||||
|
||||
if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
|
||||
final File dict = loadDictionaryFromAssets(locale.toString(), context);
|
||||
final AssetFileAddress fallbackAsset;
|
||||
if (dict == null) {
|
||||
// fall back to the old way (maybe remove? will not work if files are compressed)
|
||||
final int fallbackResId =
|
||||
DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale);
|
||||
final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
|
||||
fallbackAsset = loadFallbackResource(context, fallbackResId);
|
||||
} else {
|
||||
fallbackAsset = AssetFileAddress.makeFromFileName(dict.getPath());
|
||||
}
|
||||
if (null != fallbackAsset) {
|
||||
fileList.add(fallbackAsset);
|
||||
}
|
||||
|
@ -275,4 +285,75 @@ final public class BinaryDictionaryGetter {
|
|||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best matching main dictionary from assets.
|
||||
*
|
||||
* Actually copies the dictionary to cache folder, and then returns that file. This allows
|
||||
* the dictionaries to be stored in a compressed way, reducing APK size.
|
||||
* On next load, the dictionary in cache folder is found by getCachedWordLists
|
||||
*
|
||||
* Returns null on IO errors or if no matching dictionary is found
|
||||
*/
|
||||
public static File loadDictionaryFromAssets(final String locale, final Context context) {
|
||||
final String[] dictionaryList = getAssetsDictionaryList(context);
|
||||
if (null == dictionaryList) return null;
|
||||
String bestMatchName = null;
|
||||
int bestMatchLevel = 0;
|
||||
for (String dictionary : dictionaryList) {
|
||||
final String dictLocale =
|
||||
extractLocaleFromAssetsDictionaryFile(dictionary);
|
||||
if (dictLocale == null) continue;
|
||||
// assets files may contain the locale in lowercase, but dictionary headers usually
|
||||
// have an upper case country code, so we compare lowercase here
|
||||
final int matchLevel = LocaleUtils.getMatchLevel(dictLocale.toLowerCase(Locale.ENGLISH), locale.toLowerCase(Locale.ENGLISH));
|
||||
if (LocaleUtils.isMatch(matchLevel) && matchLevel > bestMatchLevel) {
|
||||
bestMatchName = dictionary;
|
||||
}
|
||||
}
|
||||
if (bestMatchName == null) return null;
|
||||
|
||||
// we have a match, now copy contents of the dictionary to cached word lists folder
|
||||
final String bestMatchLocale = extractLocaleFromAssetsDictionaryFile(bestMatchName);
|
||||
if (bestMatchLocale == null) return null;
|
||||
File dictFile = new File(DictionaryInfoUtils.getCacheDirectoryForLocale(bestMatchLocale, context) +
|
||||
File.separator + DictionaryInfoUtils.getMainDictFilename(bestMatchLocale));
|
||||
try {
|
||||
FileUtils.copyStreamToNewFile(
|
||||
context.getAssets().open(ASSETS_DICTIONARY_FOLDER + File.separator + bestMatchName),
|
||||
dictFile);
|
||||
return dictFile;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "exception while looking for locale " + locale, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale for a dictionary file name stored in assets.
|
||||
*
|
||||
* Assumes file name main_[locale].dict
|
||||
*
|
||||
* Returns the locale, or null if file name does not match the pattern
|
||||
*/
|
||||
public static String extractLocaleFromAssetsDictionaryFile(final String dictionaryFileName) {
|
||||
if (dictionaryFileName.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX)
|
||||
&& dictionaryFileName.endsWith(".dict")) {
|
||||
return dictionaryFileName.substring(
|
||||
DictionaryInfoUtils.MAIN_DICT_PREFIX.length(),
|
||||
dictionaryFileName.lastIndexOf('.')
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String[] getAssetsDictionaryList(final Context context) {
|
||||
final String[] dictionaryList;
|
||||
try {
|
||||
dictionaryList = context.getAssets().list(ASSETS_DICTIONARY_FOLDER);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return dictionaryList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
package org.dslul.openboard.inputmethod.latin.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A simple class to help with removing directories recursively.
|
||||
|
@ -58,4 +61,19 @@ public class FileUtils {
|
|||
toFile.delete();
|
||||
return fromFile.renameTo(toFile);
|
||||
}
|
||||
|
||||
public static void copyStreamToNewFile(InputStream in, File outfile) throws IOException {
|
||||
File parentFile = outfile.getParentFile();
|
||||
if (parentFile == null || (!parentFile.exists() && !parentFile.mkdirs())) {
|
||||
throw new IOException("could not create parent folder");
|
||||
}
|
||||
FileOutputStream out = new FileOutputStream(outfile);
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
package org.dslul.openboard.inputmethod.latin.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.Preference
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.TypedValue
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
|
||||
import org.dslul.openboard.inputmethod.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.common.FileUtils
|
||||
import org.dslul.openboard.inputmethod.latin.common.LocaleUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DialogUtils
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class DictionarySettingsFragment : SubScreenFragment() {
|
||||
|
||||
// dict for which dialog is currently open (if any)
|
||||
private var currentDictLocale: Locale? = null
|
||||
private var currentDictState: Int? = null
|
||||
|
||||
private val cachedDictionaryFile by lazy { File(activity.cacheDir.path + File.separator + "temp_dict") }
|
||||
private val currentDictExistsForUser get() = currentDictState == DICT_INTERNAL_AND_USER || currentDictState == DICT_USER_ONLY
|
||||
private val currentDictExistsInternal get() = currentDictState == DICT_INTERNAL_AND_USER || currentDictState == DICT_INTERNAL_ONLY
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.additional_subtype_settings)
|
||||
reloadDictionaries()
|
||||
// + button to add dictionary
|
||||
setHasOptionsMenu(true)
|
||||
activity.actionBar?.setTitle(R.string.dictionary_settings_category)
|
||||
}
|
||||
|
||||
// shows existing dictionaries as preferences
|
||||
private fun reloadDictionaries() {
|
||||
val screen = preferenceScreen ?: return
|
||||
screen.removeAll()
|
||||
val userDicts = mutableSetOf<Locale>()
|
||||
val internalDicts = mutableSetOf<Locale>()
|
||||
// get available dictionaries
|
||||
// cached (internal in use and user dicts)
|
||||
DictionaryInfoUtils.getCachedDirectoryList(activity)?.forEach { dir ->
|
||||
if (!dir.isDirectory)
|
||||
return@forEach
|
||||
dir.list()?.forEach {
|
||||
if (it.endsWith(USER_DICTIONARY_SUFFIX))
|
||||
userDicts.add(dir.name.toLocale())
|
||||
else if (it.startsWith(DictionaryInfoUtils.MAIN_DICT_PREFIX))
|
||||
internalDicts.add(dir.name.toLocale())
|
||||
}
|
||||
}
|
||||
// internal only
|
||||
BinaryDictionaryGetter.getAssetsDictionaryList(activity)?.forEach { dictFile ->
|
||||
BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictFile)?.let {
|
||||
internalDicts.add(it.toLocale())
|
||||
}
|
||||
}
|
||||
|
||||
// first show user-added dictionaries
|
||||
userDicts.sortedBy { it.displayName() }.forEach { dict ->
|
||||
val pref = Preference(activity).apply {
|
||||
title = dict.displayName()
|
||||
setSummary(R.string.user_dictionary_summary)
|
||||
setOnPreferenceClickListener {
|
||||
// open dialog for update or delete / reset
|
||||
currentDictLocale = dict
|
||||
currentDictState = if (internalDicts.contains(dict)) DICT_INTERNAL_AND_USER else DICT_USER_ONLY
|
||||
showUpdateDialog()
|
||||
true
|
||||
}
|
||||
}
|
||||
screen.addPreference(pref)
|
||||
}
|
||||
|
||||
// TODO: only show if language is actually used?
|
||||
internalDicts.sortedBy { it.displayName() }.forEach { dict ->
|
||||
if (userDicts.contains(dict)) return@forEach // don't show a second time
|
||||
val pref = Preference(activity).apply {
|
||||
title = dict.displayName()
|
||||
setSummary(R.string.internal_dictionary_summary)
|
||||
setOnPreferenceClickListener {
|
||||
// open dialog for update, maybe disabling if i can make it work?
|
||||
currentDictLocale = dict
|
||||
currentDictState = DICT_INTERNAL_ONLY
|
||||
showUpdateDialog()
|
||||
true
|
||||
}
|
||||
}
|
||||
screen.addPreference(pref)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUpdateDialog() {
|
||||
// -1: adding new dict, don't know where it may exist
|
||||
// 0: user only -> offer delete
|
||||
// 1: internal only -> only update (and maybe later: disable)
|
||||
// 2: user and internal -> offer reset to internal
|
||||
|
||||
if (currentDictState == null) return
|
||||
if (currentDictLocale == null && currentDictState != DICT_NEW)
|
||||
return
|
||||
|
||||
val link = "<a href='$DICTIONARY_URL'>" +
|
||||
resources.getString(R.string.dictionary_link_text) + "</a>"
|
||||
val message = if (currentDictState == DICT_NEW)
|
||||
Html.fromHtml(resources.getString(R.string.add_new_dictionary, link))
|
||||
else
|
||||
Html.fromHtml(resources.getString(R.string.update_dictionary, link))
|
||||
val title = if (currentDictState == DICT_NEW) R.string.add_new_dictionary_title
|
||||
else R.string.dictionary_settings_category
|
||||
val updateButtonTitle = if (currentDictExistsForUser) R.string.update_dictionary_button
|
||||
else R.string.user_dict_settings_add_menu_title
|
||||
|
||||
val builder = AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setMessage(message)
|
||||
.setTitle(title)
|
||||
.setPositiveButton(updateButtonTitle) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/octet-stream")
|
||||
startActivityForResult(intent, DICTIONARY_REQUEST_CODE)
|
||||
}
|
||||
|
||||
// allow removing dictionaries
|
||||
if (currentDictExistsForUser) {
|
||||
builder.setNeutralButton(if (currentDictExistsInternal) R.string.reset_dictionary else R.string.delete_dict) { _, _ ->
|
||||
AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||
.setTitle(R.string.remove_dictionary_title)
|
||||
.setMessage(resources.getString(R.string.remove_dictionary_message, currentDictLocale?.displayName()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete_dict) { _,_ ->
|
||||
currentDictLocale?.getUserDictFilenames()?.let { files ->
|
||||
var parent: File? = null
|
||||
files.forEach {
|
||||
val f = File(it)
|
||||
parent = f.parentFile
|
||||
f.delete()
|
||||
}
|
||||
if (parent?.list()?.isEmpty() == true)
|
||||
parent?.delete()
|
||||
}
|
||||
reloadDictionaries()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
// make links in the HTML text work
|
||||
(dialog.findViewById<View>(android.R.id.message) as TextView).movementMethod =
|
||||
LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
// copied from CustomInputStyleSettingsFragment
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.add_style, menu)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val value = TypedValue()
|
||||
activity.theme.resolveAttribute(android.R.attr.colorForeground, value, true)
|
||||
menu.findItem(R.id.action_add_style).icon?.setTint(value.data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
val itemId = item.itemId
|
||||
if (itemId == R.id.action_add_style) {
|
||||
currentDictLocale = null
|
||||
currentDictState = DICT_NEW
|
||||
showUpdateDialog()
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
|
||||
if (requestCode == DICTIONARY_REQUEST_CODE) onDictionaryFileSelected(resultCode, resultData)
|
||||
}
|
||||
|
||||
private fun onDictionaryFileSelected(resultCode: Int, resultData: Intent?) {
|
||||
if (resultCode != Activity.RESULT_OK || resultData == null) {
|
||||
onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||
return
|
||||
}
|
||||
val uri = resultData.data ?: return onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||
|
||||
cachedDictionaryFile.delete()
|
||||
try {
|
||||
FileUtils.copyStreamToNewFile(
|
||||
activity.contentResolver.openInputStream(uri),
|
||||
cachedDictionaryFile
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||
return
|
||||
}
|
||||
|
||||
val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length())
|
||||
if (newHeader == null) {
|
||||
cachedDictionaryFile.delete()
|
||||
onDictionaryLoadingError(R.string.dictionary_file_error.resString())
|
||||
return
|
||||
}
|
||||
val locale = newHeader.mLocaleString.toLocale()
|
||||
if (currentDictLocale != null && locale != currentDictLocale) {
|
||||
cachedDictionaryFile.delete()
|
||||
onDictionaryLoadingError(resources.getString(R.string.dictionary_file_wrong_locale, locale.displayName(), currentDictLocale?.displayName()))
|
||||
return
|
||||
}
|
||||
// idString is content of 'dictionary' key, in format <type>:<locale>
|
||||
val dictionaryType = newHeader.mIdString.substringBefore(":")
|
||||
|
||||
val userDictFile = File(locale.getUserDictFilename(dictionaryType))
|
||||
// ask for user confirmation if it would be a version downgrade or if user pressed add new,
|
||||
// but we already have a user dictionary for the same locale
|
||||
val shouldAskMessageId = if (userDictFile.exists()) {
|
||||
val oldHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(userDictFile, 0, userDictFile.length())
|
||||
if (oldHeader != null && oldHeader.mVersionString.toInt() > newHeader.mVersionString.toInt())
|
||||
R.string.overwrite_old_dicitonary_messsage
|
||||
else if (currentDictState == DICT_NEW && currentDictLocale == null)
|
||||
R.string.replace_dictionary_message
|
||||
else 0
|
||||
} else 0
|
||||
if (shouldAskMessageId != 0)
|
||||
showConfirmReplaceDialog(locale, dictionaryType, shouldAskMessageId)
|
||||
else
|
||||
moveCachedFileToDictionaries(locale, dictionaryType)
|
||||
}
|
||||
|
||||
private fun showConfirmReplaceDialog(locale: Locale, dictionaryType: String, messageId: Int) {
|
||||
AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||
.setTitle(R.string.replace_dictionary)
|
||||
.setMessage(resources.getString(messageId, locale.displayName()))
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(R.string.cancel, ) { _,_ ->
|
||||
cachedDictionaryFile.delete()
|
||||
}
|
||||
.setPositiveButton(R.string.replace_dictionary) { _,_ ->
|
||||
moveCachedFileToDictionaries(locale, dictionaryType)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun moveCachedFileToDictionaries(locale: Locale, dictionaryType: String) {
|
||||
val dictFile = File(locale.getUserDictFilename(dictionaryType))
|
||||
if (!cachedDictionaryFile.renameTo(dictFile)) {
|
||||
cachedDictionaryFile.delete()
|
||||
onDictionaryLoadingError(R.string.dictionary_load_error.resString())
|
||||
return
|
||||
}
|
||||
|
||||
// success, now remove internal dictionary file if a main dictionary was added
|
||||
if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT)
|
||||
File(locale.getInternalDictFilename()).delete()
|
||||
|
||||
// inform user about success
|
||||
val successMessageForLocale = resources
|
||||
.getString(R.string.dictionary_load_success, locale.displayName())
|
||||
Toast.makeText(activity, successMessageForLocale, Toast.LENGTH_LONG).show()
|
||||
|
||||
// inform LatinIME about new dictionary
|
||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||
activity.sendBroadcast(newDictBroadcast)
|
||||
reloadDictionaries()
|
||||
}
|
||||
|
||||
private fun onDictionaryLoadingError(message: String) {
|
||||
AlertDialog.Builder(DialogUtils.getPlatformDialogThemeContext(activity))
|
||||
.setNegativeButton(android.R.string.ok, null)
|
||||
.setMessage(message)
|
||||
.setTitle("loading error")
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun Locale.getUserDictFilename(dictionaryType: String) =
|
||||
DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), activity) + File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX
|
||||
|
||||
private fun Locale.getUserDictFilenames(): List<String> {
|
||||
val dicts = mutableListOf<String>()
|
||||
val p = DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), activity)
|
||||
DictionaryInfoUtils.getCachedDirectoryList(activity)?.forEach { dir ->
|
||||
if (!dir.isDirectory)
|
||||
return@forEach
|
||||
dir.list()?.forEach {
|
||||
if (it.endsWith(USER_DICTIONARY_SUFFIX))
|
||||
dicts.add(p + File.separator + it)
|
||||
}
|
||||
}
|
||||
return dicts
|
||||
}
|
||||
|
||||
private fun Locale.getInternalDictFilename() =
|
||||
DictionaryInfoUtils.getCacheDirectoryForLocale(this.toString(), activity) + File.separator + DictionaryInfoUtils.getMainDictFilename(this.toString())
|
||||
|
||||
private fun String.displayName() = LocaleUtils.constructLocaleFromString(this).displayName()
|
||||
|
||||
private fun String.toLocale() = LocaleUtils.constructLocaleFromString(this)
|
||||
|
||||
private fun Locale.displayName() = getDisplayName(resources.configuration.locale)
|
||||
|
||||
private fun Int.resString() = resources.getString(this)
|
||||
|
||||
companion object {
|
||||
private const val DICTIONARY_REQUEST_CODE = 96834
|
||||
private const val DICTIONARY_URL =
|
||||
"https://github.com/Helium314/openboard/dictionaries/dict"
|
||||
private const val USER_DICTIONARY_SUFFIX = "user.dict"
|
||||
|
||||
private const val DICT_INTERNAL_AND_USER = 2
|
||||
private const val DICT_INTERNAL_ONLY = 1
|
||||
private const val DICT_USER_ONLY = 0
|
||||
private const val DICT_NEW = -1
|
||||
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
|
||||
public static final String PREF_CLIPBOARD_CLIPBOARD_KEY = "pref_clipboard_clipboard_key";
|
||||
public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
|
||||
public static final String PREF_ADD_DICTIONARY = "add_dictionary";
|
||||
public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction";
|
||||
public static final String PREF_AUTO_CORRECTION_CONFIDENCE = "pref_key_auto_correction_confidence";
|
||||
// PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE is obsolete. Use PREF_SHOW_SUGGESTIONS instead.
|
||||
|
|
|
@ -52,8 +52,9 @@ import javax.annotation.Nullable;
|
|||
public class DictionaryInfoUtils {
|
||||
private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
|
||||
public static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
|
||||
private static final String DEFAULT_MAIN_DICT = "main";
|
||||
private static final String MAIN_DICT_PREFIX = "main_";
|
||||
public static final String DEFAULT_MAIN_DICT = "main";
|
||||
public static final String MAIN_DICT_PREFIX = DEFAULT_MAIN_DICT + "_";
|
||||
private static final String DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION = "[" + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + "_]";
|
||||
private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
|
||||
// 6 digits - unicode is limited to 21 bits
|
||||
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
|
||||
|
@ -151,7 +152,7 @@ public class DictionaryInfoUtils {
|
|||
/**
|
||||
* Helper method to get the top level cache directory.
|
||||
*/
|
||||
private static String getWordListCacheDirectory(final Context context) {
|
||||
public static String getWordListCacheDirectory(final Context context) {
|
||||
return context.getFilesDir() + File.separator + "dicts";
|
||||
}
|
||||
|
||||
|
@ -212,10 +213,12 @@ public class DictionaryInfoUtils {
|
|||
@Nullable
|
||||
public static String getCategoryFromFileName(@Nonnull final String fileName) {
|
||||
final String id = getWordListIdFromFileName(fileName);
|
||||
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
|
||||
final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION);
|
||||
// An id is supposed to be in format category:locale, so splitting on the separator
|
||||
// should yield a 2-elements array
|
||||
if (2 != idArray.length) {
|
||||
// Also allow '_' as separator, this is ok for locales like pt_br because
|
||||
// we're interested in the part before first separator anyway
|
||||
if (1 == idArray.length) {
|
||||
return null;
|
||||
}
|
||||
return idArray[0];
|
||||
|
@ -225,7 +228,7 @@ public class DictionaryInfoUtils {
|
|||
* Find out the cache directory associated with a specific locale.
|
||||
*/
|
||||
public static String getCacheDirectoryForLocale(final String locale, final Context context) {
|
||||
final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
|
||||
final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale).toLowerCase(Locale.ENGLISH);
|
||||
final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
|
||||
+ relativeDirectoryName;
|
||||
final File directory = new File(absoluteDirectoryName);
|
||||
|
@ -238,10 +241,12 @@ public class DictionaryInfoUtils {
|
|||
}
|
||||
|
||||
public static boolean isMainWordListId(final String id) {
|
||||
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
|
||||
final String[] idArray = id.split(DICTIONARY_CATEGORY_SEPARATOR_EXPRESSION);
|
||||
// An id is supposed to be in format category:locale, so splitting on the separator
|
||||
// should yield a 2-elements array
|
||||
if (2 != idArray.length) {
|
||||
// Also allow '_' as separator, this is ok for locales like pt_br because
|
||||
// we're interested in the part before first separator anyway
|
||||
if (1 == idArray.length) {
|
||||
return false;
|
||||
}
|
||||
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
|
||||
|
@ -318,6 +323,10 @@ public class DictionaryInfoUtils {
|
|||
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
|
||||
}
|
||||
|
||||
public static String getMainDictFilename(@Nonnull final String locale) {
|
||||
return MAIN_DICT_PREFIX + locale.toLowerCase(Locale.ENGLISH) + ".dict";
|
||||
}
|
||||
|
||||
public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
|
||||
final long offset, final long length) {
|
||||
try {
|
||||
|
|
|
@ -467,6 +467,44 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM
|
|||
<string name="dictionary_settings_title">Add-on dictionaries</string>
|
||||
<!-- Title for the prompt dialog which informs the user that a dictionary is available for the current language and asks to decide whether to download it over 3g -->
|
||||
<string name="dictionary_settings_summary">Settings for dictionaries</string>
|
||||
<!-- Title for dictionaries preference screen -->
|
||||
<string name="dictionary_settings_category">Dictionaries</string>
|
||||
<!-- Summary text for dictionary added by user -->
|
||||
<string name="user_dictionary_summary">User-added dictionary</string>
|
||||
<!-- Summary text for built-in dictionary -->
|
||||
<string name="internal_dictionary_summary">Built-in dictionary</string>
|
||||
<!-- Title for the adding new user dictionary dialog -->
|
||||
<string name="add_new_dictionary_title">"Add dictionary from file"</string>
|
||||
<!-- Button text for updating a dictionary -->
|
||||
<string name="update_dictionary_button">"Update"</string>
|
||||
<!-- Message for user dictionary replacement dialog -->
|
||||
<string name="replace_dictionary_message">"Really replace existing user-added dictionary for %s?"</string>
|
||||
<!-- Message when trying to replace user dictionary with older version -->
|
||||
<string name="overwrite_old_dicitonary_messsage">"New dictionary file contains older version code than current file. Really replace current dictionary for %s?"</string>
|
||||
<!-- Title and confirm button text for user dictionary replacement dialog -->
|
||||
<string name="replace_dictionary">"Replace dictionary"</string>
|
||||
<!-- Message for user dictionary remove dialog -->
|
||||
<string name="remove_dictionary_message">"Really remove user-added dictionaries for %s?"</string>
|
||||
<!-- Title for user dictionary remove dialog -->
|
||||
<string name="remove_dictionary_title">"Remove dictionary"</string>
|
||||
<!-- Button text in user dictionary remove dialog for resetting to default -->
|
||||
<string name="reset_dictionary">"Reset to default"</string>
|
||||
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
|
||||
<string name="update_dictionary">"Select a dictionary to replace the current main dictionary. Dictionaries can be downloaded at %s."</string>
|
||||
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
|
||||
<string name="add_new_dictionary">"Select a new dictionary to be added to the list. Dictionaries can be downloaded at the %s."</string>
|
||||
<!-- Title of the link to the download page inserted into selection message (above) -->
|
||||
<string name="dictionary_link_text">"project repository"</string>
|
||||
<!-- Button text for dictionary file selection -->
|
||||
<string name="load_dictionary_file">"Load dictionary"</string>
|
||||
<!-- Toast text shown when dictionary file for a locale was added successfully -->
|
||||
<string name="dictionary_load_success">"Dictionary for %s added"</string>
|
||||
<!-- Text shown when dictionary file could not be read -->
|
||||
<string name="dictionary_file_error">"Error: Selected file is not a valid dictionary file"</string>
|
||||
<!-- Text shown when dictionary file is not for the selected locale -->
|
||||
<string name="dictionary_file_wrong_locale">"Error: Selected file is for %1$s, but %2$s was expected"</string>
|
||||
<!-- Text shown on other errors when loading dictionary file -->
|
||||
<string name="dictionary_load_error">"Error loading dictionary file"</string>
|
||||
<!-- Name of the user dictionaries settings category -->
|
||||
<string name="user_dictionaries">User dictionaries</string>
|
||||
<!-- Name for the "user dictionary" preference item when there is only one -->
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
android:title="@string/settings_screen_correction"
|
||||
android:key="screen_correction"
|
||||
android:icon="@drawable/ic_settings_correction"/>
|
||||
<PreferenceScreen
|
||||
android:fragment="org.dslul.openboard.inputmethod.latin.settings.DictionarySettingsFragment"
|
||||
android:title="@string/dictionary_settings_category"
|
||||
android:key="add_dictionary"
|
||||
android:icon="@drawable/ic_notify_dictionary"/>
|
||||
<PreferenceScreen
|
||||
android:fragment="org.dslul.openboard.inputmethod.latin.settings.AdvancedSettingsFragment"
|
||||
android:title="@string/settings_screen_advanced"
|
||||
|
|
BIN
dictionaries/dict/emoji_fr.dict
Normal file
BIN
dictionaries/dict/emoji_fr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_bg.dict
Normal file
BIN
dictionaries/dict/main_bg.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_cs.dict
Normal file
BIN
dictionaries/dict/main_cs.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_da.dict
Normal file
BIN
dictionaries/dict/main_da.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_de.dict
Normal file
BIN
dictionaries/dict/main_de.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_el.dict
Normal file
BIN
dictionaries/dict/main_el.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_en_au.dict
Normal file
BIN
dictionaries/dict/main_en_au.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_en_gb.dict
Normal file
BIN
dictionaries/dict/main_en_gb.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_en_us.dict
Normal file
BIN
dictionaries/dict/main_en_us.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_eo.dict
Normal file
BIN
dictionaries/dict/main_eo.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_es.dict
Normal file
BIN
dictionaries/dict/main_es.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_fi.dict
Normal file
BIN
dictionaries/dict/main_fi.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_fr.dict
Normal file
BIN
dictionaries/dict/main_fr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_hr.dict
Normal file
BIN
dictionaries/dict/main_hr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_hu.dict
Normal file
BIN
dictionaries/dict/main_hu.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_hy.dict
Normal file
BIN
dictionaries/dict/main_hy.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_it.dict
Normal file
BIN
dictionaries/dict/main_it.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_iw.dict
Normal file
BIN
dictionaries/dict/main_iw.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_ka.dict
Normal file
BIN
dictionaries/dict/main_ka.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_lb.dict
Normal file
BIN
dictionaries/dict/main_lb.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_lt.dict
Normal file
BIN
dictionaries/dict/main_lt.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_lv.dict
Normal file
BIN
dictionaries/dict/main_lv.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_nb.dict
Normal file
BIN
dictionaries/dict/main_nb.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_nl.dict
Normal file
BIN
dictionaries/dict/main_nl.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_pl.dict
Normal file
BIN
dictionaries/dict/main_pl.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_pt_br.dict
Normal file
BIN
dictionaries/dict/main_pt_br.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_pt_pt.dict
Normal file
BIN
dictionaries/dict/main_pt_pt.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_ro.dict
Normal file
BIN
dictionaries/dict/main_ro.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_ru.dict
Normal file
BIN
dictionaries/dict/main_ru.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_sl.dict
Normal file
BIN
dictionaries/dict/main_sl.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_sr.dict
Normal file
BIN
dictionaries/dict/main_sr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_sv.dict
Normal file
BIN
dictionaries/dict/main_sv.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_tr.dict
Normal file
BIN
dictionaries/dict/main_tr.dict
Normal file
Binary file not shown.
BIN
dictionaries/dict/main_uk.dict
Normal file
BIN
dictionaries/dict/main_uk.dict
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue