mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-18 16:03:12 +00:00
register app for opening .dict files (content uri only)
This commit is contained in:
parent
e623d14829
commit
e3240965a8
6 changed files with 183 additions and 80 deletions
|
@ -111,6 +111,22 @@
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- this seems to require files permission, so better don't do it
|
||||||
|
todo: consider adding read storage permission, and ask if the user opens a file
|
||||||
|
but
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:mimeType="application/octet-stream" android:scheme="file" android:host="*" android:pathPattern=".*\\.dict" />
|
||||||
|
</intent-filter>
|
||||||
|
-->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:mimeType="application/octet-stream" android:scheme="content" android:host="*" android:pathPattern=".*\\.dict" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".spellcheck.SpellCheckerSettingsActivity"
|
<activity android:name=".spellcheck.SpellCheckerSettingsActivity"
|
||||||
|
|
|
@ -15,12 +15,9 @@ import androidx.core.view.isVisible
|
||||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants
|
||||||
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
|
import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter
|
||||||
import org.dslul.openboard.inputmethod.latin.R
|
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.common.LocaleUtils
|
||||||
import org.dslul.openboard.inputmethod.latin.makedict.DictionaryHeader
|
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.*
|
import org.dslul.openboard.inputmethod.latin.utils.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
@ -37,7 +34,6 @@ class LanguageSettingsDialog(
|
||||||
private val view = LayoutInflater.from(context).inflate(R.layout.locale_settings_dialog, null)
|
private val view = LayoutInflater.from(context).inflate(R.layout.locale_settings_dialog, null)
|
||||||
private val mainLocaleString = subtypes.first().subtype.locale
|
private val mainLocaleString = subtypes.first().subtype.locale
|
||||||
private val mainLocale = mainLocaleString.toLocale()
|
private val mainLocale = mainLocaleString.toLocale()
|
||||||
private val cachedDictionaryFile by lazy { File(context.cacheDir.path + File.separator + "temp_dict") }
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setTitle(subtypes.first().displayName)
|
setTitle(subtypes.first().displayName)
|
||||||
|
@ -207,82 +203,10 @@ class LanguageSettingsDialog(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewDictionary(uri: Uri?) {
|
override fun onNewDictionary(uri: Uri?) {
|
||||||
if (uri == null)
|
NewDictionaryAdder(context) { replaced, dictFile ->
|
||||||
return onDictionaryLoadingError(R.string.dictionary_load_error)
|
|
||||||
|
|
||||||
cachedDictionaryFile.delete()
|
|
||||||
try {
|
|
||||||
FileUtils.copyStreamToNewFile(
|
|
||||||
context.contentResolver.openInputStream(uri),
|
|
||||||
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()
|
|
||||||
// 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)
|
|
||||||
)
|
|
||||||
Builder(context)
|
|
||||||
.setMessage(message)
|
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ -> 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)
|
|
||||||
if (!replaced)
|
if (!replaced)
|
||||||
addDictionaryToView(dictFile, view.findViewById(R.id.dictionaries))
|
addDictionaryToView(dictFile, view.findViewById(R.id.dictionaries))
|
||||||
}
|
}.addDictionary(uri, mainLocale)
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) {
|
private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) {
|
||||||
|
@ -395,5 +319,4 @@ private fun getAvailableDictionaryLocales(context: Context, mainLocaleString: St
|
||||||
return locales
|
return locales
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toLocale() = LocaleUtils.constructLocaleFromString(this)
|
|
||||||
private const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries"
|
private const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries"
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.preference.PreferenceActivity;
|
||||||
|
|
||||||
import org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager;
|
import org.dslul.openboard.inputmethod.latin.permissions.PermissionsManager;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.FragmentUtils;
|
import org.dslul.openboard.inputmethod.latin.utils.FragmentUtils;
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.NewDictionaryAdder;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
|
|
||||||
|
@ -46,6 +47,10 @@ public final class SettingsActivity extends PreferenceActivity
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
actionBar.setHomeButtonEnabled(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
|
@Override
|
||||||
|
|
|
@ -133,6 +133,16 @@ fun getSystemLocales(): List<Locale> {
|
||||||
return systemLocales
|
return systemLocales
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasMatchingSubtypeForLocaleString(localeString: String): Boolean {
|
||||||
|
require(initialized)
|
||||||
|
return !resourceSubtypesByLocale[localeString].isNullOrEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvailableSubtypeLocaleStrings(): Collection<String> {
|
||||||
|
require(initialized)
|
||||||
|
return resourceSubtypesByLocale.keys
|
||||||
|
}
|
||||||
|
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
if (initialized) return
|
if (initialized) return
|
||||||
SubtypeLocaleUtils.init(context) // necessary to get the correct getKeyboardLayoutSetName
|
SubtypeLocaleUtils.init(context) // necessary to get the correct getKeyboardLayoutSetName
|
||||||
|
|
|
@ -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<Locale>()
|
||||||
|
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)
|
|
@ -491,12 +491,18 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM
|
||||||
<string name="internal_dictionary_summary">Built-in dictionary</string>
|
<string name="internal_dictionary_summary">Built-in dictionary</string>
|
||||||
<!-- Title for the adding new user dictionary dialog -->
|
<!-- Title for the adding new user dictionary dialog -->
|
||||||
<string name="add_new_dictionary_title">"Add dictionary from file"</string>
|
<string name="add_new_dictionary_title">"Add dictionary from file"</string>
|
||||||
|
<!-- Message when opening a dictionary file, showing type and dictionary language, prompting to choose a language to add it to -->
|
||||||
|
<string name="add_new_dictionary_ask_locale">"To which language should the dictionary \"%1$s\" for %2$s be added?"</string>
|
||||||
|
<!-- Button in dialog for choosing a locale -->
|
||||||
|
<string name="button_select_language">"Select language"</string>
|
||||||
|
<!-- Button in dialog for adding dictionary to specific locale -->
|
||||||
|
<string name="button_add_to_language">"Add to %s"</string>
|
||||||
<!-- Message for user dictionary replacement dialog -->
|
<!-- Message for user dictionary replacement dialog -->
|
||||||
<string name="replace_dictionary_message">"Really replace user-added dictionary \"%s\"?"</string>
|
<string name="replace_dictionary_message">"Really replace user-added dictionary \"%s\"?"</string>
|
||||||
<!-- Title and confirm button text for user dictionary replacement dialog -->
|
<!-- Title and confirm button text for user dictionary replacement dialog -->
|
||||||
<string name="replace_dictionary">"Replace dictionary"</string>
|
<string name="replace_dictionary">"Replace dictionary"</string>
|
||||||
<!-- Message for user dictionary remove dialog -->
|
<!-- Message for user dictionary remove dialog -->
|
||||||
<string name="remove_dictionary_message">"Really remove user-added dictionary \"%1$s\"?"</string>
|
<string name="remove_dictionary_message">"Really remove user-added dictionary \"%s\"?"</string>
|
||||||
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
|
<!-- Message for the user dictionary selection dialog. This string will be interpreted as HTML -->
|
||||||
<string name="add_dictionary">"Select a dictionary to add. Dictionaries in .dict format can be downloaded %s."</string>
|
<string name="add_dictionary">"Select a dictionary to add. Dictionaries in .dict format can be downloaded %s."</string>
|
||||||
<!-- Title of the link to the download page inserted into selection message (above) -->
|
<!-- Title of the link to the download page inserted into selection message (above) -->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue