From 3758cfe4035c741dfe7e748ca02c4dbb3665d218 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 30 Aug 2023 11:45:30 +0200 Subject: [PATCH] offer opening dictionary repository if no local dictionary is found when enabling a subtype --- .../settings/LanguageFilterListPreference.kt | 2 + .../latin/settings/LanguageSettingsDialog.kt | 76 +++++++------------ .../settings/LanguageSettingsFragment.kt | 10 ++- .../inputmethod/latin/settings/Settings.java | 2 + .../latin/utils/DictionaryUtils.kt | 70 +++++++++++++++++ app/src/main/res/values/strings.xml | 11 ++- 6 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterListPreference.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterListPreference.kt index e7691faa2..8ead25561 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterListPreference.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageFilterListPreference.kt @@ -134,6 +134,8 @@ class LanguageAdapter(list: List> = listOf(), context: setOnCheckedChangeListener { _, b -> if (b) { if (infos.size == 1) { + if (!infos.first().hasDictionary) + showMissingDictionaryDialog(context, infos.first().subtype.locale.toLocale()) addEnabledSubtype(prefs, infos.first().subtype) infos.single().isEnabled = true } else { diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt index d9f50f38b..91cefaa52 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsDialog.kt @@ -12,6 +12,7 @@ import android.view.View import android.widget.* import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.core.view.size import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter import org.dslul.openboard.inputmethod.latin.R @@ -19,12 +20,11 @@ import org.dslul.openboard.inputmethod.latin.common.LocaleUtils import org.dslul.openboard.inputmethod.latin.utils.* import java.io.File import java.util.* -import kotlin.collections.HashSet @Suppress("deprecation") class LanguageSettingsDialog( context: Context, - private val subtypes: MutableList, + private val infos: MutableList, private val fragment: LanguageSettingsFragment?, private val onlySystemLocales: Boolean, private val onSubtypesChanged: () -> Unit @@ -32,11 +32,11 @@ class LanguageSettingsDialog( private val context = ContextThemeWrapper(context, R.style.platformDialogTheme) private val prefs = DeviceProtectedUtils.getSharedPreferences(context)!! private val view = LayoutInflater.from(context).inflate(R.layout.locale_settings_dialog, null) - private val mainLocaleString = subtypes.first().subtype.locale + private val mainLocaleString = infos.first().subtype.locale private val mainLocale = mainLocaleString.toLocale() init { - setTitle(subtypes.first().displayName) + setTitle(infos.first().displayName) setView(ScrollView(context).apply { addView(view) }) setButton(BUTTON_NEGATIVE, context.getString(R.string.dialog_close)) { _, _ -> dismiss() @@ -61,21 +61,21 @@ class LanguageSettingsDialog( } private fun fillSubtypesView(subtypesView: LinearLayout) { - if (subtypes.any { it.subtype.isAsciiCapable }) { // currently can only add subtypes for latin keyboards + if (infos.any { it.subtype.isAsciiCapable }) { // currently can only add subtypes for latin keyboards subtypesView.findViewById(R.id.add_subtype).setOnClickListener { val layouts = context.resources.getStringArray(R.array.predefined_layouts) - .filterNot { layoutName -> subtypes.any { SubtypeLocaleUtils.getKeyboardLayoutSetName(it.subtype) == layoutName } } + .filterNot { layoutName -> infos.any { SubtypeLocaleUtils.getKeyboardLayoutSetName(it.subtype) == layoutName } } val displayNames = layouts.map { SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it) } Builder(context) .setTitle(R.string.keyboard_layout_set) .setItems(displayNames.toTypedArray()) { di, i -> di.dismiss() val newSubtype = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(mainLocaleString, layouts[i]) - val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true) // enabled by default, because why else add them + val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default, because why else add them addAdditionalSubtype(prefs, context.resources, newSubtype) addEnabledSubtype(prefs, newSubtype) addSubtypeToView(newSubtypeInfo, subtypesView) - subtypes.add(newSubtypeInfo) + infos.add(newSubtypeInfo) onSubtypesChanged() } .setNegativeButton(android.R.string.cancel, null) @@ -85,7 +85,7 @@ class LanguageSettingsDialog( subtypesView.findViewById(R.id.add_subtype).isGone = true // add subtypes - subtypes.sortedBy { it.displayName }.forEach { + infos.sortedBy { it.displayName }.forEach { addSubtypeToView(it, subtypesView) } } @@ -100,8 +100,11 @@ class LanguageSettingsDialog( isChecked = subtype.isEnabled isEnabled = !onlySystemLocales setOnCheckedChangeListener { _, b -> - if (b) + if (b) { + if (!infos.first().hasDictionary) + showMissingDictionaryDialog(context, mainLocale) addEnabledSubtype(prefs, subtype.subtype) + } else removeEnabledSubtype(prefs, subtype.subtype) subtype.isEnabled = b @@ -115,7 +118,7 @@ class LanguageSettingsDialog( setOnClickListener { // can be re-added easily, no need for confirmation dialog subtypesView.removeView(row) - subtypes.remove(subtype) + infos.remove(subtype) removeAdditionalSubtype(prefs, context.resources, subtype.subtype) removeEnabledSubtype(prefs, subtype.subtype) @@ -128,10 +131,10 @@ class LanguageSettingsDialog( private fun fillSecondaryLocaleView(secondaryLocalesView: LinearLayout) { // can only use multilingual typing if there is more than one dictionary available - val availableSecondaryLocales = getAvailableDictionaryLocales( + val availableSecondaryLocales = getAvailableSecondaryLocales( context, mainLocaleString, - subtypes.first().subtype.isAsciiCapable + infos.first().subtype.isAsciiCapable ) val selectedSecondaryLocales = Settings.getSecondaryLocales(prefs, mainLocaleString) selectedSecondaryLocales.forEach { @@ -210,6 +213,9 @@ class LanguageSettingsDialog( } private fun addDictionaryToView(dictFile: File, dictionariesView: LinearLayout) { + if (!infos.first().hasDictionary) { + infos.forEach { it.hasDictionary = true } + } val dictType = dictFile.name.substringBefore("_${USER_DICTIONARY_SUFFIX}") val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView) row.findViewById(R.id.language_name).text = dictType @@ -235,6 +241,10 @@ class LanguageSettingsDialog( val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) fragment?.activity?.sendBroadcast(newDictBroadcast) dictionariesView.removeView(row) + if (dictionariesView.size < 2) { // first view is "Dictionaries" + infos.forEach { it.hasDictionary = false } + } + } } } @@ -276,47 +286,19 @@ fun getUserAndInternalDictionaries(context: Context, locale: String): Pair { +private fun getAvailableSecondaryLocales(context: Context, mainLocaleString: String, asciiCapable: Boolean): Set { val mainLocale = mainLocaleString.toLocale() - val locales = HashSet() + val locales = getDictionaryLocales(context) val mainScript = if (asciiCapable) ScriptUtils.SCRIPT_LATIN else ScriptUtils.getScriptFromSpellCheckerLocale(mainLocale) // ScriptUtils.getScriptFromSpellCheckerLocale may return latin when it should not // e.g. for persian or chinese // workaround: don't allow secondary locales for these locales - if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return locales + if (!asciiCapable && mainScript == ScriptUtils.SCRIPT_LATIN) return emptySet() - // get cached dictionaries: extracted or user-added dictionaries - val cachedDirectoryList = DictionaryInfoUtils.getCachedDirectoryList(context) - if (cachedDirectoryList != null) { - for (directory in cachedDirectoryList) { - if (!directory.isDirectory) continue - if (directory.list()?.isNotEmpty() != true) continue - val dirLocale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name) - if (dirLocale == mainLocaleString) continue - val locale = dirLocale.toLocale() - if (locale.language == mainLocale.language) continue - val localeScript = ScriptUtils.getScriptFromSpellCheckerLocale(locale) - if (localeScript != mainScript) continue - locales.add(locale) - } - } - // get assets dictionaries - val assetsDictionaryList = BinaryDictionaryGetter.getAssetsDictionaryList(context) - if (assetsDictionaryList != null) { - for (dictionary in assetsDictionaryList) { - val dictLocale = - BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictionary) - ?: continue - if (dictLocale == mainLocaleString) continue - val locale = dictLocale.toLocale() - if (locale.language == mainLocale.language) continue - val localeScript = ScriptUtils.getScriptFromSpellCheckerLocale(locale) - if (localeScript != mainScript) continue - locales.add(locale) - } + locales.removeAll { + it.language == mainLocale.language + || ScriptUtils.getScriptFromSpellCheckerLocale(it) != mainScript } return locales } - -private const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries" diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt index ec59989f3..a32304e8b 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/LanguageSettingsFragment.kt @@ -12,6 +12,7 @@ import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.common.LocaleUtils import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils import org.dslul.openboard.inputmethod.latin.utils.SubtypeLocaleUtils +import org.dslul.openboard.inputmethod.latin.utils.getDictionaryLocales import java.util.Locale @@ -22,6 +23,7 @@ class LanguageSettingsFragment : SubScreenFragment() { private val enabledSubtypes = mutableListOf() private val systemLocales = mutableListOf() private val languageFilterListPreference by lazy { findPreference("pref_language_filter") as LanguageFilterListPreference } + private val dictionaryLocales by lazy { getDictionaryLocales(activity) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -137,7 +139,7 @@ class LanguageSettingsFragment : SubScreenFragment() { } private fun InputMethodSubtype.toSubtypeInfo(locale: Locale, isEnabled: Boolean = false) = - toSubtypeInfo(locale, activity, isEnabled) + toSubtypeInfo(locale, activity, isEnabled, dictionaryLocales.contains(locale)) private fun List.addToSortedSubtypes() { forEach { @@ -169,7 +171,7 @@ class LanguageSettingsFragment : SubScreenFragment() { } -class SubtypeInfo(val displayName: String, val subtype: InputMethodSubtype, var isEnabled: Boolean) { +class SubtypeInfo(val displayName: String, val subtype: InputMethodSubtype, var isEnabled: Boolean, var hasDictionary: Boolean) { override fun equals(other: Any?): Boolean { if (other !is SubtypeInfo) return false return subtype == other.subtype @@ -180,8 +182,8 @@ class SubtypeInfo(val displayName: String, val subtype: InputMethodSubtype, var } } -fun InputMethodSubtype.toSubtypeInfo(locale: Locale, context: Context, isEnabled: Boolean): SubtypeInfo = - SubtypeInfo(LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context), this, isEnabled) +fun InputMethodSubtype.toSubtypeInfo(locale: Locale, context: Context, isEnabled: Boolean, hasDictionary: Boolean): SubtypeInfo = + SubtypeInfo(LocaleUtils.getLocaleDisplayNameInSystemLocale(locale, context), this, isEnabled, hasDictionary) private const val DICTIONARY_REQUEST_CODE = 96834 const val USER_DICTIONARY_SUFFIX = "user.dict" diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java index dc1a3b305..d07289a5c 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/settings/Settings.java @@ -152,6 +152,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_SELECTED_INPUT_STYLE = "pref_selected_input_style"; public static final String PREF_USE_SYSTEM_LOCALES = "pref_use_system_locales"; + public static final String PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG = "pref_dont_show_missing_dict_dialog"; + // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead. // This is being used only for the backward compatibility. private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY = diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt new file mode 100644 index 000000000..600ac5c47 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/latin/utils/DictionaryUtils.kt @@ -0,0 +1,70 @@ +package org.dslul.openboard.inputmethod.latin.utils + +import android.app.AlertDialog +import android.content.Context +import android.text.Html +import android.text.method.LinkMovementMethod +import android.view.View +import android.widget.TextView +import androidx.core.content.edit +import org.dslul.openboard.inputmethod.latin.BinaryDictionaryGetter +import org.dslul.openboard.inputmethod.latin.R +import org.dslul.openboard.inputmethod.latin.settings.Settings +import java.util.* +import kotlin.collections.HashSet + +fun getDictionaryLocales(context: Context): MutableSet { + val locales = HashSet() + + // get cached dictionaries: extracted or user-added dictionaries + val cachedDirectoryList = DictionaryInfoUtils.getCachedDirectoryList(context) + if (cachedDirectoryList != null) { + for (directory in cachedDirectoryList) { + if (!directory.isDirectory) continue + if (directory.list()?.isNotEmpty() != true) continue + val dirLocale = DictionaryInfoUtils.getWordListIdFromFileName(directory.name) + val locale = dirLocale.toLocale() + locales.add(locale) + } + } + // get assets dictionaries + val assetsDictionaryList = BinaryDictionaryGetter.getAssetsDictionaryList(context) + if (assetsDictionaryList != null) { + for (dictionary in assetsDictionaryList) { + val dictLocale = + BinaryDictionaryGetter.extractLocaleFromAssetsDictionaryFile(dictionary) + ?: continue + val locale = dictLocale.toLocale() + locales.add(locale) + } + } + return locales +} + +fun showMissingDictionaryDialog(context: Context, locale: Locale) { + val prefs = DeviceProtectedUtils.getSharedPreferences(context) + if (prefs.getBoolean(Settings.PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG, false) || locale.toString() == "zz") + return + val repositoryLink = "" + context.getString(R.string.dictionary_link_text) + "" + val dictionaryLink = "" + context.getString( + R.string.dictionary_link_text) + "" + + val message = Html.fromHtml(context.getString( + R.string.no_dictionary_message, + repositoryLink, + locale.toString(), + dictionaryLink, + )) + val dialog = AlertDialog.Builder(context) + .setTitle(R.string.no_dictionaries_available) + .setMessage(message) + .setNegativeButton(R.string.dialog_close, null) + .setNeutralButton(R.string.no_dictionary_dont_show_again_button) { _, _ -> + prefs.edit { putBoolean(Settings.PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG, true) } + } + .create() + dialog.show() + (dialog.findViewById(android.R.id.message) as? TextView)?.movementMethod = LinkMovementMethod.getInstance() +} + +const val DICTIONARY_URL = "https://codeberg.org/Helium314/aosp-dictionaries" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index efc017b0b..280474809 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -499,9 +499,16 @@ disposition rather than other common dispositions for Latin languages. [CHAR LIM "Replace dictionary" "Really remove user-added dictionary \"%s\"?" - + + "Without a dictionary, you will only get suggestions for text you entered before.<br> + You can download dictionaries %1$s, or check whether a dictionary for \"%2$s\" can be downloaded directly %3$s." + + "Don't show again" + "Select a dictionary to add. Dictionaries in .dict format can be downloaded %s." - + "here" "Error: Selected file is not a valid dictionary file"