Merge branch 'Helium314:main' into inline-code-point-loops

This commit is contained in:
Devy Ballard 2025-03-11 17:59:33 -06:00 committed by GitHub
commit 84f73cc3da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 246 additions and 100 deletions

View file

@ -59,8 +59,7 @@ object LayoutParser {
/** Parse simple layouts, defined only as rows of (normal) keys with popup keys. */
fun parseSimpleString(layoutText: String): List<List<KeyData>> {
val rowStrings = layoutText.replace("\r\n", "\n").split("\\n\\s*\\n".toRegex()).filter { it.isNotBlank() }
return rowStrings.map { row ->
return LayoutUtils.getSimpleRowStrings(layoutText).map { row ->
row.split("\n").mapNotNull { parseKey(it) }
}
}

View file

@ -161,7 +161,7 @@ fun checkVersionUpgrade(context: Context) {
split[1] = newName
split.joinToString(":")
}
Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";"))
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, newSubtypeStrings.joinToString(";")).apply()
}
// rename other custom layouts
LayoutUtilsCustom.onLayoutFileChanged()
@ -630,7 +630,7 @@ private fun upgradesWhenComingFromOldAppName(context: Context) {
val localeString = it.substringBefore(":")
additionalSubtypes.add(it.replace(localeString, localeString.constructLocale().toLanguageTag()))
}
Settings.writePrefAdditionalSubtypes(prefs, additionalSubtypes.joinToString(";"))
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, additionalSubtypes.joinToString(";")).apply()
}
// move pinned clips to credential protected storage if device is not locked (should never happen)
if (!prefs.contains(Settings.PREF_PINNED_CLIPS)) return

View file

@ -523,6 +523,11 @@ public class LatinIME extends InputMethodService implements
}
final class SubtypeState {
// When HintLocales causes a subtype override, we store
// the overridden subtype here in order to restore it when
// we switch to another input context that has no HintLocales.
private InputMethodSubtype mOverriddenByLocale;
private InputMethodSubtype mLastActiveSubtype;
private boolean mCurrentSubtypeHasBeenUsed = true; // starting with true avoids immediate switch
@ -530,6 +535,70 @@ public class LatinIME extends InputMethodService implements
mCurrentSubtypeHasBeenUsed = true;
}
// TextFields can provide locale/language hints that the IME should use via 'hintLocales'.
// If a matching subtype is found, we temporarily switch to that subtype until
// we return to a context that does not provide any hints, or until the user
// explicitly changes the language/subtype in use.
public InputMethodSubtype getSubtypeForLocales(final RichInputMethodManager richImm, final Iterable<Locale> locales) {
final InputMethodSubtype overriddenByLocale = mOverriddenByLocale;
if (locales == null) {
if (overriddenByLocale != null) {
// no locales provided, so switch back to
// whatever subtype was used last time.
mOverriddenByLocale = null;
return overriddenByLocale;
}
return null;
}
final InputMethodSubtype currentSubtype = richImm.getCurrentSubtype().getRawSubtype();
final Locale currentSubtypeLocale = richImm.getCurrentSubtypeLocale();
final int minimumMatchLevel = 3; // LocaleUtils.LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
// Try finding a subtype matching the hint language.
for (final Locale hintLocale : locales) {
if (LocaleUtils.INSTANCE.getMatchLevel(hintLocale, currentSubtypeLocale) >= minimumMatchLevel
|| CollectionsKt.any(mSettings.getCurrent().mSecondaryLocales,
(secLocale) -> LocaleUtils.INSTANCE.getMatchLevel(hintLocale, secLocale) >= minimumMatchLevel)) {
// current locales are already a good match, and we want to avoid unnecessary layout switches.
return null;
}
final InputMethodSubtype subtypeForHintLocale = richImm.findSubtypeForHintLocale(hintLocale);
if (subtypeForHintLocale == null) {
continue;
}
if (subtypeForHintLocale.equals(currentSubtype)) {
// no need to switch, we already use the correct locale.
return null;
}
if (overriddenByLocale == null) {
// auto-switching based on hint locale, so store
// whatever subtype was in use so we can switch back
// to it later when there are no hint locales.
mOverriddenByLocale = currentSubtype;
}
return subtypeForHintLocale;
}
return null;
}
public void onSubtypeChanged(final InputMethodSubtype oldSubtype,
final InputMethodSubtype newSubtype) {
if (oldSubtype != mOverriddenByLocale) {
// Whenever the subtype is changed, clear tracking
// the subtype that is overridden by a HintLocale as
// we no longer have a subtype to automatically switch back to.
mOverriddenByLocale = null;
}
}
public void switchSubtype(final RichInputMethodManager richImm) {
final InputMethodSubtype currentSubtype = richImm.getCurrentSubtype().getRawSubtype();
final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype;
@ -858,6 +927,8 @@ public class LatinIME extends InputMethodService implements
return;
}
InputMethodSubtype oldSubtype = mRichImm.getCurrentSubtype().getRawSubtype();
mSubtypeState.onSubtypeChanged(oldSubtype, subtype);
StatsUtils.onSubtypeChanged(oldSubtype, subtype);
mRichImm.onSubtypeChanged(subtype);
mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype),
@ -876,20 +947,10 @@ public class LatinIME extends InputMethodService implements
super.onStartInput(editorInfo, restarting);
final List<Locale> hintLocales = EditorInfoCompatUtils.getHintLocales(editorInfo);
if (hintLocales == null) {
return;
}
// Try switching to a subtype matching the hint language.
for (final Locale hintLocale : hintLocales) {
if (LocaleUtils.INSTANCE.getMatchLevel(hintLocale, mRichImm.getCurrentSubtypeLocale()) >= 3
|| CollectionsKt.any(mSettings.getCurrent().mSecondaryLocales, (secLocale) -> LocaleUtils.INSTANCE.getMatchLevel(hintLocale, secLocale) >= 3))
return; // current locales are already a good match, and we want to avoid unnecessary layout switches
final InputMethodSubtype newSubtype = mRichImm.findSubtypeForHintLocale(hintLocale);
if (newSubtype == null) continue;
if (newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype()))
return; // no need to switch, we already use the correct locale
mHandler.postSwitchLanguage(newSubtype);
break;
final InputMethodSubtype subtypeForLocales = mSubtypeState.getSubtypeForLocales(mRichImm, hintLocales);
if (subtypeForLocales != null) {
// found a better subtype using hint locales that we should switch to.
mHandler.postSwitchLanguage(subtypeForLocales);
}
}

View file

@ -9,7 +9,6 @@ import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.BuildConfig
import helium314.keyboard.latin.common.Constants.Separators
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue
import helium314.keyboard.latin.utils.JniUtils
import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.POPUP_KEYS_LABEL_DEFAULT
import helium314.keyboard.latin.utils.POPUP_KEYS_ORDER_DEFAULT
@ -74,9 +73,9 @@ object Defaults {
const val PREF_LANGUAGE_SWITCH_KEY = "internal"
const val PREF_SHOW_EMOJI_KEY = false
const val PREF_VARIABLE_TOOLBAR_DIRECTION = true
const val PREF_ADDITIONAL_SUBTYPES = "de${Separators.SET}${ExtraValue.KEYBOARD_LAYOUT_SET}=qwerty${Separators.SETS}" +
"fr${Separators.SET}${ExtraValue.KEYBOARD_LAYOUT_SET}=qwertz${Separators.SETS}" +
"hu${Separators.SET}${ExtraValue.KEYBOARD_LAYOUT_SET}=qwerty"
const val PREF_ADDITIONAL_SUBTYPES = "de${Separators.SET}${ExtraValue.KEYBOARD_LAYOUT_SET}=MAIN:qwerty${Separators.SETS}" +
"fr${Separators.SET}${ExtraValue.KEYBOARD_LAYOUT_SET}=MAIN:qwertz${Separators.SETS}" +
"hu${Separators.SET}${ExtraValue.KEYBOARD_LAYOUT_SET}=MAIN:qwerty"
const val PREF_ENABLE_SPLIT_KEYBOARD = false
const val PREF_ENABLE_SPLIT_KEYBOARD_LANDSCAPE = false
const val PREF_SPLIT_SPACER_SCALE = SettingsValues.DEFAULT_SIZE_SCALE
@ -152,8 +151,6 @@ object Defaults {
const val PREF_EMOJI_RECENT_KEYS = ""
const val PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID = 0
const val PREF_PINNED_CLIPS = ""
@JvmField
val PREF_LIBRARY_CHECKSUM: String = JniUtils.expectedDefaultChecksum()
const val PREF_SHOW_DEBUG_SETTINGS = false
val PREF_DEBUG_MODE = BuildConfig.DEBUG
const val PREF_SHOW_SUGGESTION_INFOS = false

View file

@ -312,10 +312,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
mPrefs.edit().putBoolean(Settings.PREF_ALWAYS_INCOGNITO_MODE, !oldValue).apply();
}
public static void writePrefAdditionalSubtypes(final SharedPreferences prefs, final String prefSubtypes) {
prefs.edit().putString(PREF_ADDITIONAL_SUBTYPES, prefSubtypes).apply();
}
public static int readHorizontalSpaceSwipe(final SharedPreferences prefs) {
return switch (prefs.getString(PREF_SPACE_HORIZONTAL_SWIPE, Defaults.PREF_SPACE_HORIZONTAL_SWIPE)) {
case "move_cursor" -> KeyboardActionListener.SWIPE_MOVE_CURSOR;

View file

@ -11,11 +11,9 @@ import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.define.DebugFlags
import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutType.Companion.toExtraValue
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ScriptUtils
import helium314.keyboard.latin.utils.ScriptUtils.script
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional
import helium314.keyboard.latin.utils.locale
import java.util.Locale
@ -27,23 +25,9 @@ data class SettingsSubtype(val locale: Locale, val extraValues: String) {
/** Creates an additional subtype from the SettingsSubtype.
* Resulting InputMethodSubtypes are equal if SettingsSubtypes are equal */
fun toAdditionalSubtype(): InputMethodSubtype? {
fun toAdditionalSubtype(): InputMethodSubtype {
val asciiCapable = locale.script() == ScriptUtils.SCRIPT_LATIN
val subtype = SubtypeUtilsAdditional.createAdditionalSubtype(locale, extraValues, asciiCapable, true)
// todo: this is returns null for all non-latin layouts
// either fix it, or remove the check
// if removed, removing a layout will result in fallback qwerty even for non-ascii, but this is better than the current alternative
/* if (subtype.nameResId == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT
&& mainLayoutName()?.endsWith("+") != true // "+" layouts and custom layouts are always "unknown"
&& !LayoutUtilsCustom.isCustomLayout(mainLayoutName() ?: SubtypeLocaleUtils.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
return SubtypeUtilsAdditional.createAdditionalSubtype(locale, extraValues, asciiCapable, true)
}
fun mainLayoutName() = LayoutType.getMainLayoutFromExtraValue(extraValues)
@ -54,7 +38,7 @@ data class SettingsSubtype(val locale: Locale, val extraValues: String) {
val newList = extraValues.split(",")
.filterNot { it.isBlank() || it.startsWith("$extraValueKey=") || it == extraValueKey }
val newValue = if (extraValue == null) extraValueKey else "$extraValueKey=$extraValue"
val newValues = (newList + newValue).joinToString(",")
val newValues = (newList + newValue).sorted().joinToString(",")
return copy(extraValues = newValues)
}

View file

@ -13,7 +13,6 @@ import android.text.TextUtils;
import helium314.keyboard.latin.App;
import helium314.keyboard.latin.BuildConfig;
import helium314.keyboard.latin.settings.Defaults;
import helium314.keyboard.latin.settings.Settings;
import java.io.File;
@ -63,7 +62,7 @@ public final class JniUtils {
// we want the default preferences, because storing the checksum in device protected storage is discouraged
// see https://developer.android.com/reference/android/content/Context#createDeviceProtectedStorageContext()
// if device is locked, this will throw an IllegalStateException
wantedChecksum = KtxKt.protectedPrefs(app).getString(Settings.PREF_LIBRARY_CHECKSUM, Defaults.PREF_LIBRARY_CHECKSUM);
wantedChecksum = KtxKt.protectedPrefs(app).getString(Settings.PREF_LIBRARY_CHECKSUM, expectedDefaultChecksum());
}
final FileInputStream libStream = new FileInputStream(userSuppliedLibrary);
final String checksum = ChecksumCalculator.INSTANCE.checksum(libStream);

View file

@ -1,6 +1,8 @@
package helium314.keyboard.latin.utils
import android.content.Context
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.SimplePopups
import helium314.keyboard.keyboard.internal.keyboard_parser.getOrCreate
import helium314.keyboard.latin.R
import helium314.keyboard.latin.settings.Defaults.default
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
@ -26,6 +28,7 @@ object LayoutUtils {
fun getLMainLayoutsForLocales(locales: List<Locale>, context: Context): Collection<String> =
locales.flatMapTo(HashSet()) { getAvailableLayouts(LayoutType.MAIN, context, it) }.sorted()
/** gets content for built-in (non-custom) layout [layoutName], with fallback to qwerty */
fun getContent(layoutType: LayoutType, layoutName: String, context: Context): String {
val layouts = context.assets.list(layoutType.folder)!!
layouts.firstOrNull { it.startsWith("$layoutName.") }
@ -33,4 +36,27 @@ object LayoutUtils {
val fallback = layouts.first { it.startsWith(layoutType.default) } // must exist!
return context.assets.open(layoutType.folder + File.separator + fallback).reader().readText()
}
fun getContentWithPlus(mainLayoutName: String, locale: Locale, context: Context): String {
val content = getContent(LayoutType.MAIN, mainLayoutName, context)
if (!mainLayoutName.endsWith("+"))
return content
// the stuff below will not work if we add "+" layouts in json format
// ideally we should serialize keyData to json to solve this
val rows = getSimpleRowStrings(content)
val localeKeyboardInfos = getOrCreate(context, locale)
return rows.mapIndexed { i, row ->
val extraKeys = localeKeyboardInfos.getExtraKeys(i + 1) ?: return@mapIndexed row
val rowList = row.split("\n").filterNot { it.isEmpty() }.toMutableList()
extraKeys.forEach { key ->
val popups = (key.popup as? SimplePopups)?.popupKeys?.joinToString(" ")
?.takeIf { it.isNotEmpty() }?.let { " $it" } ?: ""
rowList.add(key.label + popups)
}
rowList.joinToString("\n")
}.joinToString("\n\n")
}
fun getSimpleRowStrings(layoutContent: String): List<String> =
layoutContent.replace("\r\n", "\n").split("\\n\\s*\\n".toRegex()).filter { it.isNotBlank() }
}

View file

@ -52,9 +52,8 @@ object SubtypeSettings {
fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) {
val subtype = newSubtype.toSettingsSubtype()
val subtypes = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!
.split(Separators.SETS).filter { it.isNotBlank() }.map { it.toSettingsSubtype() } + subtype
val newString = subtypes.map { it.toPref() }.toSortedSet().joinToString(Separators.SETS)
val subtypes = createSettingsSubtypes(prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!) + subtype
val newString = createPrefSubtypes(subtypes)
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, newString) }
if (newSubtype !in enabledSubtypes) {
@ -74,10 +73,8 @@ object SubtypeSettings {
fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype {
val selectedSubtype = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)!!.toSettingsSubtype()
if (selectedSubtype.isAdditionalSubtype(prefs)) {
val selectedAdditionalSubtype = selectedSubtype.toAdditionalSubtype()
if (selectedAdditionalSubtype != null) return selectedAdditionalSubtype
}
if (selectedSubtype.isAdditionalSubtype(prefs))
return selectedSubtype.toAdditionalSubtype()
// no additional subtype, must be a resource subtype
val subtype = enabledSubtypes.firstOrNull { it.toSettingsSubtype() == selectedSubtype }
@ -157,6 +154,15 @@ object SubtypeSettings {
RichInputMethodManager.getInstance().refreshSubtypeCaches()
}
fun createSettingsSubtypes(prefSubtypes: String): List<SettingsSubtype> =
prefSubtypes.split(Separators.SETS).mapNotNull {
if (it.isEmpty()) null
else it.toSettingsSubtype()
}
fun createPrefSubtypes(subtypes: Collection<SettingsSubtype>): String =
subtypes.map { it.toPref() }.toSortedSet().joinToString(Separators.SETS)
fun init(context: Context) {
SubtypeLocaleUtils.init(context) // necessary to get the correct getKeyboardLayoutSetName
@ -207,7 +213,8 @@ object SubtypeSettings {
}
if (subtypesToRemove.isEmpty()) return
Log.w(TAG, "removing custom subtypes without main layout files: $subtypesToRemove")
Settings.writePrefAdditionalSubtypes(prefs, additionalSubtypes.filterNot { it in subtypesToRemove }.joinToString(Separators.SETS))
// todo: now we have a qwerty fallback anyway, consider removing this method (makes bugs more obvious to users)
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, additionalSubtypes.filterNot { it in subtypesToRemove }.joinToString(Separators.SETS)).apply()
}
private fun loadAdditionalSubtypes(prefs: SharedPreferences) {
@ -220,15 +227,11 @@ object SubtypeSettings {
// requires loadResourceSubtypes to be called before
private fun loadEnabledSubtypes(context: Context) {
val prefs = context.prefs()
val settingsSubtypes = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!
.split(Separators.SETS).filter { it.isNotEmpty() }.map { it.toSettingsSubtype() }
val settingsSubtypes = createSettingsSubtypes(prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!)
for (settingsSubtype in settingsSubtypes) {
if (settingsSubtype.isAdditionalSubtype(prefs)) {
val additionalSubtype = settingsSubtype.toAdditionalSubtype()
if (additionalSubtype != null) {
enabledSubtypes.add(additionalSubtype)
continue
}
enabledSubtypes.add(settingsSubtype.toAdditionalSubtype())
continue
}
val subtypesForLocale = resourceSubtypesByLocale[settingsSubtype.locale]
if (subtypesForLocale == null) {
@ -258,12 +261,11 @@ object SubtypeSettings {
/** @return whether pref was changed */
private fun removeEnabledSubtype(prefs: SharedPreferences, subtype: SettingsSubtype): Boolean {
val oldSubtypes = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!
.split(Separators.SETS).filter { it.isNotEmpty() }.map { it.toSettingsSubtype() }
val oldSubtypes = createSettingsSubtypes(prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!)
val newSubtypes = oldSubtypes - subtype
if (oldSubtypes == newSubtypes)
return false // already removed
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, newSubtypes.joinToString(Separators.SETS) { it.toPref() }) }
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, createPrefSubtypes(newSubtypes)) }
if (subtype == prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)!!.toSettingsSubtype()) {
// switch subtype if the currently used one has been disabled
try {

View file

@ -53,10 +53,11 @@ object SubtypeUtilsAdditional {
val prefs = context.prefs()
SubtypeSettings.removeEnabledSubtype(context, subtype)
val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
val oldAdditionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString)
val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != subtype }
val newAdditionalSubtypesString = createPrefSubtypes(newAdditionalSubtypes)
Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString)
val oldAdditionalSubtypes = SubtypeSettings.createSettingsSubtypes(oldAdditionalSubtypesString)
val settingsSubtype = subtype.toSettingsSubtype()
val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != settingsSubtype }
val newAdditionalSubtypesString = SubtypeSettings.createPrefSubtypes(newAdditionalSubtypes)
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, newAdditionalSubtypesString).apply()
}
// updates additional subtypes, enabled subtypes, and selected subtype
@ -66,34 +67,37 @@ object SubtypeUtilsAdditional {
val isSelected = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)!!.toSettingsSubtype() == from
val isEnabled = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!.split(Separators.SETS)
.any { it.toSettingsSubtype() == from }
val new = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!
.split(Separators.SETS).mapNotNullTo(sortedSetOf()) {
if (it == from.toPref()) null else it
} + to.toPref()
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, new.joinToString(Separators.SETS)).apply()
val fromSubtype = from.toAdditionalSubtype() // will be null if we edit a resource subtype
val toSubtype = to.toAdditionalSubtype() // should never be null
if (isSelected && toSubtype != null) {
SubtypeSettings.setSelectedSubtype(prefs, toSubtype)
val additionalSubtypes = SubtypeSettings.createSettingsSubtypes(prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!)
.toMutableList()
additionalSubtypes.remove(from)
if (SubtypeSettings.getResourceSubtypesForLocale(to.locale).none { it.toSettingsSubtype() == to }) {
// We only add the "to" subtype if it's not equal to a resource subtype.
// This means we make additional subtype disappear as magically as it was added if all settings are default.
// If we don't do this, enabling the base subtype will result in the additional subtype being enabled,
// as both have the same settingsSubtype.
additionalSubtypes.add(to)
}
if (fromSubtype != null && isEnabled && toSubtype != null) {
SubtypeSettings.removeEnabledSubtype(context, fromSubtype)
SubtypeSettings.addEnabledSubtype(prefs, toSubtype)
val editor = prefs.edit()
editor.putString(Settings.PREF_ADDITIONAL_SUBTYPES, SubtypeSettings.createPrefSubtypes(additionalSubtypes))
if (isSelected) {
editor.putString(Settings.PREF_SELECTED_SUBTYPE, to.toPref())
}
if (isEnabled) {
val enabled = SubtypeSettings.createSettingsSubtypes(prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!)
.toMutableList()
enabled.remove(from)
enabled.add(to)
editor.putString(Settings.PREF_ENABLED_SUBTYPES, SubtypeSettings.createPrefSubtypes(enabled))
}
editor.apply()
SubtypeSettings.reloadEnabledSubtypes(context)
}
fun createAdditionalSubtypes(prefSubtypes: String): List<InputMethodSubtype> {
if (prefSubtypes.isEmpty())
return emptyList()
return prefSubtypes.split(Separators.SETS).mapNotNull { it.toSettingsSubtype().toAdditionalSubtype() }
}
fun createPrefSubtypes(subtypes: Collection<InputMethodSubtype>): String {
if (subtypes.isEmpty())
return ""
return subtypes.joinToString(Separators.SETS) { it.toSettingsSubtype().toPref() }
}
fun createAdditionalSubtypes(prefSubtypes: String): List<InputMethodSubtype> =
prefSubtypes.split(Separators.SETS).mapNotNull {
if (it.isEmpty()) null
else it.toSettingsSubtype().toAdditionalSubtype()
}
private fun getNameResId(locale: Locale, mainLayoutName: String): Int {
val nameId = SubtypeLocaleUtils.getSubtypeNameResId(locale, mainLayoutName)

View file

@ -125,8 +125,8 @@ fun SubtypeDialog(
onConfirmed = { onConfirmed(currentSubtype) },
neutralButtonText = if (initialSubtype.isAdditionalSubtype(prefs)) stringResource(R.string.delete) else null,
onNeutral = {
SubtypeUtilsAdditional.removeAdditionalSubtype(ctx, initialSubtype.toAdditionalSubtype()!!)
SubtypeSettings.removeEnabledSubtype(ctx, initialSubtype.toAdditionalSubtype()!!)
SubtypeUtilsAdditional.removeAdditionalSubtype(ctx, initialSubtype.toAdditionalSubtype())
SubtypeSettings.removeEnabledSubtype(ctx, initialSubtype.toAdditionalSubtype())
onDismissRequest()
},
title = {
@ -393,7 +393,7 @@ private fun MainLayoutRow(
if (showLayoutEditDialog != null) {
val layoutName = showLayoutEditDialog!!.first
val startContent = showLayoutEditDialog?.second
?: if (layoutName in appLayouts) LayoutUtils.getContent(LayoutType.MAIN, layoutName, ctx)
?: if (layoutName in appLayouts) LayoutUtils.getContentWithPlus(layoutName, currentSubtype.locale, ctx)
else null
LayoutEditDialog(
onDismissRequest = { showLayoutEditDialog = null },

View file

@ -35,10 +35,13 @@ fun LoadGestureLibPreference(setting: Setting) {
val abi = Build.SUPPORTED_ABIS[0]
val libFile = File(ctx.filesDir?.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME)
fun renameToLibFileAndRestart(file: File, checksum: String) {
libFile.setWritable(true)
libFile.delete()
// store checksum in default preferences (soo JniUtils)
// store checksum in default preferences (see JniUtils)
prefs.edit().putString(Settings.PREF_LIBRARY_CHECKSUM, checksum).commit()
file.renameTo(libFile)
file.copyTo(libFile)
libFile.setReadOnly()
file.delete()
Runtime.getRuntime().exit(0) // exit will restart the app, so library will be loaded
}
var tempFilePath: String? by rememberSaveable { mutableStateOf(null) }

View file

@ -0,0 +1,75 @@
package helium314.keyboard
import helium314.keyboard.keyboard.KeyboardId
import helium314.keyboard.keyboard.KeyboardLayoutSet
import helium314.keyboard.keyboard.internal.KeyboardParams
import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
import helium314.keyboard.latin.LatinIME
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.SettingsSubtype.Companion.toSettingsSubtype
import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT
import helium314.keyboard.latin.utils.SubtypeSettings
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional
import helium314.keyboard.latin.utils.prefs
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowLog
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@RunWith(RobolectricTestRunner::class)
@Config(shadows = [
ShadowInputMethodManager2::class
])
class SubtypeTest {
private lateinit var latinIME: LatinIME
private lateinit var params: KeyboardParams
@BeforeTest fun setUp() {
latinIME = Robolectric.setupService(LatinIME::class.java)
ShadowLog.setupLogging()
ShadowLog.stream = System.out
params = KeyboardParams()
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL)
}
@Test fun emptyAdditionalSubtypesResultsInEmptyList() {
// avoid issues where empty string results in additional subtype for undefined locale
val prefs = latinIME.prefs()
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, "").apply()
assertTrue(SubtypeSettings.getAdditionalSubtypes().isEmpty())
val from = SubtypeSettings.getResourceSubtypesForLocale("es".constructLocale()).first()
// no change, and "changed" subtype actually is resource subtype -> still expect empty list
SubtypeUtilsAdditional.changeAdditionalSubtype(from.toSettingsSubtype(), from.toSettingsSubtype(), latinIME)
assertEquals(emptyList(), SubtypeSettings.getAdditionalSubtypes().map { it.toSettingsSubtype() })
}
@Test fun subtypeStaysEnabledOnEdits() {
val prefs = latinIME.prefs()
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, "").apply() // clear it for convenience
// edit enabled resource subtype
val from = SubtypeSettings.getResourceSubtypesForLocale("es".constructLocale()).first()
SubtypeSettings.addEnabledSubtype(prefs, from)
val to = from.toSettingsSubtype().withLayout(LayoutType.SYMBOLS, "symbols_arabic")
SubtypeUtilsAdditional.changeAdditionalSubtype(from.toSettingsSubtype(), to, latinIME)
assertEquals(to, SubtypeSettings.getEnabledSubtypes(false).single().toSettingsSubtype())
// change the new subtype to effectively be the same as original resource subtype
val toNew = to.withoutLayout(LayoutType.SYMBOLS)
assertEquals(from.toSettingsSubtype(), toNew)
SubtypeUtilsAdditional.changeAdditionalSubtype(to, toNew, latinIME)
assertEquals(emptyList(), SubtypeSettings.getAdditionalSubtypes().map { it.toSettingsSubtype() })
assertEquals(from.toSettingsSubtype(), SubtypeSettings.getEnabledSubtypes(false).single().toSettingsSubtype())
}
}