mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-25 19:29:27 +00:00
store layouts in the same way in selected, enabled and additional layouts
This commit is contained in:
parent
d005ffac06
commit
a25ed6d5e0
16 changed files with 272 additions and 296 deletions
|
@ -13,8 +13,8 @@ android {
|
||||||
applicationId = "helium314.keyboard"
|
applicationId = "helium314.keyboard"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 2306
|
versionCode = 2307
|
||||||
versionName = "2.3+dev5"
|
versionName = "2.3+dev6"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters.clear()
|
abiFilters.clear()
|
||||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
||||||
|
|
|
@ -9,6 +9,7 @@ import helium314.keyboard.keyboard.KeyboardTheme
|
||||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
||||||
import helium314.keyboard.latin.common.ColorType
|
import helium314.keyboard.latin.common.ColorType
|
||||||
import helium314.keyboard.latin.common.Constants.Separators
|
import helium314.keyboard.latin.common.Constants.Separators
|
||||||
|
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue
|
||||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||||
import helium314.keyboard.latin.common.encodeBase36
|
import helium314.keyboard.latin.common.encodeBase36
|
||||||
import helium314.keyboard.latin.settings.Defaults
|
import helium314.keyboard.latin.settings.Defaults
|
||||||
|
@ -22,8 +23,14 @@ import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
||||||
import helium314.keyboard.latin.utils.LayoutUtilsCustom
|
import helium314.keyboard.latin.utils.LayoutUtilsCustom
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils.SCRIPT_LATIN
|
import helium314.keyboard.latin.utils.ScriptUtils.SCRIPT_LATIN
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||||
|
import helium314.keyboard.latin.utils.SettingsSubtype
|
||||||
|
import helium314.keyboard.latin.utils.SettingsSubtype.Companion.toSettingsSubtype
|
||||||
|
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional
|
||||||
import helium314.keyboard.latin.utils.ToolbarKey
|
import helium314.keyboard.latin.utils.ToolbarKey
|
||||||
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
|
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
|
||||||
|
import helium314.keyboard.latin.utils.getResourceSubtypes
|
||||||
|
import helium314.keyboard.latin.utils.locale
|
||||||
|
import helium314.keyboard.latin.utils.mainLayoutName
|
||||||
import helium314.keyboard.latin.utils.prefs
|
import helium314.keyboard.latin.utils.prefs
|
||||||
import helium314.keyboard.latin.utils.protectedPrefs
|
import helium314.keyboard.latin.utils.protectedPrefs
|
||||||
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
|
import helium314.keyboard.latin.utils.upgradeToolbarPrefs
|
||||||
|
@ -361,7 +368,7 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
if (locale.script() != SCRIPT_LATIN) return@forEach
|
if (locale.script() != SCRIPT_LATIN) return@forEach
|
||||||
// change language tag to SCRIPT_LATIN, but
|
// change language tag to SCRIPT_LATIN, but
|
||||||
// avoid overwriting if 2 layouts have a different language tag, but the same name
|
// avoid overwriting if 2 layouts have a different language tag, but the same name
|
||||||
val layoutDisplayName = LayoutUtilsCustom.getSecondaryLayoutDisplayName(it.name)
|
val layoutDisplayName = LayoutUtilsCustom.getDisplayName(it.name)
|
||||||
var newFile = File(it.parentFile!!, LayoutUtilsCustom.getMainLayoutName(layoutDisplayName, locale))
|
var newFile = File(it.parentFile!!, LayoutUtilsCustom.getMainLayoutName(layoutDisplayName, locale))
|
||||||
var i = 1
|
var i = 1
|
||||||
while (newFile.exists()) // make sure name is not already in use, e.g. custom.en.abcd. and custom.it.abcd. would both be custom.Latn.abcd
|
while (newFile.exists()) // make sure name is not already in use, e.g. custom.en.abcd. and custom.it.abcd. would both be custom.Latn.abcd
|
||||||
|
@ -392,6 +399,54 @@ fun checkVersionUpgrade(context: Context) {
|
||||||
prefs.edit().putString(it, newValue).apply()
|
prefs.edit().putString(it, newValue).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion <= 2306) {
|
||||||
|
// upgrade additional, enabled, and selected subtypes to same format of locale and (filtered) extra value
|
||||||
|
if (prefs.contains(Settings.PREF_ADDITIONAL_SUBTYPES)) {
|
||||||
|
val new = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, "")!!.split(Separators.SETS).mapNotNull { pref ->
|
||||||
|
val oldSplit = pref.split(Separators.SET)
|
||||||
|
val languageTag = oldSplit[0]
|
||||||
|
val mainLayoutName = oldSplit[1]
|
||||||
|
val extraValue = oldSplit[2]
|
||||||
|
SettingsSubtype(
|
||||||
|
languageTag.constructLocale(),
|
||||||
|
ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN" + Separators.KV + mainLayoutName + "," + extraValue
|
||||||
|
).toAdditionalSubtype()?.let { it.toSettingsSubtype().toPref() }
|
||||||
|
}.joinToString(Separators.SETS)
|
||||||
|
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, new).apply()
|
||||||
|
}
|
||||||
|
listOf(Settings.PREF_ENABLED_SUBTYPES, Settings.PREF_SELECTED_SUBTYPE).forEach { key ->
|
||||||
|
if (!prefs.contains(key)) return@forEach
|
||||||
|
val resourceSubtypes = getResourceSubtypes(context.resources)
|
||||||
|
val additionalSubtypeString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
|
||||||
|
val additionalSubtypes = SubtypeUtilsAdditional.createAdditionalSubtypes(additionalSubtypeString)
|
||||||
|
val new = prefs.getString(key, "")!!.split(Separators.SETS).joinToString(Separators.SETS) { pref ->
|
||||||
|
val oldSplit = pref.split(Separators.SET)
|
||||||
|
val languageTag = oldSplit[0]
|
||||||
|
val mainLayoutName = oldSplit[1]
|
||||||
|
// we now need more information than just locale and main layout name, get it from existing subtypes
|
||||||
|
val filtered = additionalSubtypes.filter {
|
||||||
|
it.locale().toLanguageTag() == languageTag && (it.mainLayoutName() ?: "qwerty") == mainLayoutName
|
||||||
|
}
|
||||||
|
if (filtered.isNotEmpty())
|
||||||
|
return@joinToString filtered.first().toSettingsSubtype().toPref()
|
||||||
|
// find best matching resource subtype
|
||||||
|
val goodMatch = resourceSubtypes.filter {
|
||||||
|
it.locale().toLanguageTag() == languageTag && (it.mainLayoutName() ?: "qwerty") == mainLayoutName
|
||||||
|
}
|
||||||
|
if (goodMatch.isNotEmpty())
|
||||||
|
return@joinToString goodMatch.first().toSettingsSubtype().toPref()
|
||||||
|
// not sure how we can get here, but better deal with it
|
||||||
|
val okMatch = resourceSubtypes.filter {
|
||||||
|
it.locale().language == languageTag.constructLocale().language && (it.mainLayoutName() ?: "qwerty") == mainLayoutName
|
||||||
|
}
|
||||||
|
if (okMatch.isNotEmpty())
|
||||||
|
okMatch.first().toSettingsSubtype().toPref()
|
||||||
|
else resourceSubtypes.first { it.locale().language == languageTag.constructLocale().language }
|
||||||
|
.toSettingsSubtype().toPref()
|
||||||
|
}
|
||||||
|
prefs.edit().putString(key, new).apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
upgradeToolbarPrefs(prefs)
|
upgradeToolbarPrefs(prefs)
|
||||||
LayoutUtilsCustom.onLayoutFileChanged() // just to be sure
|
LayoutUtilsCustom.onLayoutFileChanged() // just to be sure
|
||||||
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
||||||
|
|
|
@ -8,10 +8,10 @@ package helium314.keyboard.latin;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import helium314.keyboard.latin.utils.Log;
|
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
||||||
import helium314.keyboard.latin.common.StringUtils;
|
import helium314.keyboard.latin.common.StringUtilsKt;
|
||||||
|
import helium314.keyboard.latin.utils.Log;
|
||||||
import helium314.keyboard.latin.utils.InputTypeUtils;
|
import helium314.keyboard.latin.utils.InputTypeUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -256,10 +256,9 @@ public final class InputAttributes {
|
||||||
mTargetApplicationPackageName);
|
mTargetApplicationPackageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean inPrivateImeOptions(final String packageName, final String key,
|
public static boolean inPrivateImeOptions(final String packageName, final String key, final EditorInfo editorInfo) {
|
||||||
final EditorInfo editorInfo) {
|
|
||||||
if (editorInfo == null) return false;
|
if (editorInfo == null) return false;
|
||||||
final String findingKey = (packageName != null) ? packageName + "." + key : key;
|
final String findingKey = (packageName != null) ? packageName + "." + key : key;
|
||||||
return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
|
return StringUtilsKt.containsValueWhenSplit(editorInfo.privateImeOptions, findingKey, ",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,51 +123,6 @@ public final class StringUtils {
|
||||||
return new String(Character.toChars(codePoint));
|
return new String(Character.toChars(codePoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean containsInArray(@NonNull final String text,
|
|
||||||
@NonNull final String[] array) {
|
|
||||||
for (final String element : array) {
|
|
||||||
if (text.equals(element)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax.
|
|
||||||
* Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
|
|
||||||
* a comma character in it.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
|
|
||||||
|
|
||||||
public static boolean containsInCommaSplittableText(@NonNull final String text,
|
|
||||||
@Nullable final String extraValues) {
|
|
||||||
if (isEmpty(extraValues)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static String removeFromCommaSplittableTextIfExists(@NonNull final String text,
|
|
||||||
@Nullable final String extraValues) {
|
|
||||||
if (isEmpty(extraValues)) {
|
|
||||||
return EMPTY_STRING;
|
|
||||||
}
|
|
||||||
final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
|
|
||||||
if (!containsInArray(text, elements)) {
|
|
||||||
return extraValues;
|
|
||||||
}
|
|
||||||
final ArrayList<String> result = new ArrayList<>(elements.length - 1);
|
|
||||||
for (final String element : elements) {
|
|
||||||
if (!text.equals(element)) {
|
|
||||||
result.add(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove duplicates from an array of strings.
|
* Remove duplicates from an array of strings.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -129,6 +129,11 @@ fun encodeBase36(string: String): String = BigInteger(string.toByteArray()).toSt
|
||||||
|
|
||||||
fun decodeBase36(string: String) = BigInteger(string, 36).toByteArray().decodeToString()
|
fun decodeBase36(string: String) = BigInteger(string, 36).toByteArray().decodeToString()
|
||||||
|
|
||||||
|
fun containsValueWhenSplit(string: String?, value: String, split: String): Boolean {
|
||||||
|
if (string == null) return false
|
||||||
|
return string.split(split).contains(value)
|
||||||
|
}
|
||||||
|
|
||||||
fun isEmoji(c: Int): Boolean = mightBeEmoji(c) && isEmoji(newSingleCodePointString(c))
|
fun isEmoji(c: Int): Boolean = mightBeEmoji(c) && isEmoji(newSingleCodePointString(c))
|
||||||
|
|
||||||
fun isEmoji(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex)
|
fun isEmoji(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex)
|
||||||
|
|
|
@ -209,7 +209,7 @@ class LanguageSettingsDialog(
|
||||||
reloadSetting()
|
reloadSetting()
|
||||||
}
|
}
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
confirmDialog(context, context.getString(R.string.delete_layout, LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
confirmDialog(context, context.getString(R.string.delete_layout, LayoutUtilsCustom.getDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
||||||
} else {
|
} else {
|
||||||
delete()
|
delete()
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,8 +199,8 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
|
||||||
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
|
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
|
||||||
Settings.getInstance().loadSettings(this, locale, new InputAttributes(editorInfo, false, getPackageName()));
|
Settings.getInstance().loadSettings(this, locale, new InputAttributes(editorInfo, false, getPackageName()));
|
||||||
}
|
}
|
||||||
final String keyboardLayoutName = SubtypeSettingsKt.getMatchingLayoutSetNameForLocale(locale);
|
final String mainLayoutName = SubtypeSettingsKt.getMatchingMainLayoutNameForLocale(locale);
|
||||||
final InputMethodSubtype subtype = SubtypeUtilsAdditional.INSTANCE.createDummyAdditionalSubtype(locale, keyboardLayoutName);
|
final InputMethodSubtype subtype = SubtypeUtilsAdditional.INSTANCE.createDummyAdditionalSubtype(locale, mainLayoutName);
|
||||||
final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype);
|
final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype);
|
||||||
return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
|
return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package helium314.keyboard.latin.utils
|
||||||
|
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
import helium314.keyboard.latin.common.Constants.Separators
|
import helium314.keyboard.latin.common.Constants.Separators
|
||||||
|
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.EnumMap
|
import java.util.EnumMap
|
||||||
|
|
||||||
|
@ -12,9 +13,9 @@ enum class LayoutType {
|
||||||
companion object {
|
companion object {
|
||||||
fun EnumMap<LayoutType, String>.toExtraValue() = map { it.key.name + Separators.KV + it.value }.joinToString(Separators.ENTRY)
|
fun EnumMap<LayoutType, String>.toExtraValue() = map { it.key.name + Separators.KV + it.value }.joinToString(Separators.ENTRY)
|
||||||
|
|
||||||
fun getLayoutMap(extraValue: String): EnumMap<LayoutType, String> {
|
fun getLayoutMap(string: String): EnumMap<LayoutType, String> {
|
||||||
val map = EnumMap<LayoutType, String>(LayoutType::class.java)
|
val map = EnumMap<LayoutType, String>(LayoutType::class.java)
|
||||||
extraValue.split(Separators.ENTRY).forEach {
|
string.split(Separators.ENTRY).forEach {
|
||||||
val s = it.split(Separators.KV)
|
val s = it.split(Separators.KV)
|
||||||
runCatching { map[LayoutType.valueOf(s[0])] = s[1] }
|
runCatching { map[LayoutType.valueOf(s[0])] = s[1] }
|
||||||
}
|
}
|
||||||
|
@ -37,5 +38,12 @@ enum class LayoutType {
|
||||||
EMOJI_BOTTOM -> R.string.layout_emoji_bottom_row
|
EMOJI_BOTTOM -> R.string.layout_emoji_bottom_row
|
||||||
CLIPBOARD_BOTTOM -> R.string.layout_clip_bottom_row
|
CLIPBOARD_BOTTOM -> R.string.layout_clip_bottom_row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMainLayoutFromExtraValue(extraValue: String): String? {
|
||||||
|
val value = extraValue.split(",")
|
||||||
|
.firstOrNull { it.startsWith("${ExtraValue.KEYBOARD_LAYOUT_SET}=") }?.substringAfter("=")
|
||||||
|
if (value == null) return null
|
||||||
|
return getLayoutMap(value)[MAIN]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ object LayoutUtilsCustom {
|
||||||
customLayoutMap.clear()
|
customLayoutMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSecondaryLayoutDisplayName(layoutName: String) =
|
fun getDisplayName(layoutName: String) =
|
||||||
try {
|
try {
|
||||||
if (layoutName.count { it == '.' } == 3) // main layout: "custom.<locale or script>.<name>.", other: custom.<name>.
|
if (layoutName.count { it == '.' } == 3) // main layout: "custom.<locale or script>.<name>.", other: custom.<name>.
|
||||||
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringAfter(".").substringBeforeLast("."))
|
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringAfter(".").substringBeforeLast("."))
|
||||||
|
@ -175,7 +175,7 @@ object LayoutUtilsCustom {
|
||||||
layoutName
|
layoutName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLayoutName(displayName: String) = CUSTOM_LAYOUT_PREFIX + encodeBase36(displayName) + "."
|
fun getSecondaryLayoutName(displayName: String) = CUSTOM_LAYOUT_PREFIX + encodeBase36(displayName) + "."
|
||||||
|
|
||||||
fun getMainLayoutName(displayName: String, locale: Locale) =
|
fun getMainLayoutName(displayName: String, locale: Locale) =
|
||||||
if (locale.script() == ScriptUtils.SCRIPT_LATIN)
|
if (locale.script() == ScriptUtils.SCRIPT_LATIN)
|
||||||
|
@ -196,7 +196,7 @@ object LayoutUtilsCustom {
|
||||||
setText(startContent ?: file.readText())
|
setText(startContent ?: file.readText())
|
||||||
}
|
}
|
||||||
val builder = AlertDialog.Builder(context)
|
val builder = AlertDialog.Builder(context)
|
||||||
.setTitle(getSecondaryLayoutDisplayName(layoutName))
|
.setTitle(getDisplayName(layoutName))
|
||||||
.setView(editText)
|
.setView(editText)
|
||||||
.setPositiveButton(R.string.save) { _, _ ->
|
.setPositiveButton(R.string.save) { _, _ ->
|
||||||
val content = editText.text.toString()
|
val content = editText.text.toString()
|
||||||
|
|
|
@ -267,7 +267,7 @@ public final class SubtypeLocaleUtils {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String getMainLayoutDisplayName(@NonNull final String layoutName) {
|
public static String getMainLayoutDisplayName(@NonNull final String layoutName) {
|
||||||
if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName))
|
if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName))
|
||||||
return LayoutUtilsCustom.INSTANCE.getSecondaryLayoutDisplayName(layoutName);
|
return LayoutUtilsCustom.INSTANCE.getDisplayName(layoutName);
|
||||||
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
|
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,22 +5,19 @@ package helium314.keyboard.latin.utils
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
import android.view.inputmethod.InputMethodSubtype
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.LocaleManagerCompat
|
import androidx.core.app.LocaleManagerCompat
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import helium314.keyboard.keyboard.KeyboardSwitcher
|
import helium314.keyboard.keyboard.KeyboardSwitcher
|
||||||
import helium314.keyboard.latin.R
|
|
||||||
import helium314.keyboard.latin.RichInputMethodManager
|
import helium314.keyboard.latin.RichInputMethodManager
|
||||||
import helium314.keyboard.latin.common.Constants.Separators
|
import helium314.keyboard.latin.common.Constants.Separators
|
||||||
import helium314.keyboard.latin.common.LocaleUtils
|
import helium314.keyboard.latin.common.LocaleUtils
|
||||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
|
||||||
import helium314.keyboard.latin.define.DebugFlags
|
import helium314.keyboard.latin.define.DebugFlags
|
||||||
import helium314.keyboard.latin.settings.Defaults
|
import helium314.keyboard.latin.settings.Defaults
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import helium314.keyboard.latin.utils.SettingsSubtype.Companion.toSettingsSubtype
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
/** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided,
|
/** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided,
|
||||||
|
@ -39,7 +36,7 @@ fun getAllAvailableSubtypes(): List<InputMethodSubtype> {
|
||||||
return resourceSubtypesByLocale.values.flatten() + additionalSubtypes
|
return resourceSubtypesByLocale.values.flatten() + additionalSubtypes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMatchingLayoutSetNameForLocale(locale: Locale): String {
|
fun getMatchingMainLayoutNameForLocale(locale: Locale): String {
|
||||||
val subtypes = resourceSubtypesByLocale.values.flatten()
|
val subtypes = resourceSubtypesByLocale.values.flatten()
|
||||||
val name = LocaleUtils.getBestMatch(locale, subtypes) { it.locale() }?.mainLayoutName()
|
val name = LocaleUtils.getBestMatch(locale, subtypes) { it.locale() }?.mainLayoutName()
|
||||||
if (name != null) return name
|
if (name != null) return name
|
||||||
|
@ -57,7 +54,7 @@ fun getMatchingLayoutSetNameForLocale(locale: Locale): String {
|
||||||
|
|
||||||
fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) {
|
fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) {
|
||||||
require(initialized)
|
require(initialized)
|
||||||
val subtypeString = newSubtype.prefString()
|
val subtypeString = newSubtype.toSettingsSubtype().toPref()
|
||||||
val oldSubtypeStrings = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!.split(Separators.SETS)
|
val oldSubtypeStrings = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!.split(Separators.SETS)
|
||||||
val newString = (oldSubtypeStrings + subtypeString).filter { it.isNotBlank() }.toSortedSet().joinToString(Separators.SETS)
|
val newString = (oldSubtypeStrings + subtypeString).filter { it.isNotBlank() }.toSortedSet().joinToString(Separators.SETS)
|
||||||
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, newString) }
|
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, newString) }
|
||||||
|
@ -72,32 +69,37 @@ fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype)
|
||||||
/** returns whether subtype was actually removed, does not remove last subtype */
|
/** returns whether subtype was actually removed, does not remove last subtype */
|
||||||
fun removeEnabledSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
fun removeEnabledSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||||
require(initialized)
|
require(initialized)
|
||||||
removeEnabledSubtype(prefs, subtype.prefString())
|
removeEnabledSubtype(prefs, subtype.toSettingsSubtype().toPref())
|
||||||
enabledSubtypes.remove(subtype)
|
enabledSubtypes.remove(subtype)
|
||||||
RichInputMethodManager.getInstance().refreshSubtypeCaches()
|
RichInputMethodManager.getInstance().refreshSubtypeCaches()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype {
|
fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype {
|
||||||
require(initialized)
|
require(initialized)
|
||||||
val localeAndLayout = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)!!.toLocaleAndLayout()
|
val selectedSubtype = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)!!.toSettingsSubtype()
|
||||||
|
val selectedAdditionalSubtype = selectedSubtype.toAdditionalSubtype()
|
||||||
|
if (selectedAdditionalSubtype != null && additionalSubtypes.contains(selectedAdditionalSubtype))
|
||||||
|
return selectedAdditionalSubtype // don't even care whether it's enabled
|
||||||
|
// no additional subtype, must be a resource subtype
|
||||||
val subtypes = if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, Defaults.PREF_USE_SYSTEM_LOCALES)) getDefaultEnabledSubtypes()
|
val subtypes = if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, Defaults.PREF_USE_SYSTEM_LOCALES)) getDefaultEnabledSubtypes()
|
||||||
else enabledSubtypes
|
else enabledSubtypes
|
||||||
val subtype = subtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getMainLayoutName(it) }
|
|
||||||
|
val subtype = subtypes.firstOrNull { it.toSettingsSubtype() == selectedSubtype }
|
||||||
if (subtype != null) {
|
if (subtype != null) {
|
||||||
return subtype
|
return subtype
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "selected subtype $localeAndLayout / ${prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)} not found")
|
Log.w(TAG, "selected subtype $selectedSubtype / ${prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)} not found")
|
||||||
}
|
}
|
||||||
if (subtypes.isNotEmpty())
|
if (subtypes.isNotEmpty())
|
||||||
return subtypes.first()
|
return subtypes.first()
|
||||||
val defaultSubtypes = getDefaultEnabledSubtypes()
|
val defaultSubtypes = getDefaultEnabledSubtypes()
|
||||||
return defaultSubtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getMainLayoutName(it) }
|
return defaultSubtypes.firstOrNull { it.locale() == selectedSubtype.locale && it.mainLayoutName() == it.mainLayoutName() }
|
||||||
?: defaultSubtypes.firstOrNull { localeAndLayout.first.language == it.locale().language && localeAndLayout.second == SubtypeLocaleUtils.getMainLayoutName(it) }
|
?: defaultSubtypes.firstOrNull { it.locale().language == selectedSubtype.locale.language }
|
||||||
?: defaultSubtypes.first()
|
?: defaultSubtypes.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||||
val subtypeString = subtype.prefString()
|
val subtypeString = subtype.toSettingsSubtype().toPref()
|
||||||
if (subtype.locale().toLanguageTag().isEmpty() || prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE) == subtypeString)
|
if (subtype.locale().toLanguageTag().isEmpty() || prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE) == subtypeString)
|
||||||
return
|
return
|
||||||
prefs.edit { putString(Settings.PREF_SELECTED_SUBTYPE, subtypeString) }
|
prefs.edit { putString(Settings.PREF_SELECTED_SUBTYPE, subtypeString) }
|
||||||
|
@ -180,52 +182,9 @@ private fun getDefaultEnabledSubtypes(): List<InputMethodSubtype> {
|
||||||
return systemSubtypes
|
return systemSubtypes
|
||||||
}
|
}
|
||||||
|
|
||||||
/** string for for identifying a subtype, does not contain all necessary information to actually create it */
|
|
||||||
private fun InputMethodSubtype.prefString(): String {
|
|
||||||
if (DebugFlags.DEBUG_ENABLED && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && locale().toLanguageTag() == "und") {
|
|
||||||
@Suppress("deprecation") // it's debug logging, better get all information
|
|
||||||
Log.e(TAG, "unknown language, should not happen ${locale}, $languageTag, $extraValue, ${hashCode()}, $nameResId")
|
|
||||||
}
|
|
||||||
return locale().toLanguageTag() + Separators.SET + SubtypeLocaleUtils.getMainLayoutName(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.toLocaleAndLayout(): Pair<Locale, String> =
|
|
||||||
substringBefore(Separators.SET).constructLocale() to substringAfter(Separators.SET)
|
|
||||||
|
|
||||||
private fun Pair<Locale, String>.prefString() =
|
|
||||||
first.toLanguageTag() + Separators.SET + second
|
|
||||||
|
|
||||||
private fun loadResourceSubtypes(resources: Resources) {
|
private fun loadResourceSubtypes(resources: Resources) {
|
||||||
val xml = resources.getXml(R.xml.method)
|
getResourceSubtypes(resources).forEach {
|
||||||
xml.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
|
resourceSubtypesByLocale.getOrPut(it.locale()) { ArrayList(2) }.add(it)
|
||||||
val namespace = "http://schemas.android.com/apk/res/android"
|
|
||||||
var eventType = xml.eventType
|
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
|
||||||
if (eventType == XmlPullParser.START_TAG && xml.name == "subtype") {
|
|
||||||
val icon = xml.getAttributeResourceValue(namespace, "icon", 0)
|
|
||||||
val label = xml.getAttributeResourceValue(namespace, "label", 0)
|
|
||||||
val subtypeId = xml.getAttributeIntValue(namespace, "subtypeId", 0)
|
|
||||||
val localeString = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern()
|
|
||||||
val languageTag = xml.getAttributeValue(namespace, "languageTag").intern()
|
|
||||||
val imeSubtypeMode = xml.getAttributeValue(namespace, "imeSubtypeMode")
|
|
||||||
val imeSubtypeExtraValue = xml.getAttributeValue(namespace, "imeSubtypeExtraValue").intern()
|
|
||||||
val isAsciiCapable = xml.getAttributeBooleanValue(namespace, "isAsciiCapable", false)
|
|
||||||
val b = InputMethodSubtype.InputMethodSubtypeBuilder()
|
|
||||||
b.setSubtypeIconResId(icon)
|
|
||||||
b.setSubtypeNameResId(label)
|
|
||||||
if (subtypeId != 0)
|
|
||||||
b.setSubtypeId(subtypeId)
|
|
||||||
b.setSubtypeLocale(localeString)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
|
||||||
b.setLanguageTag(languageTag)
|
|
||||||
b.setSubtypeMode(imeSubtypeMode)
|
|
||||||
b.setSubtypeExtraValue(imeSubtypeExtraValue)
|
|
||||||
b.setIsAsciiCapable(isAsciiCapable)
|
|
||||||
val locale = if (languageTag.isEmpty()) localeString.constructLocale()
|
|
||||||
else languageTag.constructLocale()
|
|
||||||
resourceSubtypesByLocale.getOrPut(locale) { ArrayList(2) }.add(b.build())
|
|
||||||
}
|
|
||||||
eventType = xml.next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,30 +214,33 @@ private fun loadAdditionalSubtypes(prefs: SharedPreferences) {
|
||||||
// requires loadResourceSubtypes to be called before
|
// requires loadResourceSubtypes to be called before
|
||||||
private fun loadEnabledSubtypes(context: Context) {
|
private fun loadEnabledSubtypes(context: Context) {
|
||||||
val prefs = context.prefs()
|
val prefs = context.prefs()
|
||||||
val subtypeStrings = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!
|
val settingsSubtypes = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!
|
||||||
.split(Separators.SETS).filter { it.isNotEmpty() }.map { it.toLocaleAndLayout() }
|
.split(Separators.SETS).filter { it.isNotEmpty() }.map { it.toSettingsSubtype() }
|
||||||
|
for (settingsSubtype in settingsSubtypes) {
|
||||||
for (localeAndLayout in subtypeStrings) {
|
val additionalSubtype = settingsSubtype.toAdditionalSubtype()
|
||||||
val subtypesForLocale = resourceSubtypesByLocale[localeAndLayout.first]
|
if (additionalSubtype != null && additionalSubtypes.contains(additionalSubtype)) {
|
||||||
|
enabledSubtypes.add(additionalSubtype)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val subtypesForLocale = resourceSubtypesByLocale[settingsSubtype.locale]
|
||||||
if (subtypesForLocale == null) {
|
if (subtypesForLocale == null) {
|
||||||
val message = "no resource subtype for $localeAndLayout"
|
val message = "no resource subtype for $settingsSubtype"
|
||||||
Log.w(TAG, message)
|
Log.w(TAG, message)
|
||||||
if (DebugFlags.DEBUG_ENABLED)
|
if (DebugFlags.DEBUG_ENABLED)
|
||||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||||
else // don't remove in debug mode
|
else // don't remove in debug mode
|
||||||
removeEnabledSubtype(prefs, localeAndLayout.prefString())
|
removeEnabledSubtype(prefs, settingsSubtype.toPref())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getMainLayoutName(it) == localeAndLayout.second }
|
val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getMainLayoutName(it) == (settingsSubtype.mainLayoutName() ?: "qwerty") }
|
||||||
?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first && SubtypeLocaleUtils.getMainLayoutName(it) == localeAndLayout.second }
|
|
||||||
if (subtype == null) {
|
if (subtype == null) {
|
||||||
val message = "subtype $localeAndLayout could not be loaded"
|
val message = "subtype $settingsSubtype could not be loaded"
|
||||||
Log.w(TAG, message)
|
Log.w(TAG, message)
|
||||||
if (DebugFlags.DEBUG_ENABLED)
|
if (DebugFlags.DEBUG_ENABLED)
|
||||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||||
else // don't remove in debug mode
|
else // don't remove in debug mode
|
||||||
removeEnabledSubtype(prefs, localeAndLayout.prefString())
|
removeEnabledSubtype(prefs, settingsSubtype.toPref())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +258,7 @@ private fun removeEnabledSubtype(prefs: SharedPreferences, subtypeString: String
|
||||||
// switch subtype if the currently used one has been disabled
|
// switch subtype if the currently used one has been disabled
|
||||||
try {
|
try {
|
||||||
val nextSubtype = RichInputMethodManager.getInstance().getNextSubtypeInThisIme(true)
|
val nextSubtype = RichInputMethodManager.getInstance().getNextSubtypeInThisIme(true)
|
||||||
if (subtypeString == nextSubtype?.prefString())
|
if (subtypeString == nextSubtype?.toSettingsSubtype()?.toPref())
|
||||||
KeyboardSwitcher.getInstance().switchToSubtype(getDefaultEnabledSubtypes().first())
|
KeyboardSwitcher.getInstance().switchToSubtype(getDefaultEnabledSubtypes().first())
|
||||||
else
|
else
|
||||||
KeyboardSwitcher.getInstance().switchToSubtype(nextSubtype)
|
KeyboardSwitcher.getInstance().switchToSubtype(nextSubtype)
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
package helium314.keyboard.latin.utils
|
package helium314.keyboard.latin.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
import android.view.inputmethod.InputMethodSubtype
|
||||||
|
import helium314.keyboard.latin.R
|
||||||
|
import helium314.keyboard.latin.common.Constants.Separators
|
||||||
|
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue
|
||||||
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET
|
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET
|
||||||
import helium314.keyboard.latin.common.LocaleUtils
|
import helium314.keyboard.latin.common.LocaleUtils
|
||||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||||
|
import helium314.keyboard.latin.define.DebugFlags
|
||||||
|
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
fun InputMethodSubtype.locale(): Locale {
|
fun InputMethodSubtype.locale(): Locale {
|
||||||
|
@ -21,11 +28,92 @@ fun InputMethodSubtype.mainLayoutName(): String? {
|
||||||
return map[LayoutType.MAIN]
|
return map[LayoutType.MAIN]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getResourceSubtypes(resources: Resources): List<InputMethodSubtype> {
|
||||||
|
val subtypes = mutableListOf<InputMethodSubtype>()
|
||||||
|
val xml = resources.getXml(R.xml.method)
|
||||||
|
xml.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
|
||||||
|
val namespace = "http://schemas.android.com/apk/res/android"
|
||||||
|
var eventType = xml.eventType
|
||||||
|
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (eventType == XmlPullParser.START_TAG && xml.name == "subtype") {
|
||||||
|
val icon = xml.getAttributeResourceValue(namespace, "icon", 0)
|
||||||
|
val label = xml.getAttributeResourceValue(namespace, "label", 0)
|
||||||
|
val subtypeId = xml.getAttributeIntValue(namespace, "subtypeId", 0)
|
||||||
|
val localeString = xml.getAttributeValue(namespace, "imeSubtypeLocale").intern()
|
||||||
|
val languageTag = xml.getAttributeValue(namespace, "languageTag").intern()
|
||||||
|
val imeSubtypeMode = xml.getAttributeValue(namespace, "imeSubtypeMode")
|
||||||
|
val imeSubtypeExtraValue = xml.getAttributeValue(namespace, "imeSubtypeExtraValue").intern()
|
||||||
|
val isAsciiCapable = xml.getAttributeBooleanValue(namespace, "isAsciiCapable", false)
|
||||||
|
val b = InputMethodSubtype.InputMethodSubtypeBuilder()
|
||||||
|
b.setSubtypeIconResId(icon)
|
||||||
|
b.setSubtypeNameResId(label)
|
||||||
|
if (subtypeId != 0)
|
||||||
|
b.setSubtypeId(subtypeId)
|
||||||
|
b.setSubtypeLocale(localeString)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||||
|
b.setLanguageTag(languageTag)
|
||||||
|
b.setSubtypeMode(imeSubtypeMode)
|
||||||
|
b.setSubtypeExtraValue(imeSubtypeExtraValue)
|
||||||
|
b.setIsAsciiCapable(isAsciiCapable)
|
||||||
|
subtypes.add(b.build())
|
||||||
|
}
|
||||||
|
eventType = xml.next()
|
||||||
|
}
|
||||||
|
return subtypes
|
||||||
|
}
|
||||||
|
|
||||||
/** Workaround for SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale ignoring custom layout names */
|
/** Workaround for SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale ignoring custom layout names */
|
||||||
// todo (later): this should be done properly and in SubtypeLocaleUtils
|
// todo (later): this should be done properly and in SubtypeLocaleUtils
|
||||||
fun InputMethodSubtype.displayName(context: Context): CharSequence {
|
fun InputMethodSubtype.displayName(context: Context): CharSequence {
|
||||||
val layoutName = SubtypeLocaleUtils.getMainLayoutName(this)
|
val layoutName = SubtypeLocaleUtils.getMainLayoutName(this)
|
||||||
if (LayoutUtilsCustom.isCustomLayout(layoutName))
|
if (LayoutUtilsCustom.isCustomLayout(layoutName))
|
||||||
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutName)})"
|
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getDisplayName(layoutName)})"
|
||||||
return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
|
return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class SettingsSubtype(val locale: Locale, val extraValue: String) {
|
||||||
|
|
||||||
|
fun toPref() = locale.toLanguageTag() + Separators.SET + extraValue
|
||||||
|
|
||||||
|
/** Creates an additional subtype from the SettingsSubtype.
|
||||||
|
* Resulting InputMethodSubtypes are equal if SettingsSubtypes are equal */
|
||||||
|
fun toAdditionalSubtype(): InputMethodSubtype? {
|
||||||
|
val asciiCapable = locale.script() == ScriptUtils.SCRIPT_LATIN
|
||||||
|
val subtype = SubtypeUtilsAdditional.createAdditionalSubtype(locale, extraValue, asciiCapable, true)
|
||||||
|
if (subtype.nameResId == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !LayoutUtilsCustom.isCustomLayout(mainLayoutName() ?: "qwerty")) {
|
||||||
|
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
|
||||||
|
// layout has been removed.
|
||||||
|
Log.w(SettingsSubtype::class.simpleName, "unknown additional subtype $this")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return subtype
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mainLayoutName() = LayoutType.getMainLayoutFromExtraValue(extraValue)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun String.toSettingsSubtype() =
|
||||||
|
SettingsSubtype(substringBefore(Separators.SET).constructLocale(), substringAfter(Separators.SET))
|
||||||
|
|
||||||
|
/** Creates a SettingsSubtype from the given InputMethodSubtype.
|
||||||
|
* Will strip some extra values that are set when creating the InputMethodSubtype from SettingsSubtype */
|
||||||
|
fun InputMethodSubtype.toSettingsSubtype(): SettingsSubtype {
|
||||||
|
if (DebugFlags.DEBUG_ENABLED && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && locale().toLanguageTag() == "und") {
|
||||||
|
@Suppress("deprecation") // it's debug logging, better get all information
|
||||||
|
Log.e(SettingsSubtype::class.simpleName, "unknown language, should not happen ${locale}, $languageTag, $extraValue, ${hashCode()}, $nameResId")
|
||||||
|
}
|
||||||
|
val filteredExtraValue = extraValue.split(",").filterNot {
|
||||||
|
it == ExtraValue.ASCII_CAPABLE
|
||||||
|
|| it == ExtraValue.EMOJI_CAPABLE
|
||||||
|
|| it == ExtraValue.IS_ADDITIONAL_SUBTYPE
|
||||||
|
// todo: this is in "old" additional subtypes, but where was it set?
|
||||||
|
// must have been by app in 2.3, but not any more?
|
||||||
|
// anyway, a. we can easily create it again, and b. it may contain "bad" characters messing up the extra value
|
||||||
|
|| it.startsWith(ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
|
||||||
|
}.joinToString(",")
|
||||||
|
require(!filteredExtraValue.contains(Separators.SETS) && !filteredExtraValue.contains(Separators.SET))
|
||||||
|
{ "extra value contains not allowed characters $filteredExtraValue" }
|
||||||
|
return SettingsSubtype(locale(), filteredExtraValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,60 +2,60 @@ package helium314.keyboard.latin.utils
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
import android.view.inputmethod.InputMethodSubtype
|
||||||
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder
|
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder
|
||||||
import helium314.keyboard.latin.R
|
import helium314.keyboard.latin.R
|
||||||
import helium314.keyboard.latin.common.Constants
|
import helium314.keyboard.latin.common.Constants
|
||||||
import helium314.keyboard.latin.common.Constants.Separators
|
import helium314.keyboard.latin.common.Constants.Separators
|
||||||
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue
|
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue
|
||||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
|
||||||
import helium314.keyboard.latin.common.StringUtils
|
|
||||||
import helium314.keyboard.latin.settings.Defaults
|
import helium314.keyboard.latin.settings.Defaults
|
||||||
import helium314.keyboard.latin.settings.Settings
|
import helium314.keyboard.latin.settings.Settings
|
||||||
import helium314.keyboard.latin.utils.LayoutUtilsCustom.isCustomLayout
|
import helium314.keyboard.latin.utils.SettingsSubtype.Companion.toSettingsSubtype
|
||||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
object SubtypeUtilsAdditional {
|
object SubtypeUtilsAdditional {
|
||||||
|
|
||||||
fun isAdditionalSubtype(subtype: InputMethodSubtype): Boolean {
|
fun isAdditionalSubtype(subtype: InputMethodSubtype): Boolean {
|
||||||
return subtype.containsExtraValueKey(ExtraValue.IS_ADDITIONAL_SUBTYPE)
|
return subtype.containsExtraValueKey(ExtraValue.IS_ADDITIONAL_SUBTYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAdditionalSubtypeInternal(locale: Locale, keyboardLayoutSetName: String,
|
// todo: extra value does not contain UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME for custom layout
|
||||||
isAsciiCapable: Boolean, isEmojiCapable: Boolean
|
// it did contain that key in 2.3, but where was it set? anyway, need to be careful with separators if we want to use it
|
||||||
): InputMethodSubtype {
|
// see also todo in SettingsSubtype
|
||||||
val nameId = SubtypeLocaleUtils.getSubtypeNameResId(locale, keyboardLayoutSetName)
|
fun createAdditionalSubtype(locale: Locale, extraValue: String, isAsciiCapable: Boolean,
|
||||||
val platformVersionDependentExtraValues =
|
isEmojiCapable: Boolean): InputMethodSubtype {
|
||||||
getPlatformVersionDependentExtraValue(locale, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable)
|
val mainLayoutName = LayoutType.getMainLayoutFromExtraValue(extraValue) ?: "qwerty"
|
||||||
val platformVersionIndependentSubtypeId =
|
val nameId = SubtypeLocaleUtils.getSubtypeNameResId(locale, mainLayoutName)
|
||||||
getPlatformVersionIndependentSubtypeId(locale, keyboardLayoutSetName)
|
val fullExtraValue = extraValue + "," + getAdditionalExtraValues(locale, mainLayoutName, isAsciiCapable, isEmojiCapable)
|
||||||
|
val subtypeId = getSubtypeId(locale, fullExtraValue, isAsciiCapable)
|
||||||
val builder = InputMethodSubtypeBuilder()
|
val builder = InputMethodSubtypeBuilder()
|
||||||
.setSubtypeNameResId(nameId)
|
.setSubtypeNameResId(nameId)
|
||||||
.setSubtypeIconResId(R.drawable.ic_ime_switcher)
|
.setSubtypeIconResId(R.drawable.ic_ime_switcher)
|
||||||
.setSubtypeLocale(locale.toString())
|
.setSubtypeLocale(locale.toString())
|
||||||
.setSubtypeMode(Constants.Subtype.KEYBOARD_MODE)
|
.setSubtypeMode(Constants.Subtype.KEYBOARD_MODE)
|
||||||
.setSubtypeExtraValue(platformVersionDependentExtraValues)
|
.setSubtypeExtraValue(fullExtraValue)
|
||||||
.setIsAuxiliary(false)
|
.setIsAuxiliary(false)
|
||||||
.setOverridesImplicitlyEnabledSubtype(false)
|
.setOverridesImplicitlyEnabledSubtype(false)
|
||||||
.setSubtypeId(platformVersionIndependentSubtypeId)
|
.setSubtypeId(subtypeId)
|
||||||
.setIsAsciiCapable(isAsciiCapable)
|
.setIsAsciiCapable(isAsciiCapable)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||||
builder.setLanguageTag(locale.toLanguageTag())
|
builder.setLanguageTag(locale.toLanguageTag())
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && LayoutUtilsCustom.isCustomLayout(mainLayoutName))
|
||||||
|
builder.setSubtypeNameOverride(LayoutUtilsCustom.getDisplayName(mainLayoutName))
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createDummyAdditionalSubtype(locale: Locale, keyboardLayoutSetName: String) =
|
fun createDummyAdditionalSubtype(locale: Locale, mainLayoutName: String) =
|
||||||
createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, false, false)
|
createAdditionalSubtype(locale, "MAIN${Separators.KV}$mainLayoutName", false, false)
|
||||||
|
|
||||||
fun createEmojiCapableAdditionalSubtype(locale: Locale, keyboardLayoutSetName: String, asciiCapable: Boolean) =
|
fun createEmojiCapableAdditionalSubtype(locale: Locale, mainLayoutName: String, asciiCapable: Boolean) =
|
||||||
createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true)
|
createAdditionalSubtype(locale, "MAIN${Separators.KV}$mainLayoutName", asciiCapable, true)
|
||||||
|
|
||||||
fun addAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
fun addAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) {
|
||||||
val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
|
val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
|
||||||
val additionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString).toMutableSet()
|
val additionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString).toMutableSet()
|
||||||
additionalSubtypes.add(subtype)
|
additionalSubtypes.add(subtype)
|
||||||
val newAdditionalSubtypesString = createPrefSubtypes(additionalSubtypes.toTypedArray())
|
val newAdditionalSubtypesString = createPrefSubtypes(additionalSubtypes)
|
||||||
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,123 +63,25 @@ object SubtypeUtilsAdditional {
|
||||||
val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
|
val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
|
||||||
val oldAdditionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString)
|
val oldAdditionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString)
|
||||||
val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != subtype }
|
val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != subtype }
|
||||||
val newAdditionalSubtypesString = createPrefSubtypes(newAdditionalSubtypes.toTypedArray())
|
val newAdditionalSubtypesString = createPrefSubtypes(newAdditionalSubtypes)
|
||||||
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: adjust so we can store more stuff in extra values
|
|
||||||
private fun getPrefSubtype(subtype: InputMethodSubtype): String {
|
|
||||||
val mainLayoutName = SubtypeLocaleUtils.getMainLayoutName(subtype)
|
|
||||||
val layoutExtraValue = ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN" + Separators.KV + mainLayoutName
|
|
||||||
val extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
|
|
||||||
layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
|
|
||||||
ExtraValue.IS_ADDITIONAL_SUBTYPE, subtype.extraValue
|
|
||||||
)
|
|
||||||
)
|
|
||||||
require(!extraValue.contains(Separators.SETS) && !extraValue.contains(Separators.SET))
|
|
||||||
{ "extra value contains not allowed characters $extraValue" }
|
|
||||||
val basePrefSubtype = subtype.locale().toLanguageTag() + Separators.SET + mainLayoutName
|
|
||||||
return if (extraValue.isEmpty()) basePrefSubtype
|
|
||||||
else basePrefSubtype + Separators.SET + extraValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createAdditionalSubtypes(prefSubtypes: String): List<InputMethodSubtype> {
|
fun createAdditionalSubtypes(prefSubtypes: String): List<InputMethodSubtype> {
|
||||||
if (TextUtils.isEmpty(prefSubtypes)) {
|
if (prefSubtypes.isEmpty())
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
return prefSubtypes.split(Separators.SETS).mapNotNull { it.toSettingsSubtype().toAdditionalSubtype() }
|
||||||
return prefSubtypes.split(Separators.SETS)
|
|
||||||
.mapNotNull { createSubtypeFromString(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// use string created with getPrefSubtype
|
fun createPrefSubtypes(subtypes: Collection<InputMethodSubtype>): String {
|
||||||
fun createSubtypeFromString(prefSubtype: String): InputMethodSubtype? {
|
if (subtypes.isEmpty())
|
||||||
val elems = prefSubtype.split(Separators.SET)
|
|
||||||
if (elems.size != LENGTH_WITHOUT_EXTRA_VALUE && elems.size != LENGTH_WITH_EXTRA_VALUE) {
|
|
||||||
Log.w(TAG, "Unknown additional subtype specified: $prefSubtype")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val languageTag = elems[INDEX_OF_LANGUAGE_TAG]
|
|
||||||
val locale = languageTag.constructLocale()
|
|
||||||
val keyboardLayoutSet = elems[INDEX_OF_KEYBOARD_LAYOUT]
|
|
||||||
val asciiCapable = locale.script() == ScriptUtils.SCRIPT_LATIN
|
|
||||||
// Here we assume that all the additional subtypes are EmojiCapable
|
|
||||||
val subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSet, asciiCapable)
|
|
||||||
if (subtype.nameResId == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !isCustomLayout(keyboardLayoutSet)) {
|
|
||||||
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
|
|
||||||
// layout has been removed.
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return subtype
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createPrefSubtypes(subtypes: Array<InputMethodSubtype>?): String {
|
|
||||||
if (subtypes.isNullOrEmpty()) {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
return subtypes.joinToString(Separators.SETS) { it.toSettingsSubtype().toPref() }
|
||||||
val sb = StringBuilder()
|
|
||||||
for (subtype in subtypes) {
|
|
||||||
if (sb.isNotEmpty()) {
|
|
||||||
sb.append(Separators.SETS)
|
|
||||||
}
|
|
||||||
sb.append(getPrefSubtype(subtype))
|
|
||||||
}
|
|
||||||
return sb.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createPrefSubtypes(prefSubtypes: Array<String>?): String {
|
|
||||||
if (prefSubtypes.isNullOrEmpty()) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
val sb = StringBuilder()
|
|
||||||
for (prefSubtype in prefSubtypes) {
|
|
||||||
if (sb.isNotEmpty()) {
|
|
||||||
sb.append(Separators.SETS)
|
|
||||||
}
|
|
||||||
sb.append(prefSubtype)
|
|
||||||
}
|
|
||||||
return sb.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the extra value that is optimized for the running OS.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Historically the extra value has been used as the last resort to annotate various kinds of
|
|
||||||
* attributes. Some of these attributes are valid only on some platform versions. Thus we cannot
|
|
||||||
* assume that the extra values stored in a persistent storage are always valid. We need to
|
|
||||||
* regenerate the extra value on the fly instead.
|
|
||||||
*
|
|
||||||
* @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
|
|
||||||
* @param isAsciiCapable true when ASCII characters are supported with this layout.
|
|
||||||
* @param isEmojiCapable true when Unicode Emoji characters are supported with this layout.
|
|
||||||
* @return extra value that is optimized for the running OS.
|
|
||||||
* @see .getPlatformVersionIndependentSubtypeId
|
|
||||||
*/
|
|
||||||
private fun getPlatformVersionDependentExtraValue(locale: Locale,
|
|
||||||
keyboardLayoutSetName: String, isAsciiCapable: Boolean, isEmojiCapable: Boolean
|
|
||||||
): String {
|
|
||||||
val extraValueItems = mutableListOf<String>()
|
|
||||||
extraValueItems.add(ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName)
|
|
||||||
if (isAsciiCapable) {
|
|
||||||
extraValueItems.add(ExtraValue.ASCII_CAPABLE)
|
|
||||||
}
|
|
||||||
if (SubtypeLocaleUtils.isExceptionalLocale(locale)) {
|
|
||||||
extraValueItems.add(
|
|
||||||
ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
|
|
||||||
SubtypeLocaleUtils.getMainLayoutDisplayName(keyboardLayoutSetName)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isEmojiCapable) {
|
|
||||||
extraValueItems.add(ExtraValue.EMOJI_CAPABLE)
|
|
||||||
}
|
|
||||||
extraValueItems.add(ExtraValue.IS_ADDITIONAL_SUBTYPE)
|
|
||||||
return extraValueItems.joinToString(",")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the subtype ID that is supposed to be compatible between different version of OSes.
|
* Returns the subtype ID that is supposed to be compatible between different version of OSes.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* From the compatibility point of view, it is important to keep subtype id predictable and
|
* From the compatibility point of view, it is important to keep subtype id predictable and
|
||||||
* stable between different OSes. For this purpose, the calculation code in this method is
|
* stable between different OSes. For this purpose, the calculation code in this method is
|
||||||
* carefully chosen and then fixed. Treat the following code as no more or less than a
|
* carefully chosen and then fixed. Treat the following code as no more or less than a
|
||||||
|
@ -188,43 +90,45 @@ object SubtypeUtilsAdditional {
|
||||||
* For example, you don't need to update `compatibilityExtraValueItems` in this
|
* For example, you don't need to update `compatibilityExtraValueItems` in this
|
||||||
* method even when we need to add some new extra values for the actual instance of
|
* method even when we need to add some new extra values for the actual instance of
|
||||||
* [InputMethodSubtype].
|
* [InputMethodSubtype].
|
||||||
*
|
|
||||||
* @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
|
|
||||||
* @return a platform-version independent subtype ID.
|
|
||||||
* @see .getPlatformVersionDependentExtraValue
|
|
||||||
*/
|
*/
|
||||||
private fun getPlatformVersionIndependentSubtypeId(locale: Locale, keyboardLayoutSetName: String): Int {
|
private fun getSubtypeId(locale: Locale, extraValue: String, asciiCapable: Boolean): Int {
|
||||||
// For compatibility reasons, we concatenate the extra values in the following order.
|
// basically we use the hashCode as specified for id in https://developer.android.com/reference/android/view/inputmethod/InputMethodSubtype
|
||||||
// - KeyboardLayoutSet
|
|
||||||
// - AsciiCapable
|
|
||||||
// - UntranslatableReplacementStringInSubtypeName
|
|
||||||
// - EmojiCapable
|
|
||||||
// - isAdditionalSubtype
|
|
||||||
val compatibilityExtraValueItems = mutableListOf<String>()
|
|
||||||
compatibilityExtraValueItems.add(ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName)
|
|
||||||
compatibilityExtraValueItems.add(ExtraValue.ASCII_CAPABLE)
|
|
||||||
if (SubtypeLocaleUtils.isExceptionalLocale(locale)) {
|
|
||||||
compatibilityExtraValueItems.add(
|
|
||||||
ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
|
|
||||||
SubtypeLocaleUtils.getMainLayoutDisplayName(keyboardLayoutSetName)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
compatibilityExtraValueItems.add(ExtraValue.EMOJI_CAPABLE)
|
|
||||||
compatibilityExtraValueItems.add(ExtraValue.IS_ADDITIONAL_SUBTYPE)
|
|
||||||
val compatibilityExtraValues = compatibilityExtraValueItems.joinToString(",")
|
|
||||||
return arrayOf(
|
return arrayOf(
|
||||||
locale,
|
locale,
|
||||||
Constants.Subtype.KEYBOARD_MODE,
|
Constants.Subtype.KEYBOARD_MODE,
|
||||||
compatibilityExtraValues,
|
extraValue,
|
||||||
false, // isAuxiliary
|
false, // isAuxiliary
|
||||||
false // overrideImplicitlyEnabledSubtype
|
false, // overrideImplicitlyEnabledSubtype
|
||||||
|
asciiCapable // asciiCapable
|
||||||
).contentHashCode()
|
).contentHashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TAG: String = SubtypeUtilsAdditional::class.java.simpleName
|
/**
|
||||||
private const val INDEX_OF_LANGUAGE_TAG: Int = 0
|
* Returns the extra value that is optimized for the running OS.
|
||||||
private const val INDEX_OF_KEYBOARD_LAYOUT: Int = 1
|
*
|
||||||
private const val INDEX_OF_EXTRA_VALUE: Int = 2
|
* Historically the extra value has been used as the last resort to annotate various kinds of
|
||||||
private const val LENGTH_WITHOUT_EXTRA_VALUE: Int = (INDEX_OF_KEYBOARD_LAYOUT + 1)
|
* attributes. Some of these attributes are valid only on some platform versions. Thus we cannot
|
||||||
private const val LENGTH_WITH_EXTRA_VALUE: Int = (INDEX_OF_EXTRA_VALUE + 1)
|
* assume that the extra values stored in a persistent storage are always valid. We need to
|
||||||
|
* regenerate the extra value on the fly instead.
|
||||||
|
*
|
||||||
|
* @param mainLayoutName the keyboard main layout name (e.g., "dvorak").
|
||||||
|
* @param isAsciiCapable true when ASCII characters are supported with this layout.
|
||||||
|
* @param isEmojiCapable true when Unicode Emoji characters are supported with this layout.
|
||||||
|
* @return extra value that is optimized for the running OS.
|
||||||
|
* @see .getPlatformVersionIndependentSubtypeId
|
||||||
|
*/
|
||||||
|
private fun getAdditionalExtraValues(locale: Locale, mainLayoutName: String, isAsciiCapable: Boolean, isEmojiCapable: Boolean): String {
|
||||||
|
val extraValueItems = mutableListOf<String>()
|
||||||
|
if (isAsciiCapable)
|
||||||
|
extraValueItems.add(ExtraValue.ASCII_CAPABLE)
|
||||||
|
if (SubtypeLocaleUtils.isExceptionalLocale(locale)) {
|
||||||
|
extraValueItems.add(
|
||||||
|
ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + SubtypeLocaleUtils.getMainLayoutDisplayName(mainLayoutName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isEmojiCapable)
|
||||||
|
extraValueItems.add(ExtraValue.EMOJI_CAPABLE)
|
||||||
|
extraValueItems.add(ExtraValue.IS_ADDITIONAL_SUBTYPE)
|
||||||
|
return extraValueItems.joinToString(",")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,14 +44,14 @@ fun LayoutEditDialog(
|
||||||
val startIsCustom = LayoutUtilsCustom.isCustomLayout(initialLayoutName)
|
val startIsCustom = LayoutUtilsCustom.isCustomLayout(initialLayoutName)
|
||||||
var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||||
mutableStateOf(TextFieldValue(
|
mutableStateOf(TextFieldValue(
|
||||||
if (startIsCustom) LayoutUtilsCustom.getSecondaryLayoutDisplayName(initialLayoutName)
|
if (startIsCustom) LayoutUtilsCustom.getDisplayName(initialLayoutName)
|
||||||
else initialLayoutName.getStringResourceOrName("layout_", ctx)
|
else initialLayoutName.getStringResourceOrName("layout_", ctx)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
val nameValid = displayNameValue.text.isNotBlank()
|
val nameValid = displayNameValue.text.isNotBlank()
|
||||||
&& (
|
&& (
|
||||||
(startIsCustom && LayoutUtilsCustom.getLayoutName(displayNameValue.text) == initialLayoutName)
|
(startIsCustom && LayoutUtilsCustom.getSecondaryLayoutName(displayNameValue.text) == initialLayoutName)
|
||||||
|| isNameValid(LayoutUtilsCustom.getLayoutName(displayNameValue.text))
|
|| isNameValid(LayoutUtilsCustom.getSecondaryLayoutName(displayNameValue.text))
|
||||||
)
|
)
|
||||||
|
|
||||||
TextInputDialog(
|
TextInputDialog(
|
||||||
|
@ -60,7 +60,7 @@ fun LayoutEditDialog(
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
onConfirmed = {
|
onConfirmed = {
|
||||||
val newLayoutName = LayoutUtilsCustom.getLayoutName(displayNameValue.text)
|
val newLayoutName = LayoutUtilsCustom.getSecondaryLayoutName(displayNameValue.text)
|
||||||
if (startIsCustom && initialLayoutName != newLayoutName)
|
if (startIsCustom && initialLayoutName != newLayoutName)
|
||||||
LayoutUtilsCustom.getLayoutFile(initialLayoutName, layoutType, ctx).delete()
|
LayoutUtilsCustom.getLayoutFile(initialLayoutName, layoutType, ctx).delete()
|
||||||
LayoutUtilsCustom.getLayoutFile(newLayoutName, layoutType, ctx).writeText(it)
|
LayoutUtilsCustom.getLayoutFile(newLayoutName, layoutType, ctx).writeText(it)
|
||||||
|
|
|
@ -167,7 +167,7 @@ private fun AddLayoutRow(onNewLayout: (String) -> Unit, userLayouts: Collection<
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = textValue.text.isNotEmpty() && LayoutUtilsCustom.getLayoutName(textValue.text) !in userLayouts,
|
enabled = textValue.text.isNotEmpty() && LayoutUtilsCustom.getSecondaryLayoutName(textValue.text) !in userLayouts,
|
||||||
onClick = { onNewLayout(textValue.text) }
|
onClick = { onNewLayout(textValue.text) }
|
||||||
) { Icon(painterResource(R.drawable.ic_edit), null) }
|
) { Icon(painterResource(R.drawable.ic_edit), null) }
|
||||||
}
|
}
|
||||||
|
@ -206,7 +206,7 @@ private fun LayoutItemRow(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = if (isCustom) LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutName)
|
text = if (isCustom) LayoutUtilsCustom.getDisplayName(layoutName)
|
||||||
else layoutName.getStringResourceOrName("layout_", ctx),
|
else layoutName.getStringResourceOrName("layout_", ctx),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
@ -219,7 +219,7 @@ private fun LayoutItemRow(
|
||||||
if (showDeleteDialog)
|
if (showDeleteDialog)
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
onDismissRequest = { showDeleteDialog = false },
|
onDismissRequest = { showDeleteDialog = false },
|
||||||
text = { Text(stringResource(R.string.delete_layout, LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutName))) },
|
text = { Text(stringResource(R.string.delete_layout, LayoutUtilsCustom.getDisplayName(layoutName))) },
|
||||||
confirmButtonText = stringResource(R.string.delete),
|
confirmButtonText = stringResource(R.string.delete),
|
||||||
onConfirmed = {
|
onConfirmed = {
|
||||||
showDeleteDialog = false
|
showDeleteDialog = false
|
||||||
|
|
|
@ -50,7 +50,7 @@ fun createLayoutSettings(context: Context) = listOf(
|
||||||
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
|
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
|
||||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
var showDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
|
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
|
||||||
val displayName = if (LayoutUtilsCustom.isCustomLayout(currentLayout)) LayoutUtilsCustom.getSecondaryLayoutDisplayName(currentLayout)
|
val displayName = if (LayoutUtilsCustom.isCustomLayout(currentLayout)) LayoutUtilsCustom.getDisplayName(currentLayout)
|
||||||
else currentLayout.getStringResourceOrName("layout_", ctx)
|
else currentLayout.getStringResourceOrName("layout_", ctx)
|
||||||
Preference(
|
Preference(
|
||||||
name = setting.title,
|
name = setting.title,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue