diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ab9f2726c..8092e3845 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -111,6 +111,22 @@ + + + + + + + cachedDictionaryFile.delete() } - .setPositiveButton(R.string.dictionary_file_wrong_locale_ok) { _, _ -> - addDictAndAskToReplace(newHeader) - } - .show() - return - } - addDictAndAskToReplace(newHeader) - } - - private fun addDictAndAskToReplace(header: DictionaryHeader) { - val dictionaryType = header.mIdString.substringBefore(":") - val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) + - File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX - val dictFile = File(dictFilename) - - fun moveDict(replaced: Boolean) { - if (!cachedDictionaryFile.renameTo(dictFile)) { - return onDictionaryLoadingError(R.string.dictionary_load_error) - } - if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT) { - // replaced main dict, remove the one created from internal data - // todo: currently not, see also BinaryDictionaryGetter.getDictionaryFiles -// val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) + -// File.separator + DictionaryInfoUtils.getMainDictFilename(mainLocaleString) -// File(internalMainDictFilename).delete() - } - val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) - fragment?.activity?.sendBroadcast(newDictBroadcast) + NewDictionaryAdder(context) { replaced, dictFile -> if (!replaced) addDictionaryToView(dictFile, view.findViewById(R.id.dictionaries)) - } - - if (!dictFile.exists()) { - return moveDict(false) - } - confirmDialog(context, context.getString(R.string.replace_dictionary_message, dictionaryType), context.getString( - R.string.replace_dictionary)) { - moveDict(true) - } - } - - private fun onDictionaryLoadingError(messageId: Int) = onDictionaryLoadingError(context.getString(messageId)) - - private fun onDictionaryLoadingError(message: String) { - cachedDictionaryFile.delete() - Toast.makeText(context, message, Toast.LENGTH_LONG).show() + }.addDictionary(uri, mainLocale) } private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) { @@ -395,5 +319,4 @@ private fun getAvailableDictionaryLocales(context: Context, mainLocaleString: St return locales } -private fun String.toLocale() = LocaleUtils.constructLocaleFromString(this) private const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries" diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsActivity.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsActivity.java index 8ceda811b..a27c0b146 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsActivity.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SettingsActivity.java @@ -23,6 +23,7 @@ import android.preference.PreferenceActivity; import org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager; import org.dslul.openboard.inputmethod.latin.utils.FragmentUtils; +import org.dslul.openboard.inputmethod.latin.utils.NewDictionaryAdder; import androidx.core.app.ActivityCompat; @@ -46,6 +47,10 @@ public final class SettingsActivity extends PreferenceActivity actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); } + final Intent i = getIntent(); + if (Intent.ACTION_VIEW.equals(i.getAction()) && i.getData() != null) { + new NewDictionaryAdder(this, null).addDictionary(i.getData(), null); + } } @Override diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SubtypeSettings.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SubtypeSettings.kt index f84da9727..a628bae46 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SubtypeSettings.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/SubtypeSettings.kt @@ -133,6 +133,16 @@ fun getSystemLocales(): List { return systemLocales } +fun hasMatchingSubtypeForLocaleString(localeString: String): Boolean { + require(initialized) + return !resourceSubtypesByLocale[localeString].isNullOrEmpty() +} + +fun getAvailableSubtypeLocaleStrings(): Collection { + require(initialized) + return resourceSubtypesByLocale.keys +} + fun init(context: Context) { if (initialized) return SubtypeLocaleUtils.init(context) // necessary to get the correct getKeyboardLayoutSetName diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt new file mode 100644 index 000000000..41084626a --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/NewDictionaryAdder.kt @@ -0,0 +1,143 @@ +package org.dslul.openboard.inputmethod.latin.utils + +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants +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.makedict.DictionaryHeader +import org.dslul.openboard.inputmethod.latin.settings.* +import java.io.File +import java.io.IOException +import java.util.* + +class NewDictionaryAdder(private val context: Context, private val onAdded: ((Boolean, File) -> Unit)?) { + private val cachedDictionaryFile = File(context.cacheDir.path + File.separator + "temp_dict") + + fun addDictionary(uri: Uri?, mainLocale: Locale?) { + if (uri == null) + return onDictionaryLoadingError(R.string.dictionary_load_error) + + cachedDictionaryFile.delete() + try { + val i = context.contentResolver.openInputStream(uri) + FileUtils.copyStreamToNewFile( + i, + cachedDictionaryFile + ) + } catch (e: IOException) { + return onDictionaryLoadingError(R.string.dictionary_load_error) + } + + val newHeader = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(cachedDictionaryFile, 0, cachedDictionaryFile.length()) + ?: return onDictionaryLoadingError(R.string.dictionary_file_error) + val locale = newHeader.mLocaleString.toLocale() + + if (mainLocale == null) { + val localeName = LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context) + val message = context.getString(R.string.add_new_dictionary_ask_locale, + newHeader.mIdString.substringBefore(":"), + localeName + ) + val b = AlertDialog.Builder(context) + .setTitle(R.string.add_new_dictionary_title) + .setMessage(message) + .setNeutralButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() } + .setNegativeButton(R.string.button_select_language) { _, _ -> selectLocaleForDictionary(newHeader, locale) } + if (hasMatchingSubtypeForLocaleString(locale.toString())) { + val buttonText = context.getString(R.string.button_add_to_language, localeName) + b.setPositiveButton(buttonText) { _, _ -> + addDictAndAskToReplace(newHeader, locale) + } + } + b.show() + return + } + + // ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not, + // e.g. for Persian or Chinese. But at least fail when dictionary certainly is incompatible + if (ScriptUtils.getScriptFromSpellCheckerLocale(locale) != ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale)) + return onDictionaryLoadingError(R.string.dictionary_file_wrong_script) + + if (locale != mainLocale) { + val message = context.resources.getString( + R.string.dictionary_file_wrong_locale, + LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context), + LocaleUtils.getLocaleDisplayNameInSystemLocale(mainLocale, context) + ) + AlertDialog.Builder(context) + .setMessage(message) + .setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() } + .setPositiveButton(R.string.dictionary_file_wrong_locale_ok) { _, _ -> + addDictAndAskToReplace(newHeader, mainLocale) + } + .show() + return + } + addDictAndAskToReplace(newHeader, mainLocale) + } + + private fun selectLocaleForDictionary(newHeader: DictionaryHeader, dictLocale: Locale) { + val localeStrings = getAvailableSubtypeLocaleStrings() + val locales = linkedSetOf() + localeStrings.forEach { + if (it.substringBefore("_") == dictLocale.language) + locales.add(it.toLocale()) + } + localeStrings.forEach { locales.add(it.toLocale()) } + val displayNamesArray = locales.map { LocaleUtils.getLocaleDisplayNameInSystemLocale(it, context) }.toTypedArray() + AlertDialog.Builder(context) + .setTitle(R.string.button_select_language) + .setItems(displayNamesArray) { di, i -> + di.dismiss() + locales.forEachIndexed { index, locale -> + if (index == i) + addDictAndAskToReplace(newHeader, locale) + } + } + .setNegativeButton(android.R.string.cancel) { _, _ -> cachedDictionaryFile.delete() } + .show() + } + + private fun addDictAndAskToReplace(header: DictionaryHeader, mainLocale: Locale) { + val dictionaryType = header.mIdString.substringBefore(":") + val dictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocale.toString(), context) + + File.separator + dictionaryType + "_" + USER_DICTIONARY_SUFFIX + val dictFile = File(dictFilename) + + fun moveDict(replaced: Boolean) { + if (!cachedDictionaryFile.renameTo(dictFile)) { + return onDictionaryLoadingError(R.string.dictionary_load_error) + } + if (dictionaryType == DictionaryInfoUtils.DEFAULT_MAIN_DICT) { + // replaced main dict, remove the one created from internal data + // todo: currently not, see also BinaryDictionaryGetter.getDictionaryFiles +// val internalMainDictFilename = DictionaryInfoUtils.getCacheDirectoryForLocale(mainLocaleString, context) + +// File.separator + DictionaryInfoUtils.getMainDictFilename(mainLocaleString) +// File(internalMainDictFilename).delete() + } + val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) + context.sendBroadcast(newDictBroadcast) + onAdded?.let { it(replaced, dictFile) } + } + + if (!dictFile.exists()) { + return moveDict(false) + } + confirmDialog(context, context.getString(R.string.replace_dictionary_message, dictionaryType), context.getString( + R.string.replace_dictionary)) { + moveDict(true) + } + } + + private fun onDictionaryLoadingError(messageId: Int) { + cachedDictionaryFile.delete() + Toast.makeText(context, messageId, Toast.LENGTH_LONG).show() + } +} + +fun String.toLocale() = LocaleUtils.constructLocaleFromString(this) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bdedc77fe..a41992cef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -491,12 +491,18 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM Built-in dictionary "Add dictionary from file" + + "To which language should the dictionary \"%1$s\" for %2$s be added?" + + "Select language" + + "Add to %s" "Really replace user-added dictionary \"%s\"?" "Replace dictionary" - "Really remove user-added dictionary \"%1$s\"?" + "Really remove user-added dictionary \"%s\"?" "Select a dictionary to add. Dictionaries in .dict format can be downloaded %s."