diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index 6423c5c9f3..e9d5f7e785 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -77,6 +77,13 @@ Use the previous word in making suggestions + + Auto-switch language + + Automatically switch to a matching language if the text field requests it (e.g. Translate) + + Advanced options + Languages diff --git a/java/src/org/futo/inputmethod/latin/InputAttributes.java b/java/src/org/futo/inputmethod/latin/InputAttributes.java index 198ca7b13f..901e16b490 100644 --- a/java/src/org/futo/inputmethod/latin/InputAttributes.java +++ b/java/src/org/futo/inputmethod/latin/InputAttributes.java @@ -20,6 +20,7 @@ import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE; import static org.futo.inputmethod.latin.common.Constants.ImeOption.NO_MICROPHONE_COMPAT; +import android.os.LocaleList; import android.text.InputType; import android.util.Log; import android.view.inputmethod.EditorInfo; @@ -56,6 +57,8 @@ public final class InputAttributes { final public Locale mLocaleOverride; @Nullable final public String mLayoutOverride; + @Nullable + final public LocaleList mHintLocales; /** * Whether the floating gesture preview should be disabled. If true, this should override the @@ -111,6 +114,7 @@ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMo mIsWebField = false; mLocaleOverride = null; mLayoutOverride = null; + mHintLocales = null; return; } // inputClass == InputType.TYPE_CLASS_TEXT @@ -191,6 +195,7 @@ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMo } mLayoutOverride = privateImeOptions.get("org.futo.inputmethod.latin.ForceLayout"); + mHintLocales = editorInfo.hintLocales; } public boolean isTypeNull() { diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index 876a415de5..4402fb208a 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -306,6 +306,50 @@ class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripCont } } + private fun updateKeyboardLanguage() { + val settings = latinIMELegacy.mSettings.current + if (!settings.mAutoSwitchLanguage) return + + val hintLocales = settings.mInputAttributes.mHintLocales ?: return + if (hintLocales.isEmpty) return + + // We need to read these right from the settings. LatinIME just seems to return a generic "keyboard" subtype + // with no language info when you get subtypes from InputMethodManager + val enabledSubtypes = getSettingBlocking(SubtypesSetting).map(Subtypes::convertToSubtype) + if (enabledSubtypes.isEmpty()) return + + val currentSubtype = RichInputMethodManager.getInstance().currentSubtype.rawSubtype + val currentSubtypeString = Subtypes.subtypeToString(currentSubtype) + val currentLanguage = Subtypes.getLocale(currentSubtype).language + val hintedLocales = (0 until hintLocales.size()).map(hintLocales::get) + + if (hintedLocales.any { it.language == currentLanguage }) { + // Some apps update hintLocales faster than keyboard view state settles. + // If the current language is already valid for hints, keep it but force one UI resync. + // Example is changing between JP/EN in translate, going from 12 key back to EN would not work correctly. + if (lastAutoSwitchResyncSubtype != currentSubtypeString) { + latinIMELegacy.onCurrentInputMethodSubtypeChanged(currentSubtype) + lastAutoSwitchResyncSubtype = currentSubtypeString + } + return + } + + // checking subtypes by tag and fallback to language if none found + val targetSubtype = hintedLocales.firstNotNullOfOrNull { hintLocale -> + enabledSubtypes.find { + Subtypes.getLocale(it).toLanguageTag().equals(hintLocale.toLanguageTag(), true) + } ?: enabledSubtypes.find { + val subtypeLocale = Subtypes.getLocale(it) + subtypeLocale.language.isNotEmpty() && subtypeLocale.language == hintLocale.language + } + } ?: return + + if (Subtypes.subtypeToString(targetSubtype) == currentSubtypeString) return + + lastAutoSwitchResyncSubtype = "" + latinIMELegacy.onCurrentInputMethodSubtypeChanged(targetSubtype) + } + fun onSizeUpdated() { val newSize = calculateSize() ?: return val shouldInvalidateKeyboard = size.value?.let { oldSize -> @@ -350,6 +394,7 @@ class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripCont } private var currentSubtype = "" + private var lastAutoSwitchResyncSubtype = "" val jobs = mutableListOf() private fun launchJob(task: suspend CoroutineScope.() -> Unit) { @@ -592,6 +637,7 @@ class LatinIME : InputMethodServiceCompose(), LatinIMELegacy.SuggestionStripCont latinIMELegacy.onStartInputView(info, restarting) lifecycleScope.launch { uixManager.showUpdateNoticeIfNeeded() } updateColorsIfDynamicChanged() + updateKeyboardLanguage() } override fun onFinishInputView(finishingInput: Boolean) { diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java index 49bd237883..8c96bc60d1 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java +++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java @@ -343,7 +343,9 @@ public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) } void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) { - + // Refresh InputAttributes even when restarting in the same field. Some apps + // (e.g Translate) update hintLocales but not inputType, leading to out of date hintLocales + loadSettings(); } public void updateMainKeyboardViewSettings() { diff --git a/java/src/org/futo/inputmethod/latin/settings/Settings.java b/java/src/org/futo/inputmethod/latin/settings/Settings.java index 61ac200881..b5fc8587e7 100644 --- a/java/src/org/futo/inputmethod/latin/settings/Settings.java +++ b/java/src/org/futo/inputmethod/latin/settings/Settings.java @@ -145,6 +145,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final int DEFAULT_ALT_SPACES_MODE = SPACES_MODE_ALL; + public static final String PREF_AUTO_SWITCH_LANGUAGE = "pref_auto_switch_language"; + // Emoji public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id"; diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java index 8c25837adb..54bd81e816 100644 --- a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java @@ -106,7 +106,8 @@ public class SettingsValues { public final int mBackspaceMode; public final int mNumberRowMode; public final int mAltSpacesMode; - + public final boolean mAutoSwitchLanguage; + // From the input box @Nonnull public final InputAttributes mInputAttributes; @@ -201,9 +202,10 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina prefs.getInt(Settings.PREF_NUMBER_ROW_MODE, Settings.NUMBER_ROW_MODE_DEFAULT) : Settings.NUMBER_ROW_MODE_DEFAULT; mAltSpacesMode = prefs.getInt(Settings.PREF_ALT_SPACES_MODE, Settings.DEFAULT_ALT_SPACES_MODE); + mAutoSwitchLanguage = prefs.getBoolean(Settings.PREF_AUTO_SWITCH_LANGUAGE, false); mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI - && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true); + && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true); // Compute other readable settings mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res); mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res); diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt index e8f1b87836..41563a235e 100644 --- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt +++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Languages.kt @@ -52,6 +52,7 @@ import org.futo.inputmethod.latin.MultilingualBucketSetting import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.Subtypes import org.futo.inputmethod.latin.SubtypesSetting +import org.futo.inputmethod.latin.settings.Settings import org.futo.inputmethod.latin.uix.FileKind import org.futo.inputmethod.latin.uix.ResourceHelper import org.futo.inputmethod.latin.uix.getSetting @@ -66,6 +67,7 @@ import org.futo.inputmethod.latin.uix.settings.pages.modelmanager.openModelImpor import org.futo.inputmethod.latin.uix.settings.useDataStore import org.futo.inputmethod.latin.uix.settings.useDataStoreValue import org.futo.inputmethod.latin.uix.settings.userSettingNavigationItem +import org.futo.inputmethod.latin.uix.settings.userSettingToggleSharedPrefs import org.futo.inputmethod.latin.uix.theme.Typography import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper import org.futo.inputmethod.latin.uix.theme.presets.DynamicDarkTheme @@ -507,6 +509,16 @@ val LanguageSettingsTop = listOf( navigateTo = "addLanguage", ) ) + +val LanguageSettingsToggles = listOf( + userSettingToggleSharedPrefs( + title = R.string.auto_switch_language_title, + subtitle = R.string.auto_switch_language_subtitle, + key = Settings.PREF_AUTO_SWITCH_LANGUAGE, + default = { false } + ) +) + val LanguageSettingsBottom = listOf( userSettingNavigationItem( title = R.string.language_settings_import_resource_from_file, @@ -750,5 +762,13 @@ fun LanguagesScreen(navController: NavHostController = rememberNavController()) items(LanguageSettingsBottom) { it.component() } + + item { + ScreenTitle(stringResource(R.string.language_settings_advanced_options)) + } + + items(LanguageSettingsToggles) { + it.component() + } } } \ No newline at end of file diff --git a/tests/src/org/futo/inputmethod/latin/InputAttributesTests.java b/tests/src/org/futo/inputmethod/latin/InputAttributesTests.java new file mode 100644 index 0000000000..1d5d6499c7 --- /dev/null +++ b/tests/src/org/futo/inputmethod/latin/InputAttributesTests.java @@ -0,0 +1,29 @@ +package org.futo.inputmethod.latin; + +import android.os.LocaleList; +import android.view.inputmethod.EditorInfo; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class InputAttributesTests { + + @Test + public void testHintLocales() { + final EditorInfo editorInfo = mock(EditorInfo.class); + final LocaleList localeList = new LocaleList(new Locale("en", "US"), new Locale("ja", "JP")); + when(editorInfo.getHintLocales()).thenReturn(localeList); + + final InputAttributes inputAttributes = new InputAttributes(editorInfo, false, ""); + + assertEquals(localeList, inputAttributes.mHintLocales); + } +} \ No newline at end of file